feat: added new routes, changed posts name

This commit is contained in:
Hackntosh 2024-01-26 17:09:38 +00:00
parent b5fa098b9d
commit e6718ca54f
36 changed files with 560 additions and 258 deletions

View 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;

View file

@ -13,37 +13,37 @@ model User {
username String @unique username String @unique
email String @unique email String @unique
password String password String
posts Post[] kweeks Kweek[]
profileImage String? profileImage String?
likedPosts PostLike[] likedKweeks KweekLike[]
likedComments CommentLike[] likedComments CommentLike[]
followers Follows[] @relation("follower") followers Follows[] @relation("follower")
following Follows[] @relation("following") following Follows[] @relation("following")
postComments Comments[] kweeksComments Comments[]
fromNotifications Notifications[] @relation("fromNotifications") fromNotifications Notifications[] @relation("fromNotifications")
toNotifications Notifications[] @relation("toNotifications") toNotifications Notifications[] @relation("toNotifications")
socketId String? socketId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
} }
model Post { model Kweek {
id String @id @default(uuid()) id String @id @default(uuid())
content String content String
authorId String authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade) author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
likes PostLike[] likes KweekLike[]
comments Comments[] comments Comments[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model PostLike { model KweekLike {
id String @id @default(uuid()) id String @id @default(uuid())
postId String kweekId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade) kweek Kweek @relation(fields: [kweekId], references: [id], onDelete: Cascade)
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) 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. // 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 content String
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
postId String kweekId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade) kweek Kweek @relation(fields: [kweekId], references: [id], onDelete: Cascade)
likes CommentLike[] likes CommentLike[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt @default(now()) updatedAt DateTime @updatedAt @default(now())

View file

@ -1,18 +1,17 @@
import { Module } from "@nestjs/common"; 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 { APP_GUARD, APP_PIPE } from "@nestjs/core";
import { ZodValidationPipe } from "nestjs-zod"; import { ZodValidationPipe } from "nestjs-zod";
import { PostModule } from "./post/post.module";
import { AuthModule } from "./auth/auth.module"; import { AuthModule } from "./auth/auth.module";
import { ConfigModule } from "@nestjs/config"; import { ConfigModule } from "@nestjs/config";
import { JwtAuthGuard } from "./auth/jwt-auth.guard"; import { JwtAuthGuard } from "./auth/jwt-auth.guard";
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler"; import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis"; import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
import { KweeksModule } from './kweeks/kweeks.module';
@Module({ @Module({
imports: [ imports: [
UserModule, UserModule,
PostModule,
AuthModule, AuthModule,
ConfigModule.forRoot({ ConfigModule.forRoot({
isGlobal: true, 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`, `redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
), ),
}), }),
KweeksModule,
], ],
providers: [ providers: [
{ {

View file

@ -24,7 +24,7 @@ export class AuthController {
@Public() @Public()
@UseGuards(LocalAuthGuard) @UseGuards(LocalAuthGuard)
@Post("/login") @Post("/")
@ApiOperation({ summary: "Authenticates a user" }) @ApiOperation({ summary: "Authenticates a user" })
@ApiOkResponse({ status: 200, description: "Authenticated successfully" }) @ApiOkResponse({ status: 200, description: "Authenticated successfully" })
@ApiUnauthorizedResponse({ description: "Wrong username or password" }) @ApiUnauthorizedResponse({ description: "Wrong username or password" })

View file

@ -2,7 +2,7 @@ import { Module } from "@nestjs/common";
import { AuthService } from "./auth.service"; import { AuthService } from "./auth.service";
import { PassportModule } from "@nestjs/passport"; import { PassportModule } from "@nestjs/passport";
import { LocalStrategy } from "./local.strategy"; 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 { AuthController } from "./auth.controller";
import { JwtModule } from "@nestjs/jwt"; import { JwtModule } from "@nestjs/jwt";
import { JwtStrategy } from "./jwt.strategy"; import { JwtStrategy } from "./jwt.strategy";

View file

@ -1,7 +1,7 @@
import { Injectable } from "@nestjs/common"; 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 * 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"; import { JwtService } from "@nestjs/jwt";
@Injectable() @Injectable()
@ -15,7 +15,7 @@ export class AuthService {
username: string, username: string,
password: string, password: string,
): Promise<UserModel | null> { ): Promise<UserModel | null> {
const user = await this.userService.search(username); const user = await this.userService.auth_search(username);
if (user === undefined) { if (user === undefined) {
return null; return null;

View file

@ -2,7 +2,7 @@ import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport"; import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local"; import { Strategy } from "passport-local";
import { AuthService } from "./auth.service"; import { AuthService } from "./auth.service";
import { UserModel } from "src/user/models/user.model"; import { UserModel } from "src/users/models/user.model";
@Injectable() @Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) { export class LocalStrategy extends PassportStrategy(Strategy) {

View file

@ -0,0 +1 @@
export class CreateKweekDto {}

View file

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateKweekDto } from './create-kweek.dto';
export class UpdateKweekDto extends PartialType(CreateKweekDto) {}

View file

@ -0,0 +1 @@
export class Kweek {}

View 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();
});
});

View 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() {}
}

View 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 {}

View 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();
});
});

View 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`;
}
}

View file

@ -34,9 +34,8 @@ async function bootstrap() {
"JWT", "JWT",
) )
.addTag("Auth") .addTag("Auth")
.addTag("Comment") .addTag("Kweeks")
.addTag("Post") .addTag("Users")
.addTag("User")
.build(); .build();
const document = SwaggerModule.createDocument(app, config); const document = SwaggerModule.createDocument(app, config);

View file

@ -1 +0,0 @@
export class CreatePostDto {}

View file

@ -1,4 +0,0 @@
import { PartialType } from '@nestjs/swagger';
import { CreatePostDto } from './create-post.dto';
export class UpdatePostDto extends PartialType(CreatePostDto) {}

View file

@ -1 +0,0 @@
export class Post {}

View file

@ -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);
}
}

View file

@ -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 {}

View file

@ -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`;
}
}

View file

@ -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();
}
}

View file

@ -0,0 +1,8 @@
import { Module } from "@nestjs/common";
import { PrismaService } from "./prisma.service";
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

View 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();
});
}
}

View file

@ -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) {}

View file

@ -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
}

View file

@ -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 {}

View file

@ -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;
}
}

View 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) {}

View 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) {}

View file

@ -0,0 +1,5 @@
export type User = {
displayName: string;
username: string;
id: string;
};

View 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
View 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
View 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,
},
});
}
}