Skip to content

Commit

Permalink
Merge branch 'main' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
EasySouls committed Jan 21, 2025
2 parents cfa22cc + 5e5092d commit 8b624b9
Show file tree
Hide file tree
Showing 39 changed files with 2,748 additions and 1,357 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: corepack enable

- name: Use Yarn v4
run: corepack prepare yarn@4.5.1 --activate
run: corepack prepare yarn@4.6.0 --activate

- name: Install dependencies
run: yarn install
Expand All @@ -51,7 +51,7 @@ jobs:
run: corepack enable

- name: Use Yarn v4
run: corepack prepare yarn@4.5.1 --activate
run: corepack prepare yarn@4.6.0 --activate

- name: Install dependencies
run: yarn install
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ yarn-error.log*
/apps/frontend/.env

.env
/.yarn
Binary file removed .yarn/install-state.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion apps/backend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const config = [
{
ignores: ['**/.prettierrc.js'],
},
...compat.extends('plugin:@typescript-eslint/recommended', 'nestjs', 'plugin:@next/next/recommended'),
...compat.extends('plugin:@typescript-eslint/recommended', 'nestjs'),
{
plugins: {
'@typescript-eslint': typescriptEslintEslintPlugin,
Expand Down
30 changes: 15 additions & 15 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@
"seed": "ts-node prisma/seed.ts"
},
"dependencies": {
"@kir-dev/passport-authsch": "^2.1.0",
"@nestjs/common": "^10.4.7",
"@kir-dev/passport-authsch": "^2.2.2",
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.7",
"@nestjs/core": "^10.4.15",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.4.7",
"@nestjs/swagger": "^8.0.3",
"@prisma/client": "^6.0.1",
"@radix-ui/react-dialog": "^1.1.2",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/swagger": "^8.1.1",
"@prisma/client": "^6.2.1",
"@radix-ui/react-dialog": "^1.1.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"nestjs-prisma": "^0.23.0",
"nestjs-prisma": "^0.24.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
Expand All @@ -40,20 +40,20 @@
"sharp": "^0.33.5"
},
"devDependencies": {
"@faker-js/faker": "^9.2.0",
"@nestjs/cli": "^10.4.7",
"@faker-js/faker": "^9.3.0",
"@nestjs/cli": "^10.4.9",
"@nestjs/schematics": "^10.2.3",
"@types/express": "^5.0.0",
"@types/multer": "^1.4.12",
"@types/node": "^20.17.6",
"eslint": "^9.14.0",
"@types/node": "^20.17.12",
"eslint": "^9.18.0",
"eslint-config-nestjs": "^0.8.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"prisma": "^6.0.1",
"prisma": "^6.2.1",
"source-map-support": "^0.5.21",
"ts-loader": "^9.5.1",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "4.2.0",
"typescript": "^5.6.3"
"typescript": "^5.7.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- CreateTable
CREATE TABLE "Upvote" (
"id" SERIAL NOT NULL,
"postId" INTEGER NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Upvote_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Upvote_postId_userId_key" ON "Upvote"("postId", "userId");

-- AddForeignKey
ALTER TABLE "Upvote" ADD CONSTRAINT "Upvote_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Upvote" ADD CONSTRAINT "Upvote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("authSchId") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "Upvote_postId_idx" ON "Upvote"("postId");
2 changes: 1 addition & 1 deletion apps/backend/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
14 changes: 14 additions & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ model User {
applicationPeriods ApplicationPeriod[]
applications Application[]
posts Post[]
upvotes Upvote[]
idNumber String?
}

Expand Down Expand Up @@ -121,6 +122,19 @@ model Post {
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
upvotes Upvote[]
@@index([authorId])
}

model Upvote {
id Int @id @default(autoincrement())
post Post @relation(fields: [postId], references: [id])
postId Int
user User @relation(fields: [userId], references: [authSchId])
userId String
createdAt DateTime @default(now())
@@unique([postId, userId])
@@index([postId])
}
26 changes: 26 additions & 0 deletions apps/backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { randomInt } from 'crypto';
const prisma = new PrismaClient();

async function main() {
await prisma.upvote.deleteMany();
await prisma.post.deleteMany();
await prisma.application.deleteMany();
await prisma.applicationPeriod.deleteMany();
await prisma.user.deleteMany();
Expand Down Expand Up @@ -34,6 +36,8 @@ async function main() {
},
});

const userIds = [];

// Create 20 users who the application will belong to
for (let i = 1; i <= 20; i++) {
const user = await prisma.user.create({
Expand All @@ -49,6 +53,8 @@ async function main() {
},
});

userIds.push(user.authSchId);

// Randomly assign a status to the application
const statuses = Object.values(ApplicationStatus);
const applicationStatus = statuses[randomInt(statuses.length)];
Expand All @@ -62,6 +68,26 @@ async function main() {
},
});
}

// Create 5 posts with upvotes
for (let j = 1; j <= 5; j++) {
await prisma.post.create({
data: {
title: faker.lorem.sentence(),
content: faker.lorem.paragraphs(3),
preview: faker.lorem.paragraph(),
visible: true,
authorId: admin.authSchId,
upvotes: {
createMany: {
data: Array.from({ length: randomInt(10) }, (_, idx) => ({
userId: userIds[j + idx],
})),
},
},
},
});
}
}

