diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..26b1d05 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, HttpCode, HttpStatus, Post } from "@nestjs/common"; +import { SignUpDto } from "src/auth/auth.dto"; +import { AuthService } from "src/auth/auth.service"; + +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) { + } + + //POST signup + @HttpCode(HttpStatus.CREATED) + @Post("signup") + async signUp(@Body() dto: SignUpDto) { + console.log(dto) + return this.authService.doRegister(dto) + } + + + + //POST signin + //GET me -- Get current user data via jwt + //DELETE me + //PATCH me +} diff --git a/src/auth/auth.dto.ts b/src/auth/auth.dto.ts new file mode 100644 index 0000000..0f9f005 --- /dev/null +++ b/src/auth/auth.dto.ts @@ -0,0 +1,50 @@ +import { + IsEmail, + IsNotEmpty, + IsString, + IsStrongPassword, + MaxLength, + MinLength, +} from "class-validator"; + +export class SignUpDto { + @MinLength(1) + @MaxLength(24) + @IsNotEmpty() + @IsString() + firstName: string; + + @MinLength(1) + @MaxLength(24) + @IsNotEmpty() + @IsString() + lastName: string; + + @MaxLength(32) + @IsEmail() + @IsNotEmpty() + email: string; + + @IsString() + @IsNotEmpty() + @IsStrongPassword({ + minLength: 6, + minSymbols: 1, + }) + password: string; +} + +export class SignInDto { + @MaxLength(32) + @IsEmail() + @IsNotEmpty() + email: string; + + @IsString() + @IsNotEmpty() + @IsStrongPassword({ + minLength: 6, + minSymbols: 1, + }) + password: string; +} \ No newline at end of file diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 230f4d3..72c2206 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,10 +1,12 @@ import { Module } from "@nestjs/common"; -import { DrizzleModule } from "src/drizzle/drizzle.module"; -import { AuthService } from "./auth.service"; import { CredentialsModule } from "src/credentials/credentials.module"; +import { DrizzleModule } from "src/drizzle/drizzle.module"; +import { AuthController } from "./auth.controller"; +import { AuthService } from "./auth.service"; @Module({ imports: [DrizzleModule, CredentialsModule], providers: [AuthService], + controllers: [AuthController], }) export class AuthModule {} diff --git a/src/auth/auth.schema.ts b/src/auth/auth.schema.ts new file mode 100644 index 0000000..21cf3e3 --- /dev/null +++ b/src/auth/auth.schema.ts @@ -0,0 +1,31 @@ +import { UsersTableInsertSchema } from "src/schema"; +import { z } from "zod"; + +export const SignUpBodySchema = z.object({ + firstName: z.string({ message: "'firstName' should be a string." }).max(24), + lastName: z.string({ message: "'lastName' should be a string." }).max(24), + email: z + .string({ message: "'email' should be a string." }) + .max(32, "'email' should be less than 32 characters") + .email("Of course 'email' should be an email."), + password: z + .string({ message: "'password' should be a string." }) + .min(6) + //regex for strong password + .regex( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,32}$/, + ) + .max(32), +}); + +export const SignInBodySchema = z.object({ + email: z + .string({ message: "'email' should be a string." }) + .max(32, "'email' should be less than 32 characters") + .email("Of course 'email' should be an email."), + + password: z + .string({ message: "'password' should be a string." }) + .min(6) + .max(32), +}); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 13cceed..62d1d3d 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,27 +1,80 @@ -// biome-ignore lint/style/useImportType: used by Next.js -import { Injectable, OnModuleInit } from "@nestjs/common"; -// biome-ignore lint/style/useImportType: used by Next.js +import * as console from "node:console"; +import { + Injectable, + OnModuleInit, + UnauthorizedException, +} from "@nestjs/common"; +import { eq } from "drizzle-orm"; +import { SignUpBodySchema } from "src/auth/auth.schema"; +import { CredentialsService } from "src/credentials/credentials.service"; import { DrizzleService } from "src/drizzle/drizzle.service"; import { UsersTable } from "src/schema"; -// biome-ignore lint/style/useImportType: used by Next.js -import { CredentialsService } from "src/credentials/credentials.service"; +import { SignInDto, SignUpDto } from "src/auth/auth.dto"; @Injectable() export class AuthService implements OnModuleInit { constructor( private db: DrizzleService, - private credentials: CredentialsService + private credentials: CredentialsService, ) {} - doRegister() {} - doLogin() {} + async doRegister(data: SignUpDto) { + console.log(data); + const existingUser = await this.db + .use() + .select() + .from(UsersTable) + .where(eq(UsersTable.email, data.email)) + .prepare("userByEmail") + .execute(); + if (existingUser.length !== 0) + throw new UnauthorizedException("Already exist"); + const query = await this.db + .use() + .insert(UsersTable) + .values({ + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + hash: await this.credentials.hash(data.password), + }) + .returning() + .prepare("insertUser") + .execute() + .catch((err) => { + console.error(err); + throw new UnauthorizedException( + "Error occurred while inserting user", + err, + ); + }); + return { + message: "User created, check your email for validation.", + token: await this.credentials.signAuthToken({sub: query[0].uuid}) + } + } + + doLogin(data: SignInDto) {} + async fetchUser(userId: string) { - const userInDb = await this.db.use().select().from(UsersTable); - console.log("Users : \n", userInDb); + //TODO Pagination + const usersInDb = await this.db.use().select().from(UsersTable); + const result = { + total: usersInDb.length, + users: usersInDb.map((user)=>{ + delete user.hash + return { + ...user + } + }) + } + console.log(result) } updateUser() {} deleteUser() {} async onModuleInit() { - await this.fetchUser("ee"); + setTimeout(()=>{ + this.fetchUser("ee"); + }, 2000) } }