-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from Guzbyte-tech/feature/User-entity-crud-api
User Entity and CRUD APIs
- Loading branch information
Showing
13 changed files
with
359 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
import { DataSource } from 'typeorm'; | ||
import { TestEntity } from '../entities/testEntity'; | ||
import { User } from '../entities/User'; | ||
|
||
export const testDataSource = new DataSource({ | ||
type: 'sqlite', | ||
database: ':memory:', | ||
synchronize: true, | ||
entities: [TestEntity], | ||
entities: [TestEntity, User] | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { Request, Response } from 'express'; | ||
import UserService from '../services/User.service'; | ||
import AppDataSource from '../config/ormconfig'; | ||
|
||
const userService = new UserService(AppDataSource); | ||
|
||
export const createUser = async (req: Request, res: Response):Promise<void > => { | ||
try { | ||
const user = await userService.createUser(req.body); | ||
res.status(201).json({success: true, message:"User Created Successfully", data: user}); | ||
} catch (error) { | ||
res.status(500).json({ error: error.message }); | ||
} | ||
}; | ||
|
||
export const getUser = async (req: Request, res: Response) :Promise<void> => { | ||
try { | ||
const user = await userService.getUserById(Number(req.params.id)); | ||
if (!user) { | ||
res.status(404).json({success: false, message: 'User not found' }); | ||
} else { | ||
res.status(200).json({success: true, message:"User Retrieved Successfully", data: user}); | ||
} | ||
} catch (error) { | ||
res.status(500).json({success: false, message: "Internal Server Error", error: error.message }); | ||
} | ||
}; | ||
|
||
export const updateUser = async (req: Request, res: Response) : Promise<void> => { | ||
try { | ||
const user = await userService.updateUser(Number(req.params.id), req.body); | ||
if (!user) { | ||
res.status(404).json({success: false, message: 'User not found' }); | ||
} else { | ||
res.status(200).json({success: true, message:"User Updated Successfully", data: user}); | ||
} | ||
} catch (error) { | ||
res.status(500).json({success: false, message: "Internal Server Error", error: error.message }); | ||
} | ||
}; | ||
|
||
export const deleteUser = async (req: Request, res: Response) :Promise<void> => { | ||
try { | ||
const success = await userService.deleteUser(Number(req.params.id)); | ||
if (!success) { | ||
res.status(404).json({success: false, message: 'User not found' }); | ||
} else { | ||
res.send(204); | ||
} | ||
} catch (error) { | ||
res.status(500).json({success: false, message: "Internal Server Error", error: error.message }); | ||
} | ||
}; | ||
|
||
export const getAllUsers = async (req: Request, res: Response) :Promise<void> => { | ||
try { | ||
const users = await userService.getAllUsers(); | ||
res.status(200).json({success: true, message:"User Retrieved", data: users}); | ||
} catch (error) { | ||
res.status(500).json({success: false, message: "Internal Server Error", error: error.message }); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { IsEmail, IsNotEmpty, IsOptional, IsEnum, Matches, MinLength, MaxLength } from 'class-validator'; | ||
|
||
export class CreateUserDto { | ||
@IsNotEmpty() | ||
@Matches(/^0x[a-fA-F0-9]{40}$/, { message: 'Invalid wallet address format' }) | ||
walletAddress: string; | ||
|
||
@IsOptional() | ||
@MinLength(2, { message: 'Name is too short' }) | ||
@MaxLength(50, { message: 'Name is too long' }) | ||
name?: string; | ||
|
||
@IsOptional() | ||
@IsEmail({}, { message: 'Invalid email format' }) | ||
email?: string; | ||
|
||
@IsNotEmpty() | ||
@IsEnum(['buyer', 'seller', 'admin'], { message: 'Role must be buyer, seller, or admin' }) | ||
role: 'buyer' | 'seller' | 'admin'; | ||
} | ||
|
||
export class UpdateUserDto { | ||
@IsOptional() | ||
@MinLength(2, { message: 'Name is too short' }) | ||
@MaxLength(50, { message: 'Name is too long' }) | ||
name?: string; | ||
|
||
@IsOptional() | ||
@IsEmail({}, { message: 'Invalid email format' }) | ||
email?: string; | ||
|
||
@IsOptional() | ||
@IsEnum(['buyer', 'seller', 'admin'], { message: 'Role must be buyer, seller, or admin' }) | ||
role?: 'buyer' | 'seller' | 'admin'; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; | ||
|
||
@Entity('users') | ||
export class User { | ||
@PrimaryGeneratedColumn() | ||
id: number; | ||
|
||
@Column({ unique: true }) | ||
walletAddress: string; | ||
|
||
@Column({ nullable: true }) | ||
name: string; | ||
|
||
@Column({ nullable: true }) | ||
email: string; | ||
|
||
@Column() | ||
role: 'buyer' | 'seller' | 'admin'; | ||
|
||
@CreateDateColumn() | ||
createdAt: Date; | ||
|
||
@UpdateDateColumn() | ||
updatedAt: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { plainToInstance } from 'class-transformer'; | ||
import { validate, ValidationError } from 'class-validator'; | ||
import { Request, Response, NextFunction } from 'express'; | ||
|
||
/** | ||
* A validation middleware for validating request body data against a DTO class. | ||
* @param dtoClass The DTO class to validate against. | ||
*/ | ||
export function validationMiddleware(dtoClass: any) { | ||
return async (req: Request, res: Response, next: NextFunction): Promise<void> => { | ||
const dtoInstance = plainToInstance(dtoClass, req.body); | ||
const errors: ValidationError[] = await validate(dtoInstance); | ||
|
||
if (errors.length > 0) { | ||
const errorDetails = errors.map((error) => ({ | ||
property: error.property, | ||
constraints: error.constraints, | ||
})); | ||
|
||
res.status(422).json({ | ||
message: 'Validation failed', | ||
errors: errorDetails, | ||
}); | ||
} else { | ||
next(); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { MigrationInterface, QueryRunner } from "typeorm"; | ||
|
||
export class CreateUserTable1734140974017 implements MigrationInterface { | ||
name = 'CreateUserTable1734140974017' | ||
|
||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`CREATE TABLE "test_entity" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_cc0413536e3afc0e586996bea40" PRIMARY KEY ("id"))`); | ||
await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "walletAddress" character varying NOT NULL, "name" character varying, "email" character varying, "role" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_fc71cd6fb73f95244b23e2ef113" UNIQUE ("walletAddress"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`DROP TABLE "users"`); | ||
await queryRunner.query(`DROP TABLE "test_entity"`); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Router } from 'express'; | ||
import { createUser, deleteUser, getAllUsers, getUser, updateUser } from '../controllers/UserController'; | ||
import { validationMiddleware } from '../middleware/userValidation.middleware'; | ||
import { CreateUserDto, UpdateUserDto } from '../dtos/UserDTO'; | ||
|
||
|
||
|
||
const router = Router(); | ||
|
||
router.get('/', getAllUsers); | ||
router.post('/create', validationMiddleware(CreateUserDto), createUser); | ||
router.get('/show/:id', getUser); | ||
router.put('/update/:id',validationMiddleware(UpdateUserDto), updateUser); | ||
router.delete('/delete/:id', deleteUser); | ||
|
||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { QueryFailedError, Repository } from "typeorm"; | ||
import { User } from "../entities/User"; | ||
import { DataSource } from "typeorm"; | ||
|
||
|
||
export class UserService { | ||
private userRepository: Repository<User>; | ||
|
||
constructor(dataSource: DataSource) { | ||
this.userRepository = dataSource.getRepository(User); | ||
} | ||
|
||
async createUser(data: Partial<User>): Promise<User> { | ||
|
||
try { | ||
const user = this.userRepository.create(data); | ||
return await this.userRepository.save(user); | ||
} catch (error) { | ||
if (error instanceof QueryFailedError && error.message.includes("UNIQUE constraint failed")) { | ||
throw new Error("The wallet address is already in use. Please use a unique wallet address."); | ||
} | ||
if (error.code === '23505' || error.code === 'SQLITE_CONSTRAINT') { // Handle unique constraint errors | ||
if (error.message.includes('UQ_fc71cd6fb73f95244b23e2ef113')) { | ||
throw new Error('The wallet address is already in use. Please use a unique wallet address.'); | ||
} | ||
} | ||
throw error; | ||
} | ||
|
||
} | ||
|
||
async getUserById(id: number): Promise<User | null> { | ||
return await this.userRepository.findOneBy({ id }); | ||
} | ||
|
||
async updateUser(id: number, data: Partial<User>): Promise<User | null> { | ||
try { | ||
await this.userRepository.update(id, data); | ||
return await this.getUserById(id); | ||
} catch (error) { | ||
if (error.code === '23505' || error.code === 'SQLITE_CONSTRAINT') { // Handle unique constraint errors | ||
if (error.message.includes('UQ_fc71cd6fb73f95244b23e2ef113')) { | ||
throw new Error('The wallet address is already in use. Please use a unique wallet address.'); | ||
} | ||
} | ||
|
||
if (error.code === '23505' || error.code === 'SQLITE_CONSTRAINT') { // Handle unique constraint errors | ||
if (error.message.includes('UQ_fc71cd6fb73f95244b23e2ef113')) { | ||
throw new Error('The wallet address is already in use. Please use a unique wallet address.'); | ||
} | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
async deleteUser(id: number): Promise<boolean> { | ||
const result = await this.userRepository.delete(id); | ||
return result.affected === 1; | ||
} | ||
|
||
async getAllUsers(): Promise<User[]> { | ||
return await this.userRepository.find(); | ||
} | ||
} | ||
|
||
export default UserService; |
Oops, something went wrong.