main()
Expand Down
15 changes: 15 additions & 0 deletions apps/backend/src/auth/anon.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

/**
* This guard is used to allow anonymous users to access a route,
* but still have the user object in the request object.
*/
@Injectable()
export class AnonGuard extends AuthGuard('jwt') {
handleRequest<TUser = any>(err: any, user: any, info: any, context: ExecutionContext, _status?: any): TUser {
const request = context.switchToHttp().getRequest();
request.user = user;
return user;
}
}
9 changes: 8 additions & 1 deletion apps/backend/src/posts/dto/create-post.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ import { OmitType } from '@nestjs/swagger';

import { PostEntity } from '../entities/post.entity';

export class CreatePostDto extends OmitType(PostEntity, ['id', 'author', 'createdAt', 'updatedAt']) {}
export class CreatePostDto extends OmitType(PostEntity, [
'id',
'author',
'createdAt',
'updatedAt',
'upvotes',
'isUpvoted',
]) {}
4 changes: 4 additions & 0 deletions apps/backend/src/posts/entities/post.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class PostEntity {
@IsOptional()
@IsBoolean()
visible: boolean = true;
@IsNumber()
upvotes: number = 0;
@IsBoolean()
isUpvoted: boolean = false;
@ApiProperty({ type: User })
author: User;
createdAt: Date;
Expand Down
16 changes: 13 additions & 3 deletions apps/backend/src/posts/posts.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CurrentUser } from '@kir-dev/passport-authsch';
import { CurrentUser, CurrentUserOptional } from '@kir-dev/passport-authsch';
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
Expand All @@ -7,6 +7,7 @@ import { Roles } from 'src/auth/decorators/Roles.decorator';
import { RolesGuard } from 'src/auth/roles.guard';
import { PaginationDto } from 'src/dto/pagination.dto';

import { AnonGuard } from 'src/auth/anon.guard';
import { CreatePostDto } from './dto/create-post.dto';
import { SimplePostDto } from './dto/simple-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
Expand All @@ -25,19 +26,28 @@ export class PostsController {
return this.postsService.create(createPostDto, user);
}

@UseGuards(AnonGuard)
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number,
@Query('page_size', ParseIntPipe) pageSize: number
@Query('page_size', ParseIntPipe) pageSize: number,
@CurrentUserOptional() user?: User
): Promise<PaginationDto<SimplePostDto>> {
return this.postsService.findAll(page, pageSize);
return this.postsService.findAll(page, pageSize, user);
}

@Get(':id')
async findOne(@Param('id') id: string) {
return this.postsService.findOne(Number(id));
}

@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
@Post(':id/upvote')
async upvote(@Param('id') id: string, @CurrentUser() user: User) {
return this.postsService.upvote(Number(id), user);
}

@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles(Role.BODY_ADMIN, Role.BODY_MEMBER)
@ApiBearerAuth()
Expand Down
83 changes: 81 additions & 2 deletions apps/backend/src/posts/posts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class PostsService {
});
}

findAll(page?: number, pageSize?: number): Promise<PaginationDto<SimplePostDto>> {
findAll(page?: number, pageSize?: number, user?: User): Promise<PaginationDto<SimplePostDto>> {
const hasPagination = page !== -1 && pageSize !== -1;
const posts = this.prisma.post.findMany({
skip: hasPagination ? page * pageSize : undefined,
Expand All @@ -41,14 +41,30 @@ export class PostsService {
nickName: true,
},
},
upvotes: {
select: {
userId: true,
},
},
},
});
const total = this.prisma.post.count();

return Promise.all([posts, total])
.then(([posts, total]) => {
const limit = hasPagination ? Math.floor(total / pageSize) : 0;
const postsWithUpvotes = posts.map((post) => {
const upvotes = post.upvotes.length;
const isUpvoted = user ? post.upvotes.some((upvote) => upvote.userId === user.authSchId) : false;

return {
...post,
upvotes,
isUpvoted,
};
});
return {
data: posts,
data: postsWithUpvotes,
total,
page,
limit,
Expand Down Expand Up @@ -124,4 +140,67 @@ export class PostsService {
throw new InternalServerErrorException('An error occurred.');
}
}

async upvote(id: number, user: User) {
try {
const postToUpvote = await this.prisma.post.findUniqueOrThrow({
where: {
id,
},
include: {
upvotes: {
where: {
userId: user.authSchId,
},
select: {
userId: true,
id: true,
},
},
},
});

const hasUpvoted = postToUpvote.upvotes.some((upvote) => upvote.userId === user.authSchId);

// If the user has already upvoted, remove the upvote
if (hasUpvoted) {
const upvoteId = postToUpvote.upvotes.find((upvote) => upvote.userId === user.authSchId).id;

return this.prisma.post.update({
where: {
id,
},
data: {
upvotes: {
delete: {
id: upvoteId,
},
},
},
});
}

// Else, add the upvote
return this.prisma.post.update({
where: {
id,
},
data: {
upvotes: {
create: {
userId: user.authSchId,
},
},
},
});
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
switch (e.code) {
case 'P2025':
throw new NotFoundException('This post does not exist.');
}
}
throw new InternalServerErrorException('An error occurred.');
}
}
}
Loading

0 comments on commit 8b624b9

Please sign in to comment.