updated living space added

This commit is contained in:
2025-12-04 15:46:24 +03:00
parent 56b42bb906
commit 53e1f1e4fc
70 changed files with 1128 additions and 824 deletions

View File

@@ -2,10 +2,12 @@ import { Module } from '@nestjs/common';
import { BuildIbanResolver } from './build-iban.resolver';
import { BuildIbanService } from './build-iban.service';
import { MongooseModule } from '@nestjs/mongoose';
import { BuildIban, BuildIbanSchema } from '@/models/build.model';
import { BuildIban, BuildIbanSchema, Build, BuildSchema } from '@/models/build.model';
@Module({
imports: [MongooseModule.forFeature([{ name: BuildIban.name, schema: BuildIbanSchema }])],
imports: [MongooseModule.forFeature([
{ name: BuildIban.name, schema: BuildIbanSchema }, { name: Build.name, schema: BuildSchema }
])],
providers: [BuildIbanResolver, BuildIbanService]
})
export class BuildIbanModule { }

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Types, Model } from 'mongoose';
import { BuildIban, BuildIbanDocument } from '@/models/build.model';
import { Build, BuildDocument, BuildIban, BuildIbanDocument } from '@/models/build.model';
import { ListBuildIbanResponse } from './dto/list-build-ibans.response';
import { CreateBuildIbanInput } from './dto/create-build-ibans.input';
import { UpdateBuildIbanInput } from './dto/update-build-ibans.input';
@@ -9,10 +9,14 @@ import { UpdateBuildIbanInput } from './dto/update-build-ibans.input';
@Injectable()
export class BuildIbanService {
constructor(@InjectModel(BuildIban.name) private readonly buildIbanModel: Model<BuildIbanDocument>) { }
constructor(
@InjectModel(BuildIban.name) private readonly buildIbanModel: Model<BuildIbanDocument>,
@InjectModel(Build.name) private readonly buildModel: Model<BuildDocument>
) { }
async findAll(projection: any, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListBuildIbanResponse> {
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
if (!!query?.buildId) { query.buildId = new Types.ObjectId(query?.buildId) };
const totalCount = await this.buildIbanModel.countDocuments(query).exec();
const data = await this.buildIbanModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec();
return { data, totalCount };
@@ -22,17 +26,21 @@ export class BuildIbanService {
return this.buildIbanModel.findById(id, projection, { lean: false }).populate({ path: 'buildIban', select: projection?.buildIban }).exec();
}
async create(input: CreateBuildIbanInput): Promise<BuildIbanDocument> { const buildIban = new this.buildIbanModel(input); return buildIban.save() }
async create(input: CreateBuildIbanInput): Promise<BuildIbanDocument> {
const build = await this.buildModel.findOne({ _id: new Types.ObjectId(input.buildId) });
if (!build) { throw new Error('Build not found') }
const buildIban = new this.buildIbanModel({ ...input, buildId: build._id }); return buildIban.save()
}
async update(uuid: string, input: UpdateBuildIbanInput): Promise<BuildIbanDocument> { const buildIban = await this.buildIbanModel.findOne({ uuid }); if (!buildIban) { throw new Error('BuildIban not found') }; buildIban.set(input); return buildIban.save() }
async update(uuid: string, input: UpdateBuildIbanInput): Promise<BuildIbanDocument> {
const buildIban = await this.buildIbanModel.findOne({ uuid }); if (!buildIban) { throw new Error('BuildIban not found') }; buildIban.set(input); return buildIban.save()
}
async delete(uuid: string): Promise<boolean> { const buildIban = await this.buildIbanModel.deleteMany({ uuid }); return buildIban.deletedCount > 0 }
buildProjection(fields: Record<string, any>): any {
const projection: any = {};
for (const key in fields) {
if (key === 'buildIban' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`buildIban.${subField}`] = 1 } } else { projection[key] = 1 }
}; return projection;
for (const key in fields) { if (key === 'buildIban' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`buildIban.${subField}`] = 1 } } else { projection[key] = 1 } }; return projection;
}
}

View File

