diff --git a/packages/common/src/IAPIDef.ts b/packages/common/src/IAPIDef.ts index 78148ce..6b82df1 100644 --- a/packages/common/src/IAPIDef.ts +++ b/packages/common/src/IAPIDef.ts @@ -1,11 +1,12 @@ import {ICommentTree} from './ICommentTree' import {IComment} from './IComment' import {ICredentials} from './ICredentials' +import {INewComment} from './INewComment' import {ISite} from './ISite' import {IStory} from './IStory' import {ITeam} from './ITeam' -import {IUser} from './IUser' import {IUserTeam} from './IUserTeam' +import {IUser} from './IUser' export interface IAPIDef { '/auth/register': { @@ -157,30 +158,37 @@ export interface IAPIDef { } 'post': { response: IComment, - body: IComment, + body: INewComment, params: { storyId: number } } } - '/comments/:parentId': { - 'post': { + '/stories/:storyId/comments/:parentId': { + post: { response: IComment, params: { parentId: number + storyId: number }, - body: IComment, + body: INewComment, } } '/comments/:commentId': { - 'put': { + get: { + response: IComment + params: { + commentId: number + } + } + put: { response: IComment, body: IComment, params: { commentId: number } } - 'delete': { + delete: { params: { commentId: number } diff --git a/packages/common/src/INewComment.ts b/packages/common/src/INewComment.ts new file mode 100644 index 0000000..e166886 --- /dev/null +++ b/packages/common/src/INewComment.ts @@ -0,0 +1,3 @@ +export interface INewComment { + message: string +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 59df822..1bb5901 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -2,10 +2,12 @@ export * from './IAPIDef' export * from './IComment' export * from './ICommentTree' export * from './ICredentials' +export * from './INewComment' export * from './IRequestParams' export * from './IRole' export * from './IRoutes' export * from './ISite' +export * from './IStory' export * from './ITeam' export * from './IUser' export * from './URLFormatter' diff --git a/packages/server/src/comment/CommentRoutes.test.ts b/packages/server/src/comment/CommentRoutes.test.ts new file mode 100644 index 0000000..547b65b --- /dev/null +++ b/packages/server/src/comment/CommentRoutes.test.ts @@ -0,0 +1,110 @@ +import * as CommentTestUtils from './CommentTestUtils' +import {IStory} from '@rondo/common' +import {createSite} from '../site/SiteTestUtils' +import {getStory} from '../story/StoryTestUtils' +import {test} from '../test' + +describe('comment', () => { + + test.withDatabase() + const t = test.request('/api') + + let cookie!: string + let token!: string + let story!: IStory + + const storyUrl = 'https://test.example.com/my/story' + + beforeEach(async () => { + const session = await test.registerAccount() + cookie = session.cookie + token = session.token + t.setHeaders({cookie, 'x-csrf-token': token}) + + await createSite(t, 'test.example.com') + story = await getStory(t, storyUrl) + }) + + async function createChildComment() { + const parent = await CommentTestUtils.createRootComment(t, { + storyId: story.id, + message: 'this is a parent comment', + }) + const child = await CommentTestUtils.createComment(t, { + storyId: story.id, + parentId: parent.id, + message: 'this is a child comment', + }) + expect(child.id).toBeGreaterThan(0) + return child + } + + describe('GET /stories/:storyId/comments', () => { + it('retrieves comments by story id', async () => { + const comments = await CommentTestUtils.getComments(t, story.id) + expect(comments.rootIds).toEqual(jasmine.any(Array)) + }) + }) + + describe('POST /stories/:storyId/comments', () => { + it('adds a new root comment', async () => { + const message = 'this is a comment' + const comment = await CommentTestUtils.createRootComment(t, { + storyId: story.id, + message, + }) + expect(comment.id).toBeGreaterThan(0) + expect(comment.message).toEqual(message) + }) + }) + + describe('POST /stories/:storyId/comments/:parentId', () => { + it('adds a new child comment', async () => { + await createChildComment() + const comments = await CommentTestUtils.getComments(t, story.id) + expect(comments.rootIds).toEqual([ + jasmine.any(Number), + ]) + const id = comments.rootIds[0] + const parent = comments.commentsById[id] + expect(parent).toBeTruthy() + expect(parent.message).toMatch(/parent/) + expect(parent.childrenIds).toEqual([jasmine.any(Number)]) + const child = comments.commentsById[parent.childrenIds![0]] + expect(child).toBeTruthy() + expect(child.message).toMatch(/child/) + expect(child.childrenIds).toBe(undefined) + }) + }) + + describe('PUT /comments/:commentId', () => { + it('updates a comment', () => { + + }) + + it('fails to update a comment if user is not the owner') + + it('updates a comment if user is site moderator') // TODO later + }) + + describe('DELETE /comments/:commentId', () => { + + }) + + describe('POST /comments/:commentId/vote', () => { + + }) + + describe('DELETE /comments/:commentId/vote', () => { + + }) + + describe('POST /comments/:commentId/spam', () => { + + }) + + describe('DELETE /comments/:commentId/spam', () => { + + }) + +}) diff --git a/packages/server/src/comment/CommentRoutes.ts b/packages/server/src/comment/CommentRoutes.ts index a7fbdc4..aa6ae04 100644 --- a/packages/server/src/comment/CommentRoutes.ts +++ b/packages/server/src/comment/CommentRoutes.ts @@ -22,17 +22,34 @@ export class CommentRoutes extends BaseRoute { t.use(ensureLoggedInApi) t.post('/stories/:storyId/comments', async req => { - const {storyId} = req.params - const comment = req.body - comment.storyId = storyId - return this.commentService.saveRoot(comment, req.user!.id) + const userId = req.user!.id + const storyId = Number(req.params.storyId) + const {message} = req.body + return this.commentService.saveRoot({ + message, + storyId, + userId, + }) }) - t.post('/comments/:parentId', async req => { - const {parentId} = req.params - const comment = req.body - comment.parentId = parentId - return this.commentService.save(comment, req.user!.id) + t.post('/stories/:storyId/comments/:parentId', async req => { + const userId = req.user!.id + const parentId = Number(req.params.parentId) + const storyId = Number(req.params.storyId) + const {message} = req.body + return this.commentService.save({ + message, + userId, + parentId, + storyId, + }) + }) + + t.get('/comments/:commentId', async req => { + const commentId = req.params.commentId + const comment = await this.commentService.findOne(commentId) + // TODO return status code 404 when not found + return comment! }) t.put('/comments/:commentId', async req => { diff --git a/packages/server/src/comment/CommentService.ts b/packages/server/src/comment/CommentService.ts index cda72bb..c4fa5b3 100644 --- a/packages/server/src/comment/CommentService.ts +++ b/packages/server/src/comment/CommentService.ts @@ -1,7 +1,9 @@ import {BaseService} from '../services/BaseService' import {Comment} from '../entities/Comment' -import {ICommentService} from './ICommentService' import {IComment, ICommentTree} from '@rondo/common' +import {ICommentService} from './ICommentService' +import {INewCommentParams} from './INewCommentParams' +import {INewRootCommentParams} from './INewRootCommentParams' import {Spam} from '../entities/Spam' import {Validator} from '../validator' import {Vote} from '../entities/Vote' @@ -40,20 +42,31 @@ export class CommentService extends BaseService implements ICommentService { return this.getRepository(Comment).findOne(commentId) } - async saveRoot(comment: IComment, userId: number) { + async saveRoot(comment: INewRootCommentParams) { new Validator(comment) + .ensure('message') .ensure('storyId') + .ensure('userId') .throw() - delete comment.id - comment.parentId = 0 - comment.userId = userId - comment.votes = 0 - comment.spams = 0 - return this.getRepository(Comment).save(comment) + const { + message, + userId, + storyId, + } = comment + + return this.getRepository(Comment).save({ + message: message.trim(), + userId, + storyId, + parentId: undefined, + + votes: 0, + spams: 0, + }) } - async save(comment: IComment, userId: number) { + async save(comment: INewCommentParams) { new Validator(comment) .ensure('message') .ensure('userId') @@ -61,13 +74,22 @@ export class CommentService extends BaseService implements ICommentService { .ensure('parentId') .throw() - delete comment.id + const { + message, + userId, + storyId, + parentId, + } = comment - comment.votes = 0 - comment.spams = 0 - comment.userId = userId + return this.getRepository(Comment).save({ + message: message.trim(), + userId, + storyId, + parentId, - return this.getRepository(Comment).save(comment) + votes: 0, + spams: 0, + }) } async edit(comment: IComment, userId: number) { @@ -77,9 +99,11 @@ export class CommentService extends BaseService implements ICommentService { .throw() await this.getRepository(Comment) - .update(comment.id, { - message: comment.message, + .update({ + id: comment.id, userId, + }, { + message: comment.message, }) const editedComment = await this.findOne(comment.id) diff --git a/packages/server/src/comment/CommentTestUtils.ts b/packages/server/src/comment/CommentTestUtils.ts new file mode 100644 index 0000000..d3d3a55 --- /dev/null +++ b/packages/server/src/comment/CommentTestUtils.ts @@ -0,0 +1,54 @@ +import {RequestTester} from '../test-utils' +import {IAPIDef} from '@rondo/common' + +export async function createRootComment( + t: RequestTester, + {storyId, message}: { + storyId: number, + message: string, + }, +) { + const response = await t + .post('/stories/:storyId/comments', { + params: {storyId}, + }) + .send({ + message, + }) + .expect(200) + + return response.body! +} + +export async function createComment( + t: RequestTester, + {storyId, parentId, message}: { + storyId: number, + parentId: number, + message: string, + }, +) { + const response = await t + .post('/stories/:storyId/comments/:parentId', { + params: {storyId, parentId}, + }) + .send({ + message, + }) + .expect(200) + + return response.body! +} + +export async function getComments( + t: RequestTester, + storyId: number, +) { + const response = await t + .get('/stories/:storyId/comments', { + params: {storyId}, + }) + .expect(200) + + return response.body! +} diff --git a/packages/server/src/comment/ICommentService.ts b/packages/server/src/comment/ICommentService.ts index f962411..9b87667 100644 --- a/packages/server/src/comment/ICommentService.ts +++ b/packages/server/src/comment/ICommentService.ts @@ -1,13 +1,15 @@ import {IComment, ICommentTree} from '@rondo/common' +import {INewCommentParams} from './INewCommentParams' +import {INewRootCommentParams} from './INewRootCommentParams' export interface ICommentService { find(storyId: number): Promise findOne(commentId: number): Promise - saveRoot(comment: IComment, userId: number): Promise + saveRoot(comment: INewRootCommentParams): Promise - save(comment: IComment, userId: number): Promise + save(comment: INewCommentParams): Promise edit(comment: IComment, userId: number): Promise diff --git a/packages/server/src/comment/INewCommentParams.ts b/packages/server/src/comment/INewCommentParams.ts new file mode 100644 index 0000000..c03511e --- /dev/null +++ b/packages/server/src/comment/INewCommentParams.ts @@ -0,0 +1,6 @@ +export interface INewCommentParams { + message: string + userId: number + parentId: number + storyId: number +} diff --git a/packages/server/src/comment/INewRootCommentParams.ts b/packages/server/src/comment/INewRootCommentParams.ts new file mode 100644 index 0000000..1ad65f7 --- /dev/null +++ b/packages/server/src/comment/INewRootCommentParams.ts @@ -0,0 +1,5 @@ +export interface INewRootCommentParams { + message: string + userId: number + storyId: number +} diff --git a/packages/server/src/entities/Comment.ts b/packages/server/src/entities/Comment.ts index dc03cd0..9ed25c4 100644 --- a/packages/server/src/entities/Comment.ts +++ b/packages/server/src/entities/Comment.ts @@ -21,7 +21,7 @@ export class Comment extends BaseEntity { @Column() userId!: number - @Column() + @Column({nullable: true}) parentId!: number @Column() diff --git a/packages/server/src/migrations/1552227347990-comment-parentid-nullable.ts b/packages/server/src/migrations/1552227347990-comment-parentid-nullable.ts new file mode 100644 index 0000000..9b03fa7 --- /dev/null +++ b/packages/server/src/migrations/1552227347990-comment-parentid-nullable.ts @@ -0,0 +1,63 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class commentParentidNullable1552227347990 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`); + await queryRunner.query(`DROP INDEX "IDX_55a938fda82579fd3ec29b3c28"`); + await queryRunner.query(`DROP INDEX "IDX_4a06baede7d9cf51aef879fb0e"`); + await queryRunner.query(`DROP INDEX "IDX_e03827c061fbf85fd3aae454ae"`); + await queryRunner.query(`DROP INDEX "IDX_fe13edd1431a248a0eeac11ae4"`); + await queryRunner.query(`CREATE TABLE "temporary_spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "spam"`); + await queryRunner.query(`DROP TABLE "spam"`); + await queryRunner.query(`ALTER TABLE "temporary_spam" RENAME TO "spam"`); + await queryRunner.query(`CREATE TABLE "temporary_vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "vote"`); + await queryRunner.query(`DROP TABLE "vote"`); + await queryRunner.query(`ALTER TABLE "temporary_vote" RENAME TO "vote"`); + await queryRunner.query(`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `); + await queryRunner.query(`CREATE INDEX "IDX_55a938fda82579fd3ec29b3c28" ON "team" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_e03827c061fbf85fd3aae454ae" ON "site" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_4a06baede7d9cf51aef879fb0e" ON "site" ("teamId") `); + await queryRunner.query(`CREATE INDEX "IDX_fe13edd1431a248a0eeac11ae4" ON "comment" ("storyId") `); + await queryRunner.query(`CREATE TABLE "temporary_spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "spam_userid_commentid" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "spam"`); + await queryRunner.query(`DROP TABLE "spam"`); + await queryRunner.query(`ALTER TABLE "temporary_spam" RENAME TO "spam"`); + await queryRunner.query(`CREATE TABLE "temporary_vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "vote_userid_commentid" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "vote"`); + await queryRunner.query(`DROP TABLE "vote"`); + await queryRunner.query(`ALTER TABLE "temporary_vote" RENAME TO "vote"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "vote" RENAME TO "temporary_vote"`); + await queryRunner.query(`CREATE TABLE "vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_vote"`); + await queryRunner.query(`DROP TABLE "temporary_vote"`); + await queryRunner.query(`ALTER TABLE "spam" RENAME TO "temporary_spam"`); + await queryRunner.query(`CREATE TABLE "spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_spam"`); + await queryRunner.query(`DROP TABLE "temporary_spam"`); + await queryRunner.query(`DROP INDEX "IDX_fe13edd1431a248a0eeac11ae4"`); + await queryRunner.query(`DROP INDEX "IDX_4a06baede7d9cf51aef879fb0e"`); + await queryRunner.query(`DROP INDEX "IDX_e03827c061fbf85fd3aae454ae"`); + await queryRunner.query(`DROP INDEX "IDX_55a938fda82579fd3ec29b3c28"`); + await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`); + await queryRunner.query(`ALTER TABLE "vote" RENAME TO "temporary_vote"`); + await queryRunner.query(`CREATE TABLE "vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "UQ_5ef3b030c86a67d7c3cce97a978" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_vote"`); + await queryRunner.query(`DROP TABLE "temporary_vote"`); + await queryRunner.query(`ALTER TABLE "spam" RENAME TO "temporary_spam"`); + await queryRunner.query(`CREATE TABLE "spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "UQ_885dac94f112af83664ccd06dd9" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_spam"`); + await queryRunner.query(`DROP TABLE "temporary_spam"`); + await queryRunner.query(`CREATE INDEX "IDX_fe13edd1431a248a0eeac11ae4" ON "comment" ("storyId") `); + await queryRunner.query(`CREATE INDEX "IDX_e03827c061fbf85fd3aae454ae" ON "site" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_4a06baede7d9cf51aef879fb0e" ON "site" ("teamId") `); + await queryRunner.query(`CREATE INDEX "IDX_55a938fda82579fd3ec29b3c28" ON "team" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `); + } + +} diff --git a/packages/server/src/migrations/1552227652042-nullable.ts b/packages/server/src/migrations/1552227652042-nullable.ts new file mode 100644 index 0000000..39ed2ef --- /dev/null +++ b/packages/server/src/migrations/1552227652042-nullable.ts @@ -0,0 +1,79 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class nullable1552227652042 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`); + await queryRunner.query(`DROP INDEX "IDX_55a938fda82579fd3ec29b3c28"`); + await queryRunner.query(`DROP INDEX "IDX_4a06baede7d9cf51aef879fb0e"`); + await queryRunner.query(`DROP INDEX "IDX_e03827c061fbf85fd3aae454ae"`); + await queryRunner.query(`DROP INDEX "IDX_fe13edd1431a248a0eeac11ae4"`); + await queryRunner.query(`CREATE TABLE "temporary_spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "spam"`); + await queryRunner.query(`DROP TABLE "spam"`); + await queryRunner.query(`ALTER TABLE "temporary_spam" RENAME TO "spam"`); + await queryRunner.query(`CREATE TABLE "temporary_vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "vote"`); + await queryRunner.query(`DROP TABLE "vote"`); + await queryRunner.query(`ALTER TABLE "temporary_vote" RENAME TO "vote"`); + await queryRunner.query(`CREATE TABLE "temporary_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "message" text NOT NULL, "storyId" integer NOT NULL, "userId" integer NOT NULL, "parentId" integer NOT NULL, "spams" integer NOT NULL, "votes" integer NOT NULL, CONSTRAINT "FK_c0354a9a009d3bb45a08655ce3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_fe13edd1431a248a0eeac11ae43" FOREIGN KEY ("storyId") REFERENCES "story" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_comment"("id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes") SELECT "id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes" FROM "comment"`); + await queryRunner.query(`DROP TABLE "comment"`); + await queryRunner.query(`ALTER TABLE "temporary_comment" RENAME TO "comment"`); + await queryRunner.query(`CREATE TABLE "temporary_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "message" text NOT NULL, "storyId" integer NOT NULL, "userId" integer NOT NULL, "parentId" integer, "spams" integer NOT NULL, "votes" integer NOT NULL, CONSTRAINT "FK_c0354a9a009d3bb45a08655ce3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_fe13edd1431a248a0eeac11ae43" FOREIGN KEY ("storyId") REFERENCES "story" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_comment"("id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes") SELECT "id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes" FROM "comment"`); + await queryRunner.query(`DROP TABLE "comment"`); + await queryRunner.query(`ALTER TABLE "temporary_comment" RENAME TO "comment"`); + await queryRunner.query(`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `); + await queryRunner.query(`CREATE INDEX "IDX_55a938fda82579fd3ec29b3c28" ON "team" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_e03827c061fbf85fd3aae454ae" ON "site" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_4a06baede7d9cf51aef879fb0e" ON "site" ("teamId") `); + await queryRunner.query(`CREATE INDEX "IDX_fe13edd1431a248a0eeac11ae4" ON "comment" ("storyId") `); + await queryRunner.query(`CREATE TABLE "temporary_spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "spam_userid_commentid" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "spam"`); + await queryRunner.query(`DROP TABLE "spam"`); + await queryRunner.query(`ALTER TABLE "temporary_spam" RENAME TO "spam"`); + await queryRunner.query(`CREATE TABLE "temporary_vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "vote_userid_commentid" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "vote"`); + await queryRunner.query(`DROP TABLE "vote"`); + await queryRunner.query(`ALTER TABLE "temporary_vote" RENAME TO "vote"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "vote" RENAME TO "temporary_vote"`); + await queryRunner.query(`CREATE TABLE "vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_vote"`); + await queryRunner.query(`DROP TABLE "temporary_vote"`); + await queryRunner.query(`ALTER TABLE "spam" RENAME TO "temporary_spam"`); + await queryRunner.query(`CREATE TABLE "spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_spam"`); + await queryRunner.query(`DROP TABLE "temporary_spam"`); + await queryRunner.query(`DROP INDEX "IDX_fe13edd1431a248a0eeac11ae4"`); + await queryRunner.query(`DROP INDEX "IDX_4a06baede7d9cf51aef879fb0e"`); + await queryRunner.query(`DROP INDEX "IDX_e03827c061fbf85fd3aae454ae"`); + await queryRunner.query(`DROP INDEX "IDX_55a938fda82579fd3ec29b3c28"`); + await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`); + await queryRunner.query(`ALTER TABLE "comment" RENAME TO "temporary_comment"`); + await queryRunner.query(`CREATE TABLE "comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "message" text NOT NULL, "storyId" integer NOT NULL, "userId" integer NOT NULL, "parentId" integer NOT NULL, "spams" integer NOT NULL, "votes" integer NOT NULL, CONSTRAINT "FK_c0354a9a009d3bb45a08655ce3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_fe13edd1431a248a0eeac11ae43" FOREIGN KEY ("storyId") REFERENCES "story" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "comment"("id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes") SELECT "id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes" FROM "temporary_comment"`); + await queryRunner.query(`DROP TABLE "temporary_comment"`); + await queryRunner.query(`ALTER TABLE "comment" RENAME TO "temporary_comment"`); + await queryRunner.query(`CREATE TABLE "comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "message" text NOT NULL, "storyId" integer NOT NULL, "userId" integer NOT NULL, "parentId" integer NOT NULL, "spams" integer NOT NULL, "votes" integer NOT NULL, CONSTRAINT "FK_c0354a9a009d3bb45a08655ce3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_fe13edd1431a248a0eeac11ae43" FOREIGN KEY ("storyId") REFERENCES "story" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "comment"("id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes") SELECT "id", "createDate", "updateDate", "message", "storyId", "userId", "parentId", "spams", "votes" FROM "temporary_comment"`); + await queryRunner.query(`DROP TABLE "temporary_comment"`); + await queryRunner.query(`ALTER TABLE "vote" RENAME TO "temporary_vote"`); + await queryRunner.query(`CREATE TABLE "vote" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "UQ_5ef3b030c86a67d7c3cce97a978" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_ad37adcff60fdb9670a97868ab1" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_f5de237a438d298031d11a57c3b" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "vote"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_vote"`); + await queryRunner.query(`DROP TABLE "temporary_vote"`); + await queryRunner.query(`ALTER TABLE "spam" RENAME TO "temporary_spam"`); + await queryRunner.query(`CREATE TABLE "spam" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "createDate" datetime NOT NULL DEFAULT (datetime('now')), "updateDate" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer NOT NULL, "commentId" integer NOT NULL, CONSTRAINT "UQ_885dac94f112af83664ccd06dd9" UNIQUE ("userId", "commentId"), CONSTRAINT "FK_1bf468db8f4d18b424bb3eafae5" FOREIGN KEY ("commentId") REFERENCES "comment" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ec8bc4fa789466cf62f5949f5cc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "spam"("id", "createDate", "updateDate", "userId", "commentId") SELECT "id", "createDate", "updateDate", "userId", "commentId" FROM "temporary_spam"`); + await queryRunner.query(`DROP TABLE "temporary_spam"`); + await queryRunner.query(`CREATE INDEX "IDX_fe13edd1431a248a0eeac11ae4" ON "comment" ("storyId") `); + await queryRunner.query(`CREATE INDEX "IDX_e03827c061fbf85fd3aae454ae" ON "site" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_4a06baede7d9cf51aef879fb0e" ON "site" ("teamId") `); + await queryRunner.query(`CREATE INDEX "IDX_55a938fda82579fd3ec29b3c28" ON "team" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `); + } + +} diff --git a/packages/server/src/story/StoryRoutes.test.ts b/packages/server/src/story/StoryRoutes.test.ts index 6bf3d9f..fdcbe93 100644 --- a/packages/server/src/story/StoryRoutes.test.ts +++ b/packages/server/src/story/StoryRoutes.test.ts @@ -1,5 +1,6 @@ import {ISite} from '@rondo/common' import {createSite} from '../site/SiteTestUtils' +import {getStory} from './StoryTestUtils' import {test} from '../test' describe('story', () => { @@ -22,15 +23,6 @@ describe('story', () => { const invalidUrl = 'https://invalid.example.com/test' const validUrl = 'https://test.example.com/test' - async function getStory() { - const response = await t - .get('/stories/by-url', { - query: { url: validUrl }, - }) - .expect(200) - return response.body! - } - describe('/stories/by-url', () => { it('returns undefined when a site is not configured', async () => { const response = await t @@ -42,21 +34,21 @@ describe('story', () => { }) it('creates a story when it does not exist', async () => { - const story = await getStory() + const story = await getStory(t, validUrl) expect(story.id).toBeTruthy() expect(story.siteId).toEqual(site.id) }) it('retrieves existing story after it is created', async () => { - const story1 = await getStory() - const story2 = await getStory() + const story1 = await getStory(t, validUrl) + const story2 = await getStory(t, validUrl) expect(story1.id).toBeTruthy() expect(story1).toEqual(story2) }) it('prevents unique exceptions', async () => { - const p1 = getStory() - const p2 = getStory() + const p1 = getStory(t, validUrl) + const p2 = getStory(t, validUrl) const [story1, story2] = await Promise.all([p1, p2]) expect(story1.id).toBeTruthy() expect(story1).toEqual(story2) diff --git a/packages/server/src/story/StoryTestUtils.ts b/packages/server/src/story/StoryTestUtils.ts new file mode 100644 index 0000000..64a9366 --- /dev/null +++ b/packages/server/src/story/StoryTestUtils.ts @@ -0,0 +1,12 @@ +import {IAPIDef} from '@rondo/common' +import {RequestTester} from '../test-utils' + +export async function getStory(t: RequestTester, url: string) { + const response = await t + .get('/stories/by-url', { + query: {url}, + }) + .expect(200) + + return response.body! +}