diff --git a/migrations/20250104074851-create-user.js b/migrations/20250104074851-create-user.js new file mode 100644 index 0000000..912f654 --- /dev/null +++ b/migrations/20250104074851-create-user.js @@ -0,0 +1,62 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + email: { + type: Sequelize.STRING, + unique: true, + allowNull: false + }, + password: { + type: Sequelize.STRING, + allowNull: false + }, + role: { + type: Sequelize.STRING, + defaultValue: 'user' + }, + firstName: { + type: Sequelize.STRING, + allowNull: false + }, + lastName: { + type: Sequelize.STRING, + allowNull: false + }, + username: { + type: Sequelize.STRING, + unique: true, + allowNull: false + }, + phoneNumber: { + type: Sequelize.STRING, + unique: true, + allowNull: false + }, + gender: { + type: Sequelize.ENUM("male", "female"), + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('Users'); + } +}; diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..c584076 --- /dev/null +++ b/models/user.js @@ -0,0 +1,30 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class User extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + } + } + User.init({ + email: DataTypes.STRING, + password: DataTypes.STRING, + role: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + username: DataTypes.STRING, + phoneNumber: DataTypes.STRING, + gender: DataTypes.ENUM + }, { + sequelize, + modelName: 'User', + }); + return User; +}; \ No newline at end of file diff --git a/src/cart/entities/cart.entity.ts b/src/cart/entities/cart.entity.ts index 6369839..5d96d83 100644 --- a/src/cart/entities/cart.entity.ts +++ b/src/cart/entities/cart.entity.ts @@ -8,14 +8,14 @@ export class Cart extends Model { @Column userId: number; - @BelongsTo(() => User) + @BelongsTo(() => User, { onDelete: 'CASCADE' }) user: User; @ForeignKey(() => Product) @Column productId: number; - @BelongsTo(() => Product) + @BelongsTo(() => Product, { onDelete: 'CASCADE' }) product: Product; @Column({ diff --git a/src/invoice/entities/invoice.entity.ts b/src/invoice/entities/invoice.entity.ts index f72cf23..f90ba5f 100644 --- a/src/invoice/entities/invoice.entity.ts +++ b/src/invoice/entities/invoice.entity.ts @@ -1,14 +1,16 @@ import { Table, Model, Column, BelongsTo, ForeignKey } from "sequelize-typescript"; import { User } from "../../users/entities/user.entity"; -import { Product } from "../../products/entities/product.entity"; +import { Product } from "../../products/entities/product.entity"; @Table export class Invoice extends Model { @ForeignKey(() => User) @Column userId: number; - @BelongsTo(() => User) + + @BelongsTo(() => User, { onDelete: 'CASCADE' }) user: User; + @Column totalAmount: number; } diff --git a/src/main.ts b/src/main.ts index f76bc8d..9be6371 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,10 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useGlobalPipes(new ValidationPipe()); await app.listen(process.env.PORT ?? 3000); } bootstrap(); diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts new file mode 100644 index 0000000..0594f8a --- /dev/null +++ b/src/users/dto/create-user.dto.ts @@ -0,0 +1,29 @@ +import { IsString, IsEmail, IsEnum, IsNotEmpty, IsOptional, Matches } from "class-validator"; + +export class CreateUserDto { + @IsEmail({}, { message: "Invalid email format" }) + email: string; + + @IsString() + @IsNotEmpty({ message: "Password is required" }) + password: string; + + @IsString() + @IsNotEmpty({ message: "First name is required" }) + firstName: string; + + @IsString() + @IsNotEmpty({ message: "Last name is required" }) + lastName: string; + + @IsString() + @IsNotEmpty({ message: "Username is required" }) + username: string; + + @IsString() + @Matches(/^[0-9]{11}$/, { message: "Phone number must be 10 digits" }) + phoneNumber: string; + + @IsEnum(["male", "female"], { message: "Gender must be 'male' or 'female'" }) + gender: string; +} diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index b115632..54ab7fb 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -1,11 +1,31 @@ -import { Column, Table, Model } from "sequelize-typescript"; +import { Column, Table, Model, DataType } from "sequelize-typescript"; @Table export class User extends Model { @Column({ unique: true }) email: string; + @Column password: string; + @Column({ defaultValue: "user" }) role: string; + + @Column + firstName: string; + + @Column + lastName: string; + + @Column({ unique: true }) + username: string; + + @Column({ unique: true }) + phoneNumber: string; + + @Column({ + type: DataType.ENUM("male", "female"), + allowNull: false, + }) + gender: string; } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index f47fd16..6de2520 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,15 +1,15 @@ -import { Controller, Post, Body, Res, UseGuards, Get } from "@nestjs/common"; +import { Controller, Post, Body} from "@nestjs/common"; import { UsersService } from "./users.service"; import { User } from "./entities/user.entity"; +import { CreateUserDto } from "./dto/create-user.dto"; @Controller("user") export class UsersController { constructor(private readonly usersService: UsersService) {} @Post("register") - async register(@Body() body: { email: string; password: string }):Promise { - const { email, password } = body; - return this.usersService.register(email, password); + async register(@Body() createUserDto: CreateUserDto):Promise { + return this.usersService.register(createUserDto); } @Post("login") diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 229c563..4e4f17d 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -2,41 +2,44 @@ import { HttpException, HttpStatus, Injectable, UnauthorizedException, BadReques import { InjectModel } from "@nestjs/sequelize"; import { User } from "./entities/user.entity"; import * as bcrypt from "bcrypt"; -import { JwtService } from '@nestjs/jwt'; -import { ConfigService } from '@nestjs/config'; +import { JwtService } from "@nestjs/jwt"; +import { ConfigService } from "@nestjs/config"; +import { CreateUserDto } from "./dto/create-user.dto"; @Injectable() export class UsersService { constructor( @InjectModel(User) private readonly userModel: typeof User, - private readonly jwtService: JwtService, - private readonly configService: ConfigService + private readonly jwtService: JwtService, + private readonly configService: ConfigService, ) {} // Register method - async register(email: string, password: string): Promise { - if (!email) throw new BadRequestException("Email should be entered"); - if (!password) throw new BadRequestException("Password should be entered"); - + async register(createUserDto: CreateUserDto): Promise { try { - const hashedPassword = await bcrypt.hash(password, 10); + createUserDto.password = await bcrypt.hash( + createUserDto.password, + parseInt(process.env.BCRYPT_SALT_ROUNDS || "10", 10) + ); - const userExists = await this.userModel.findOne({ where: { email } }); + const userExists = await this.userModel.findOne({ + where: { email: createUserDto.email }, + }); if (userExists) { - throw new BadRequestException('Email already exists'); + throw new BadRequestException("Email already exists"); } - const user = await this.userModel.create({ - email, - password: hashedPassword, - }); - + const user = await this.userModel.create(createUserDto); return user; } catch (error) { - throw new HttpException(`An error occurred: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR); + throw new HttpException( + `An error occurred: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR + ); } } + // Login method async login(email: string, password: string): Promise<{ token: string }> { if (!email) throw new BadRequestException("Email should be entered"); diff --git a/src/wallet/entities/wallet.entity.ts b/src/wallet/entities/wallet.entity.ts index a3ebca3..35e23b3 100644 --- a/src/wallet/entities/wallet.entity.ts +++ b/src/wallet/entities/wallet.entity.ts @@ -1,4 +1,4 @@ -import { Model, Table, Column, ForeignKey, BelongsTo } from 'sequelize-typescript'; +import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from 'sequelize-typescript'; import { User } from '../../users/entities/user.entity'; @Table @@ -7,9 +7,13 @@ export class Wallet extends Model { @Column userId: number; - @BelongsTo(() => User) + @BelongsTo(() => User, { onDelete: 'CASCADE' }) user: User; - @Column + @Column({ + type: DataType.DECIMAL(10, 2), + allowNull: false, + defaultValue: 0, + }) balance: number; }