diff --git a/src/services/api/routes/communities.ts b/src/services/api/routes/communities.ts index ef6f8b2..a06fa0e 100644 --- a/src/services/api/routes/communities.ts +++ b/src/services/api/routes/communities.ts @@ -22,27 +22,31 @@ import { ParamPack } from '@/types/common/param-pack'; const createNewCommunitySchema = z.object({ name: z.string(), - description: z.string(), + description: z.string().optional(), icon: z.string(), - app_data: z.string() + app_data: z.string().optional() }); const router: express.Router = express.Router(); -function respondCommunityNotFound(response: express.Response) : void { - response.status(404); - response.send(xmlbuilder.create({ +function respondCommunityError(response: express.Response, httpStatusCode: number, errorCode: number): void { + response.status(httpStatusCode).send(xmlbuilder.create({ result: { has_error: 1, version: 1, - code: 404, - error_code: 919, - message: 'COMMUNITY_NOT_FOUND' + code: httpStatusCode, + error_code: errorCode, + message: 'COMMUNITY_ERROR' // This field is unused by the entire nn_olv.rpl } }).end({ pretty: true })); } -async function commonGetSubCommunity(paramPack: ParamPack, communityID: string | undefined) : Promise { +function respondCommunityNotFound(response: express.Response): void { + respondCommunityError(response, 404, 919); +} + + +async function commonGetSubCommunity(paramPack: ParamPack, communityID: string | undefined): Promise { const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(paramPack.title_id); if (!parentCommunity) { @@ -75,17 +79,17 @@ router.get('/', async function (request: express.Request, response: express.Resp const type: string | undefined = getValueFromQueryString(request.query, 'type'); const limitString: string | undefined = getValueFromQueryString(request.query, 'limit'); - let limit: number = 8; + let limit: number = 4; if (limitString) { limit = parseInt(limitString); } if (isNaN(limit)) { - limit = 8; + limit = 4; } - if (limit > 32) { - limit = 32; + if (limit > 16) { + limit = 16; } const query: SubCommunityQuery = { @@ -94,7 +98,7 @@ router.get('/', async function (request: express.Request, response: express.Resp if (type === 'my') { query.owner = request.pid; - } else if (type ==='favorite') { + } else if (type === 'favorite') { query.user_favorites = request.pid; } @@ -144,17 +148,7 @@ router.get('/:communityID/posts', async function (request: express.Request, resp } if (!community) { - 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 })); - return; + return respondCommunityNotFound(response); } const query: CommunityPostsQuery = { @@ -253,23 +247,51 @@ router.post('/', multer().none(), async function (request: express.Request, resp const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id); if (!parentCommunity) { - respondCommunityNotFound(response); - return; + return respondCommunityNotFound(response); } // TODO - Better error codes, maybe do defaults? 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; + return respondCommunityError(response, 400, 20); + } + + request.body.name = request.body.name.trim(); + request.body.icon = request.body.icon.trim(); + + if (request.body.description) { + request.body.description = request.body.description.trim(); + } + + if (request.body.app_data) { + request.body.app_data = request.body.app_data.trim(); + } + + // Name must be at least 4 character long + if (request.body.name.length < 4) { + return respondCommunityError(response, 400, 20); + } + + // Each user can only have 4 subcommunities per title + const ownedQuery: SubCommunityQuery = { + parent: parentCommunity.olive_community_id, + owner: request.pid + }; + + const ownedSubcommunityCount: number = await Community.countDocuments(ownedQuery); + if (ownedSubcommunityCount >= 4) { + return respondCommunityError(response, 401, 911); + } + + // Each user can only have 16 favorite subcommunities per title + const favoriteQuery: SubCommunityQuery = { + parent: parentCommunity.olive_community_id, + user_favorites: request.pid + }; + + const ownedFavoriteCount: number = await Community.countDocuments(favoriteQuery); + if (ownedFavoriteCount >= 16) { + return respondCommunityError(response, 401, 912); } const communitiesCount: number = await Community.count(); @@ -277,7 +299,7 @@ router.post('/', multer().none(), async function (request: express.Request, resp const community: HydratedCommunityDocument = await Community.create({ platform_id: 0, // WiiU name: request.body.name, - description: request.body.description, + description: request.body.description || '', open: true, allows_comments: true, type: 1, @@ -288,7 +310,8 @@ router.post('/', multer().none(), async function (request: express.Request, resp title_id: request.paramPack.title_id, community_id: communityId.toString(), olive_community_id: communityId.toString(), - app_data: request.body.app_data, + app_data: request.body.app_data || '', + user_favorites: [request.pid] }); response.send(xmlbuilder.create({ @@ -336,6 +359,17 @@ router.post('/:community_id.favorite', multer().none(), async function (request: return; } + // Each user can only have 16 favorite subcommunities per title + const favoriteQuery: SubCommunityQuery = { + parent: community.parent, + user_favorites: request.pid + }; + + const ownedFavoriteCount: number = await Community.countDocuments(favoriteQuery); + if (ownedFavoriteCount >= 16) { + return respondCommunityError(response, 401, 914); + } + await community.addUserFavorite(request.pid); response.send(xmlbuilder.create({ @@ -357,6 +391,11 @@ router.post('/:community_id.unfavorite', multer().none(), async function (reques return; } + // You can't remove from your favorites a community you own + if (community.owner === request.pid) { + return respondCommunityError(response, 401, 916); + } + await community.delUserFavorite(request.pid); response.send(xmlbuilder.create({ @@ -384,24 +423,22 @@ router.post('/:community_id', multer().none(), async function (request: express. 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; + if (request.body.name) { + community.name = request.body.name.trim(); + } + + if (request.body.description) { + community.description = request.body.description.trim(); + } + + if (request.body.icon) { + community.icon = request.body.icon.trim(); + } + + if (request.body.app_data) { + community.app_data = request.body.app_data.trim(); } - 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({ diff --git a/src/types/common/create-new-community-body.ts b/src/types/common/create-new-community-body.ts index 24aa2dc..a4ad82d 100644 --- a/src/types/common/create-new-community-body.ts +++ b/src/types/common/create-new-community-body.ts @@ -1,6 +1,6 @@ export interface CreateNewCommunityBody { name: string; - description: string; + description?: string; icon: string; - app_data: string; + app_data?: string; } \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 4c3bb96..9260e91 100644 --- a/src/util.ts +++ b/src/util.ts @@ -37,7 +37,7 @@ export function decodeParamPack(paramPack: string): ParamPack { const values: string[] = Buffer.from(paramPack, 'base64').toString().split('\\'); const entries: string[][] = values.filter(value => value).reduce((entries: string[][], value: string, index: number) => { if (0 === index % 2) { - entries.push([ value ]); + entries.push([value]); } else { entries[Math.ceil(index / 2 - 1)].push(value); } @@ -60,7 +60,7 @@ export function getPIDFromServiceToken(token: string): number { return unpackedToken.pid; } catch (e) { - // TODO - Log this + console.error(e); return 0; } } @@ -215,5 +215,5 @@ export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): } export function mapToObject(map: Map): object { - return Object.fromEntries(Array.from(map.entries(), ([ k, v ]) => v instanceof Map ? [ k, mapToObject(v) ] : [ k, v ])); + return Object.fromEntries(Array.from(map.entries(), ([k, v]) => v instanceof Map ? [k, mapToObject(v)] : [k, v])); } \ No newline at end of file