user address

This commit is contained in:
2025-11-23 00:35:20 +03:30
parent c8b0ee3c56
commit d9dde39a20
8 changed files with 215 additions and 51 deletions
@@ -1,25 +0,0 @@
import { Controller, Get, UseGuards, Query, ValidationPipe } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiOkResponse } from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { UserService } from '../user.service';
import { FindUsersDto } from '../dto/find-user.dto';
import { RestId } from 'src/common/decorators';
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@ApiTags('Admin/User')
@Controller('admin/user')
export class AdminUserController {
constructor(private readonly userService: UserService) {}
@ApiOperation({ summary: 'Get paginated list of users with filters' })
@ApiOkResponse({ description: 'Paginated list of users' })
@Get('/')
async findAll(
@Query(new ValidationPipe({ transform: true, whitelist: true }))
query: FindUsersDto,
@RestId() restId: string,
) {
return this.userService.findAll(restId, query);
}
}
@@ -0,0 +1,53 @@
import { Controller, Get, UseGuards, Patch, Body, ValidationPipe, Post } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody, ApiOkResponse } from '@nestjs/swagger';
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
import { UserService } from '../user.service';
import { UpdateUserDto } from '../dto/update-user.dto';
import { CreateUserAddressDto } from '../dto/create-user-address.dto';
import { UserId } from 'src/common/decorators/user-id.decorator';
@UseGuards(AuthGuard)
@ApiBearerAuth()
@ApiTags('User')
@Controller('user')
export class PublicUserController {
constructor(private readonly userService: UserService) {}
@ApiOperation({ summary: 'Get the current authenticated user profile' })
@Get('/me')
async getUser(@UserId() userId: string) {
const user = await this.userService.findById(userId);
return user;
}
@ApiOperation({ summary: 'Update the authenticated user profile' })
@ApiBody({ type: UpdateUserDto })
@Patch('/')
async updateUser(
@UserId() userId: string,
@Body(new ValidationPipe({ transform: true, whitelist: true })) dto: UpdateUserDto,
) {
const user = await this.userService.updateUser(userId, dto);
return user;
}
@ApiOperation({ summary: 'Get all addresses for the authenticated user' })
@ApiOkResponse({ description: 'List of user addresses' })
@Get('/addresses')
async getUserAddresses(@UserId() userId: string) {
const addresses = await this.userService.getUserAddresses(userId);
return addresses;
}
@ApiOperation({ summary: 'Create a new address for the authenticated user' })
@ApiBody({ type: CreateUserAddressDto })
@ApiOkResponse({ description: 'Created user address' })
@Post('/addresses')
async createUserAddress(
@UserId() userId: string,
@Body(new ValidationPipe({ transform: true, whitelist: true })) dto: CreateUserAddressDto,
) {
const address = await this.userService.createUserAddress(userId, dto);
return address;
}
}
@@ -1,32 +1,25 @@
import { Controller, Get, UseGuards, Patch, Body, ValidationPipe } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody } from '@nestjs/swagger';
import { Controller, Get, UseGuards, Query, ValidationPipe } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiOkResponse } from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { UserService } from '../user.service';
import { UpdateUserDto } from '../dto/update-user.dto';
import { UserId } from 'src/common/decorators/user-id.decorator';
import { FindUsersDto } from '../dto/find-user.dto';
import { RestId } from 'src/common/decorators';
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@ApiTags('User')
@Controller('user')
export class UserController {
@ApiTags('Admin/User')
@Controller('admin/user')
export class UsersController {
constructor(private readonly userService: UserService) {}
@ApiOperation({ summary: 'Get the current authenticated user profile' })
@Get('/me')
async getUser(@UserId() userId: string) {
const user = await this.userService.findById(userId);
return user;
}
@ApiOperation({ summary: 'Update the authenticated user profile' })
@ApiBody({ type: UpdateUserDto })
@Patch('/')
async updateUser(
@UserId() userId: string,
@Body(new ValidationPipe({ transform: true, whitelist: true })) dto: UpdateUserDto,
@ApiOperation({ summary: 'Get paginated list of users with filters' })
@ApiOkResponse({ description: 'Paginated list of users' })
@Get('/')
async findAll(
@Query(new ValidationPipe({ transform: true, whitelist: true }))
query: FindUsersDto,
@RestId() restId: string,
) {
const user = await this.userService.updateUser(userId, dto);
return user;
return this.userService.findAll(restId, query);
}
}
@@ -0,0 +1,52 @@
import { IsString, IsOptional, IsNumber, IsBoolean, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
/**
* DTO for creating a new user address.
*/
export class CreateUserAddressDto {
@ApiProperty({ description: 'Address title/label (e.g., "Home", "Work")', example: 'Home' })
@IsString()
title!: string;
@ApiProperty({ description: 'Street address', example: '123 Main Street' })
@IsString()
address!: string;
@ApiProperty({ description: 'City name', example: 'Tehran' })
@IsString()
city!: string;
@ApiPropertyOptional({ description: 'Province', example: 'Tehran Province' })
@IsOptional()
@IsString()
province?: string;
@ApiPropertyOptional({ description: 'Postal/ZIP code', example: '12345-6789' })
@IsOptional()
@IsString()
postalCode?: string;
@ApiProperty({ description: 'Latitude coordinate', example: 35.6892, minimum: -90, maximum: 90 })
@IsNumber()
@Min(-90)
@Max(90)
latitude!: number;
@ApiProperty({ description: 'Longitude coordinate', example: 51.3890, minimum: -180, maximum: 180 })
@IsNumber()
@Min(-180)
@Max(180)
longitude!: number;
@ApiPropertyOptional({ description: 'Contact phone for this address', example: '+989123456789' })
@IsOptional()
@IsString()
phone?: string;
@ApiPropertyOptional({ description: 'Set as default address', example: false, default: false })
@IsOptional()
@IsBoolean()
isDefault?: boolean;
}
@@ -0,0 +1,36 @@
import { Entity, Property, ManyToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { User } from './user.entity';
@Entity({ tableName: 'user_addresses' })
export class UserAddress extends BaseEntity {
@ManyToOne(() => User)
user!: User;
@Property()
title!: string; // e.g., "Home", "Work", "Office"
@Property()
address!: string;
@Property()
city!: string;
@Property({ nullable: true })
province?: string;
@Property({ nullable: true })
postalCode?: string | null = null;
@Property({ type: 'float' })
latitude!: number;
@Property({ type: 'float' })
longitude!: number;
@Property({ nullable: true })
phone?: string; // Contact phone for this address
@Property({ default: false })
isDefault: boolean = false; // Whether this is the default address
}
+8 -1
View File
@@ -1,6 +1,7 @@
import { Entity, Property, OneToOne } from '@mikro-orm/core';
import { Entity, Property, OneToOne, OneToMany, Collection, Cascade } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { UserAddress } from './user-address.entity';
@Entity({ tableName: 'users' })
export class User extends BaseEntity {
@@ -36,4 +37,10 @@ export class User extends BaseEntity {
@Property({ default: 0, type: 'int' })
points: number = 0;
@OneToMany(() => UserAddress, address => address.user, {
cascade: [Cascade.ALL],
orphanRemoval: true,
})
addresses = new Collection<UserAddress>(this);
}
+5 -3
View File
@@ -1,16 +1,18 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { AdminUserController } from './controllers/adminUser.controller';
import { UsersController } from './controllers/user.controller';
import { PublicUserController } from './controllers/public-user.controller';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from './entities/user.entity';
import { UserAddress } from './entities/user-address.entity';
import { JwtModule } from '@nestjs/jwt';
import { UserRepository } from './repositories/user.repository';
import { RefreshToken } from './entities/refresh-token.entity';
@Module({
providers: [UserService, UserRepository],
controllers: [AdminUserController],
imports: [MikroOrmModule.forFeature([User, RefreshToken]), JwtModule],
controllers: [UsersController, PublicUserController],
imports: [MikroOrmModule.forFeature([User, UserAddress, RefreshToken]), JwtModule],
exports: [UserService, UserRepository],
})
export class UserModule {}
+46
View File
@@ -1,9 +1,11 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { FilterQuery, RequiredEntityData } from '@mikro-orm/core';
import { User } from './entities/user.entity';
import { UserAddress } from './entities/user-address.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { UpdateUserDto } from './dto/update-user.dto';
import { FindUsersDto } from './dto/find-user.dto';
import { CreateUserAddressDto } from './dto/create-user-address.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { UserRepository } from './repositories/user.repository';
@@ -137,4 +139,48 @@ export class UserService {
},
};
}
async getUserAddresses(userId: string): Promise<UserAddress[]> {
const user = await this.userRepository.findOne({ id: userId }, { populate: ['addresses'] });
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`);
}
// Load addresses if not already loaded
await user.addresses.loadItems();
return user.addresses.getItems();
}
async createUserAddress(userId: string, dto: CreateUserAddressDto): Promise<UserAddress> {
const user = await this.userRepository.findOne({ id: userId });
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`);
}
// If setting as default, unset other default addresses
if (dto.isDefault) {
const existingAddresses = await this.em.find(UserAddress, { user: { id: userId }, isDefault: true });
for (const address of existingAddresses) {
address.isDefault = false;
}
await this.em.flush();
}
// Create new address
const address = this.em.create(UserAddress, {
user,
title: dto.title,
address: dto.address,
city: dto.city,
province: dto.province,
postalCode: dto.postalCode,
latitude: dto.latitude,
longitude: dto.longitude,
phone: dto.phone,
isDefault: dto.isDefault ?? false,
});
await this.em.persistAndFlush(address);
return address;
}
}