mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-12 06:40:58 +04:30
Initial commit: Meshkee CMS API
NestJS backend with Prisma, Docker Compose for Postgres/Redis, and deploy docs for the production VM.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get, Param, Query } from '@nestjs/common';
|
||||
import { CitiesService } from './cities.service';
|
||||
import { ListCitiesDto } from './dto/list-cities.dto';
|
||||
|
||||
@Controller('cities')
|
||||
export class CitiesController {
|
||||
constructor(private readonly service: CitiesService) {}
|
||||
|
||||
@Get()
|
||||
list(@Query() query: ListCitiesDto) {
|
||||
return this.service.list(query);
|
||||
}
|
||||
|
||||
@Get(':cityId')
|
||||
getOne(@Param('cityId') cityId: string) {
|
||||
return this.service.getOne(cityId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CitiesController } from './cities.controller';
|
||||
import { CitiesService } from './cities.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CitiesController],
|
||||
providers: [CitiesService],
|
||||
exports: [CitiesService],
|
||||
})
|
||||
export class CitiesModule {}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CityLevel, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ListCitiesDto } from './dto/list-cities.dto';
|
||||
|
||||
type CityRecord = Prisma.CityGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
parentId: true;
|
||||
level: true;
|
||||
nameFa: true;
|
||||
nameEn: true;
|
||||
landlineCode: true;
|
||||
slug: true;
|
||||
sortOrder: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
@Injectable()
|
||||
export class CitiesService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(query: ListCitiesDto) {
|
||||
const where: Prisma.CityWhereInput = {
|
||||
isActive: true,
|
||||
...(query.level ? { level: query.level } : {}),
|
||||
};
|
||||
|
||||
if (query.parentId) {
|
||||
where.parentId = BigInt(query.parentId);
|
||||
} else if (query.parentSlug) {
|
||||
const parent = await this.prisma.city.findFirst({
|
||||
where: { slug: query.parentSlug, isActive: true },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!parent) {
|
||||
return { items: [] };
|
||||
}
|
||||
|
||||
where.parentId = parent.id;
|
||||
} else if (query.level === CityLevel.province || query.level === CityLevel.city) {
|
||||
throw new BadRequestException('parentId or parentSlug is required for this level');
|
||||
} else {
|
||||
where.level = CityLevel.country;
|
||||
}
|
||||
|
||||
const items = await this.prisma.city.findMany({
|
||||
where,
|
||||
orderBy: [{ sortOrder: 'asc' }, { nameEn: 'asc' }],
|
||||
select: {
|
||||
id: true,
|
||||
parentId: true,
|
||||
level: true,
|
||||
nameFa: true,
|
||||
nameEn: true,
|
||||
landlineCode: true,
|
||||
slug: true,
|
||||
sortOrder: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { items: items.map((item) => this.serialize(item)) };
|
||||
}
|
||||
|
||||
async getOne(cityIdRaw: string) {
|
||||
const city = await this.prisma.city.findFirst({
|
||||
where: { id: BigInt(cityIdRaw), isActive: true },
|
||||
select: {
|
||||
id: true,
|
||||
parentId: true,
|
||||
level: true,
|
||||
nameFa: true,
|
||||
nameEn: true,
|
||||
landlineCode: true,
|
||||
slug: true,
|
||||
sortOrder: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!city) {
|
||||
throw new NotFoundException('City not found');
|
||||
}
|
||||
|
||||
return { city: this.serialize(city) };
|
||||
}
|
||||
|
||||
private serialize(city: CityRecord) {
|
||||
return {
|
||||
id: city.id.toString(),
|
||||
parentId: city.parentId?.toString() ?? null,
|
||||
level: city.level,
|
||||
nameFa: city.nameFa,
|
||||
nameEn: city.nameEn,
|
||||
landlineCode: city.landlineCode,
|
||||
slug: city.slug,
|
||||
sortOrder: city.sortOrder,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CityLevel } from '@prisma/client';
|
||||
import { IsEnum, IsOptional, IsString, Matches } from 'class-validator';
|
||||
|
||||
export class ListCitiesDto {
|
||||
@IsOptional()
|
||||
@IsEnum(CityLevel)
|
||||
level?: CityLevel;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
parentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {
|
||||
message: 'parentSlug must be lowercase letters, numbers, and hyphens',
|
||||
})
|
||||
parentSlug?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user