@@ -4,6 +4,9 @@ import { InputType, Field } from "@nestjs/graphql";
@InputType()
export class CreateBuildIbanInput extends ExpiryBaseInput {
@Field()
buildId: string;
@Field()
iban: string;

View File

@@ -0,0 +1,8 @@
import { randomBytes, createHash } from "crypto";
export function generateResetToken(length: number = 128) {
const resetToken = randomBytes(length).toString("hex");
const hashedToken = createHash("sha256").update(resetToken).digest("hex");
const expires = Date.now() + 1000 * 60 * 60 * 24 * 3;
return { resetToken, hashedToken, expires };
}

View File

@@ -13,8 +13,8 @@ export class CreateLivingSpaceInput extends ExpiryBaseInput {
@Field()
userTypeID: string;
@Field()
companyID: string;
@Field({ nullable: true })
companyID?: string;
@Field()
personID: string;

View File

@@ -0,0 +1,12 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class GetLivingSpaceInput {
@Field()
buildID: string;
@Field()
uuid: string;
}

View File

@@ -4,11 +4,13 @@ import { LivingSpaceResolver } from './living-space.resolver';
import { MongooseModule } from '@nestjs/mongoose';
import { LivingSpaces, LivingSpacesSchema } from '@/models/living-spaces.model';
import { UserType, UserTypeSchema } from '@/models/user-type.model';
import { Build, BuildSchema } from '@/models/build.model';
@Module({
imports: [MongooseModule.forFeature([
{ name: LivingSpaces.name, schema: LivingSpacesSchema },
{ name: UserType.name, schema: UserTypeSchema },
{ name: Build.name, schema: BuildSchema },
])],
providers: [LivingSpaceService, LivingSpaceResolver]
})

View File

@@ -8,6 +8,7 @@ import { UpdateLivingSpaceInput } from './dto/update-living-space.input';
import { LivingSpaceService } from './living-space.service';
import type { GraphQLResolveInfo } from 'graphql';
import graphqlFields from 'graphql-fields';
import { GetLivingSpaceInput } from './dto/get-living-space.input';
@Resolver()
export class LivingSpaceResolver {
@@ -25,6 +26,9 @@ export class LivingSpaceResolver {
const fields = graphqlFields(info); const projection = this.livingSpaceService.buildProjection(fields); return this.livingSpaceService.findById(buildID, new Types.ObjectId(id), projection);
}
@Query(() => LivingSpaces, { name: 'getLivingSpaceDetail', nullable: true })
async getLivingSpaceDetail(@Args('uuid') uuid: string, @Args('buildID') buildID: string): Promise<LivingSpaces | null> { return this.livingSpaceService.getDetail(buildID, uuid) }
@Mutation(() => LivingSpaces, { name: 'createLivingSpace' })
async createLivingSpace(@Args('input') input: CreateLivingSpaceInput): Promise<LivingSpaces> { return this.livingSpaceService.create(input) }

View File

@@ -6,6 +6,7 @@ import { CreateLivingSpaceInput } from './dto/create-living-space.input';
import { UpdateLivingSpaceInput } from './dto/update-living-space.input';
import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { UserTypeDocument } from '@/models/user-type.model';
import { BuildDocument } from '@/models/build.model';
interface UpdateInput {
userType?: Types.ObjectId;
@@ -21,17 +22,30 @@ export class LivingSpaceService {
constructor(
@InjectConnection() private readonly connection: Connection,
@InjectModel('UserType') private readonly userTypeModel: Model<UserTypeDocument>
@InjectModel('UserType') private readonly userTypeModel: Model<UserTypeDocument>,
@InjectModel('Build') private readonly buildModel: Model<BuildDocument>
) { }
async findAll(buildID: string, projection: any, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListLivingSpaceResponse> {
const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection);
const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
const totalCount = await selectedModel.countDocuments(query).exec();
const data = await selectedModel.find(query, projection, { lean: true }).skip(skip).limit(limit).sort(sort).exec();
const data = await selectedModel.find(query, projection, { lean: false })
.populate({ path: 'build', select: { ...projection?.build, _id: 0 } })
.populate({ path: 'person', select: { ...projection?.person, _id: 0 } })
.populate({ path: 'company', select: { ...projection?.company, _id: 0 } })
.populate({ path: 'userType', select: { ...projection?.userType, _id: 0 } })
.populate({ path: 'part', select: { ...projection?.part, _id: 0 } })
.skip(skip).limit(limit).sort(sort).exec();
return { data, totalCount };
}
async getDetail(buildID: string, uuid: string): Promise<any | null> {
const buildObjectID = new Types.ObjectId(buildID); const build = await this.buildModel.findById(buildObjectID); if (!build) { throw new Error('Build not found') };
const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection); const data = await selectedModel.findOne({ uuid }, { lean: false }).exec();
if (!data) { throw new Error('Living space not found') }; return data;
}
async findById(buildID: string, id: Types.ObjectId, projection?: any): Promise<LivingSpacesDocument | null> {
const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection);
return selectedModel.findById(id, projection, { lean: false }).populate({ path: 'company', select: projection?.company }).exec();
@@ -42,7 +56,7 @@ export class LivingSpaceService {
const LivingSpaceModel = getDynamicLivingSpaceModel(input.buildID, this.connection);
const docInput: Partial<LivingSpacesDocument> = {
build: new Types.ObjectId(input.buildID), part: new Types.ObjectId(input.partID), userType: new Types.ObjectId(input.userTypeID),
company: new Types.ObjectId(input.companyID), person: new Types.ObjectId(input.personID),
company: !!input.companyID ? new Types.ObjectId(input.companyID) : undefined, person: new Types.ObjectId(input.personID),
expiryStarts: input.expiryStarts ? new Date(input.expiryStarts) : new Date(), expiryEnds: input.expiryEnds ? new Date(input.expiryEnds) : new Date('2099-12-31'),
}; const doc = new LivingSpaceModel(docInput); return await doc.save()
}
@@ -51,10 +65,10 @@ export class LivingSpaceService {
if (!buildID) { throw new Error('Build ID is required') }
const selectedModel = getDynamicLivingSpaceModel(buildID, this.connection);
const livingSpace = await selectedModel.findOne({ uuid }); const userTypeSelected = await this.userTypeModel.findById(new Types.ObjectId(input.userTypeID)).exec(); const newInput: UpdateInput = {}
if (userTypeSelected?.isProperty) { if (input?.partID) { newInput.part = new Types.ObjectId(input.partID) } } else { newInput.part = null }
if (input?.companyID) { newInput.company = new Types.ObjectId(input.companyID) }
if (input?.personID) { newInput.person = new Types.ObjectId(input.personID) }; if (input?.userTypeID) { newInput.userType = new Types.ObjectId(input.userTypeID) }
if (input?.expiryStarts) { newInput.expiryStarts = input.expiryStarts }; if (input?.expiryEnds) { newInput.expiryEnds = input.expiryEnds }
if (userTypeSelected?.isProperty) { if (!!input?.partID) { newInput.part = new Types.ObjectId(input.partID) } } else { newInput.part = null }
if (!!input?.companyID) { newInput.company = new Types.ObjectId(input.companyID) }
if (!!input?.personID) { newInput.person = new Types.ObjectId(input.personID) }; if (!!input?.userTypeID) { newInput.userType = new Types.ObjectId(input.userTypeID) }
if (!!input?.expiryStarts) { newInput.expiryStarts = input.expiryStarts }; if (!!input?.expiryEnds) { newInput.expiryEnds = input.expiryEnds }
if (!livingSpace) { throw new Error('Company not found') }; livingSpace.set(newInput); return livingSpace.save();
}

View File

@@ -10,9 +10,17 @@ export class BuildParts extends Base {
@Field(() => ID)
readonly _id: string;
@Field(() => ID)
@Prop({ type: Types.ObjectId, ref: 'Build', required: true })
buildId: Types.ObjectId;
@Field(() => ID, { nullable: true })
@Prop({ type: Types.ObjectId, ref: 'Build', required: false })
buildId?: Types.ObjectId;
@Field(() => ID, { nullable: true })
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: false })
directionId?: Types.ObjectId;
@Field(() => ID, { nullable: true })
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: false })
typeId?: Types.ObjectId;
@Field()
@Prop({ required: true })
@@ -50,14 +58,6 @@ export class BuildParts extends Base {
@Prop({ required: true })
key: string;
@Field(() => ID, { nullable: true })
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: false })
directionId: Types.ObjectId;
@Field(() => ID, { nullable: true })
@Prop({ type: Types.ObjectId, ref: 'ApiEnumDropdown', required: false })
typeId: Types.ObjectId;
}
export type BuildPartsDocument = BuildParts & Document;

View File

@@ -2,8 +2,6 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
import { Base, CreatedBase } from '@/models/base.model';
import { Person } from '@/models/person.model';
import { Company } from '@/models/company.model';
import { BuildTypes } from './build-types.model';
@ObjectType()
@@ -13,6 +11,10 @@ export class BuildIban extends Base {
@Field(() => ID)
readonly _id: string;
@Field(() => ID, { nullable: true })
@Prop({ type: Types.ObjectId, ref: 'Build', required: false })
buildId?: Types.ObjectId;
@Field()
@Prop({ required: true })
iban: string;
@@ -144,6 +146,7 @@ export class Build extends CreatedBase {
@Prop({ type: [String], default: [] })
areas?: string[];
// collect String(ObjectID) to
@Field(() => [String], { nullable: true, defaultValue: [] })
@Prop({ type: [String], default: [] })
ibans?: string[];

View File

@@ -8,7 +8,6 @@ import { Person } from '@/models/person.model';
import { Company } from '@/models/company.model';
import { UserType } from '@/models/user-type.model';
@ObjectType()
@Schema({ timestamps: true })
export class LivingSpaces extends Base {
@@ -16,23 +15,23 @@ export class LivingSpaces extends Base {
@Field(() => ID)
readonly _id: string
@Field(() => ID)
@Field(() => Build)
@Prop({ type: Types.ObjectId, ref: Build.name, required: true })
build: Types.ObjectId;
@Field(() => ID, { nullable: true })
@Field(() => BuildParts, { nullable: true })
@Prop({ type: Types.ObjectId, ref: BuildParts.name, required: false })
part?: Types.ObjectId | null;
@Field(() => ID)
@Field(() => UserType)
@Prop({ type: Types.ObjectId, ref: UserType.name, required: true })
userType: Types.ObjectId;
@Field(() => ID)
@Prop({ type: Types.ObjectId, ref: Company.name, required: true })
company: Types.ObjectId;
@Field(() => Company, { nullable: true })
@Prop({ type: Types.ObjectId, ref: Company.name, required: false })
company?: Types.ObjectId;
@Field(() => ID)
@Field(() => Person)
@Prop({ type: Types.ObjectId, ref: Person.name, required: true })
person: Types.ObjectId;

View File

@@ -3,6 +3,7 @@ import { ObjectType, Field, ID } from '@nestjs/graphql';
import { Document, Types } from 'mongoose';
import { Base } from '@/models/base.model';
import { Person } from '@/models/person.model';
import { generateResetToken } from '@/lib/generateToken';
@ObjectType()
export class CollectionToken {
@@ -35,12 +36,12 @@ export class User extends Base {
expiresAt?: Date;
@Field({ nullable: true })
@Prop()
@Prop({ required: false, default: () => generateResetToken().hashedToken })
resetToken?: string;
@Field()
@Prop({ required: true })
password: string;
@Field({ nullable: true })
@Prop({ required: false, default: '' })
password?: string;
@Field(() => [String], { nullable: 'itemsAndList' })
@Prop({ type: [String], default: [], validate: [(val: string[]) => val.length <= 3, 'History can have max 3 items'] })

View File

@@ -20,11 +20,8 @@ export class CreateUserInput {
@Field({ nullable: true })
avatar?: string;
@Field()
password: string;
@Field(() => [String], { nullable: true })
history?: string[];
// @Field()
// password: string;
@Field()
tag: string;

View File

@@ -6,8 +6,9 @@ mutation {
email: "test@example.com",
phone: "555123456",
collectionTokens: {
default: "default-token",
tokens: [{ prefix: "main", token: "abc123" }]
defaultSelection: "default-token",
selectedBuildIDS: ["64f8b2a4e1234567890abcdef"],
selectedCompanyIDS: ["64f8b2a4e1234567890abcdef"]
},
person: "64f8b2a4e1234567890abcdef"
}) {
@@ -18,11 +19,9 @@ mutation {
phone
tag
collectionTokens {
default
tokens {
prefix
token
}
defaultSelection
selectedBuildIDS
selectedCompanyIDS
}
person
}