From aeb7a73c4cfe98cb25a58bc5acb54690023467f4 Mon Sep 17 00:00:00 2001 From: OkaykOrhmn Date: Wed, 23 Jul 2025 11:18:47 +0330 Subject: [PATCH] databases interaction fixed --- src/attachment/attachment.service.ts | 38 +++++++++++- src/attachment/dto/create_attachment.dto.ts | 3 +- src/attachment/entity/attachment.entity.ts | 4 ++ src/config/database.config.ts | 4 +- src/form/dto/create-form.dto.ts | 6 +- src/form/entity/form.entity.ts | 24 +++----- src/mock/alice.mock.json | 46 +++++++++++++++ src/participant/participant.service.ts | 17 ++++-- src/realm/dto/create-realm.dto.ts | 9 +-- src/realm/entity/realm.entity.ts | 9 +-- src/realm/realm.service.ts | 65 ++++++++++++++++++--- 11 files changed, 176 insertions(+), 49 deletions(-) create mode 100644 src/mock/alice.mock.json diff --git a/src/attachment/attachment.service.ts b/src/attachment/attachment.service.ts index 3319fed..ba192ec 100644 --- a/src/attachment/attachment.service.ts +++ b/src/attachment/attachment.service.ts @@ -1,9 +1,11 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Attachment } from './entity/attachment.entity'; import { CreateAttachmentDto } from './dto/create_attachment.dto'; import { UpdateAttachmentDto } from './dto/update_attachment.dto'; +import axios from 'axios'; +import { decode } from 'jsonwebtoken'; @Injectable() export class AttachmentService { @@ -14,14 +16,44 @@ export class AttachmentService { async create(data: CreateAttachmentDto): Promise { try { + // Here we send a sample request, in final implementation, there'll be no need for that, we'll get the decoded token + const authResponse = await axios.post( + 'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token', + new URLSearchParams({ + client_id: 'didvan-app', + username: 'bob', + password: 'developer_password', + grant_type: 'password', + }), + { + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + }, + ); + + // Decode the access token to extract the 'sub' claim + const token = authResponse.data.access_token; + const decodedToken: any = decode(token); + if (!decodedToken || !decodedToken.sub) { + throw new InternalServerErrorException('Failed to decode token or extract sub'); + } + + + // Create the attachment with ownerId set to sub const attachment = this.attachmentRepository.create({ ...data, - metadata: data.metadata || { entries: [] }, + ownerId: decodedToken.sub, // Set ownerId to the token's sub + metadata: data.metadata + ? { entries: data.metadata.entries } + : { entries: [] }, status: data.status || 'active', }); + + // Save the attachment to the database return await this.attachmentRepository.save(attachment); } catch (error) { - throw new Error(`Failed to create attachment: ${error.message}`); + throw new InternalServerErrorException(`Failed to create attachment: ${error.message}`); } } diff --git a/src/attachment/dto/create_attachment.dto.ts b/src/attachment/dto/create_attachment.dto.ts index dec3faf..1c2333c 100644 --- a/src/attachment/dto/create_attachment.dto.ts +++ b/src/attachment/dto/create_attachment.dto.ts @@ -16,5 +16,6 @@ export class CreateAttachmentDto extends BaseDto { storageUrl: string; @IsUUID() - ownerId: string; + @IsOptional() + ownerId?: string; } \ No newline at end of file diff --git a/src/attachment/entity/attachment.entity.ts b/src/attachment/entity/attachment.entity.ts index 55d98a8..f707267 100644 --- a/src/attachment/entity/attachment.entity.ts +++ b/src/attachment/entity/attachment.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, ManyToOne } from 'typeorm'; import { BaseEntity } from 'src/_core/entity/_base.entity'; import { Question } from 'src/question/entity/question.entity'; import { FormSection } from '../../formSection/entity/formSection.entity'; +import { Form } from '../../form/entity/form.entity'; @Entity({ name: 'attachments' }) export class Attachment extends BaseEntity { @@ -30,4 +31,7 @@ export class Attachment extends BaseEntity { @ManyToOne(() => FormSection, formSection => formSection.attachments) formSection: FormSection; + + @ManyToOne(() => Form, form => form.attachments) + form: Form; } \ No newline at end of file diff --git a/src/config/database.config.ts b/src/config/database.config.ts index a348458..717d9f1 100644 --- a/src/config/database.config.ts +++ b/src/config/database.config.ts @@ -12,6 +12,7 @@ import { FormSection } from '../formSection/entity/formSection.entity'; import { FormPage } from '../formPage/entity/formPage.entity'; import { FormResult } from '../formResult/entity/formResult.entity'; import { ParticipantGroup } from '../participantGroup/entity/participantGroup.entity'; +import { BaseEntity } from '../_core/entity/_base.entity'; export const typeOrmConfig: TypeOrmModuleOptions = { type: 'postgres', @@ -19,8 +20,9 @@ export const typeOrmConfig: TypeOrmModuleOptions = { port: 5433, username: 'postgres', password: '1111', - database: 'postgres', + database: 'db', entities: [ + BaseEntity, Participant, Metadata, Form, diff --git a/src/form/dto/create-form.dto.ts b/src/form/dto/create-form.dto.ts index def1312..a54d40c 100644 --- a/src/form/dto/create-form.dto.ts +++ b/src/form/dto/create-form.dto.ts @@ -21,10 +21,12 @@ export class CreateFormDto extends BaseDto { @IsArray() @ValidateNested({ each: true }) @Type(() => CreateFormPageDto) - pages: CreateFormPageDto[]; + @IsOptional() + pages?: CreateFormPageDto[]; @IsArray() @ValidateNested({ each: true }) @Type(() => CreateParticipantDto) - participants: CreateParticipantDto[]; + @IsOptional() + participants?: CreateParticipantDto[]; } diff --git a/src/form/entity/form.entity.ts b/src/form/entity/form.entity.ts index 4eca37f..d665702 100644 --- a/src/form/entity/form.entity.ts +++ b/src/form/entity/form.entity.ts @@ -3,6 +3,7 @@ import { BaseEntity } from '../../_core/entity/_base.entity'; import { FormPage } from '../../formPage/entity/formPage.entity'; import { Participant } from '../../participant/entity/participant.entity'; import { Realm } from '../../realm/entity/realm.entity'; +import { Attachment } from '../../attachment/entity/attachment.entity'; @Entity() export class Form extends BaseEntity { @@ -20,23 +21,12 @@ export class Form extends BaseEntity { @OneToMany(() => Participant, participant => participant.form, { cascade: true, eager: true }) participants: Participant[]; - // Many-to-One relationship with Realm (ADD THIS) - @ManyToOne(() => Realm, realm => realm.forms) + + // One-to-Many relationship with Attachment + @OneToMany(() => Attachment, attachment => attachment.form, { cascade: true, eager: true }) + attachments: Attachment[]; + + @ManyToOne(() => Realm, realm => realm.forms, { /*cascade: true, eager: true */}) realm: Realm; - // Attachments are typically stored in a separate table and linked via a many-to-many or one-to-many - // For simplicity, if attachments are embedded, they would be handled like DisplayCondition in FormSection. - // If they are separate entities, they would need a relationship defined here. - // Assuming attachments are now handled as a separate entity with a relationship if needed. - // If attachments were previously embedded, you'd need to define a class for them and use @Column(() => AttachmentClass) - // For now, removing the direct 'attachments' column from Form as it was an array of AttachmentSchema (Mongoose). - // If you need attachments directly on Form, please provide the Attachment entity structure. - - - // @Prop({ - // type: [AttachmentSchema], - // default: [], - // }) - // attachments: Attachment[]; - } diff --git a/src/mock/alice.mock.json b/src/mock/alice.mock.json new file mode 100644 index 0000000..d191fe4 --- /dev/null +++ b/src/mock/alice.mock.json @@ -0,0 +1,46 @@ +{ + "token": { + "exp": 1753279286, + "iat": 1753250486, + "jti": "onrtro:4cee0913-cbe6-89c0-d4bb-dac3d8e29b04", + "iss": "https://auth.didvan.com/realms/didvan", + "aud": "account", + "sub": "010be0e5-fe3d-4686-b093-8fe37ccdc3c9", + "typ": "Bearer", + "azp": "didvan-app", + "sid": "be164904-009b-450e-98d5-553073af0fcb", + "acr": "1", + "allowed-origins": [ + "https://didvan.com", + "https://web.didvan.com", + "http://localhost:3000" + ], + "realm_access": { + "roles": [ + "delphi-user", + "offline_access", + "uma_authorization", + "default-roles-didvan" + ] + }, + "resource_access": { + "account": { + "roles": [ + "manage-account", + "manage-account-links", + "view-profile" + ] + } + }, + "scope": "email profile", + "email_verified": true, + "name": "alice alice_lastname", + "preferred_username": "alice", + "given_name": "alice", + "family_name": "alice_lastname", + "email": "alice@gmail.com" + }, + "data": { + + } +} diff --git a/src/participant/participant.service.ts b/src/participant/participant.service.ts index fa7f5ea..b8562f6 100644 --- a/src/participant/participant.service.ts +++ b/src/participant/participant.service.ts @@ -16,12 +16,12 @@ export class ParticipantService { async create(data: CreateParticipantDto): Promise { try { - // Make the authentication request to get the token + // Here we send a sample request, in final implementation, there'll be no need for that, we'll get the decoded token const authResponse = await axios.post( 'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token', new URLSearchParams({ client_id: 'didvan-app', - username: 'alice', + username: 'bob', password: 'developer_password', grant_type: 'password', }), @@ -39,16 +39,21 @@ export class ParticipantService { throw new InternalServerErrorException('Failed to decode token or extract sub'); } - // Create the participant with the 'sub' as the ID and userId + // Check if participant with this sub already exists + const existingParticipant = await this.participantRepository.findOne({ where: { id: decodedToken.sub } }); + if (existingParticipant) { + throw new InternalServerErrorException(`Participant with ID ${decodedToken.sub} already exists`); + } + + // Create the participant with the 'sub' as the ID const participant = this.participantRepository.create({ ...data, - id: decodedToken.sub, // Set the ID from the token's sub + // id: , let it assign a random id userId: decodedToken.sub, // Set userId to the same sub metadata: data.metadata - ? { entries: data.metadata.entries } // Ensure metadata is properly formatted + ? { entries: data.metadata.entries } : { entries: [] }, status: data.status || 'active', - // Note: form and realm are not set as they are optional (ManyToOne) }); // Save the participant to the database diff --git a/src/realm/dto/create-realm.dto.ts b/src/realm/dto/create-realm.dto.ts index 1f765d3..a3b1961 100644 --- a/src/realm/dto/create-realm.dto.ts +++ b/src/realm/dto/create-realm.dto.ts @@ -20,9 +20,10 @@ export class CreateRealmDto extends BaseDto { @IsArray() @ValidateNested({ each: true }) @Type(() => CreateParticipantDto) - participants: CreateParticipantDto[]; // Changed from 'participant' to 'participants' for consistency with array + @IsOptional() + participants?: CreateParticipantDto[]; - @ValidateNested() // Not IsArray, as it's a single owner - @Type(() => CreateParticipantDto) - owner: CreateParticipantDto; + // @ValidateNested() // Not IsArray, as it's a single owner + // @Type(() => CreateParticipantDto) + // owner: CreateParticipantDto; } diff --git a/src/realm/entity/realm.entity.ts b/src/realm/entity/realm.entity.ts index b98365d..4b53cbf 100644 --- a/src/realm/entity/realm.entity.ts +++ b/src/realm/entity/realm.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Column, OneToMany, OneToOne, JoinColumn } from 'typeorm'; +import { Entity, Column, OneToMany, OneToOne, JoinColumn} from 'typeorm'; import { BaseEntity } from '../../_core/entity/_base.entity'; import { Form } from '../../form/entity/form.entity'; import { Participant } from '../../participant/entity/participant.entity'; @@ -11,17 +11,14 @@ export class Realm extends BaseEntity { @Column() description: string; - // One-to-Many relationship with Form @OneToMany(() => Form, form => form.realm, { cascade: true, eager: true }) forms: Form[]; - // One-to-Many relationship with Participant (for general participants in the realm) @OneToMany(() => Participant, participant => participant.realm, { cascade: true, eager: true }) participants: Participant[]; - // One-to-One relationship with Participant (for the owner of the realm) - // Assuming 'owner' is a single Participant entity @OneToOne(() => Participant, { cascade: true, eager: true }) - @JoinColumn() // This column will be added to the Realm table to store the owner's ID + @JoinColumn({ name: 'ownerId', referencedColumnName: 'userId' }) // be explicit owner: Participant; + } diff --git a/src/realm/realm.service.ts b/src/realm/realm.service.ts index 01ffe05..1552fe1 100644 --- a/src/realm/realm.service.ts +++ b/src/realm/realm.service.ts @@ -1,23 +1,73 @@ -// src/realm/realm.service.ts -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, InternalServerErrorException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Realm } from './entity/realm.entity'; import { CreateRealmDto } from './dto/create-realm.dto'; import { UpdateRealmDto } from './dto/update-realm.dto'; +import { Participant } from '../participant/entity/participant.entity'; +import axios from 'axios'; +import { decode } from 'jsonwebtoken'; @Injectable() export class RealmService { constructor( @InjectRepository(Realm) private readonly realmRepo: Repository, + @InjectRepository(Participant) + private readonly participantRepo: Repository, ) {} async create(data: CreateRealmDto): Promise { - const realm = this.realmRepo.create({ ...data }); - return await this.realmRepo.save(realm); + try { + // Here we send a sample request, in final implementation, there'll be no need for that, we'll get the decoded token + const authResponse = await axios.post( + 'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token', + new URLSearchParams({ + client_id: 'didvan-app', + username: 'bob', + password: 'developer_password', + grant_type: 'password', + }), + { + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + }, + ); + + // Decode the access token to extract the 'sub' claim + const token = authResponse.data.access_token; + const decodedToken: any = decode(token); + if (!decodedToken || !decodedToken.sub) { + throw new InternalServerErrorException('Failed to decode token or extract sub'); + } + + // Find or create a participant with userId set to sub + let participant = await this.participantRepo.findOne({ where: { userId: decodedToken.sub } }); + if (!participant) { + participant = this.participantRepo.create({ + displayName: data.title + ' Owner', // Default displayName + role: 'admin', // Default role for owner + userId: decodedToken.sub, + metadata: data.metadata ? { entries: data.metadata.entries } : { entries: [] }, + // status: 'active', + }); + participant = await this.participantRepo.save(participant); + } + + // Create the realm with the participant as the owner + const realm = this.realmRepo.create({ + ...data, + owner: participant, // Set owner to the participant with userId = sub + }); + + return await this.realmRepo.save(realm); + } catch (error) { + throw new InternalServerErrorException(`Failed to create realm: ${error.message}`); + } } + // Other methods (findAll, findById, update, remove) remain unchanged async findAll( page = 1, limit = 10, @@ -83,10 +133,7 @@ export class RealmService { return realm; } - async update( - id: string, - data: UpdateRealmDto, - ): Promise { + async update(id: string, data: UpdateRealmDto): Promise { const result = await this.realmRepo.update( { id }, { ...data, updatedAt: new Date() }, @@ -104,4 +151,4 @@ export class RealmService { throw new NotFoundException(`Realm with ID "${id}" not found`); } } -} +} \ No newline at end of file