mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 17:41:15 +00:00
feat: added new routes, changed posts name
This commit is contained in:
parent
b5fa098b9d
commit
e6718ca54f
36 changed files with 560 additions and 258 deletions
63
prisma/migrations/20240126133535_renamed_posts/migration.sql
Normal file
63
prisma/migrations/20240126133535_renamed_posts/migration.sql
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `postId` on the `Comments` table. All the data in the column will be lost.
|
||||
- You are about to drop the `Post` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `PostLike` table. If the table is not empty, all the data it contains will be lost.
|
||||
- Added the required column `kweekId` to the `Comments` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Comments" DROP CONSTRAINT "Comments_postId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Post" DROP CONSTRAINT "Post_authorId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PostLike" DROP CONSTRAINT "PostLike_postId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PostLike" DROP CONSTRAINT "PostLike_userId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Comments" DROP COLUMN "postId",
|
||||
ADD COLUMN "kweekId" TEXT NOT NULL;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Post";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "PostLike";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Kweek" (
|
||||
"id" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Kweek_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "KweekLike" (
|
||||
"id" TEXT NOT NULL,
|
||||
"kweekId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "KweekLike_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Kweek" ADD CONSTRAINT "Kweek_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "KweekLike" ADD CONSTRAINT "KweekLike_kweekId_fkey" FOREIGN KEY ("kweekId") REFERENCES "Kweek"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "KweekLike" ADD CONSTRAINT "KweekLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Comments" ADD CONSTRAINT "Comments_kweekId_fkey" FOREIGN KEY ("kweekId") REFERENCES "Kweek"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -13,37 +13,37 @@ model User {
|
|||
username String @unique
|
||||
email String @unique
|
||||
password String
|
||||
posts Post[]
|
||||
kweeks Kweek[]
|
||||
profileImage String?
|
||||
likedPosts PostLike[]
|
||||
likedKweeks KweekLike[]
|
||||
likedComments CommentLike[]
|
||||
followers Follows[] @relation("follower")
|
||||
following Follows[] @relation("following")
|
||||
postComments Comments[]
|
||||
kweeksComments Comments[]
|
||||
fromNotifications Notifications[] @relation("fromNotifications")
|
||||
toNotifications Notifications[] @relation("toNotifications")
|
||||
socketId String?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model Post {
|
||||
model Kweek {
|
||||
id String @id @default(uuid())
|
||||
content String
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||
likes PostLike[]
|
||||
likes KweekLike[]
|
||||
comments Comments[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model PostLike {
|
||||
id String @id @default(uuid())
|
||||
postId String
|
||||
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
model KweekLike {
|
||||
id String @id @default(uuid())
|
||||
kweekId String
|
||||
kweek Kweek @relation(fields: [kweekId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
// I should join these two up? Yeah, but I will not do it since it didn't work on the first time.
|
||||
|
@ -71,8 +71,8 @@ model Comments {
|
|||
content String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
postId String
|
||||
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
kweekId String
|
||||
kweek Kweek @relation(fields: [kweekId], references: [id], onDelete: Cascade)
|
||||
likes CommentLike[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt @default(now())
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { UserModule } from "./user/user.module";
|
||||
import { UserModule } from "./users/users.module";
|
||||
import { APP_GUARD, APP_PIPE } from "@nestjs/core";
|
||||
import { ZodValidationPipe } from "nestjs-zod";
|
||||
import { PostModule } from "./post/post.module";
|
||||
import { AuthModule } from "./auth/auth.module";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { JwtAuthGuard } from "./auth/jwt-auth.guard";
|
||||
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
|
||||
import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
|
||||
import { KweeksModule } from './kweeks/kweeks.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UserModule,
|
||||
PostModule,
|
||||
AuthModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
|
@ -23,6 +22,7 @@ import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
|
|||
`redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
|
||||
),
|
||||
}),
|
||||
KweeksModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ export class AuthController {
|
|||
|
||||
@Public()
|
||||
@UseGuards(LocalAuthGuard)
|
||||
@Post("/login")
|
||||
@Post("/")
|
||||
@ApiOperation({ summary: "Authenticates a user" })
|
||||
@ApiOkResponse({ status: 200, description: "Authenticated successfully" })
|
||||
@ApiUnauthorizedResponse({ description: "Wrong username or password" })
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Module } from "@nestjs/common";
|
|||
import { AuthService } from "./auth.service";
|
||||
import { PassportModule } from "@nestjs/passport";
|
||||
import { LocalStrategy } from "./local.strategy";
|
||||
import { UserModule } from "src/user/user.module";
|
||||
import { UserModule } from "src/users/users.module";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
import { JwtStrategy } from "./jwt.strategy";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { UserService } from "src/user/user.service";
|
||||
import { UserService } from "src/users/users.service";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { UserModel } from "src/user/models/user.model";
|
||||
import { UserModel } from "src/users/models/user.model";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
|
||||
@Injectable()
|
||||
|
@ -15,7 +15,7 @@ export class AuthService {
|
|||
username: string,
|
||||
password: string,
|
||||
): Promise<UserModel | null> {
|
||||
const user = await this.userService.search(username);
|
||||
const user = await this.userService.auth_search(username);
|
||||
|
||||
if (user === undefined) {
|
||||
return null;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable, UnauthorizedException } from "@nestjs/common";
|
|||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-local";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { UserModel } from "src/user/models/user.model";
|
||||
import { UserModel } from "src/users/models/user.model";
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
|
|
1
src/kweeks/dto/create-kweek.dto.ts
Normal file
1
src/kweeks/dto/create-kweek.dto.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export class CreateKweekDto {}
|
4
src/kweeks/dto/update-kweek.dto.ts
Normal file
4
src/kweeks/dto/update-kweek.dto.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateKweekDto } from './create-kweek.dto';
|
||||
|
||||
export class UpdateKweekDto extends PartialType(CreateKweekDto) {}
|
1
src/kweeks/entities/kweek.entity.ts
Normal file
1
src/kweeks/entities/kweek.entity.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export class Kweek {}
|
20
src/kweeks/kweeks.controller.spec.ts
Normal file
20
src/kweeks/kweeks.controller.spec.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { KweeksController } from './kweeks.controller';
|
||||
import { KweeksService } from './kweeks.service';
|
||||
|
||||
describe('KweeksController', () => {
|
||||
let controller: KweeksController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [KweeksController],
|
||||
providers: [KweeksService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<KweeksController>(KweeksController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
78
src/kweeks/kweeks.controller.ts
Normal file
78
src/kweeks/kweeks.controller.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from "@nestjs/common";
|
||||
import { KweeksService } from "./kweeks.service";
|
||||
import { CreateKweekDto } from "./dto/create-kweek.dto";
|
||||
import { UpdateKweekDto } from "./dto/update-kweek.dto";
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { Public } from "src/public.decorator";
|
||||
|
||||
@ApiTags("Kweeks")
|
||||
@Controller("kweeks")
|
||||
export class KweeksController {
|
||||
constructor(private readonly kweeksService: KweeksService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Creates a kweek" })
|
||||
@ApiBearerAuth("JWT")
|
||||
create(@Body() createKweekDto: CreateKweekDto) {
|
||||
return this.kweeksService.create(createKweekDto);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Retrieves information about a kweek" })
|
||||
findOne(@Param("id") id: string) {
|
||||
return this.kweeksService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@ApiOperation({ summary: "Updates a kweek content" })
|
||||
@ApiBearerAuth("JWT")
|
||||
update(@Param("id") id: string, @Body() updateKweekDto: UpdateKweekDto) {
|
||||
return this.kweeksService.update(+id, updateKweekDto);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "Deletes a kweek" })
|
||||
@ApiBearerAuth("JWT")
|
||||
remove(@Param("id") id: string) {
|
||||
return this.kweeksService.remove(+id);
|
||||
}
|
||||
|
||||
@Post(":id/like")
|
||||
@ApiOperation({ summary: "Likes a kweek" })
|
||||
@ApiBearerAuth("JWT")
|
||||
likeKweek() {}
|
||||
|
||||
@Public()
|
||||
@Get(":id/comments")
|
||||
@ApiOperation({ summary: "Retrieves comments of a kweek" })
|
||||
comments() {}
|
||||
|
||||
@Public()
|
||||
@Get(":id/comments/:comment_id")
|
||||
@ApiOperation({ summary: "Retrieves information about a comment" })
|
||||
comment() {}
|
||||
|
||||
@Patch(":id/comments/:comment_id")
|
||||
@ApiOperation({ summary: "Updates a comment content" })
|
||||
@ApiBearerAuth("JWT")
|
||||
updateComment() {}
|
||||
|
||||
@Delete(":id/comments/:comment_id")
|
||||
@ApiOperation({ summary: "Deletes a comment" })
|
||||
@ApiBearerAuth("JWT")
|
||||
removeComment() {}
|
||||
|
||||
@Post(":id/comments/:comment_id/like")
|
||||
@ApiOperation({ summary: "Likes a comment" })
|
||||
@ApiBearerAuth("JWT")
|
||||
likeComment() {}
|
||||
}
|
9
src/kweeks/kweeks.module.ts
Normal file
9
src/kweeks/kweeks.module.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { KweeksService } from './kweeks.service';
|
||||
import { KweeksController } from './kweeks.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [KweeksController],
|
||||
providers: [KweeksService],
|
||||
})
|
||||
export class KweeksModule {}
|
18
src/kweeks/kweeks.service.spec.ts
Normal file
18
src/kweeks/kweeks.service.spec.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { KweeksService } from './kweeks.service';
|
||||
|
||||
describe('KweeksService', () => {
|
||||
let service: KweeksService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [KweeksService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<KweeksService>(KweeksService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
26
src/kweeks/kweeks.service.ts
Normal file
26
src/kweeks/kweeks.service.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateKweekDto } from './dto/create-kweek.dto';
|
||||
import { UpdateKweekDto } from './dto/update-kweek.dto';
|
||||
|
||||
@Injectable()
|
||||
export class KweeksService {
|
||||
create(createKweekDto: CreateKweekDto) {
|
||||
return 'This action adds a new kweek';
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all kweeks`;
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} kweek`;
|
||||
}
|
||||
|
||||
update(id: number, updateKweekDto: UpdateKweekDto) {
|
||||
return `This action updates a #${id} kweek`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} kweek`;
|
||||
}
|
||||
}
|
|
@ -34,9 +34,8 @@ async function bootstrap() {
|
|||
"JWT",
|
||||
)
|
||||
.addTag("Auth")
|
||||
.addTag("Comment")
|
||||
.addTag("Post")
|
||||
.addTag("User")
|
||||
.addTag("Kweeks")
|
||||
.addTag("Users")
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export class CreatePostDto {}
|
|
@ -1,4 +0,0 @@
|
|||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreatePostDto } from './create-post.dto';
|
||||
|
||||
export class UpdatePostDto extends PartialType(CreatePostDto) {}
|
|
@ -1 +0,0 @@
|
|||
export class Post {}
|
|
@ -1,44 +0,0 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from "@nestjs/common";
|
||||
import { PostService } from "./post.service";
|
||||
import { CreatePostDto } from "./dto/create-post.dto";
|
||||
import { UpdatePostDto } from "./dto/update-post.dto";
|
||||
import { ApiTags } from "@nestjs/swagger";
|
||||
|
||||
@ApiTags("Post")
|
||||
@Controller("post")
|
||||
export class PostController {
|
||||
constructor(private readonly postService: PostService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createPostDto: CreatePostDto) {
|
||||
return this.postService.create(createPostDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.postService.findAll();
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
findOne(@Param("id") id: string) {
|
||||
return this.postService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
update(@Param("id") id: string, @Body() updatePostDto: UpdatePostDto) {
|
||||
return this.postService.update(+id, updatePostDto);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
remove(@Param("id") id: string) {
|
||||
return this.postService.remove(+id);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PostService } from './post.service';
|
||||
import { PostController } from './post.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [PostController],
|
||||
providers: [PostService],
|
||||
})
|
||||
export class PostModule {}
|
|
@ -1,26 +0,0 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { CreatePostDto } from "./dto/create-post.dto";
|
||||
import { UpdatePostDto } from "./dto/update-post.dto";
|
||||
|
||||
@Injectable()
|
||||
export class PostService {
|
||||
create(createPostDto: CreatePostDto) {
|
||||
return "This action adds a new post";
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return "This action returns all post";
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} post`;
|
||||
}
|
||||
|
||||
update(id: number, updatePostDto: UpdatePostDto) {
|
||||
return `This action updates a #${id} post`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} post`;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { Injectable, OnModuleInit } from "@nestjs/common";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
}
|
8
src/prisma/prisma.module.ts
Normal file
8
src/prisma/prisma.module.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { PrismaService } from "./prisma.service";
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
18
src/prisma/prisma.service.ts
Normal file
18
src/prisma/prisma.service.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { INestApplication, Injectable, OnModuleInit } from "@nestjs/common";
|
||||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService
|
||||
extends PrismaClient<Prisma.PrismaClientOptions, "beforeExit">
|
||||
implements OnModuleInit
|
||||
{
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
this.$on("beforeExit", async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import { createZodDto } from "nestjs-zod";
|
||||
import { z } from "nestjs-zod/z";
|
||||
|
||||
// TODO: Add posts, liked_posts, liked_comments, followers, following, post_comments and notifications field
|
||||
|
||||
export const UserSchema = z
|
||||
.object({
|
||||
id: z.string().uuid(),
|
||||
displayName: z.string(),
|
||||
username: z.string(),
|
||||
email: z.string().email(),
|
||||
password: z.password(),
|
||||
profileImage: z.string().url(),
|
||||
createdAt: z.date(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export class UserModel extends createZodDto(UserSchema) {}
|
|
@ -1,43 +0,0 @@
|
|||
import { Body, Controller, Get, Post, Request } from "@nestjs/common";
|
||||
import {
|
||||
ApiBadRequestResponse,
|
||||
ApiBearerAuth,
|
||||
ApiCreatedResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiUnauthorizedResponse,
|
||||
} from "@nestjs/swagger";
|
||||
import { UserService } from "./user.service";
|
||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||
import { Public } from "src/public.decorator";
|
||||
|
||||
@ApiTags("User")
|
||||
@Controller("user")
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
// GET
|
||||
@Get("/me")
|
||||
@ApiOperation({ summary: "Returns information about the logged user" })
|
||||
@ApiBearerAuth("JWT")
|
||||
@ApiUnauthorizedResponse({
|
||||
description: "Not authenticated / Invalid JWT Token",
|
||||
})
|
||||
async me(@Request() req) {
|
||||
return req.user; // TODO: Add typing to req.user
|
||||
}
|
||||
|
||||
// POST
|
||||
@Public()
|
||||
@Post("/signup")
|
||||
@ApiOperation({ summary: "Creates a new account" })
|
||||
@ApiCreatedResponse({ description: "Account created successfully" })
|
||||
@ApiBadRequestResponse({
|
||||
description:
|
||||
"Missing field / Invalid username / Invalid email / Weak password",
|
||||
})
|
||||
async create(@Body() createUserDTO: CreateUserDTO) {
|
||||
return this.userService.create(createUserDTO);
|
||||
}
|
||||
|
||||
// PUT
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { UserController } from "./user.controller";
|
||||
import { UserService } from "./user.service";
|
||||
import { PrismaService } from "src/prisma.service";
|
||||
|
||||
@Module({
|
||||
controllers: [UserController],
|
||||
providers: [UserService, PrismaService],
|
||||
exports: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
|
@ -1,66 +0,0 @@
|
|||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||
import { PrismaService } from "src/prisma.service";
|
||||
import { UserModel } from "./models/user.model";
|
||||
import * as bcrypt from "bcrypt";
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
}: CreateUserDTO): Promise<
|
||||
Pick<UserModel, "displayName" | "username" | "createdAt">
|
||||
> {
|
||||
if ((await this.prisma.user.findFirst({ where: { username } })) != null) {
|
||||
throw new BadRequestException("Username already in use");
|
||||
}
|
||||
|
||||
if ((await this.prisma.user.findFirst({ where: { email } })) != null) {
|
||||
throw new BadRequestException("Email already in use");
|
||||
}
|
||||
|
||||
// Password encryption
|
||||
const salt = await bcrypt.genSalt(15);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password: hash,
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async search(username: string): Promise<UserModel> {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
profileImage: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
password: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
20
src/users/dto/update-name.dto.ts
Normal file
20
src/users/dto/update-name.dto.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { createZodDto } from "nestjs-zod";
|
||||
import { z } from "nestjs-zod/z";
|
||||
|
||||
export const UpdateNameSchema = z
|
||||
.object({
|
||||
username: z
|
||||
.string()
|
||||
.regex(
|
||||
/^[a-zA-Z0-9_.]{5,15}$/,
|
||||
"The username must have alphanumerics characters, underscore, dots and it must be between 5 and 15 characters",
|
||||
)
|
||||
.toLowerCase()
|
||||
.describe("New username - optional")
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
displayName: z.string({ required_error: "Display name is required" }),
|
||||
})
|
||||
.required();
|
||||
|
||||
export class UpdateNameDTO extends createZodDto(UpdateNameSchema) {}
|
22
src/users/models/user.model.ts
Normal file
22
src/users/models/user.model.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { createZodDto } from "nestjs-zod";
|
||||
import { z } from "nestjs-zod/z";
|
||||
|
||||
export const UserSchema = z
|
||||
.object({
|
||||
id: z.string().uuid(),
|
||||
displayName: z.string().optional(),
|
||||
username: z.string(),
|
||||
email: z.string().email(),
|
||||
password: z.password(),
|
||||
kweeks: z.array(z.object({})).optional(),
|
||||
profileImage: z.string().url().optional(),
|
||||
likedKweeks: z.array(z.object({})).optional(),
|
||||
likedComments: z.array(z.object({})).optional(),
|
||||
followers: z.number(),
|
||||
following: z.number(),
|
||||
kweeksComments: z.array(z.object({})).optional(),
|
||||
createdAt: z.date(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export class UserModel extends createZodDto(UserSchema) {}
|
5
src/users/types/user.type.ts
Normal file
5
src/users/types/user.type.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export type User = {
|
||||
displayName: string;
|
||||
username: string;
|
||||
id: string;
|
||||
};
|
94
src/users/users.controller.ts
Normal file
94
src/users/users.controller.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Request,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiBadRequestResponse,
|
||||
ApiBearerAuth,
|
||||
ApiCreatedResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiUnauthorizedResponse,
|
||||
} from "@nestjs/swagger";
|
||||
import { UserService } from "./users.service";
|
||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||
import { Public } from "src/public.decorator";
|
||||
import { UpdateNameDTO } from "./dto/update-name.dto";
|
||||
import { User } from "./types/user.type";
|
||||
|
||||
@ApiTags("Users")
|
||||
@Controller("users")
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
// POST
|
||||
@Public()
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Creates a new account" })
|
||||
@ApiCreatedResponse({ description: "Account created successfully" })
|
||||
@ApiBadRequestResponse({
|
||||
description:
|
||||
"Missing field / Invalid username / Invalid email / Weak password",
|
||||
})
|
||||
create(@Body() createUserDTO: CreateUserDTO) {
|
||||
return this.userService.create(createUserDTO);
|
||||
}
|
||||
|
||||
// GET
|
||||
@Get("/profile")
|
||||
@ApiOperation({ summary: "Returns information about the logged user" })
|
||||
@ApiBearerAuth("JWT")
|
||||
@ApiUnauthorizedResponse({
|
||||
description: "Not authenticated / Invalid JWT Token",
|
||||
})
|
||||
me(@Request() req) {
|
||||
return req.user; // TODO: Add typing to req.user
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(":username")
|
||||
@ApiOperation({ summary: "Returns information about a user" })
|
||||
@ApiNotFoundResponse({ description: "User not found" })
|
||||
@HttpCode(200)
|
||||
info(@Param("username") username: string) {
|
||||
return this.userService.info(username);
|
||||
}
|
||||
|
||||
// PATCH
|
||||
@Patch()
|
||||
@ApiOperation({
|
||||
summary: "Updates the username or display name of a logged user",
|
||||
})
|
||||
@ApiBearerAuth("JWT")
|
||||
updateName(@Body() { displayName, username }: UpdateNameDTO, @Request() req) {
|
||||
return this.userService.updateName(req.user as User, username, displayName);
|
||||
}
|
||||
|
||||
@Patch("/email")
|
||||
@ApiOperation({ summary: "Updates the email of a logged user" })
|
||||
@ApiBearerAuth("JWT")
|
||||
updateEmail() {}
|
||||
|
||||
@Patch("/password")
|
||||
@ApiOperation({ summary: "Updates the password of a logged user" })
|
||||
@ApiBearerAuth("JWT")
|
||||
updatePassword() {}
|
||||
|
||||
@Patch("/image")
|
||||
@ApiOperation({ summary: "Add a profile image" })
|
||||
@ApiBearerAuth("JWT")
|
||||
uploadProfileImage() {}
|
||||
|
||||
// DELETE
|
||||
@Delete()
|
||||
@ApiOperation({ summary: "Deletes the account of a logged user" })
|
||||
@ApiBearerAuth("JWT")
|
||||
remove() {}
|
||||
}
|
12
src/users/users.module.ts
Normal file
12
src/users/users.module.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { UserController } from "./users.controller";
|
||||
import { UserService } from "./users.service";
|
||||
import { PrismaModule } from "src/prisma/prisma.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [UserController],
|
||||
providers: [UserService],
|
||||
exports: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
136
src/users/users.service.ts
Normal file
136
src/users/users.service.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { UserModel } from "./models/user.model";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { User } from "./types/user.type";
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
async auth_search(username: string): Promise<UserModel> {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
profileImage: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
password: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async info(username: string): Promise<UserModel> {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { username },
|
||||
select: {
|
||||
id: true,
|
||||
profileImage: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
followers: true,
|
||||
following: true,
|
||||
kweeks: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
return {
|
||||
...user,
|
||||
followers: user.followers.length,
|
||||
following: user.following.length,
|
||||
};
|
||||
}
|
||||
|
||||
async create({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
}: CreateUserDTO): Promise<
|
||||
Pick<UserModel, "displayName" | "username" | "createdAt">
|
||||
> {
|
||||
if ((await this.prisma.user.findFirst({ where: { username } })) != null) {
|
||||
throw new BadRequestException("Username already in use");
|
||||
}
|
||||
|
||||
if ((await this.prisma.user.findFirst({ where: { email } })) != null) {
|
||||
throw new BadRequestException("Email already in use");
|
||||
}
|
||||
|
||||
// Password encryption
|
||||
const salt = await bcrypt.genSalt(15);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password: hash,
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async updateName(
|
||||
loggedUser: User,
|
||||
username: string | undefined,
|
||||
displayName: string,
|
||||
): Promise<Pick<User, "username" | "displayName">> {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { id: loggedUser.id },
|
||||
});
|
||||
|
||||
if (username !== undefined && username.trim() !== user.username) {
|
||||
const isAlreadyInUse = await this.prisma.user.findFirst({
|
||||
where: { username },
|
||||
});
|
||||
if (isAlreadyInUse != null && isAlreadyInUse.username !== user.username) {
|
||||
throw new BadRequestException("Username already in use");
|
||||
}
|
||||
}
|
||||
|
||||
return await this.prisma.user.update({
|
||||
where: {
|
||||
id: loggedUser.id,
|
||||
},
|
||||
data: {
|
||||
displayName,
|
||||
username: username ?? user.username,
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue