forked from Yara724/api
159 lines
5.3 KiB
TypeScript
159 lines
5.3 KiB
TypeScript
import { BadGatewayException, GoneException, Injectable } from "@nestjs/common";
|
|
import { Types } from "mongoose";
|
|
import {
|
|
ClientDto,
|
|
ClientDtoRs,
|
|
ClientLists,
|
|
} from "src/client/dto/create-client.dto";
|
|
import { ClientDbService } from "./entities/db-service/client.db.service";
|
|
|
|
/**
|
|
* System-wide default applied when a client document has no
|
|
* `settings.carBodyAccidentMaxAgeDays` value yet. Keep it conservative so the
|
|
* gate is enforced even for older / unconfigured clients.
|
|
*/
|
|
export const DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS = 7;
|
|
|
|
/**
|
|
* Media kinds we enforce upload-size bounds for. Each kind has its own
|
|
* default window (see {@link DEFAULT_MEDIA_LIMITS}) and an optional
|
|
* per-client override under `settings.media.<kind>`.
|
|
*/
|
|
export type MediaKind = "video" | "image" | "voice";
|
|
|
|
export interface MediaLimits {
|
|
/** Inclusive lower bound. `0` allows any size from zero up. */
|
|
minBytes: number;
|
|
/** Inclusive upper bound. */
|
|
maxBytes: number;
|
|
}
|
|
|
|
/**
|
|
* System defaults applied when a client (or kind) has no explicit override.
|
|
*
|
|
* Important: each upload route also has a `multer.limits.fileSize` hard
|
|
* ceiling — those route ceilings are the absolute maximum the API can
|
|
* receive. Per-client `maxBytes` cannot legitimately go above its route's
|
|
* multer ceiling, so the defaults below are kept at-or-below those.
|
|
*/
|
|
export const DEFAULT_MEDIA_LIMITS: Record<MediaKind, MediaLimits> = {
|
|
video: { minBytes: 256 * 1024, maxBytes: 20 * 1024 * 1024 },
|
|
image: { minBytes: 5 * 1024, maxBytes: 8 * 1024 * 1024 },
|
|
voice: { minBytes: 5 * 1024, maxBytes: 8 * 1024 * 1024 },
|
|
};
|
|
|
|
@Injectable()
|
|
export class ClientService {
|
|
constructor(private readonly clientDbService: ClientDbService) {}
|
|
async addClient(client: ClientDto): Promise<ClientDtoRs> {
|
|
try {
|
|
const newClient = await this.clientDbService.create({
|
|
clientCode: client.clientCode,
|
|
clientName: {
|
|
persian: client.clientName.persian,
|
|
english: client.clientName.english || null,
|
|
},
|
|
property: {
|
|
smsApiKey: client.property.smsApiKey || null,
|
|
},
|
|
useExpertMode: client.useExpertMode || null,
|
|
});
|
|
if (newClient) return new ClientDtoRs(newClient);
|
|
else throw new GoneException("database not connected");
|
|
} catch (er) {
|
|
throw new BadGatewayException(er.errors);
|
|
}
|
|
}
|
|
|
|
findOne(filter) {
|
|
return this.clientDbService.findOne(filter);
|
|
}
|
|
|
|
async findClientWithPersianName(filter: string) {
|
|
return await this.clientDbService.find({ "clientName.persian": filter });
|
|
}
|
|
|
|
async findClientWithCompanyCode(companyCode: number) {
|
|
return await this.clientDbService.find({ clientCode: companyCode });
|
|
}
|
|
|
|
async getClients(): Promise<ClientDtoRs[]> {
|
|
const clients = await this.clientDbService.findAll();
|
|
const show = clients.map((c) => new ClientDtoRs(c));
|
|
return show;
|
|
}
|
|
|
|
async getClientList(): Promise<ClientLists[]> {
|
|
const client = await this.clientDbService.findAll();
|
|
const list = client.map((element) => new ClientLists(element));
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Resolve the per-client byte bounds for a media kind. Missing bounds fall
|
|
* back to {@link DEFAULT_MEDIA_LIMITS}. If no clientId is supplied (or it
|
|
* doesn't resolve to a client document) the defaults are returned.
|
|
*
|
|
* The returned object is always fully populated — callers can compare
|
|
* directly against `file.size`.
|
|
*/
|
|
async getMediaLimits(
|
|
clientId: string | Types.ObjectId | null | undefined,
|
|
kind: MediaKind,
|
|
): Promise<MediaLimits> {
|
|
const defaults = DEFAULT_MEDIA_LIMITS[kind];
|
|
if (!clientId) return { ...defaults };
|
|
|
|
const idString = String(clientId);
|
|
if (!Types.ObjectId.isValid(idString)) return { ...defaults };
|
|
|
|
const client = await this.clientDbService.findOne({
|
|
_id: new Types.ObjectId(idString),
|
|
});
|
|
const override = client?.settings?.media?.[kind];
|
|
const minBytes =
|
|
typeof override?.minBytes === "number" &&
|
|
Number.isFinite(override.minBytes) &&
|
|
override.minBytes >= 0
|
|
? override.minBytes
|
|
: defaults.minBytes;
|
|
const maxBytes =
|
|
typeof override?.maxBytes === "number" &&
|
|
Number.isFinite(override.maxBytes) &&
|
|
override.maxBytes > 0
|
|
? override.maxBytes
|
|
: defaults.maxBytes;
|
|
return { minBytes, maxBytes };
|
|
}
|
|
|
|
/**
|
|
* Resolve the per-client CAR_BODY accident-age window (in days).
|
|
*
|
|
* Falls back to {@link DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS} when:
|
|
* - no client id is supplied,
|
|
* - the id is malformed,
|
|
* - the client document is missing,
|
|
* - the client has no `settings.carBodyAccidentMaxAgeDays` configured,
|
|
* - or the configured value is not a positive finite number.
|
|
*/
|
|
async getCarBodyAccidentMaxAgeDays(
|
|
clientId?: string | Types.ObjectId | null,
|
|
): Promise<number> {
|
|
if (!clientId) return DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS;
|
|
|
|
const idString = String(clientId);
|
|
if (!Types.ObjectId.isValid(idString)) {
|
|
return DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS;
|
|
}
|
|
|
|
const client = await this.clientDbService.findOne({
|
|
_id: new Types.ObjectId(idString),
|
|
});
|
|
const configured = client?.settings?.carBodyAccidentMaxAgeDays;
|
|
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) {
|
|
return configured;
|
|
}
|
|
return DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS;
|
|
}
|
|
}
|