diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa5c74..8715cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - findUser not being implemented - style issues - graphql schema for denyRequest using the wrong parameters +- sendRequest allowing duplicates + +### Added + +- Added `deleteable' field on post ## [0.9] - 2019-10-29 diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index af83dcd..c78e14c 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -125,7 +125,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -156,11 +156,11 @@ export function resolver(req: any, res: any): any { value: user.token(), }; } catch (err) { - res.status(400); - return err.graphqlError; + res.status(status.BAD_REQUEST); + return err.graphqlError ?? new GraphQLError(err.message); } } else { - res.status(400); + res.status(status.BAD_REQUEST); return new GraphQLError("No email or password specified."); } }, @@ -178,7 +178,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -193,7 +193,7 @@ export function resolver(req: any, res: any): any { await user.save(); return user.settings; } catch (err) { - res.status(400); + res.status(status.BAD_REQUEST); return new GraphQLError("Invalid settings json."); } } else { @@ -278,7 +278,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -291,7 +291,12 @@ export function resolver(req: any, res: any): any { return new NotLoggedInGqlError(); } if (receiver && type) { - return await dataaccess.createRequest(req.session.userId, receiver, type); + try { + return await dataaccess.createRequest(req.session.userId, receiver, type); + } catch (err) { + res.status(status.BAD_REQUEST); + return err.graphqlError ?? new GraphQLError(err.message); + } } else { res.status(status.BAD_REQUEST); return new GraphQLError("No receiver or type given."); @@ -325,7 +330,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -346,7 +351,12 @@ export function resolver(req: any, res: any): any { }, async createGroup({name, members}: { name: string, members: number[] }) { if (req.session.userId) { - return await dataaccess.createGroup(name, req.session.userId, members); + try { + return await dataaccess.createGroup(name, req.session.userId, members); + } catch (err) { + res.status(status.BAD_REQUEST); + return err.graphqlError ?? new GraphQLError(err.message); + } } else { return new NotLoggedInGqlError(); } @@ -358,7 +368,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.ADD); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.UNAUTHORIZED); @@ -372,7 +382,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.REMOVE); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.UNAUTHORIZED); @@ -392,7 +402,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.OP); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { @@ -417,7 +427,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.DEOP); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.UNAUTHORIZED); diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index 58e9b03..c5307cd 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -284,6 +284,9 @@ type Post { "the type of vote the user performed on the post" userVote(userId: ID!): VoteType + + "if the post can be deleted by the specified user" + deleteable(userId: ID!): Boolean } "represents a request of any type" diff --git a/src/lib/dataAccess.ts b/src/lib/dataAccess.ts index 4c67ea8..9e19163 100644 --- a/src/lib/dataAccess.ts +++ b/src/lib/dataAccess.ts @@ -2,7 +2,9 @@ import * as crypto from "crypto"; import * as sqz from "sequelize"; import {Sequelize} from "sequelize-typescript"; import {ChatNotFoundError} from "./errors/ChatNotFoundError"; +import {DuplicatedRequestError} from "./errors/DuplicatedRequestError"; import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError"; +import {GroupAlreadyExistsError} from "./errors/GroupAlreadyExistsError"; import {GroupNotFoundError} from "./errors/GroupNotFoundError"; import {InvalidLoginError} from "./errors/InvalidLoginError"; import {NoActionSpecifiedError} from "./errors/NoActionSpecifiedError"; @@ -231,9 +233,16 @@ namespace dataaccess { export async function createRequest(sender: number, receiver: number, requestType?: RequestType) { requestType = requestType || RequestType.FRIENDREQUEST; - const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType}); - globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, request); - return request; + const requestExists = !!await models.Request.findOne({where: + {senderId: sender, receiverId: receiver, requestType}}); + + if (!requestExists) { + const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType}); + globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, request); + return request; + } else { + throw new DuplicatedRequestError(); + } } /** @@ -243,19 +252,25 @@ namespace dataaccess { * @param members */ export async function createGroup(name: string, creator: number, members: number[]): Promise { - members = members || []; - return sequelize.transaction(async (t) => { - members.push(creator); - const groupChat = await createChat(...members); - const group = await models.Group.create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t}); - const creatorUser = await models.User.findByPk(creator, {transaction: t}); - await group.$add("rAdmins", creatorUser, {transaction: t}); - for (const member of members) { - const user = await models.User.findByPk(member, {transaction: t}); - await group.$add("rMembers", user, {transaction: t}); - } - return group; - }); + const groupNameExists = !!await models.Group.findOne({where: {name}}); + if (!groupNameExists) { + members = members || []; + return sequelize.transaction(async (t) => { + members.push(creator); + const groupChat = await createChat(...members); + const group = await models.Group + .create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t}); + const creatorUser = await models.User.findByPk(creator, {transaction: t}); + await group.$add("rAdmins", creatorUser, {transaction: t}); + for (const member of members) { + const user = await models.User.findByPk(member, {transaction: t}); + await group.$add("rMembers", user, {transaction: t}); + } + return group; + }); + } else { + throw new GroupAlreadyExistsError(name); + } } /** diff --git a/src/lib/errors/DuplicatedRequestError.ts b/src/lib/errors/DuplicatedRequestError.ts new file mode 100644 index 0000000..404b3c0 --- /dev/null +++ b/src/lib/errors/DuplicatedRequestError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class DuplicatedRequestError extends BaseError { + constructor() { + super(`Request already exists.`); + } +} diff --git a/src/lib/errors/GroupAlreadyExistsError.ts b/src/lib/errors/GroupAlreadyExistsError.ts new file mode 100644 index 0000000..02dffe2 --- /dev/null +++ b/src/lib/errors/GroupAlreadyExistsError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class GroupAlreadyExistsError extends BaseError { + constructor(name: string) { + super(`A group with the name "${name}" already exists.`); + } +} diff --git a/src/lib/models/Group.ts b/src/lib/models/Group.ts index 92b2b2c..7bf801f 100644 --- a/src/lib/models/Group.ts +++ b/src/lib/models/Group.ts @@ -1,4 +1,14 @@ -import {BelongsTo, BelongsToMany, Column, ForeignKey, HasMany, Model, NotNull, Table} from "sequelize-typescript"; +import { + BelongsTo, + BelongsToMany, + Column, + ForeignKey, + HasMany, + Model, + NotNull, + Table, + Unique, +} from "sequelize-typescript"; import {ChatRoom} from "./ChatRoom"; import {Event} from "./Event"; import {GroupAdmin} from "./GroupAdmin"; @@ -8,7 +18,8 @@ import {User} from "./User"; @Table({underscored: true}) export class Group extends Model { @NotNull - @Column({allowNull: false}) + @Unique + @Column({allowNull: false, unique: true}) public name: string; @NotNull diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index 0feade3..d304e38 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -96,4 +96,12 @@ export class Post extends Model { const votes = await this.$get("rVotes", {where: {id: userId}}) as Array; return votes[0]?.PostVote?.voteType; } + + /** + * Returns if the post can be deleted by the user with the given id. + * @param userId + */ + public async deleteable({userId}: {userId: number}): Promise { + return Number(userId) === Number(this.authorId); + } } diff --git a/src/lib/models/Request.ts b/src/lib/models/Request.ts index 52b4058..c1faf8d 100644 --- a/src/lib/models/Request.ts +++ b/src/lib/models/Request.ts @@ -35,10 +35,23 @@ export class Request extends Model { @BelongsTo(() => User, "receiverId") public rReceiver: User; + /** + * Wrapper to return the request type for the request + */ + public get type(): RequestType { + return this.requestType; + } + + /** + * The receiver of the request + */ public async receiver(): Promise { return await this.$get("rReceiver") as User; } + /** + * The sender of the request. + */ public async sender(): Promise { return await this.$get("rSender") as User; }