databases interaction fixed

This commit is contained in:
OkaykOrhmn 2025-07-23 11:18:47 +03:30
parent af6d37c7ca
commit aeb7a73c4c
11 changed files with 176 additions and 49 deletions

View File

@ -1,9 +1,11 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Attachment } from './entity/attachment.entity'; import { Attachment } from './entity/attachment.entity';
import { CreateAttachmentDto } from './dto/create_attachment.dto'; import { CreateAttachmentDto } from './dto/create_attachment.dto';
import { UpdateAttachmentDto } from './dto/update_attachment.dto'; import { UpdateAttachmentDto } from './dto/update_attachment.dto';
import axios from 'axios';
import { decode } from 'jsonwebtoken';
@Injectable() @Injectable()
export class AttachmentService { export class AttachmentService {
@ -14,14 +16,44 @@ export class AttachmentService {
async create(data: CreateAttachmentDto): Promise<Attachment> { async create(data: CreateAttachmentDto): Promise<Attachment> {
try { 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({ const attachment = this.attachmentRepository.create({
...data, ...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', status: data.status || 'active',
}); });
// Save the attachment to the database
return await this.attachmentRepository.save(attachment); return await this.attachmentRepository.save(attachment);
} catch (error) { } catch (error) {
throw new Error(`Failed to create attachment: ${error.message}`); throw new InternalServerErrorException(`Failed to create attachment: ${error.message}`);
} }
} }

View File

@ -16,5 +16,6 @@ export class CreateAttachmentDto extends BaseDto {
storageUrl: string; storageUrl: string;
@IsUUID() @IsUUID()
ownerId: string; @IsOptional()
ownerId?: string;
} }

View File

@ -2,6 +2,7 @@ import { Column, Entity, ManyToOne } from 'typeorm';
import { BaseEntity } from 'src/_core/entity/_base.entity'; import { BaseEntity } from 'src/_core/entity/_base.entity';
import { Question } from 'src/question/entity/question.entity'; import { Question } from 'src/question/entity/question.entity';
import { FormSection } from '../../formSection/entity/formSection.entity'; import { FormSection } from '../../formSection/entity/formSection.entity';
import { Form } from '../../form/entity/form.entity';
@Entity({ name: 'attachments' }) @Entity({ name: 'attachments' })
export class Attachment extends BaseEntity { export class Attachment extends BaseEntity {
@ -30,4 +31,7 @@ export class Attachment extends BaseEntity {
@ManyToOne(() => FormSection, formSection => formSection.attachments) @ManyToOne(() => FormSection, formSection => formSection.attachments)
formSection: FormSection; formSection: FormSection;
@ManyToOne(() => Form, form => form.attachments)
form: Form;
} }

View File

@ -12,6 +12,7 @@ import { FormSection } from '../formSection/entity/formSection.entity';
import { FormPage } from '../formPage/entity/formPage.entity'; import { FormPage } from '../formPage/entity/formPage.entity';
import { FormResult } from '../formResult/entity/formResult.entity'; import { FormResult } from '../formResult/entity/formResult.entity';
import { ParticipantGroup } from '../participantGroup/entity/participantGroup.entity'; import { ParticipantGroup } from '../participantGroup/entity/participantGroup.entity';
import { BaseEntity } from '../_core/entity/_base.entity';
export const typeOrmConfig: TypeOrmModuleOptions = { export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'postgres', type: 'postgres',
@ -19,8 +20,9 @@ export const typeOrmConfig: TypeOrmModuleOptions = {
port: 5433, port: 5433,
username: 'postgres', username: 'postgres',
password: '1111', password: '1111',
database: 'postgres', database: 'db',
entities: [ entities: [
BaseEntity,
Participant, Participant,
Metadata, Metadata,
Form, Form,

View File

@ -21,10 +21,12 @@ export class CreateFormDto extends BaseDto {
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateFormPageDto) @Type(() => CreateFormPageDto)
pages: CreateFormPageDto[]; @IsOptional()
pages?: CreateFormPageDto[];
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateParticipantDto) @Type(() => CreateParticipantDto)
participants: CreateParticipantDto[]; @IsOptional()
participants?: CreateParticipantDto[];
} }

View File

@ -3,6 +3,7 @@ import { BaseEntity } from '../../_core/entity/_base.entity';
import { FormPage } from '../../formPage/entity/formPage.entity'; import { FormPage } from '../../formPage/entity/formPage.entity';
import { Participant } from '../../participant/entity/participant.entity'; import { Participant } from '../../participant/entity/participant.entity';
import { Realm } from '../../realm/entity/realm.entity'; import { Realm } from '../../realm/entity/realm.entity';
import { Attachment } from '../../attachment/entity/attachment.entity';
@Entity() @Entity()
export class Form extends BaseEntity { export class Form extends BaseEntity {
@ -20,23 +21,12 @@ export class Form extends BaseEntity {
@OneToMany(() => Participant, participant => participant.form, { cascade: true, eager: true }) @OneToMany(() => Participant, participant => participant.form, { cascade: true, eager: true })
participants: Participant[]; 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; 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[];
} }

46
src/mock/alice.mock.json Normal file
View File

@ -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": {
}
}

View File

@ -16,12 +16,12 @@ export class ParticipantService {
async create(data: CreateParticipantDto): Promise<Participant> { async create(data: CreateParticipantDto): Promise<Participant> {
try { 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( const authResponse = await axios.post(
'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token', 'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token',
new URLSearchParams({ new URLSearchParams({
client_id: 'didvan-app', client_id: 'didvan-app',
username: 'alice', username: 'bob',
password: 'developer_password', password: 'developer_password',
grant_type: 'password', grant_type: 'password',
}), }),
@ -39,16 +39,21 @@ export class ParticipantService {
throw new InternalServerErrorException('Failed to decode token or extract sub'); 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({ const participant = this.participantRepository.create({
...data, ...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 userId: decodedToken.sub, // Set userId to the same sub
metadata: data.metadata metadata: data.metadata
? { entries: data.metadata.entries } // Ensure metadata is properly formatted ? { entries: data.metadata.entries }
: { entries: [] }, : { entries: [] },
status: data.status || 'active', status: data.status || 'active',
// Note: form and realm are not set as they are optional (ManyToOne)
}); });
// Save the participant to the database // Save the participant to the database

View File

@ -20,9 +20,10 @@ export class CreateRealmDto extends BaseDto {
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateParticipantDto) @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 // @ValidateNested() // Not IsArray, as it's a single owner
@Type(() => CreateParticipantDto) // @Type(() => CreateParticipantDto)
owner: CreateParticipantDto; // owner: CreateParticipantDto;
} }

View File

@ -11,17 +11,14 @@ export class Realm extends BaseEntity {
@Column() @Column()
description: string; description: string;
// One-to-Many relationship with Form
@OneToMany(() => Form, form => form.realm, { cascade: true, eager: true }) @OneToMany(() => Form, form => form.realm, { cascade: true, eager: true })
forms: Form[]; forms: Form[];
// One-to-Many relationship with Participant (for general participants in the realm)
@OneToMany(() => Participant, participant => participant.realm, { cascade: true, eager: true }) @OneToMany(() => Participant, participant => participant.realm, { cascade: true, eager: true })
participants: Participant[]; 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 }) @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; owner: Participant;
} }

View File

@ -1,23 +1,73 @@
// src/realm/realm.service.ts import { Injectable, NotFoundException, InternalServerErrorException } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Realm } from './entity/realm.entity'; import { Realm } from './entity/realm.entity';
import { CreateRealmDto } from './dto/create-realm.dto'; import { CreateRealmDto } from './dto/create-realm.dto';
import { UpdateRealmDto } from './dto/update-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() @Injectable()
export class RealmService { export class RealmService {
constructor( constructor(
@InjectRepository(Realm) @InjectRepository(Realm)
private readonly realmRepo: Repository<Realm>, private readonly realmRepo: Repository<Realm>,
@InjectRepository(Participant)
private readonly participantRepo: Repository<Participant>,
) {} ) {}
async create(data: CreateRealmDto): Promise<Realm> { async create(data: CreateRealmDto): Promise<Realm> {
const realm = this.realmRepo.create({ ...data }); try {
return await this.realmRepo.save(realm); // 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( async findAll(
page = 1, page = 1,
limit = 10, limit = 10,
@ -83,10 +133,7 @@ export class RealmService {
return realm; return realm;
} }
async update( async update(id: string, data: UpdateRealmDto): Promise<Realm | null> {
id: string,
data: UpdateRealmDto,
): Promise<Realm | null> {
const result = await this.realmRepo.update( const result = await this.realmRepo.update(
{ id }, { id },
{ ...data, updatedAt: new Date() }, { ...data, updatedAt: new Date() },