Commit 08db2e37 by Augusto Fonte

Inspection changes

parent c8c5d592
{
"name": "api-cellnex",
"version": "0.0.1",
"version": "0.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "api-cellnex",
"version": "0.0.1",
"version": "0.0.2",
"license": "UNLICENSED",
"dependencies": {
"@nestjs-modules/mailer": "^2.0.2",
......@@ -17,14 +17,16 @@
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.0",
"@nestjs/swagger": "^11.1.1",
"@prisma/client": "^6.6.0",
"@prisma/client": "^6.9.0",
"@types/nodemailer": "^6.4.17",
"@types/uuid": "^10.0.0",
"adm-zip": "^0.5.16",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"csv-parser": "^3.2.0",
"date-fns": "^4.1.0",
"fast-xml-parser": "^5.2.5",
"jimp": "^0.22.12",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.0",
......@@ -4457,9 +4459,9 @@
}
},
"node_modules/@prisma/client": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.6.0.tgz",
"integrity": "sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg==",
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz",
"integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
......@@ -5983,6 +5985,15 @@
"node": ">=0.8"
}
},
"node_modules/adm-zip": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
"license": "MIT",
"engines": {
"node": ">=12.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
......@@ -8753,6 +8764,24 @@
],
"license": "BSD-3-Clause"
},
"node_modules/fast-xml-parser": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
"integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"strnum": "^2.1.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
......@@ -15245,6 +15274,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strnum": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz",
"integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/strtok3": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz",
......
......@@ -29,14 +29,16 @@
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.0",
"@nestjs/swagger": "^11.1.1",
"@prisma/client": "^6.6.0",
"@prisma/client": "^6.9.0",
"@types/nodemailer": "^6.4.17",
"@types/uuid": "^10.0.0",
"adm-zip": "^0.5.16",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"csv-parser": "^3.2.0",
"date-fns": "^4.1.0",
"fast-xml-parser": "^5.2.5",
"jimp": "^0.22.12",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.0",
......
import fs from 'fs';
import AdmZip from 'adm-zip';
import { XMLParser } from 'fast-xml-parser';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function importKmzToSites(kmzPath: string) {
// 1. Extract KML from KMZ
const zip = new AdmZip(kmzPath);
const kmlEntry = zip.getEntries().find((e) => e.entryName.endsWith('.kml'));
if (!kmlEntry) throw new Error('No KML file found in KMZ');
const kmlString = kmlEntry.getData().toString('utf8');
// 2. Parse KML
const parser = new XMLParser({ ignoreAttributes: false });
const kml = parser.parse(kmlString);
// 3. Extract Placemarks (handle both array and single object)
const placemarks = (() => {
const doc = kml.kml.Document;
if (Array.isArray(doc.Placemark)) return doc.Placemark;
if (doc.Placemark) return [doc.Placemark];
return [];
})();
for (const placemark of placemarks) {
const name = placemark.name || '';
const [longitude, latitude] = placemark.Point.coordinates
.split(',')
.map(Number);
// You can customize how you extract siteCode/siteName here
const siteCode = name;
const siteName = name;
// 4. Insert into Site model (skip if missing lat/lon)
if (latitude && longitude && siteCode) {
try {
await prisma.site.create({
data: {
siteCode,
siteName,
latitude,
longitude,
},
});
console.log(`Imported site: ${siteCode} (${latitude}, ${longitude})`);
} catch (err) {
console.error(`Failed to import site ${siteCode}:`, err.message);
}
}
}
console.log('Import complete!');
}
// Usage: npx ts-node prisma/import-sites-from-kmz.ts path/to/your/file.kmz
const kmzPath = process.argv[2];
if (!kmzPath) {
console.error(
'Usage: npx ts-node prisma/import-sites-from-kmz.ts <file.kmz>',
);
process.exit(1);
}
importKmzToSites(kmzPath)
.catch(console.error)
.finally(() => prisma.$disconnect());
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding maintenance questions...');
// Clear existing questions
await prisma.maintenanceQuestion.deleteMany({});
// Create questions
const questions = [
{
question: 'Site access condition',
orderIndex: 1,
},
{
question: 'Site infrastructure condition',
orderIndex: 2,
},
{
question: 'Equipment condition',
orderIndex: 3,
},
{
question: 'Power system condition',
orderIndex: 4,
},
{
question: 'Cooling system condition',
orderIndex: 5,
},
{
question: 'Security features condition',
orderIndex: 6,
},
{
question: 'Safety equipment presence and condition',
orderIndex: 7,
},
{
question: 'Site cleanliness',
orderIndex: 8,
},
{
question: 'Vegetation control',
orderIndex: 9,
},
{
question: 'Surrounding area condition',
orderIndex: 10,
},
];
for (const question of questions) {
await prisma.maintenanceQuestion.create({
data: question,
});
}
console.log('Maintenance questions have been seeded!');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
\ No newline at end of file
......@@ -4,32 +4,129 @@ import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
const hashedPassword = await bcrypt.hash('brandit123465', 10);
const hashedPassword = await bcrypt.hash('brandit123465', 10);
const superadmin = await prisma.user.upsert({
where: { email: 'augusto.fonte@brandit.pt' },
update: {
isActive: true,
role: Role.SUPERADMIN,
password: hashedPassword,
},
create: {
email: 'augusto.fonte@brandit.pt',
name: 'Augusto Fonte',
password: hashedPassword,
role: Role.SUPERADMIN,
isActive: true,
},
});
const superadmin = await prisma.user.upsert({
where: { email: 'augusto.fonte@brandit.pt' },
update: {
isActive: true,
role: Role.SUPERADMIN,
password: hashedPassword,
},
create: {
email: 'augusto.fonte@brandit.pt',
name: 'Augusto Fonte',
password: hashedPassword,
role: Role.SUPERADMIN,
isActive: true,
},
});
console.log('Created/Updated superadmin user:', superadmin);
console.log('Created/Updated superadmin user:', superadmin);
// Inspection questions to seed
const inspectionQuestions = [
{
question:
'Cada componente de carril-guia foi ficado pelo meus uma vez, conforme dispositivo na secção B',
},
{
question:
'As distâncias entre fixações são no máx 1,68cm (2,24m escadas gémeas ZAL, Conforme dispositivo na secção B',
},
{
question:
'Nas uniões, as juntas são todas inferiores a 5mm, conforme dispositivo na secção B',
},
{
question:
'As uniões roscadas entre edificação e os elementos de fixação correspondem ai dispositivo da secção B',
},
{
question:
'Os elementos de fixação estão corretamente montados e todas as uniões roscadas (incluindo as de fábrica) estão firmemente apertadas. (binários de aperto conforme secção B)',
},
{
question:
'Todas as uniões roscadas estão protegidas contra o desaperto, em conformidade com o dispositivo da secção E',
},
{
question:
"No início do percurrso de subida encontra-se montado um 'batente de bloqueio inferior' em conformidade com o dispositivo em B",
},
{
question:
"No fim do percurso de subida encontra-se montado um 'batente de bloqueio superior' ou um 'batente de bloqueio terminal', em conforme indicado na secção B",
},
{
question:
"No início do percurso de subida, não montado diretamente ai nível do solo, estão montados dois (2) batentes de bloqueio 'inferior', conforme indicado na secção B",
},
{
question:
'o carril-guia passa pelo menos 1 metro acima da aresta superior do patamar, conforme o dispositivo na secção B',
},
{
question:
'Em conformidade com o dispositivo na secção B, não existem extensões acima da escada com mais de 38cm sem reforço de lonfarina (52,5xm em escadas YAL e ZAL)',
},
{
question:
'O reforço da longarina está corretamente montada conforme o dispositivo na secção B',
},
{
question:
'O ângulo de flexão máximo nas peças flexiveis foi observado (ver secção C)',
},
{
question:
'Todos os troços estão montados corretamente, em conformidade com o dispositivo da secção B',
},
{
question:
'As uniões dos trilhos-guia estão corretamente instaladas, conforme com o dispositivo da secção B',
},
{ question: 'A passagem entre trilhos-guia está alinhada' },
{ question: 'O carril-guia está livre de sujidades' },
{
question:
'Só foram utilizados elementos de fixação e uniões roscadas protegidos contra a corrosão (inspeções: os elementos de fixação e uniões não apresentam corrosão)',
},
{
question:
'O aparelho anti queda Soll só se deixa montar no sentido correto do seu duncionamento no percurso de subida e descida',
},
{ question: 'Existe a placa de identificação do fabricante' },
{
question:
'Foi realizado um percurso de ensaio e não forma detetadas quaisquer falhas',
},
{ question: 'Só foram instalados componentes do fabricante' },
{
question:
'A Escada e/ou elementos de suporte não apresentam danos visiveis, indício de deficiente fixação ou falhas de componentes que ponham em causa a sua utilização',
},
];
// Seed InspectionQuestions
await prisma.inspectionQuestion.deleteMany();
for (let i = 0; i < inspectionQuestions.length; i++) {
const q = inspectionQuestions[i];
await prisma.inspectionQuestion.create({
data: {
question: q.question,
orderIndex: i + 1,
},
});
}
console.log('Seeded InspectionQuestions:', inspectionQuestions.length);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
\ No newline at end of file
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
......@@ -15,7 +15,7 @@ import { join } from 'path';
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
import { DashboardModule } from './modules/dashboard/dashboard.module';
import { PartnersModule } from './modules/partners/partners.module';
import { MaintenanceModule } from './modules/maintenance/maintenance.module';
import { InspectionModule } from './modules/inspection/inspection.module';
@Module({
imports: [
......@@ -55,7 +55,7 @@ import { MaintenanceModule } from './modules/maintenance/maintenance.module';
CommentsModule,
DashboardModule,
PartnersModule,
MaintenanceModule,
InspectionModule,
],
controllers: [AppController],
providers: [
......
......@@ -39,7 +39,7 @@ async function bootstrap() {
.addTag('users', 'User management endpoints')
.addTag('sites', 'Site management endpoints')
.addTag('candidates', 'Candidate management endpoints')
.addTag('maintenance', 'Site maintenance management endpoints')
.addTag('inspection', 'Site inspection management endpoints')
.addBearerAuth(
{
type: 'http',
......
......@@ -6,12 +6,12 @@ import {
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { MaintenanceResponseOption } from './maintenance-response-option.enum';
import { InspectionResponseOption } from './inspection-response-option.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateMaintenanceResponseDto {
export class CreateInspectionResponseDto {
@ApiProperty({
description: 'The ID of the maintenance question being answered',
description: 'The ID of the inspection question being answered',
example: 1,
type: Number,
})
......@@ -19,13 +19,13 @@ export class CreateMaintenanceResponseDto {
questionId: number;
@ApiProperty({
description: 'The response to the maintenance question',
enum: MaintenanceResponseOption,
example: MaintenanceResponseOption.YES,
enumName: 'MaintenanceResponseOption',
description: 'The response to the inspection question',
enum: InspectionResponseOption,
example: InspectionResponseOption.YES,
enumName: 'InspectionResponseOption',
})
@IsString()
response: MaintenanceResponseOption;
response: InspectionResponseOption;
@ApiPropertyOptional({
description:
......@@ -39,9 +39,9 @@ export class CreateMaintenanceResponseDto {
comment?: string;
}
export class CreateMaintenanceDto {
export class CreateInspectionDto {
@ApiProperty({
description: 'Date when the maintenance was performed',
description: 'Date when the inspection was performed',
example: '2025-05-21T13:00:00.000Z',
type: String,
})
......@@ -49,7 +49,7 @@ export class CreateMaintenanceDto {
date: string;
@ApiProperty({
description: 'ID of the site where the maintenance was performed',
description: 'ID of the site where the inspection was performed',
example: 1,
type: Number,
})
......@@ -57,8 +57,8 @@ export class CreateMaintenanceDto {
siteId: number;
@ApiPropertyOptional({
description: 'Optional general comment about the maintenance',
example: 'Regular annual maintenance. Site is in good overall condition.',
description: 'Optional general comment about the inspection',
example: 'Regular annual inspection. Site is in good overall condition.',
type: String,
})
@IsString()
......@@ -66,8 +66,8 @@ export class CreateMaintenanceDto {
comment?: string;
@ApiProperty({
description: 'Responses to maintenance questions',
type: [CreateMaintenanceResponseDto],
description: 'Responses to inspection questions',
type: [CreateInspectionResponseDto],
example: [
{
questionId: 1,
......@@ -87,6 +87,6 @@ export class CreateMaintenanceDto {
],
})
@ValidateNested({ each: true })
@Type(() => CreateMaintenanceResponseDto)
responses: CreateMaintenanceResponseDto[];
@Type(() => CreateInspectionResponseDto)
responses: CreateInspectionResponseDto[];
}
import { IsDateString, IsInt, IsOptional } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
export class FindMaintenanceDto {
export class FindInspectionDto {
@ApiPropertyOptional({
description: 'Filter maintenance records by site ID',
description: 'Filter inspection records by site ID',
example: 1,
type: Number,
})
......@@ -13,7 +13,7 @@ export class FindMaintenanceDto {
@ApiPropertyOptional({
description:
'Filter maintenance records with date greater than or equal to this date',
'Filter inspection records with date greater than or equal to this date',
example: '2025-01-01T00:00:00.000Z',
type: String,
})
......@@ -23,7 +23,7 @@ export class FindMaintenanceDto {
@ApiPropertyOptional({
description:
'Filter maintenance records with date less than or equal to this date',
'Filter inspection records with date less than or equal to this date',
example: '2025-12-31T23:59:59.999Z',
type: String,
})
......
export * from './create-inspection.dto';
export * from './find-inspection.dto';
export * from './inspection-response.dto';
export * from './inspection-response-option.enum';
/**
* Response options for maintenance questions
* Response options for inspection questions
*
* YES - Item is in good condition/working properly
* NO - Item needs attention/repair
* NA - Not applicable for this site
*/
export enum MaintenanceResponseOption {
export enum InspectionResponseOption {
YES = 'YES',
NO = 'NO',
NA = 'NA',
......
import { MaintenanceResponseOption } from './maintenance-response-option.enum';
import { InspectionResponseOption } from './inspection-response-option.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class MaintenanceQuestionDto {
export class InspectionQuestionDto {
@ApiProperty({
description: 'Unique identifier of the maintenance question',
description: 'Unique identifier of the inspection question',
example: 1,
type: Number,
})
id: number;
@ApiProperty({
description: 'Text of the maintenance question',
description: 'Text of the inspection question',
example: 'Site access condition',
type: String,
})
......@@ -24,9 +24,9 @@ export class MaintenanceQuestionDto {
orderIndex: number;
}
export class MaintenanceResponseDto {
export class InspectionResponseDto {
@ApiProperty({
description: 'Unique identifier of the maintenance response',
description: 'Unique identifier of the inspection response',
example: 1,
type: Number,
})
......@@ -34,11 +34,11 @@ export class MaintenanceResponseDto {
@ApiProperty({
description: 'Response option selected for the question',
enum: MaintenanceResponseOption,
example: MaintenanceResponseOption.YES,
enumName: 'MaintenanceResponseOption',
enum: InspectionResponseOption,
example: InspectionResponseOption.YES,
enumName: 'InspectionResponseOption',
})
response: MaintenanceResponseOption;
response: InspectionResponseOption;
@ApiPropertyOptional({
description: 'Optional comment providing additional details',
......@@ -49,14 +49,14 @@ export class MaintenanceResponseDto {
@ApiProperty({
description: 'The question this response answers',
type: MaintenanceQuestionDto,
type: InspectionQuestionDto,
})
question: MaintenanceQuestionDto;
question: InspectionQuestionDto;
}
export class MaintenancePhotoDto {
export class InspectionPhotoDto {
@ApiProperty({
description: 'Unique identifier of the maintenance photo',
description: 'Unique identifier of the inspection photo',
example: 1,
type: Number,
})
......@@ -64,7 +64,7 @@ export class MaintenancePhotoDto {
@ApiProperty({
description: 'URL to access the photo',
example: '/uploads/maintenance/1/photo1.jpg',
example: '/uploads/inspection/1/photo1.jpg',
type: String,
})
url: string;
......@@ -77,30 +77,30 @@ export class MaintenancePhotoDto {
filename: string;
}
export class MaintenanceDto {
export class InspectionDto {
@ApiProperty({
description: 'Unique identifier of the maintenance record',
description: 'Unique identifier of the inspection record',
example: 1,
type: Number,
})
id: number;
@ApiProperty({
description: 'Date when the maintenance was performed',
description: 'Date when the inspection was performed',
example: '2025-05-21T13:00:00.000Z',
type: Date,
})
date: Date;
@ApiPropertyOptional({
description: 'Optional general comment about the maintenance',
example: 'Annual preventive maintenance completed with minor issues noted',
description: 'Optional general comment about the inspection',
example: 'Annual preventive inspection completed with minor issues noted',
type: String,
})
comment?: string;
@ApiProperty({
description: 'ID of the site where maintenance was performed',
description: 'ID of the site where inspection was performed',
example: 1,
type: Number,
})
......@@ -121,16 +121,16 @@ export class MaintenanceDto {
updatedAt: Date;
@ApiProperty({
description: 'Responses to maintenance questions',
type: [MaintenanceResponseDto],
description: 'Responses to inspection questions',
type: [InspectionResponseDto],
isArray: true,
})
responses: MaintenanceResponseDto[];
responses: InspectionResponseDto[];
@ApiProperty({
description: 'Photos attached to the maintenance record',
type: [MaintenancePhotoDto],
description: 'Photos attached to the inspection record',
type: [InspectionPhotoDto],
isArray: true,
})
photos: MaintenancePhotoDto[];
photos: InspectionPhotoDto[];
}
/**
* Example requests and responses for the Maintenance API
* Example requests and responses for the Inspection API
* This file is for documentation purposes only
*/
/**
* Example request for creating a maintenance record
* Example request for creating a inspection record
*/
export const createMaintenanceExample = {
export const createInspectionExample = {
date: '2025-05-21T13:00:00.000Z',
siteId: 1,
comment: 'Regular annual maintenance. Site is in good overall condition.',
comment: 'Regular annual inspection. Site is in good overall condition.',
responses: [
{
questionId: 1,
......@@ -41,12 +41,12 @@ export const createMaintenanceExample = {
};
/**
* Example response for a maintenance record
* Example response for a inspection record
*/
export const maintenanceResponseExample = {
export const inspectionResponseExample = {
id: 1,
date: '2025-05-21T13:00:00.000Z',
comment: 'Regular annual maintenance. Site is in good overall condition.',
comment: 'Regular annual inspection. Site is in good overall condition.',
siteId: 1,
createdAt: '2025-05-21T13:15:30.000Z',
updatedAt: '2025-05-21T13:15:30.000Z',
......@@ -106,26 +106,26 @@ export const maintenanceResponseExample = {
photos: [
{
id: 1,
url: '/uploads/maintenance/1/entrance.jpg',
url: '/uploads/inspection/1/entrance.jpg',
filename: 'entrance.jpg',
},
{
id: 2,
url: '/uploads/maintenance/1/damaged_fence.jpg',
url: '/uploads/inspection/1/damaged_fence.jpg',
filename: 'damaged_fence.jpg',
},
{
id: 3,
url: '/uploads/maintenance/1/equipment.jpg',
url: '/uploads/inspection/1/equipment.jpg',
filename: 'equipment.jpg',
},
],
};
/**
* Example response for maintenance questions
* Example response for inspection questions
*/
export const maintenanceQuestionsExample = [
export const inspectionQuestionsExample = [
{
id: 1,
question: 'Site access condition',
......
import { Module } from '@nestjs/common';
import { InspectionController } from './inspection.controller';
import { InspectionService } from './inspection.service';
import { PrismaModule } from '../../common/prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [InspectionController],
providers: [InspectionService],
exports: [InspectionService],
})
export class InspectionModule {}
......@@ -6,14 +6,14 @@ const mkdir = promisify(fs.mkdir);
const writeFile = promisify(fs.writeFile);
/**
* Saves uploaded maintenance photos to the file system
* Saves uploaded inspection photos to the file system
* @param files Array of uploaded files
* @param maintenanceId The ID of the maintenance record
* @param inspectionId The ID of the inspection record
* @returns Array of saved file paths
*/
export async function saveMaintenancePhotos(
export async function saveInspectionPhotos(
files: Express.Multer.File[],
maintenanceId: number,
inspectionId: number,
): Promise<string[]> {
if (!files || files.length === 0) {
return [];
......@@ -21,12 +21,12 @@ export async function saveMaintenancePhotos(
const uploadDir =
process.env.NODE_ENV === 'production'
? `/home/api-cellnex/public_html/uploads/maintenance/${maintenanceId}`
? `/home/api-cellnex/public_html/uploads/inspection/${inspectionId}`
: path.join(
process.cwd(),
'uploads',
'maintenance',
maintenanceId.toString(),
'inspection',
inspectionId.toString(),
);
// Create directory if it doesn't exist
......@@ -45,7 +45,7 @@ export async function saveMaintenancePhotos(
try {
await writeFile(filePath, file.buffer);
savedPaths.push(`/uploads/maintenance/${maintenanceId}/${filename}`);
savedPaths.push(`/uploads/inspection/${inspectionId}/${filename}`);
} catch (error) {
console.error(`Error saving file ${filename}:`, error);
throw new Error(`Failed to save file ${filename}: ${error.message}`);
......
export * from './create-maintenance.dto';
export * from './find-maintenance.dto';
export * from './maintenance-response.dto';
export * from './maintenance-response-option.enum';
import { Module } from '@nestjs/common';
import { MaintenanceController } from './maintenance.controller';
import { MaintenanceService } from './maintenance.service';
import { PrismaModule } from '../../common/prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [MaintenanceController],
providers: [MaintenanceService],
exports: [MaintenanceService],
})
export class MaintenanceModule {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment