From 9df8d3f29d4ba64ee37cb49a3690349b0b32f1d0 Mon Sep 17 00:00:00 2001 From: EpicUsername12 <39063367+EpicUsername12@users.noreply.github.com> Date: Tue, 30 May 2023 18:06:51 +0200 Subject: [PATCH 1/4] Updated POST /v1/communities/%s: - Implemented ``/v1/communities/%s`` (update user-made sub community) - Implemented ``/v1/communities/%s.delete`` (delete user-made sub community) - Implemented ``/v1/communities/%s.favorite`` - Implemented ``/v1/communities/%s.unfavorite`` --- src/models/community.ts | 22 +++ src/services/api/routes/communities.ts | 177 ++++++++++++++++++++--- src/types/mongoose/community.ts | 3 + src/types/mongoose/subcommunity-query.ts | 6 +- 4 files changed, 184 insertions(+), 24 deletions(-) diff --git a/src/models/community.ts b/src/models/community.ts index 96ae086..9cf2675 100644 --- a/src/models/community.ts +++ b/src/models/community.ts @@ -64,6 +64,10 @@ const CommunitySchema = new Schema { @@ -94,6 +98,24 @@ CommunitySchema.method('downFollower', async function downFollower(): Promise { + const userFavorites: number[] = this.get('user_favorites'); + if (!userFavorites.includes(pid)) { + userFavorites.push(pid); + } + + await this.save(); +}); + +CommunitySchema.method('delUserFavorite', async function delUserFavorite(pid: number): Promise { + const userFavorites: number[] = this.get('user_favorites'); + if (userFavorites.includes(pid)) { + userFavorites.splice(userFavorites.indexOf(pid), 1); + } + + await this.save(); +}); + CommunitySchema.method('json', function json(): Record { return { community_id: this.community_id, diff --git a/src/services/api/routes/communities.ts b/src/services/api/routes/communities.ts index c9628d3..a576aef 100644 --- a/src/services/api/routes/communities.ts +++ b/src/services/api/routes/communities.ts @@ -28,22 +28,48 @@ const createNewCommunitySchema = z.object({ const router: express.Router = express.Router(); +async function respondCommunityNotFound(response: express.Response) : Promise { + response.status(404); + response.send(xmlbuilder.create({ + result: { + has_error: 1, + version: 1, + code: 404, + error_code: 919, + message: 'COMMUNITY_NOT_FOUND' + } + }).end({ pretty: true })); +} + +async function commonGetSubCommunity(request: express.Request, response: express.Response, communityID: string | undefined) : Promise { + + const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); + if (!parentCommunity) { + await respondCommunityNotFound(response); + return parentCommunity; + } + + const query: SubCommunityQuery = { + parent: parentCommunity.olive_community_id, + community_id: communityID + }; + + const community: HydratedCommunityDocument | null = await Community.findOne(query); + if (!community) { + await respondCommunityNotFound(response); + return community; + } + + return community; +} + /* GET post titles. */ router.get('/', async function (request: express.Request, response: express.Response): Promise { response.type('application/xml'); const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); if (!parentCommunity) { - response.status(404); - response.send(xmlbuilder.create({ - result: { - has_error: 1, - version: 1, - code: 404, - error_code: 919, - message: 'COMMUNITY_NOT_FOUND' - } - }).end({ pretty: true })); + await respondCommunityNotFound(response); return; } @@ -70,7 +96,7 @@ router.get('/', async function (request: express.Request, response: express.Resp if (type === 'my') { query.owner = request.pid; } else if (type ==='favorite') { - // TODO + query.user_favorites = request.pid; } const communities: HydratedCommunityDocument[] = await Community.find(query).limit(limit); @@ -227,16 +253,7 @@ router.post('/', multer().none(), async function (request: express.Request, resp const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); if (!parentCommunity) { - response.status(404); - response.send(xmlbuilder.create({ - result: { - has_error: 1, - version: 1, - code: 404, - error_code: 919, - message: 'COMMUNITY_NOT_FOUND' - } - }).end({ pretty: true })); + await respondCommunityNotFound(response); return; } @@ -284,4 +301,122 @@ router.post('/', multer().none(), async function (request: express.Request, resp }).end({ pretty: true, allowEmpty: true })); }); +router.post('/:community_action', multer().none(), async function (request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); + if (!parentCommunity) { + await respondCommunityNotFound(response); + return; + } + + const communityActions: string[] = request.params.community_action.split('.'); + const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request, response, communityActions[0]); + if (!community) { + return; + } + + if (communityActions.length > 1) { + if (communityActions[1] === 'delete') { + await deleteSubCommunity(community, request, response); + } else if (communityActions[1] === 'favorite') { + await addFavoriteSubCommunity(community, request, response); + } else if (communityActions[1] === 'unfavorite') { + await delFavoriteSubCommunity(community, request, response); + } else { // '%s.search' + response.sendStatus(501); // Not Implemented + } + } else { + await updateSubCommunity(community, request, response); + } +}); + +async function updateSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + if (community.owner != request.pid) { + response.sendStatus(403); // Forbidden + return; + } + + const bodyCheck: z.SafeParseReturnType = createNewCommunitySchema.safeParse(request.body); + if (!bodyCheck.success) { + response.send(xmlbuilder.create({ + result: { + has_error: 1, + version: 1, + code: 404, + error_code: 20, + message: 'BAD_COMMUNITY_DATA' + } + }).end({ pretty: true })); + return; + } + + community.name = request.body.name; + community.description = request.body.description; + community.icon = request.body.icon; + community.app_data = request.body.app_data; + await community.save(); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); +} + +async function deleteSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + if (community.owner != request.pid) { + response.sendStatus(403); // Forbidden + return; + } + + await Community.deleteOne({ _id: community._id }); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); +} + +async function addFavoriteSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + await community.addUserFavorite(request.pid); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); +} + +async function delFavoriteSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + await community.delUserFavorite(request.pid); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); +} + export default router; diff --git a/src/types/mongoose/community.ts b/src/types/mongoose/community.ts index edf5a2a..152c3b2 100644 --- a/src/types/mongoose/community.ts +++ b/src/types/mongoose/community.ts @@ -28,6 +28,7 @@ export interface ICommunity { olive_community_id: string; is_recommended: number; app_data: string; + user_favorites: Types.Array; } export interface ICommunityMethods { @@ -35,6 +36,8 @@ export interface ICommunityMethods { downEmpathy(): Promise; upFollower(): Promise; downFollower(): Promise; + addUserFavorite(pid: number): Promise; + delUserFavorite(pid: number): Promise; json(): Record; } diff --git a/src/types/mongoose/subcommunity-query.ts b/src/types/mongoose/subcommunity-query.ts index 736b0cc..3ee3824 100644 --- a/src/types/mongoose/subcommunity-query.ts +++ b/src/types/mongoose/subcommunity-query.ts @@ -3,7 +3,7 @@ export interface SubCommunityQuery { parent: string; owner?: number; - olive_community_id?: { - $in: string[] - } + user_favorites?: number; + olive_community_id?: string; + community_id?: string; } \ No newline at end of file From 3ef566789e953f7df0f31c5104704bb60bd4a4c2 Mon Sep 17 00:00:00 2001 From: EpicUsername12 <39063367+EpicUsername12@users.noreply.github.com> Date: Tue, 30 May 2023 19:28:35 +0200 Subject: [PATCH 2/4] Hydrate Post documents on the aggregation path of ``/v1/communities/%s/posts`` --- src/services/api/routes/communities.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/api/routes/communities.ts b/src/services/api/routes/communities.ts index a576aef..334ced5 100644 --- a/src/services/api/routes/communities.ts +++ b/src/services/api/routes/communities.ts @@ -17,7 +17,7 @@ import { HydratedCommunityDocument } from '@/types/mongoose/community'; import { SubCommunityQuery } from '@/types/mongoose/subcommunity-query'; import { CommunityPostsQuery } from '@/types/mongoose/community-posts-query'; import { HydratedContentDocument } from '@/types/mongoose/content'; -import { HydratedPostDocument } from '@/types/mongoose/post'; +import {HydratedPostDocument, IPost} from '@/types/mongoose/post'; const createNewCommunitySchema = z.object({ name: z.string(), @@ -218,6 +218,7 @@ router.get('/:communityID/posts', async function (request: express.Request, resp { $replaceRoot: { newRoot: '$doc' } }, // replace the root with the 'doc' field { $limit: limit } // only return the top 10 results ]); + posts = posts.map((post: IPost) => Post.hydrate(post)); } else { posts = await Post.find(query).sort({ created_at: -1 }).limit(limit); } From 0c46b33be648f640845edade32836523a2edd55b Mon Sep 17 00:00:00 2001 From: EpicUsername12 <39063367+EpicUsername12@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:33:37 +0200 Subject: [PATCH 3/4] Separated /v1/communities/%s route to multiple routes --- src/services/api/routes/communities.ts | 156 ++++++++++++------------- src/types/mongoose/community.ts | 46 ++++---- 2 files changed, 98 insertions(+), 104 deletions(-) diff --git a/src/services/api/routes/communities.ts b/src/services/api/routes/communities.ts index 334ced5..5694d0d 100644 --- a/src/services/api/routes/communities.ts +++ b/src/services/api/routes/communities.ts @@ -17,7 +17,8 @@ import { HydratedCommunityDocument } from '@/types/mongoose/community'; import { SubCommunityQuery } from '@/types/mongoose/subcommunity-query'; import { CommunityPostsQuery } from '@/types/mongoose/community-posts-query'; import { HydratedContentDocument } from '@/types/mongoose/content'; -import {HydratedPostDocument, IPost} from '@/types/mongoose/post'; +import { HydratedPostDocument, IPost } from '@/types/mongoose/post'; +import { ParamPack } from '@/types/common/param-pack'; const createNewCommunitySchema = z.object({ name: z.string(), @@ -28,7 +29,7 @@ const createNewCommunitySchema = z.object({ const router: express.Router = express.Router(); -async function respondCommunityNotFound(response: express.Response) : Promise { +function respondCommunityNotFound(response: express.Response) : void { response.status(404); response.send(xmlbuilder.create({ result: { @@ -41,12 +42,11 @@ async function respondCommunityNotFound(response: express.Response) : Promise { +async function commonGetSubCommunity(paramPack: ParamPack, communityID: string | undefined) : Promise { - const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); + const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(paramPack.title_id); if (!parentCommunity) { - await respondCommunityNotFound(response); - return parentCommunity; + return null; } const query: SubCommunityQuery = { @@ -56,8 +56,7 @@ async function commonGetSubCommunity(request: express.Request, response: express const community: HydratedCommunityDocument | null = await Community.findOne(query); if (!community) { - await respondCommunityNotFound(response); - return community; + return null; } return community; @@ -302,39 +301,84 @@ router.post('/', multer().none(), async function (request: express.Request, resp }).end({ pretty: true, allowEmpty: true })); }); -router.post('/:community_action', multer().none(), async function (request: express.Request, response: express.Response): Promise { +router.post('/:community_id.delete', multer().none(), async function (request: express.Request, response: express.Response): Promise { response.type('application/xml'); - const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); - if (!parentCommunity) { - await respondCommunityNotFound(response); - return; - } - - const communityActions: string[] = request.params.community_action.split('.'); - const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request, response, communityActions[0]); + const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id); if (!community) { + respondCommunityNotFound(response); return; } - if (communityActions.length > 1) { - if (communityActions[1] === 'delete') { - await deleteSubCommunity(community, request, response); - } else if (communityActions[1] === 'favorite') { - await addFavoriteSubCommunity(community, request, response); - } else if (communityActions[1] === 'unfavorite') { - await delFavoriteSubCommunity(community, request, response); - } else { // '%s.search' - response.sendStatus(501); // Not Implemented - } - } else { - await updateSubCommunity(community, request, response); + if (community.owner != request.pid) { + response.sendStatus(403); // Forbidden + return; } + + await Community.deleteOne({ _id: community._id }); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); }); -async function updateSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { +router.post('/:community_id.favorite', multer().none(), async function (request: express.Request, response: express.Response): Promise { response.type('application/xml'); + const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id); + if (!community) { + respondCommunityNotFound(response); + return; + } + + await community.addUserFavorite(request.pid); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); +}); + +router.post('/:community_id.unfavorite', multer().none(), async function (request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id); + if (!community) { + respondCommunityNotFound(response); + return; + } + + await community.delUserFavorite(request.pid); + + response.send(xmlbuilder.create({ + result: { + has_error: '0', + version: '1', + request_name: 'community', + community: community.json() + } + }).end({ pretty: true, allowEmpty: true })); +}); + + +router.post('/:community_id', multer().none(), async function (request: express.Request, response: express.Response): Promise { + response.type('application/xml'); + + const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id); + if (!community) { + respondCommunityNotFound(response); + return; + } + if (community.owner != request.pid) { response.sendStatus(403); // Forbidden return; @@ -368,56 +412,6 @@ async function updateSubCommunity(community: HydratedCommunityDocument, request: community: community.json() } }).end({ pretty: true, allowEmpty: true })); -} - -async function deleteSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { - response.type('application/xml'); - - if (community.owner != request.pid) { - response.sendStatus(403); // Forbidden - return; - } - - await Community.deleteOne({ _id: community._id }); - - response.send(xmlbuilder.create({ - result: { - has_error: '0', - version: '1', - request_name: 'community', - community: community.json() - } - }).end({ pretty: true, allowEmpty: true })); -} - -async function addFavoriteSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { - response.type('application/xml'); - - await community.addUserFavorite(request.pid); - - response.send(xmlbuilder.create({ - result: { - has_error: '0', - version: '1', - request_name: 'community', - community: community.json() - } - }).end({ pretty: true, allowEmpty: true })); -} - -async function delFavoriteSubCommunity(community: HydratedCommunityDocument, request: express.Request, response: express.Response): Promise { - response.type('application/xml'); - - await community.delUserFavorite(request.pid); - - response.send(xmlbuilder.create({ - result: { - has_error: '0', - version: '1', - request_name: 'community', - community: community.json() - } - }).end({ pretty: true, allowEmpty: true })); -} +}); export default router; diff --git a/src/types/mongoose/community.ts b/src/types/mongoose/community.ts index 152c3b2..52ea2c7 100644 --- a/src/types/mongoose/community.ts +++ b/src/types/mongoose/community.ts @@ -9,26 +9,26 @@ enum COMMUNITY_TYPE { export interface ICommunity { platform_id: number; - name: string; - description: string; - open: boolean; - allows_comments: boolean; - type: COMMUNITY_TYPE; - parent: string; - admins: Types.Array; - owner: number; - created_at: Date; - empathy_count: number; - followers: number; - has_shop_page: number; - icon: string; - title_ids: Types.Array; - title_id: Types.Array; - community_id: string; - olive_community_id: string; - is_recommended: number; - app_data: string; - user_favorites: Types.Array; + name: string; + description: string; + open: boolean; + allows_comments: boolean; + type: COMMUNITY_TYPE; + parent: string; + admins: Types.Array; + owner: number; + created_at: Date; + empathy_count: number; + followers: number; + has_shop_page: number; + icon: string; + title_ids: Types.Array; + title_id: Types.Array; + community_id: string; + olive_community_id: string; + is_recommended: number; + app_data: string; + user_favorites: Types.Array; } export interface ICommunityMethods { @@ -36,9 +36,9 @@ export interface ICommunityMethods { downEmpathy(): Promise; upFollower(): Promise; downFollower(): Promise; - addUserFavorite(pid: number): Promise; - delUserFavorite(pid: number): Promise; - json(): Record; + addUserFavorite(pid: number): Promise; + delUserFavorite(pid: number): Promise; + json(): Record; } interface ICommunityQueryHelpers {} From acd9a211f2362ce4314c3d43bcb780ef0e547e8c Mon Sep 17 00:00:00 2001 From: EpicUsername12 <39063367+EpicUsername12@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:51:29 +0200 Subject: [PATCH 4/4] Removed 'await' on respondCommunityNotFound() calls --- src/services/api/routes/communities.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/api/routes/communities.ts b/src/services/api/routes/communities.ts index 5694d0d..ef6f8b2 100644 --- a/src/services/api/routes/communities.ts +++ b/src/services/api/routes/communities.ts @@ -68,7 +68,7 @@ router.get('/', async function (request: express.Request, response: express.Resp const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); if (!parentCommunity) { - await respondCommunityNotFound(response); + respondCommunityNotFound(response); return; } @@ -253,7 +253,7 @@ router.post('/', multer().none(), async function (request: express.Request, resp const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); if (!parentCommunity) { - await respondCommunityNotFound(response); + respondCommunityNotFound(response); return; }