Backend half
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
import { Company } from './company.entity';
|
||||
import { QBank } from './qbank.entity';
|
||||
|
||||
// Get the database type from environment variables or default to MariaDB
|
||||
const dbType = process.env.DB_TYPE || 'mariadb';
|
||||
|
||||
// Create the options object with correct typing
|
||||
const options: DataSourceOptions = {
|
||||
type: dbType as any, // Using 'as any' to bypass TypeScript's strict type checking
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: Number(process.env.DB_PORT) || 3306,
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_DATABASE || 'serpent_race',
|
||||
synchronize: process.env.NODE_ENV !== 'production',
|
||||
logging: process.env.NODE_ENV !== 'production',
|
||||
entities: [User, Company, QBank],
|
||||
migrations: [],
|
||||
subscribers: [],
|
||||
};
|
||||
|
||||
// Create the DataSource with properly typed options
|
||||
export const AppDataSourceOne = new DataSource(options);
|
||||
@@ -1,52 +0,0 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
JoinColumn
|
||||
} from 'typeorm';
|
||||
import { Company } from './company.entity';
|
||||
import { QBank } from './qbank.entity';
|
||||
|
||||
// User entity for the SerpentRace API
|
||||
// This entity represents a user in the system, with fields for personal information,
|
||||
|
||||
@Entity('users')
|
||||
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false, unique: true })
|
||||
Username!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
FirstName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
LastName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
Password!: string; // Consider hashing this password before storing
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false, unique: true })
|
||||
Email!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
RegDate!: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
CompanyId: number = 0;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
CompanyToken: string = '';
|
||||
|
||||
@ManyToOne(() => Company, (company) => company.users)
|
||||
@JoinColumn({ name: 'CompanyId' })
|
||||
company!: Company;
|
||||
|
||||
@OneToMany(() => QBank, qbank => qbank.creator)
|
||||
questionBanks!: QBank[];
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
export class CompanyBasicDto {
|
||||
CompanyId: number;
|
||||
Name: string;
|
||||
|
||||
constructor(CompanyId: number, Name: string) {
|
||||
this.CompanyId = CompanyId;
|
||||
this.Name = Name;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompanyUpdateDto {
|
||||
Name?: string;
|
||||
ContactFirstName?: string;
|
||||
ContactLastName?: string;
|
||||
ContactEmail?: string;
|
||||
FirstAPI?: string;
|
||||
TokenAPI?: string;
|
||||
|
||||
constructor(data: Partial<CompanyUpdateDto> = {}) {
|
||||
this.Name = data.Name;
|
||||
this.ContactFirstName = data.ContactFirstName;
|
||||
this.ContactLastName = data.ContactLastName;
|
||||
this.ContactEmail = data.ContactEmail;
|
||||
this.FirstAPI = data.FirstAPI;
|
||||
this.TokenAPI = data.TokenAPI;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompanyCreateDto {
|
||||
Name: string;
|
||||
ContactFirstName: string;
|
||||
ContactLastName: string;
|
||||
ContactEmail: string;
|
||||
FirstAPI: string;
|
||||
TokenAPI: string;
|
||||
|
||||
constructor(
|
||||
Name: string,
|
||||
ContactFirstName: string,
|
||||
ContactLastName: string,
|
||||
ContactEmail: string,
|
||||
FirstAPI: string,
|
||||
TokenAPI: string
|
||||
) {
|
||||
this.Name = Name;
|
||||
this.ContactFirstName = ContactFirstName;
|
||||
this.ContactLastName = ContactLastName;
|
||||
this.ContactEmail = ContactEmail;
|
||||
this.FirstAPI = FirstAPI;
|
||||
this.TokenAPI = TokenAPI;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompanyResponseDto {
|
||||
CompanyId: number;
|
||||
Name: string;
|
||||
ContactFirstName: string;
|
||||
ContactLastName: string;
|
||||
ContactEmail: string;
|
||||
FirstAPI: string;
|
||||
TokenAPI: string;
|
||||
RegDate: Date;
|
||||
|
||||
constructor(
|
||||
CompanyId: number,
|
||||
Name: string,
|
||||
ContactFirstName: string,
|
||||
ContactLastName: string,
|
||||
ContactEmail: string,
|
||||
FirstAPI: string,
|
||||
TokenAPI: string,
|
||||
RegDate: Date
|
||||
) {
|
||||
this.CompanyId = CompanyId;
|
||||
this.Name = Name;
|
||||
this.ContactFirstName = ContactFirstName;
|
||||
this.ContactLastName = ContactLastName;
|
||||
this.ContactEmail = ContactEmail;
|
||||
this.FirstAPI = FirstAPI;
|
||||
this.TokenAPI = TokenAPI;
|
||||
this.RegDate = RegDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export class QBankBasicDto {
|
||||
QBankId: number;
|
||||
Title: string;
|
||||
|
||||
constructor(QBankId: number, Title: string) {
|
||||
this.QBankId = QBankId;
|
||||
this.Title = Title;
|
||||
}
|
||||
}
|
||||
|
||||
export class QBankUpdateDto {
|
||||
Title?: string;
|
||||
no_question?: number;
|
||||
|
||||
constructor(data: Partial<QBankUpdateDto> = {}) {
|
||||
this.Title = data.Title;
|
||||
this.no_question = data.no_question;
|
||||
}
|
||||
}
|
||||
|
||||
export class QBankCreateDto {
|
||||
Title: string;
|
||||
no_question: number;
|
||||
|
||||
constructor(Title: string, no_question: number) {
|
||||
this.Title = Title;
|
||||
this.no_question = no_question;
|
||||
}
|
||||
}
|
||||
|
||||
export class QBankResponseDto {
|
||||
QBankId: number;
|
||||
Title: string;
|
||||
Creator: number;
|
||||
Creation_Date: Date;
|
||||
no_question: number;
|
||||
|
||||
constructor(
|
||||
QBankId: number,
|
||||
Title: string,
|
||||
Creator: number,
|
||||
Creation_Date: Date,
|
||||
no_question: number
|
||||
) {
|
||||
this.QBankId = QBankId;
|
||||
this.Title = Title;
|
||||
this.Creator = Creator;
|
||||
this.Creation_Date = Creation_Date;
|
||||
this.no_question = no_question;
|
||||
}
|
||||
}
|
||||
|
||||
export class QBankListResponseDto {
|
||||
questionBanks: QBankResponseDto[];
|
||||
|
||||
constructor(questionBanks: QBankResponseDto[]) {
|
||||
this.questionBanks = questionBanks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
export class UserBasicDto3 {
|
||||
id: number;
|
||||
username: string;
|
||||
CompanyId?: number;
|
||||
|
||||
constructor(id: number, username: string, CompanyId?: number) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.CompanyId = CompanyId || 0;
|
||||
}
|
||||
}
|
||||
export class UserBasicDto {
|
||||
id: number;
|
||||
username: string;
|
||||
CompanyId?: number;
|
||||
|
||||
constructor(id: number, username: string, CompanyId?: number) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.CompanyId = CompanyId || 0; // Default to 0 if not provided
|
||||
}
|
||||
}
|
||||
|
||||
export class UserUpdateDto {
|
||||
username?: string;
|
||||
FirstName?: string;
|
||||
LastName?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
|
||||
constructor(data: Partial<UserUpdateDto> = {}) {
|
||||
this.username = data.username;
|
||||
this.FirstName = data.FirstName;
|
||||
this.LastName = data.LastName;
|
||||
this.email = data.email;
|
||||
this.password = data.password;
|
||||
}
|
||||
}
|
||||
|
||||
export class UserCreateDto {
|
||||
username: string;
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
CompanyId?: number; // Add optional CompanyId
|
||||
|
||||
constructor(
|
||||
username: string,
|
||||
FirstName: string,
|
||||
LastName: string,
|
||||
email: string,
|
||||
password: string,
|
||||
CompanyId?: number // Add optional parameter
|
||||
) {
|
||||
this.username = username;
|
||||
this.FirstName = FirstName;
|
||||
this.LastName = LastName;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.CompanyId = CompanyId; // Don't set default value
|
||||
}
|
||||
}
|
||||
|
||||
export class UserResponseDto {
|
||||
id: number;
|
||||
username: string;
|
||||
FirstName: string;
|
||||
LastName: string;
|
||||
email: string;
|
||||
RegDate: Date;
|
||||
|
||||
constructor(
|
||||
id: number,
|
||||
username: string,
|
||||
FirstName: string,
|
||||
LastName: string,
|
||||
email: string,
|
||||
RegDate: Date
|
||||
) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.FirstName = FirstName;
|
||||
this.LastName = LastName;
|
||||
this.email = email;
|
||||
this.RegDate = RegDate;
|
||||
}
|
||||
}
|
||||
|
||||
export class UsersListResponseDto {
|
||||
users: UserResponseDto[];
|
||||
|
||||
constructor(users: UserResponseDto[]) {
|
||||
this.users = users;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@ import {
|
||||
} from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('companies')
|
||||
@Entity('Company')
|
||||
export class Company {
|
||||
@PrimaryGeneratedColumn()
|
||||
CompanyId!: number;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('q_bank')
|
||||
@Entity('QBank')
|
||||
export class QBank {
|
||||
@PrimaryGeneratedColumn()
|
||||
QBankId!: number;
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
OneToMany
|
||||
} from "typeorm";
|
||||
|
||||
import {Company} from "./company.entity";
|
||||
import {QBank} from "./qbank.entity";
|
||||
|
||||
@Entity('user')
|
||||
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 , nullable: false, unique: true })
|
||||
username!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
FirstName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
LastName!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 , nullable: false, unique: true })
|
||||
email!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 , nullable: false })
|
||||
password!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
RegDate!: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
CompanyId?: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
CompanyToken?: string;
|
||||
|
||||
@ManyToOne(() => Company, (company) => company.users)
|
||||
@JoinColumn({ name: 'CompanyId' })
|
||||
company?: Company;
|
||||
|
||||
@OneToMany(() => QBank, qbank => qbank.creator)
|
||||
questionBanks!: QBank[];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Company } from '../entities/company.entity';
|
||||
import { CompanyBasicDto, CompanyUpdateDto, CompanyCreateDto, CompanyResponseDto } from '../dto/company.dto';
|
||||
|
||||
export class CompanyMapper {
|
||||
static toBasicDto(company: Company): CompanyBasicDto {
|
||||
return new CompanyBasicDto(company.CompanyId, company.Name);
|
||||
}
|
||||
|
||||
static toResponseDto(company: Company): CompanyResponseDto {
|
||||
return new CompanyResponseDto(
|
||||
company.CompanyId,
|
||||
company.Name,
|
||||
company.ContactFirstName,
|
||||
company.ContactLastName,
|
||||
company.ContactEmail,
|
||||
company.FirstAPI,
|
||||
company.TokenAPI,
|
||||
company.RegDate
|
||||
);
|
||||
}
|
||||
|
||||
static toEntity(createDto: CompanyCreateDto): Company {
|
||||
const company = new Company();
|
||||
company.Name = createDto.Name;
|
||||
company.ContactFirstName = createDto.ContactFirstName;
|
||||
company.ContactLastName = createDto.ContactLastName;
|
||||
company.ContactEmail = createDto.ContactEmail;
|
||||
company.FirstAPI = createDto.FirstAPI;
|
||||
company.TokenAPI = createDto.TokenAPI;
|
||||
return company;
|
||||
}
|
||||
|
||||
static updateEntity(original: Company, updateDto: CompanyUpdateDto): Company {
|
||||
if (updateDto.Name !== undefined) original.Name = updateDto.Name;
|
||||
if (updateDto.ContactFirstName !== undefined) original.ContactFirstName = updateDto.ContactFirstName;
|
||||
if (updateDto.ContactLastName !== undefined) original.ContactLastName = updateDto.ContactLastName;
|
||||
if (updateDto.ContactEmail !== undefined) original.ContactEmail = updateDto.ContactEmail;
|
||||
if (updateDto.FirstAPI !== undefined) original.FirstAPI = updateDto.FirstAPI;
|
||||
if (updateDto.TokenAPI !== undefined) original.TokenAPI = updateDto.TokenAPI;
|
||||
|
||||
return original;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { QBank } from '../entities/qbank.entity';
|
||||
import { QBankBasicDto, QBankUpdateDto, QBankCreateDto, QBankResponseDto } from '../dto/qbank.dto';
|
||||
|
||||
export class QBankMapper {
|
||||
static toBasicDto(qbank: QBank): QBankBasicDto {
|
||||
return new QBankBasicDto(qbank.QBankId, qbank.Title);
|
||||
}
|
||||
|
||||
static toResponseDto(qbank: QBank): QBankResponseDto {
|
||||
return new QBankResponseDto(
|
||||
qbank.QBankId,
|
||||
qbank.Title,
|
||||
qbank.Creator,
|
||||
qbank.Creation_Date,
|
||||
qbank.no_question
|
||||
);
|
||||
}
|
||||
|
||||
static toEntity(createDto: QBankCreateDto, creatorId: number): QBank {
|
||||
const qbank = new QBank();
|
||||
qbank.Title = createDto.Title;
|
||||
qbank.Creator = creatorId;
|
||||
qbank.Creation_Date = new Date();
|
||||
qbank.no_question = createDto.no_question;
|
||||
return qbank;
|
||||
}
|
||||
|
||||
static updateEntity(original: QBank, updateDto: QBankUpdateDto): QBank {
|
||||
if (updateDto.Title !== undefined) original.Title = updateDto.Title;
|
||||
if (updateDto.no_question !== undefined) original.no_question = updateDto.no_question;
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
static QBankListResponseDto(qbanks: QBank[]): QBankResponseDto[] {
|
||||
return qbanks.map(qbank => this.toResponseDto(qbank));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { User } from '../entities/user.entity';
|
||||
import { UserBasicDto, UserUpdateDto, UserCreateDto, UserResponseDto } from '../dto/user.dto';
|
||||
|
||||
export class UserMapper {
|
||||
static toBasicDto(user: User): UserBasicDto {
|
||||
return new UserBasicDto(user.id, user.username, user.CompanyId || undefined);
|
||||
}
|
||||
|
||||
static toResponseDto(user: User): UserResponseDto {
|
||||
return new UserResponseDto(
|
||||
user.id,
|
||||
user.username,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.email,
|
||||
user.RegDate
|
||||
);
|
||||
}
|
||||
static toEntity(createDto: UserCreateDto): User {
|
||||
const user = new User();
|
||||
user.username = createDto.username;
|
||||
user.FirstName = createDto.FirstName;
|
||||
user.LastName = createDto.LastName;
|
||||
user.email = createDto.email;
|
||||
user.password = createDto.password;
|
||||
|
||||
// Only set CompanyId if it's provided and not null/undefined
|
||||
if (createDto.CompanyId !== undefined && createDto.CompanyId !== null) {
|
||||
user.CompanyId = createDto.CompanyId;
|
||||
}
|
||||
// If not provided, leave it as undefined (which will be NULL in database)
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
static updateEntity(original: User, updateDto: UserUpdateDto): User {
|
||||
if (updateDto.username !== undefined) original.username = updateDto.username;
|
||||
if (updateDto.FirstName !== undefined) original.FirstName = updateDto.FirstName;
|
||||
if (updateDto.LastName !== undefined) original.LastName = updateDto.LastName;
|
||||
if (updateDto.email !== undefined) original.email = updateDto.email;
|
||||
if (updateDto.password !== undefined) original.password = updateDto.password;
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
static UsersListResponseDto(users: User[]): UserResponseDto[] {
|
||||
return users.map(user => this.toResponseDto(user));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ModifiedUser1751827771881 implements MigrationInterface {
|
||||
name = 'ModifiedUser1751827771881'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE \`Company\` (\`CompanyId\` int NOT NULL AUTO_INCREMENT, \`Name\` varchar(255) NOT NULL, \`ContactFirstName\` varchar(255) NOT NULL, \`ContactLastName\` varchar(255) NOT NULL, \`ContactEmail\` varchar(255) NOT NULL, \`FirstAPI\` varchar(255) NOT NULL, \`TokenAPI\` varchar(255) NOT NULL, \`RegDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), PRIMARY KEY (\`CompanyId\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`CREATE TABLE \`user\` (\`id\` int NOT NULL AUTO_INCREMENT, \`username\` varchar(255) NOT NULL, \`FirstName\` varchar(255) NOT NULL, \`LastName\` varchar(255) NOT NULL, \`email\` varchar(255) NOT NULL, \`password\` varchar(255) NOT NULL, \`RegDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`CompanyId\` int NULL, \`CompanyToken\` varchar(255) NULL, UNIQUE INDEX \`IDX_78a916df40e02a9deb1c4b75ed\` (\`username\`), UNIQUE INDEX \`IDX_e12875dfb3b1d92d7d7c5377e2\` (\`email\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`CREATE TABLE \`QBank\` (\`QBankId\` int NOT NULL AUTO_INCREMENT, \`Title\` varchar(255) NOT NULL, \`Creator\` int NOT NULL, \`Creation_Date\` datetime NOT NULL, \`no_question\` int NOT NULL, PRIMARY KEY (\`QBankId\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`ALTER TABLE \`user\` ADD CONSTRAINT \`FK_85994badd6742add52ae81cfb15\` FOREIGN KEY (\`CompanyId\`) REFERENCES \`Company\`(\`CompanyId\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE \`QBank\` ADD CONSTRAINT \`FK_ce24a25a13d18a471bd7c0df014\` FOREIGN KEY (\`Creator\`) REFERENCES \`user\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`QBank\` DROP FOREIGN KEY \`FK_ce24a25a13d18a471bd7c0df014\``);
|
||||
await queryRunner.query(`ALTER TABLE \`user\` DROP FOREIGN KEY \`FK_85994badd6742add52ae81cfb15\``);
|
||||
await queryRunner.query(`DROP TABLE \`QBank\``);
|
||||
await queryRunner.query(`DROP INDEX \`IDX_e12875dfb3b1d92d7d7c5377e2\` ON \`user\``);
|
||||
await queryRunner.query(`DROP INDEX \`IDX_78a916df40e02a9deb1c4b75ed\` ON \`user\``);
|
||||
await queryRunner.query(`DROP TABLE \`user\``);
|
||||
await queryRunner.query(`DROP TABLE \`Company\``);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ModifiedUser1751827770376 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { Company } from '../Database/entities/company.entity';
|
||||
import { CompanyCreateDto, CompanyUpdateDto, CompanyResponseDto, CompanyBasicDto } from '../Database/dto/company.dto';
|
||||
import { CompanyMapper } from '../Database/mappers/company.mapper';
|
||||
import { ICompanyRepository } from './interfaces/ICompanyRepository';
|
||||
|
||||
export class CompanyRepository implements ICompanyRepository {
|
||||
private repository: Repository<Company>;
|
||||
|
||||
constructor(dataSource: DataSource) {
|
||||
this.repository = dataSource.getRepository(Company);
|
||||
}
|
||||
|
||||
// Basic CRUD operations
|
||||
async create(companyCreateDto: CompanyCreateDto): Promise<CompanyResponseDto> {
|
||||
const company = CompanyMapper.toEntity(companyCreateDto);
|
||||
const savedCompany = await this.repository.save(company);
|
||||
return CompanyMapper.toResponseDto(savedCompany);
|
||||
}
|
||||
|
||||
async findById(id: number): Promise<CompanyResponseDto | null> {
|
||||
const company = await this.repository.findOne({ where: { CompanyId: id } });
|
||||
return company ? CompanyMapper.toResponseDto(company) : null;
|
||||
}
|
||||
|
||||
async findByName(name: string): Promise<CompanyResponseDto | null> {
|
||||
const company = await this.repository.findOne({ where: { Name: name } });
|
||||
return company ? CompanyMapper.toResponseDto(company) : null;
|
||||
}
|
||||
|
||||
// async findByContactEmail(email: string): Promise<CompanyResponseDto | null> {
|
||||
// const company = await this.repository.findOne({ where: { ContactEmail: email } });
|
||||
// return company ? CompanyMapper.toResponseDto(company) : null;
|
||||
// }
|
||||
|
||||
async findAll(): Promise<CompanyResponseDto[]> {
|
||||
const companies = await this.repository.find({
|
||||
order: { RegDate: 'DESC' }
|
||||
});
|
||||
return companies.map(company => CompanyMapper.toResponseDto(company));
|
||||
}
|
||||
|
||||
async update(id: number, companyUpdateDto: CompanyUpdateDto): Promise<CompanyResponseDto | null> {
|
||||
const company = await this.repository.findOne({ where: { CompanyId: id } });
|
||||
if (!company) return null;
|
||||
|
||||
const updatedCompany = CompanyMapper.updateEntity(company, companyUpdateDto);
|
||||
const savedCompany = await this.repository.save(updatedCompany);
|
||||
return CompanyMapper.toResponseDto(savedCompany);
|
||||
}
|
||||
|
||||
async deleteById(id: number): Promise<boolean> {
|
||||
const result = await this.repository.delete({ CompanyId: id });
|
||||
return result.affected !== 0;
|
||||
}
|
||||
|
||||
// async exists(id: number): Promise<boolean> {
|
||||
// const count = await this.repository.count({ where: { CompanyId: id } });
|
||||
// return count > 0;
|
||||
// }
|
||||
|
||||
// async nameExists(name: string): Promise<boolean> {
|
||||
// const count = await this.repository.count({ where: { Name: name } });
|
||||
// return count > 0;
|
||||
// }
|
||||
|
||||
// async contactEmailExists(email: string): Promise<boolean> {
|
||||
// const count = await this.repository.count({ where: { ContactEmail: email } });
|
||||
// return count > 0;
|
||||
// }
|
||||
|
||||
// Search and filtering
|
||||
// async findByPartialName(partialName: string): Promise<CompanyResponseDto[]> {
|
||||
// const companies = await this.repository
|
||||
// .createQueryBuilder('company')
|
||||
// .where('company.Name LIKE :name', { name: `%${partialName}%` })
|
||||
// .orderBy('company.RegDate', 'DESC')
|
||||
// .getMany();
|
||||
// return companies.map(company => CompanyMapper.toResponseDto(company));
|
||||
// }
|
||||
|
||||
// async findByPartialContactName(partialName: string): Promise<CompanyResponseDto[]> {
|
||||
// const companies = await this.repository
|
||||
// .createQueryBuilder('company')
|
||||
// .where('company.ContactFirstName LIKE :name', { name: `%${partialName}%` })
|
||||
// .orWhere('company.ContactLastName LIKE :name', { name: `%${partialName}%` })
|
||||
// .orderBy('company.RegDate', 'DESC')
|
||||
// .getMany();
|
||||
// return companies.map(company => CompanyMapper.toResponseDto(company));
|
||||
// }
|
||||
|
||||
// Pagination
|
||||
// async findWithPagination(skip: number, take: number): Promise<{ companies: CompanyResponseDto[], total: number }> {
|
||||
// const [companies, total] = await this.repository.findAndCount({
|
||||
// skip,
|
||||
// take,
|
||||
// order: { RegDate: 'DESC' }
|
||||
// });
|
||||
// const companyDtos = companies.map(company => CompanyMapper.toResponseDto(company));
|
||||
// return {
|
||||
// companies: companyDtos,
|
||||
// total
|
||||
// };
|
||||
// }
|
||||
|
||||
// Basic info
|
||||
async findBasicById(id: number): Promise<CompanyBasicDto | null> {
|
||||
const company = await this.repository.findOne({ where: { CompanyId: id } });
|
||||
return company ? CompanyMapper.toBasicDto(company) : null;
|
||||
}
|
||||
|
||||
async findAllBasic(): Promise<CompanyBasicDto[]> {
|
||||
const companies = await this.repository.find({
|
||||
order: { RegDate: 'DESC' }
|
||||
});
|
||||
return companies.map(company => CompanyMapper.toBasicDto(company));
|
||||
}
|
||||
|
||||
// Relations
|
||||
async findWithUsers(id: number): Promise<CompanyResponseDto | null> {
|
||||
const company = await this.repository.findOne({
|
||||
where: { CompanyId: id },
|
||||
relations: ['users']
|
||||
});
|
||||
return company ? CompanyMapper.toResponseDto(company) : null;
|
||||
}
|
||||
|
||||
// Counting
|
||||
// async count(): Promise<number> {
|
||||
// return await this.repository.count();
|
||||
// }
|
||||
|
||||
// Date range queries
|
||||
// async findByDateRange(startDate: Date, endDate: Date): Promise<CompanyResponseDto[]> {
|
||||
// const companies = await this.repository
|
||||
// .createQueryBuilder('company')
|
||||
// .where('company.RegDate >= :startDate', { startDate })
|
||||
// .andWhere('company.RegDate <= :endDate', { endDate })
|
||||
// .orderBy('company.RegDate', 'DESC')
|
||||
// .getMany();
|
||||
// return companies.map(company => CompanyMapper.toResponseDto(company));
|
||||
// }
|
||||
|
||||
// Bulk operations
|
||||
// async createMany(companyCreateDtos: CompanyCreateDto[]): Promise<CompanyResponseDto[]> {
|
||||
// const companies = companyCreateDtos.map(dto => CompanyMapper.toEntity(dto));
|
||||
// const savedCompanies = await this.repository.save(companies);
|
||||
// return savedCompanies.map(company => CompanyMapper.toResponseDto(company));
|
||||
// }
|
||||
|
||||
// async deleteByIds(ids: number[]): Promise<boolean> {
|
||||
// const result = await this.repository.delete(ids);
|
||||
// return result.affected !== 0;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { QBank } from '../Database/entities/qbank.entity';
|
||||
import { QBankCreateDto, QBankUpdateDto, QBankResponseDto, QBankBasicDto, QBankListResponseDto } from '../Database/dto/qbank.dto';
|
||||
import { QBankMapper } from '../Database/mappers/qbank.mapper';
|
||||
import { IQBankRepository } from './interfaces/IQBankRepository';
|
||||
|
||||
export class QBankRepository implements IQBankRepository {
|
||||
private repository: Repository<QBank>;
|
||||
|
||||
constructor(dataSource: DataSource) {
|
||||
this.repository = dataSource.getRepository(QBank);
|
||||
}
|
||||
|
||||
// Basic CRUD operations
|
||||
async create(qbankCreateDto: QBankCreateDto, creatorId: number): Promise<QBankResponseDto> {
|
||||
const qbank = QBankMapper.toEntity(qbankCreateDto, creatorId);
|
||||
const savedQBank = await this.repository.save(qbank);
|
||||
return QBankMapper.toResponseDto(savedQBank);
|
||||
}
|
||||
|
||||
async findById(id: number): Promise<QBankResponseDto | null> {
|
||||
const qbank = await this.repository.findOne({ where: { QBankId: id } });
|
||||
return qbank ? QBankMapper.toResponseDto(qbank) : null;
|
||||
}
|
||||
|
||||
async findByTitle(title: string): Promise<QBankResponseDto | null> {
|
||||
const qbank = await this.repository.findOne({ where: { Title: title } });
|
||||
return qbank ? QBankMapper.toResponseDto(qbank) : null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<QBankListResponseDto> {
|
||||
const qbanks = await this.repository.find();
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
async update(id: number, qbankUpdateDto: QBankUpdateDto): Promise<QBankResponseDto | null> {
|
||||
const qbank = await this.repository.findOne({ where: { QBankId: id } });
|
||||
if (!qbank) return null;
|
||||
|
||||
const updatedQBank = QBankMapper.updateEntity(qbank, qbankUpdateDto);
|
||||
const savedQBank = await this.repository.save(updatedQBank);
|
||||
return QBankMapper.toResponseDto(savedQBank);
|
||||
}
|
||||
|
||||
async deleteById(id: number): Promise<boolean> {
|
||||
const result = await this.repository.delete({ QBankId: id });
|
||||
return result.affected !== 0;
|
||||
}
|
||||
|
||||
async exists(id: number): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { QBankId: id } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
async titleExists(title: string): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { Title: title } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
// Creator related queries
|
||||
async findByCreator(creatorId: number): Promise<QBankListResponseDto> {
|
||||
const qbanks = await this.repository.find({ where: { Creator: creatorId } });
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
async findByCreatorAndTitle(creatorId: number, title: string): Promise<QBankResponseDto | null> {
|
||||
const qbank = await this.repository.findOne({
|
||||
where: { Creator: creatorId, Title: title }
|
||||
});
|
||||
return qbank ? QBankMapper.toResponseDto(qbank) : null;
|
||||
}
|
||||
|
||||
// Search and filtering
|
||||
async findByPartialTitle(partialTitle: string): Promise<QBankListResponseDto> {
|
||||
const qbanks = await this.repository
|
||||
.createQueryBuilder('qbank')
|
||||
.where('qbank.Title LIKE :title', { title: `%${partialTitle}%` })
|
||||
.getMany();
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
async findByQuestionCount(minCount: number, maxCount?: number): Promise<QBankListResponseDto> {
|
||||
let query = this.repository
|
||||
.createQueryBuilder('qbank')
|
||||
.where('qbank.no_question >= :minCount', { minCount });
|
||||
|
||||
if (maxCount !== undefined) {
|
||||
query = query.andWhere('qbank.no_question <= :maxCount', { maxCount });
|
||||
}
|
||||
|
||||
const qbanks = await query.getMany();
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
// Pagination
|
||||
async findWithPagination(skip: number, take: number): Promise<{ qbanks: QBankListResponseDto, total: number }> {
|
||||
const [qbanks, total] = await this.repository.findAndCount({
|
||||
skip,
|
||||
take,
|
||||
order: { Creation_Date: 'DESC' }
|
||||
});
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return {
|
||||
qbanks: new QBankListResponseDto(qbankDtos),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
// Basic info
|
||||
async findBasicById(id: number): Promise<QBankBasicDto | null> {
|
||||
const qbank = await this.repository.findOne({ where: { QBankId: id } });
|
||||
return qbank ? QBankMapper.toBasicDto(qbank) : null;
|
||||
}
|
||||
|
||||
async findAllBasic(): Promise<QBankBasicDto[]> {
|
||||
const qbanks = await this.repository.find();
|
||||
return qbanks.map(qbank => QBankMapper.toBasicDto(qbank));
|
||||
}
|
||||
|
||||
async findBasicByCreator(creatorId: number): Promise<QBankBasicDto[]> {
|
||||
const qbanks = await this.repository.find({ where: { Creator: creatorId } });
|
||||
return qbanks.map(qbank => QBankMapper.toBasicDto(qbank));
|
||||
}
|
||||
|
||||
// Relations
|
||||
async findWithCreator(id: number): Promise<QBankResponseDto | null> {
|
||||
const qbank = await this.repository.findOne({
|
||||
where: { QBankId: id },
|
||||
relations: ['creator']
|
||||
});
|
||||
return qbank ? QBankMapper.toResponseDto(qbank) : null;
|
||||
}
|
||||
|
||||
// Counting
|
||||
async count(): Promise<number> {
|
||||
return await this.repository.count();
|
||||
}
|
||||
|
||||
async countByCreator(creatorId: number): Promise<number> {
|
||||
return await this.repository.count({ where: { Creator: creatorId } });
|
||||
}
|
||||
|
||||
// Date range queries
|
||||
async findByDateRange(startDate: Date, endDate: Date): Promise<QBankListResponseDto> {
|
||||
const qbanks = await this.repository
|
||||
.createQueryBuilder('qbank')
|
||||
.where('qbank.Creation_Date >= :startDate', { startDate })
|
||||
.andWhere('qbank.Creation_Date <= :endDate', { endDate })
|
||||
.getMany();
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
async findByCreatorAndDateRange(creatorId: number, startDate: Date, endDate: Date): Promise<QBankListResponseDto> {
|
||||
const qbanks = await this.repository
|
||||
.createQueryBuilder('qbank')
|
||||
.where('qbank.Creator = :creatorId', { creatorId })
|
||||
.andWhere('qbank.Creation_Date >= :startDate', { startDate })
|
||||
.andWhere('qbank.Creation_Date <= :endDate', { endDate })
|
||||
.getMany();
|
||||
const qbankDtos = qbanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
// Bulk operations
|
||||
async createMany(qbankCreateDtos: QBankCreateDto[], creatorId: number): Promise<QBankListResponseDto> {
|
||||
const qbanks = qbankCreateDtos.map(dto => QBankMapper.toEntity(dto, creatorId));
|
||||
const savedQBanks = await this.repository.save(qbanks);
|
||||
const qbankDtos = savedQBanks.map(qbank => QBankMapper.toResponseDto(qbank));
|
||||
return new QBankListResponseDto(qbankDtos);
|
||||
}
|
||||
|
||||
async deleteByIds(ids: number[]): Promise<boolean> {
|
||||
const result = await this.repository.delete(ids);
|
||||
return result.affected !== 0;
|
||||
}
|
||||
|
||||
async deleteByCreator(creatorId: number): Promise<boolean> {
|
||||
const result = await this.repository.delete({ Creator: creatorId });
|
||||
return result.affected !== 0;
|
||||
}
|
||||
|
||||
// Statistics
|
||||
async getTotalQuestions(): Promise<number> {
|
||||
const result = await this.repository
|
||||
.createQueryBuilder('qbank')
|
||||
.select('SUM(qbank.no_question)', 'total')
|
||||
.getRawOne();
|
||||
return parseInt(result.total) || 0;
|
||||
}
|
||||
|
||||
async getAverageQuestionsPerBank(): Promise<number> {
|
||||
const result = await this.repository
|
||||
.createQueryBuilder('qbank')
|
||||
.select('AVG(qbank.no_question)', 'average')
|
||||
.getRawOne();
|
||||
return parseFloat(result.average) || 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { User } from '../Database/entities/user.entity';
|
||||
import { UserCreateDto, UserUpdateDto, UserResponseDto, UserBasicDto, UsersListResponseDto } from '../Database/dto/user.dto';
|
||||
import { UserMapper } from '../Database/mappers/user.mapper';
|
||||
import { IUserRepository } from './interfaces/IUserRepository';
|
||||
|
||||
export class UserRepository implements IUserRepository {
|
||||
private repository: Repository<User>;
|
||||
|
||||
constructor(dataSource: DataSource) {
|
||||
// console.log('🗃️ UserRepository constructor called with dataSource:', !!dataSource);
|
||||
this.repository = dataSource.getRepository(User);
|
||||
// console.log('🗃️ TypeORM Repository initialized:', !!this.repository);
|
||||
}
|
||||
|
||||
// Basic CRUD operations
|
||||
async create(userCreateDto: UserCreateDto): Promise<UserResponseDto> {
|
||||
// console.log('Creating user with DTO:', userCreateDto);
|
||||
const user = UserMapper.toEntity(userCreateDto);
|
||||
// console.log('Creating user with DTO:', user);
|
||||
const savedUser = await this.repository.save(user);
|
||||
// console.log('User created:', savedUser);
|
||||
return UserMapper.toResponseDto(savedUser);
|
||||
}
|
||||
|
||||
async findById(id: number): Promise<UserResponseDto | null> {
|
||||
const user = await this.repository.findOne({ where: { id } });
|
||||
return user ? UserMapper.toResponseDto(user) : null;
|
||||
}
|
||||
|
||||
async findByUsername(username: string): Promise<UserResponseDto | null> {
|
||||
const user = await this.repository.findOne({ where: { username } });
|
||||
return user ? UserMapper.toResponseDto(user) : null;
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<UserResponseDto | null> {
|
||||
const user = await this.repository.findOne({ where: { email } });
|
||||
return user ? UserMapper.toResponseDto(user) : null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<UsersListResponseDto> {
|
||||
const users = await this.repository.find();
|
||||
const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
return new UsersListResponseDto(userDtos);
|
||||
}
|
||||
|
||||
async update(id: number, userUpdateDto: UserUpdateDto): Promise<UserResponseDto | null> {
|
||||
const user = await this.repository.findOne({ where: { id } });
|
||||
if (!user) return null;
|
||||
|
||||
const updatedUser = UserMapper.updateEntity(user, userUpdateDto);
|
||||
const savedUser = await this.repository.save(updatedUser);
|
||||
return UserMapper.toResponseDto(savedUser);
|
||||
}
|
||||
|
||||
async deleteById(id: number): Promise<boolean> {
|
||||
const result = await this.repository.delete(id);
|
||||
return result.affected !== 0;
|
||||
}
|
||||
|
||||
async exists(id: number): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { id } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
async usernameExists(username: string): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { username } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
async emailExists(email: string): Promise<boolean> {
|
||||
const count = await this.repository.count({ where: { email } });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
// Company related queries
|
||||
// async findByCompanyId(companyId: number): Promise<UsersListResponseDto> {
|
||||
// const users = await this.repository.find({ where: { CompanyId: companyId } });
|
||||
// const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
// return new UsersListResponseDto(userDtos);
|
||||
// }
|
||||
|
||||
// async findByCompanyToken(companyToken: string): Promise<UsersListResponseDto> {
|
||||
// const users = await this.repository.find({ where: { CompanyToken: companyToken } });
|
||||
// const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
// return new UsersListResponseDto(userDtos);
|
||||
// }
|
||||
|
||||
// Search and filtering
|
||||
// async findByPartialUsername(partialUsername: string): Promise<UsersListResponseDto> {
|
||||
// const users = await this.repository
|
||||
// .createQueryBuilder('user')
|
||||
// .where('user.username LIKE :username', { username: `%${partialUsername}%` })
|
||||
// .getMany();
|
||||
// const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
// return new UsersListResponseDto(userDtos);
|
||||
// }
|
||||
|
||||
// async findByPartialName(partialName: string): Promise<UsersListResponseDto> {
|
||||
// const users = await this.repository
|
||||
// .createQueryBuilder('user')
|
||||
// .where('user.FirstName LIKE :name', { name: `%${partialName}%` })
|
||||
// .orWhere('user.LastName LIKE :name', { name: `%${partialName}%` })
|
||||
// .getMany();
|
||||
// const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
// return new UsersListResponseDto(userDtos);
|
||||
// }
|
||||
|
||||
// Pagination
|
||||
// async findWithPagination(skip: number, take: number): Promise<{ users: UsersListResponseDto, total: number }> {
|
||||
// const [users, total] = await this.repository.findAndCount({
|
||||
// skip,
|
||||
// take,
|
||||
// order: { RegDate: 'DESC' }
|
||||
// });
|
||||
// const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
// return {
|
||||
// users: new UsersListResponseDto(userDtos),
|
||||
// total
|
||||
// };
|
||||
// }
|
||||
|
||||
// Basic info
|
||||
// async findBasicById(id: number): Promise<UserBasicDto | null> {
|
||||
// const user = await this.repository.findOne({ where: { id } });
|
||||
// return user ? UserMapper.toBasicDto(user) : null;
|
||||
// }
|
||||
|
||||
async findAllBasic(): Promise<UserBasicDto[]> {
|
||||
const users = await this.repository.find();
|
||||
return users.map(user => UserMapper.toBasicDto(user));
|
||||
}
|
||||
|
||||
// Relations
|
||||
async findWithCompany(id: number): Promise<UserResponseDto | null> {
|
||||
const user = await this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ['company']
|
||||
});
|
||||
return user ? UserMapper.toResponseDto(user) : null;
|
||||
}
|
||||
|
||||
async findWithQuestionBanks(id: number): Promise<UserResponseDto | null> {
|
||||
const user = await this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ['questionBanks']
|
||||
});
|
||||
return user ? UserMapper.toResponseDto(user) : null;
|
||||
}
|
||||
|
||||
// async findWithAllRelations(id: number): Promise<UserResponseDto | null> {
|
||||
// const user = await this.repository.findOne({
|
||||
// where: { id },
|
||||
// relations: ['company', 'questionBanks']
|
||||
// });
|
||||
// return user ? UserMapper.toResponseDto(user) : null;
|
||||
// }
|
||||
|
||||
// Counting
|
||||
// async count(): Promise<number> {
|
||||
// return await this.repository.count();
|
||||
// }
|
||||
|
||||
// async countByCompany(companyId: number): Promise<number> {
|
||||
// return await this.repository.count({ where: { CompanyId: companyId } });
|
||||
// }
|
||||
|
||||
// Date range queries
|
||||
// async findByDateRange(startDate: Date, endDate: Date): Promise<UsersListResponseDto> {
|
||||
// const users = await this.repository
|
||||
// .createQueryBuilder('user')
|
||||
// .where('user.RegDate >= :startDate', { startDate })
|
||||
// .andWhere('user.RegDate <= :endDate', { endDate })
|
||||
// .getMany();
|
||||
// const userDtos = users.map(user => UserMapper.toResponseDto(user));
|
||||
// return new UsersListResponseDto(userDtos);
|
||||
// }
|
||||
|
||||
// Bulk operations
|
||||
// async createMany(userCreateDtos: UserCreateDto[]): Promise<UsersListResponseDto> {
|
||||
// const users = userCreateDtos.map(dto => UserMapper.toEntity(dto));
|
||||
// const savedUsers = await this.repository.save(users);
|
||||
// const userDtos = savedUsers.map(user => UserMapper.toResponseDto(user));
|
||||
// return new UsersListResponseDto(userDtos);
|
||||
// }
|
||||
|
||||
// async deleteByIds(ids: number[]): Promise<boolean> {
|
||||
// const result = await this.repository.delete(ids);
|
||||
// return result.affected !== 0;
|
||||
// }
|
||||
|
||||
// Authentication support (returns raw entity for password verification)
|
||||
async findRawByUsername(username: string): Promise<User | null> {
|
||||
console
|
||||
return await this.repository.findOne({ where: { username } });
|
||||
}
|
||||
|
||||
// async findRawById(id: number): Promise<User | null> {
|
||||
// return await this.repository.findOne({ where: { id } });
|
||||
// }
|
||||
|
||||
async findRawByEmail(email: string): Promise<User | null> {
|
||||
return await this.repository.findOne({ where: { email } });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { CompanyCreateDto, CompanyUpdateDto, CompanyResponseDto, CompanyBasicDto } from '../../Database/dto/company.dto';
|
||||
|
||||
export interface ICompanyRepository {
|
||||
// Basic CRUD operations
|
||||
create(companyCreateDto: CompanyCreateDto): Promise<CompanyResponseDto>;
|
||||
findById(id: number): Promise<CompanyResponseDto | null>;
|
||||
findByName(name: string): Promise<CompanyResponseDto | null>;
|
||||
// findByContactEmail(email: string): Promise<CompanyResponseDto | null>;
|
||||
findAll(): Promise<CompanyResponseDto[]>;
|
||||
update(id: number, companyUpdateDto: CompanyUpdateDto): Promise<CompanyResponseDto | null>;
|
||||
deleteById(id: number): Promise<boolean>;
|
||||
// exists(id: number): Promise<boolean>;
|
||||
// nameExists(name: string): Promise<boolean>;
|
||||
// contactEmailExists(email: string): Promise<boolean>;
|
||||
|
||||
// Search and filtering
|
||||
// // findByPartialName(partialName: string): Promise<CompanyResponseDto[]>;
|
||||
// // findByPartialContactName(partialName: string): Promise<CompanyResponseDto[]>;
|
||||
|
||||
// Pagination
|
||||
// findWithPagination(skip: number, take: number): Promise<{ companies: CompanyResponseDto[], total: number }>;
|
||||
|
||||
// Basic info
|
||||
findBasicById(id: number): Promise<CompanyBasicDto | null>;
|
||||
findAllBasic(): Promise<CompanyBasicDto[]>;
|
||||
|
||||
// Relations
|
||||
// findWithUsers(id: number): Promise<CompanyResponseDto | null>;
|
||||
|
||||
// Counting
|
||||
// count(): Promise<number>;
|
||||
|
||||
// Date range queries
|
||||
// findByDateRange(startDate: Date, endDate: Date): Promise<CompanyResponseDto[]>;
|
||||
|
||||
// Bulk operations
|
||||
// createMany(companyCreateDtos: CompanyCreateDto[]): Promise<CompanyResponseDto[]>;
|
||||
// deleteByIds(ids: number[]): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { QBank } from '../../Database/entities/qbank.entity';
|
||||
import { QBankCreateDto, QBankUpdateDto, QBankResponseDto, QBankBasicDto, QBankListResponseDto } from '../../Database/dto/qbank.dto';
|
||||
|
||||
export interface IQBankRepository {
|
||||
// Basic CRUD operations
|
||||
create(qbankCreateDto: QBankCreateDto, creatorId: number): Promise<QBankResponseDto>;
|
||||
findById(id: number): Promise<QBankResponseDto | null>;
|
||||
findByTitle(title: string): Promise<QBankResponseDto | null>;
|
||||
findAll(): Promise<QBankListResponseDto>;
|
||||
update(id: number, qbankUpdateDto: QBankUpdateDto): Promise<QBankResponseDto | null>;
|
||||
deleteById(id: number): Promise<boolean>;
|
||||
exists(id: number): Promise<boolean>;
|
||||
titleExists(title: string): Promise<boolean>;
|
||||
|
||||
// Creator related queries
|
||||
findByCreator(creatorId: number): Promise<QBankListResponseDto>;
|
||||
findByCreatorAndTitle(creatorId: number, title: string): Promise<QBankResponseDto | null>;
|
||||
|
||||
// Search and filtering
|
||||
findByPartialTitle(partialTitle: string): Promise<QBankListResponseDto>;
|
||||
findByQuestionCount(minCount: number, maxCount?: number): Promise<QBankListResponseDto>;
|
||||
|
||||
// Pagination
|
||||
findWithPagination(skip: number, take: number): Promise<{ qbanks: QBankListResponseDto, total: number }>;
|
||||
|
||||
// Basic info
|
||||
findBasicById(id: number): Promise<QBankBasicDto | null>;
|
||||
findAllBasic(): Promise<QBankBasicDto[]>;
|
||||
findBasicByCreator(creatorId: number): Promise<QBankBasicDto[]>;
|
||||
|
||||
// Relations
|
||||
findWithCreator(id: number): Promise<QBankResponseDto | null>;
|
||||
|
||||
// Counting
|
||||
count(): Promise<number>;
|
||||
countByCreator(creatorId: number): Promise<number>;
|
||||
|
||||
// Date range queries
|
||||
findByDateRange(startDate: Date, endDate: Date): Promise<QBankListResponseDto>;
|
||||
findByCreatorAndDateRange(creatorId: number, startDate: Date, endDate: Date): Promise<QBankListResponseDto>;
|
||||
|
||||
// Bulk operations
|
||||
createMany(qbankCreateDtos: QBankCreateDto[], creatorId: number): Promise<QBankListResponseDto>;
|
||||
deleteByIds(ids: number[]): Promise<boolean>;
|
||||
deleteByCreator(creatorId: number): Promise<boolean>;
|
||||
|
||||
// Statistics
|
||||
getTotalQuestions(): Promise<number>;
|
||||
getAverageQuestionsPerBank(): Promise<number>;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { User } from '../../Database/entities/user.entity';
|
||||
import { UserCreateDto, UserUpdateDto, UserResponseDto, UserBasicDto, UsersListResponseDto } from '../../Database/dto/user.dto';
|
||||
|
||||
export interface IUserRepository {
|
||||
// Basic CRUD operations
|
||||
create(userCreateDto: UserCreateDto): Promise<UserResponseDto>;
|
||||
findById(id: number): Promise<UserResponseDto | null>;
|
||||
// findByUsername(username: string): Promise<UserResponseDto | null>;
|
||||
findByEmail(email: string): Promise<UserResponseDto | null>;
|
||||
findAll(): Promise<UsersListResponseDto>;
|
||||
update(id: number, userUpdateDto: UserUpdateDto): Promise<UserResponseDto | null>;
|
||||
deleteById(id: number): Promise<boolean>;
|
||||
exists(id: number): Promise<boolean>;
|
||||
usernameExists(username: string): Promise<boolean>;
|
||||
emailExists(email: string): Promise<boolean>;
|
||||
|
||||
// Company related queries
|
||||
// findByCompanyId(companyId: number): Promise<UsersListResponseDto>;
|
||||
// findByCompanyToken(companyToken: string): Promise<UsersListResponseDto>;
|
||||
|
||||
// Search and filtering
|
||||
// findByPartialUsername(partialUsername: string): Promise<UsersListResponseDto>;
|
||||
// findByPartialName(partialName: string): Promise<UsersListResponseDto>;
|
||||
|
||||
// Pagination
|
||||
// findWithPagination(skip: number, take: number): Promise<{ users: UsersListResponseDto, total: number }>;
|
||||
|
||||
// Basic info
|
||||
// findBasicById(id: number): Promise<UserBasicDto | null>;
|
||||
findAllBasic(): Promise<UserBasicDto[]>;
|
||||
|
||||
// Relations
|
||||
findWithCompany(id: number): Promise<UserResponseDto | null>;
|
||||
findWithQuestionBanks(id: number): Promise<UserResponseDto | null>;
|
||||
// findWithAllRelations(id: number): Promise<UserResponseDto | null>;
|
||||
|
||||
// Counting
|
||||
// count(): Promise<number>;
|
||||
// countByCompany(companyId: number): Promise<number>;
|
||||
|
||||
// Date range queries
|
||||
// findByDateRange(startDate: Date, endDate: Date): Promise<UsersListResponseDto>;
|
||||
|
||||
// Bulk operations
|
||||
// createMany(userCreateDtos: UserCreateDto[]): Promise<UsersListResponseDto>;
|
||||
// deleteByIds(ids: number[]): Promise<boolean>;
|
||||
|
||||
// Authentication support (returns raw entity for password verification)
|
||||
findRawByUsername(username: string): Promise<User | null>;
|
||||
// findRawById(id: number): Promise<User | null>;
|
||||
findRawByEmail(email: string): Promise<User | null>;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { CompanyQueryDispatcher } from '../../functions/Company/Queries/CompanyQueryDispatcher';
|
||||
import { CompanyCommandDispatcher } from '../../functions/Company/Commands/CompanyCommandDispatcher';
|
||||
import { CreateCompanyCommand } from '../../functions/Company/Commands/CompanyCommand';
|
||||
import { CompanyCreateDto } from '../../Database/dto/company.dto';
|
||||
|
||||
export const createCompany = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: CompanyQueryDispatcher,
|
||||
commandDispatcher: CompanyCommandDispatcher
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const companyCreateDto: CompanyCreateDto = req.body;
|
||||
|
||||
console.log('Creating company with data:', companyCreateDto);
|
||||
|
||||
// Validate required fields
|
||||
if (!companyCreateDto.Name || !companyCreateDto.FirstNameContact || !companyCreateDto.LastNameContact) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Name, FirstNameContact, and LastNameContact are required',
|
||||
received: companyCreateDto
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch the command
|
||||
const result = await commandDispatcher.dispatch(new CreateCompanyCommand(companyCreateDto));
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: 'Company created successfully'
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error creating company:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import {
|
||||
GetUserByUsernameQuery,
|
||||
GetUserByEmailQuery,
|
||||
GetRawUserByUsernameQuery,
|
||||
GetRawUserByEmailQuery
|
||||
} from '../../functions/Users/Queries/UserQuery';
|
||||
import { comparePasswords } from '../../middlewares/security';
|
||||
import { createTokenFromUserResponse, clearAuthCookie } from '../../middlewares/authentication';
|
||||
|
||||
export async function authenticateUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { Email, Username, Password } = req.body;
|
||||
|
||||
if (!Password || (!Email && !Username)) {
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Password and either Email or Username are required',
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the raw user entity for password verification
|
||||
let rawUser = null;
|
||||
if (Username) {
|
||||
const rawQuery = new GetRawUserByUsernameQuery(Username);
|
||||
rawUser = await queryDispatcher.dispatch(rawQuery);
|
||||
} else if (Email) {
|
||||
const rawQuery = new GetRawUserByEmailQuery(Email);
|
||||
rawUser = await queryDispatcher.dispatch(rawQuery);
|
||||
}
|
||||
|
||||
console.log('Raw user found:', rawUser);
|
||||
if (!rawUser) {
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials',
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify password using security middleware
|
||||
const isPasswordValid = await comparePasswords(Password, rawUser.password);
|
||||
if (!isPasswordValid) {
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials',
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user response DTO (without password)
|
||||
let user = null;
|
||||
if (Username) {
|
||||
const query = new GetUserByUsernameQuery(Username);
|
||||
user = await queryDispatcher.dispatch(query);
|
||||
} else if (Email) {
|
||||
const query = new GetUserByEmailQuery(Email);
|
||||
user = await queryDispatcher.dispatch(query);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Authentication failed',
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Create JWT token with basic user data
|
||||
const token = createTokenFromUserResponse({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
CompanyId: rawUser.CompanyId || undefined // Use undefined instead of 0
|
||||
});
|
||||
|
||||
// Set token as HTTP-only cookie
|
||||
res.cookie("jwt", token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: process.env.NODE_ENV === 'production' ? "none" : "lax",
|
||||
maxAge: 60 * 60 * 1000, // 1 hour
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Authentication successful',
|
||||
data: user
|
||||
});
|
||||
} catch (error: any) {
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Authentication failed',
|
||||
autoLogout: true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { EmailExistsQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function checkEmailExists(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { email } = req.params;
|
||||
const query = new EmailExistsQuery(email);
|
||||
const exists = await queryDispatcher.dispatch(query);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
exists
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to check email existence'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { UserExistsQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function checkUserExists(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
if(!req.user || !req.user.id) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Unauthorized access - user not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const id = req.user.id.toString();
|
||||
const query = new UserExistsQuery(parseInt(id));
|
||||
const exists = await queryDispatcher.dispatch(query);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
exists
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to check user existence'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { UsernameExistsQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function checkUsernameExists(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { username } = req.params;
|
||||
const query = new UsernameExistsQuery(username);
|
||||
const exists = await queryDispatcher.dispatch(query);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
exists
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to check username existence'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { UserCreateDto } from '../../Database/dto/user.dto';
|
||||
import { CreateUserCommand } from '../../functions/Users/Commands/UserCommand';
|
||||
|
||||
export async function createUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { Username, FirstName, LastName, Password, Email } = req.body;
|
||||
// Log the request details
|
||||
// console.log('📝 Create user request:', {
|
||||
// method: req.method,
|
||||
// url: req.originalUrl,
|
||||
// body: req.body
|
||||
// });
|
||||
// Validate required fields
|
||||
if (!Username || !FirstName || !LastName || !Password || !Email) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'All fields are required: Username, FirstName, LastName, Password, Email'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userCreateDto = new UserCreateDto(Username, FirstName, LastName, Email, Password);
|
||||
// Check if dto is valid
|
||||
// console.log('UserCreateDto:', userCreateDto);
|
||||
const command = new CreateUserCommand(userCreateDto);
|
||||
// console.log('Dispatching CreateUserCommand:', command);
|
||||
|
||||
const newUser = await commandDispatcher.dispatch(command);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'User created successfully',
|
||||
data: newUser
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to create user'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { DeleteUserCommand } from '../../functions/Users/Commands/UserCommand';
|
||||
|
||||
export async function deleteUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
if(!req.user || !req.user.id) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Unauthorized access - user not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const userId = req.user.id.toString();
|
||||
|
||||
if (!userId) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'User ID is required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const command = new DeleteUserCommand(parseInt(userId));
|
||||
const deleted = await commandDispatcher.dispatch(command);
|
||||
|
||||
if (!deleted) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'User deleted successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to delete user'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { GetAllUsersQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function getAllUsers(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const query = new GetAllUsersQuery();
|
||||
const users = await queryDispatcher.dispatch(query);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: users
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to get users'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
|
||||
export async function getCurrentUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Return the authenticated user's basic info from the JWT token
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: req.user // Returns UserBasicDto
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to get current user'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { GetUserBasicByIdQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function getUserBasic(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
if(!req.user || !req.user.id) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Unauthorized access - user not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const id = req.user.id.toString();
|
||||
const query = new GetUserBasicByIdQuery(parseInt(id));
|
||||
const user = await queryDispatcher.dispatch(query);
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: user
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to get user'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { GetUserByIdQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function getUserDetails(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
if(!req.user || !req.user.id) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Unauthorized access - user not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const userId = req.user.id.toString();
|
||||
if (!userId) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'User ID is required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const query = new GetUserByIdQuery(parseInt(userId));
|
||||
const user = await queryDispatcher.dispatch(query);
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: user
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to get user details'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { GetUsersCountQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function getUsersCount(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const query = new GetUsersCountQuery();
|
||||
const count = await queryDispatcher.dispatch(query);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to get users count'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { GetUsersWithPaginationQuery } from '../../functions/Users/Queries/UserQuery';
|
||||
|
||||
export async function getUsersPaginated(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const limit = parseInt(req.query.limit as string) || 10;
|
||||
|
||||
const query = new GetUsersWithPaginationQuery(page, limit);
|
||||
const result = await queryDispatcher.dispatch(query);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result.users,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: result.total,
|
||||
totalPages: Math.ceil(result.total / limit)
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to get paginated users'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export { createUser } from './createUser';
|
||||
export { authenticateUser } from './authenticateUser';
|
||||
export { logoutUser } from './logoutUser';
|
||||
export { getCurrentUser } from './getCurrentUser';
|
||||
export { getUserDetails } from './getUserDetails';
|
||||
export { updateUser } from './updateUser';
|
||||
export { deleteUser } from './deleteUser';
|
||||
export { getAllUsers } from './getAllUsers';
|
||||
export { getUserBasic } from './getUserBasic';
|
||||
export { checkUserExists } from './checkUserExists';
|
||||
export { checkUsernameExists } from './checkUsernameExists';
|
||||
export { checkEmailExists } from './checkEmailExists';
|
||||
export { getUsersCount } from './getUsersCount';
|
||||
export { getUsersPaginated } from './getUsersPaginated';
|
||||
export { resetPassword } from './resetPassword';
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { clearAuthCookie } from '../../middlewares/authentication';
|
||||
|
||||
export async function logoutUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Clear the JWT cookie using the centralized function
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Even if there's an error, still try to clear the cookie
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Logout failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { ResetUserPasswordCommand } from '../../functions/Users/Commands/UserCommand';
|
||||
|
||||
export async function resetPassword(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
if(!req.user || !req.user.id) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Unauthorized access - user not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const id = req.user.id.toString();
|
||||
const { newPassword } = req.body;
|
||||
|
||||
if (!newPassword) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'New password is required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const command = new ResetUserPasswordCommand(parseInt(id), newPassword);
|
||||
const updatedUser = await commandDispatcher.dispatch(command);
|
||||
|
||||
if (!updatedUser) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Password reset successfully'
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to reset password'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { UserQueryDispatcher } from '../../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { UserUpdateDto } from '../../Database/dto/user.dto';
|
||||
import { UpdateUserCommand } from '../../functions/Users/Commands/UserCommand';
|
||||
|
||||
export async function updateUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
queryDispatcher: UserQueryDispatcher,
|
||||
commandDispatcher: UserCommandDispatcher
|
||||
): Promise<void> {
|
||||
try {
|
||||
|
||||
if(!req.user || !req.user.id) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Unauthorized access - user not authenticated'
|
||||
});
|
||||
return;
|
||||
}
|
||||
const userId = req.user.id.toString();
|
||||
const { Username, FirstName, LastName, Password, Email } = req.body;
|
||||
|
||||
if (!userId) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'User ID is required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userUpdateDto = new UserUpdateDto({
|
||||
username: Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
password: Password,
|
||||
email: Email
|
||||
});
|
||||
|
||||
const command = new UpdateUserCommand(parseInt(userId), userUpdateDto);
|
||||
const updatedUser = await commandDispatcher.dispatch(command);
|
||||
|
||||
if (!updatedUser) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'User updated successfully',
|
||||
data: updatedUser
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message || 'Failed to update user'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { UserRepository } from '../Repositories/UserRepository';
|
||||
import { UserQueryDispatcher } from '../functions/Users/Queries/UserQueryDispatcher';
|
||||
import { UserCommandDispatcher } from '../functions/Users/Commands/UserCommandDispatcher';
|
||||
import { auth } from '../middlewares/authentication';
|
||||
|
||||
// Import all router functions
|
||||
import { createUser, authenticateUser,
|
||||
getUserDetails, updateUser,
|
||||
deleteUser, getAllUsers,
|
||||
getUserBasic, checkUserExists,
|
||||
checkUsernameExists, checkEmailExists,
|
||||
getUsersCount, getUsersPaginated,
|
||||
resetPassword, logoutUser, getCurrentUser } from '../RouterFunctions/Users/index';
|
||||
|
||||
export class UserRouter {
|
||||
private router: Router;
|
||||
private queryDispatcher: UserQueryDispatcher;
|
||||
private commandDispatcher: UserCommandDispatcher;
|
||||
|
||||
constructor(dataSource: DataSource) {
|
||||
this.router = Router();
|
||||
const userRepository = new UserRepository(dataSource);
|
||||
this.queryDispatcher = new UserQueryDispatcher(userRepository);
|
||||
this.commandDispatcher = new UserCommandDispatcher(userRepository);
|
||||
this.initializeRoutes();
|
||||
}
|
||||
|
||||
private initializeRoutes(): void {
|
||||
|
||||
// PUBLIC ROUTES (No authentication required)
|
||||
this.router.post('/create', (req, res) => {
|
||||
createUser(req, res, this.queryDispatcher, this.commandDispatcher);
|
||||
});
|
||||
|
||||
this.router.post('/authenticate', (req, res) =>
|
||||
authenticateUser(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
// PROTECTED ROUTES (Authentication required)
|
||||
this.router.post('/logout', auth, (req, res) =>
|
||||
logoutUser(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
this.router.get('/current', auth, (req, res) =>
|
||||
getCurrentUser(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
this.router.get('/details', auth, (req, res) =>
|
||||
getUserDetails(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
this.router.put('/update', auth, (req, res) =>
|
||||
updateUser(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
this.router.delete('/delete', auth, (req, res) =>
|
||||
deleteUser(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
// Additional protected routes
|
||||
this.router.get('/all', auth, (req, res) =>
|
||||
getAllUsers(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
);
|
||||
|
||||
// this.router.get('/basic/:id', auth, (req, res) =>
|
||||
// getUserBasic(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
|
||||
// this.router.get('/exists/:id', auth, (req, res) =>
|
||||
// checkUserExists(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
|
||||
// this.router.get('/username-exists/:username', auth, (req, res) =>
|
||||
// checkUsernameExists(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
|
||||
// this.router.get('/email-exists/:email', auth, (req, res) =>
|
||||
// checkEmailExists(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
|
||||
// this.router.get('/count', auth, (req, res) =>
|
||||
// getUsersCount(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
|
||||
// this.router.get('/paginated', auth, (req, res) =>
|
||||
// getUsersPaginated(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
|
||||
// this.router.put('/reset-password/:id', auth, (req, res) =>
|
||||
// resetPassword(req, res, this.queryDispatcher, this.commandDispatcher)
|
||||
// );
|
||||
}
|
||||
|
||||
public getRouter(): Router {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
// Export a function to create the router
|
||||
export default function createUserRouter(dataSource: DataSource): Router {
|
||||
const userRouter = new UserRouter(dataSource);
|
||||
return userRouter.getRouter();
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
//User class for the all the user related operations in the SerpentRace Backend
|
||||
import { comparePasswords, hashPassword } from '../middleware/secure';
|
||||
import { FindOptionsWhere, Equal } from 'typeorm';
|
||||
import { AppDataSourceOne } from '../DB/Datasource';
|
||||
import { User } from '../DB/user.entity';
|
||||
import { Company } from '../DB/company.entity';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
export class CompanyService {
|
||||
private companyRepository = AppDataSourceOne.getRepository(Company);
|
||||
|
||||
async createCompany(company: Company): Promise<Company> {
|
||||
return this.companyRepository.save(company);
|
||||
}
|
||||
|
||||
private async findCompanyById(id: number): Promise<Company | null> {
|
||||
const where: FindOptionsWhere<Company> = { CompanyId: Equal(id) };
|
||||
return this.companyRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
private async findCompanyByName(name: string): Promise<Company | null> {
|
||||
const where: FindOptionsWhere<Company> = { Name: Equal(name) };
|
||||
return this.companyRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
async getCompanyDetails(req: Request, res: Response): Promise<void> {
|
||||
const companyId = parseInt(req.params.companyid);
|
||||
if (isNaN(companyId)) {
|
||||
res.status(400).json({ message: 'Invalid company ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
res.status(404).json({ message: 'Company not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json(company);
|
||||
}
|
||||
|
||||
async updateCompany(req: Request, res: Response): Promise<void> {
|
||||
const companyId = parseInt(req.params.companyid);
|
||||
if (isNaN(companyId)) {
|
||||
res.status(400).json({ message: 'Invalid company ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
res.status(404).json({ message: 'Company not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedData = req.body;
|
||||
Object.assign(company, updatedData);
|
||||
|
||||
const updatedCompany = await this.companyRepository.save(company);
|
||||
res.status(200).json(updatedCompany);
|
||||
}
|
||||
async deleteCompany(req: Request, res: Response): Promise<void> {
|
||||
const companyId = parseInt(req.params.companyid);
|
||||
if (isNaN(companyId)) {
|
||||
res.status(400).json({ message: 'Invalid company ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
res.status(404).json({ message: 'Company not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.companyRepository.remove(company);
|
||||
res.status(204).send();
|
||||
}
|
||||
|
||||
private async addUserToCompany(userId: number, companyId: number): Promise<void> {
|
||||
const user = await AppDataSourceOne.getRepository(User).findOneBy({ id: userId });
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
throw new Error('Company not found');
|
||||
}
|
||||
|
||||
user.CompanyId = companyId;
|
||||
await AppDataSourceOne.getRepository(User).save(user);
|
||||
// Optionally, you can also add the user to the company's user list if needed
|
||||
if (!company.users) {
|
||||
company.users = [];
|
||||
}
|
||||
company.users.push(user);
|
||||
}
|
||||
private async removeUserFromCompany(userId: number, companyId: number): Promise<void> {
|
||||
const user = await AppDataSourceOne.getRepository(User).findOneBy({ id: userId });
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const company = await this.findCompanyById(companyId);
|
||||
if (!company) {
|
||||
throw new Error('Company not found');
|
||||
}
|
||||
|
||||
user.CompanyId = 0; // Remove association
|
||||
user.CompanyToken = ''; // Remove association
|
||||
await AppDataSourceOne.getRepository(User).save(user);
|
||||
|
||||
// Optionally, remove the user from the company's user list
|
||||
company.users = company.users?.filter(u => u.id !== userId) || [];
|
||||
await this.companyRepository.save(company);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
//User class for the all the user related operations in the SerpentRace Backend
|
||||
import { comparePasswords, hashPassword } from '../middleware/secure';
|
||||
import { FindOptionsWhere, Equal } from 'typeorm';
|
||||
import { AppDataSourceOne } from '../DB/Datasource';
|
||||
import { createToken } from '../middleware/auth';
|
||||
import { User } from '../DB/user.entity';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
export class UserService {
|
||||
private userRepository = AppDataSourceOne.getRepository(User);
|
||||
|
||||
async createUser(user : User): Promise<User> {
|
||||
// Hash the password before saving the user
|
||||
user.Password = await hashPassword(user.Password);
|
||||
return this.userRepository.save(user);
|
||||
}
|
||||
|
||||
private async findUserByEmail(email: string): Promise<User | null> {
|
||||
const where: FindOptionsWhere<User> = { Email: Equal(email) };
|
||||
return this.userRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
private async findUserByUsername(username: string): Promise<User | null> {
|
||||
const where: FindOptionsWhere<User> = { Username: Equal(username) };
|
||||
return this.userRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
private async findUserById(id: number): Promise<User | null> {
|
||||
const where: FindOptionsWhere<User> = { id: Equal(id) };
|
||||
return this.userRepository.findOneBy(where);
|
||||
}
|
||||
|
||||
async authenticateUser(req: Request, res: Response): Promise<void> {
|
||||
const { String_data, Password } = req.body;
|
||||
if (!String_data || !Password) {
|
||||
res.status(400).json({ message: 'Email and password are required' });
|
||||
return;
|
||||
}
|
||||
// Check if String_data is an email or username
|
||||
let user: User | null = null;
|
||||
if (String_data.includes('@')) {
|
||||
user = await this.findUserByEmail(String_data.toLowerCase());
|
||||
}
|
||||
else {
|
||||
user = await this.findUserByUsername(String_data);
|
||||
}
|
||||
// If user is not found or password does not match, return 401
|
||||
if (!user || !(await comparePasswords(Password, user.Password))) {
|
||||
res.status(401).json({ message: 'Invalid credentials' });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = createToken(user);
|
||||
// Set the token in the response header
|
||||
res.cookie("jwt", token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "none",
|
||||
maxAge: 60 * 60 * 1000
|
||||
});
|
||||
res.status(200).json({ message: 'Authentication successful' });
|
||||
}
|
||||
|
||||
async getUserDetails(req: Request, res: Response): Promise<void> {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
const user = await this.userRepository.findOneBy({ id: userId });
|
||||
if (!user) {
|
||||
res.status(404).json({ message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
res.json(user);
|
||||
}
|
||||
|
||||
async updateUser(req: Request, res: Response): Promise<void> {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
const user = await this.findUserById(userId);
|
||||
if (!user) {
|
||||
res.status(404).json({ message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(user, req.body);
|
||||
|
||||
if (req.body.Password) {
|
||||
user.Password = await hashPassword(req.body.Password);
|
||||
}
|
||||
await this.userRepository.save(user);
|
||||
res.json({ message: 'User details updated successfully' });
|
||||
}
|
||||
|
||||
async deleteUser(req: Request, res: Response): Promise<void> {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
const user = await this.findUserById(userId);
|
||||
if (!user) {
|
||||
res.status(404).json({ message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
await this.userRepository.remove(user);
|
||||
res.json({ message: 'User deleted successfully' });
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
//Router for user-related routes
|
||||
import { Router } from 'express';
|
||||
import { UserService } from './User';
|
||||
import { auth } from '../middleware/auth';
|
||||
import { User } from '../DB/user.entity';
|
||||
|
||||
const userRouter = Router();
|
||||
const userService = new UserService();
|
||||
// Route to create a new user
|
||||
userRouter.post('/create', async (req, res) => {
|
||||
try {
|
||||
const user = await userService.createUser(req.body);
|
||||
res.status(201).json({ message: 'User created successfully'});
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error creating user', error });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to authenticate a user
|
||||
userRouter.post('/authenticate', async (req, res) => {
|
||||
try {
|
||||
await userService.authenticateUser(req, res);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error authenticating user', error });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to get user details (protected route)
|
||||
userRouter.get('/details', auth, async (req, res) => {
|
||||
try {
|
||||
await userService.getUserDetails(req, res);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error fetching user details', error });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to update user details (protected route)
|
||||
userRouter.put('/update', auth, async (req, res) =>{
|
||||
try{
|
||||
await userService.updateUser(req, res);
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ message: 'Error updating user', error });
|
||||
}
|
||||
});
|
||||
// Route to delete a user (protected route)
|
||||
userRouter.delete('/delete', auth, async (req, res) => {
|
||||
try {
|
||||
await userService.deleteUser(req, res);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error deleting user', error });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
//export default userRouter;
|
||||
export { userRouter };
|
||||
@@ -1,9 +0,0 @@
|
||||
import Express from 'express';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CompanyCreateDto, CompanyUpdateDto } from '../../../Database/dto/company.dto';
|
||||
|
||||
// Create company command
|
||||
export class CreateCompanyCommand {
|
||||
constructor(public readonly companyCreateDto: CompanyCreateDto) {}
|
||||
}
|
||||
|
||||
// Update company command
|
||||
export class UpdateCompanyCommand {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly companyUpdateDto: CompanyUpdateDto
|
||||
) {}
|
||||
}
|
||||
|
||||
// Delete company command
|
||||
export class DeleteCompanyCommand {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ICompanyRepository } from '../../../Repositories/interfaces/ICompanyRepository';
|
||||
import { CompanyCommandHandler } from './CompanyCommandHandler';
|
||||
import {
|
||||
CreateCompanyCommand,
|
||||
UpdateCompanyCommand,
|
||||
DeleteCompanyCommand
|
||||
} from './CompanyCommand';
|
||||
import { appLogger } from '../../../utils/logger';
|
||||
|
||||
export class CompanyCommandDispatcher {
|
||||
private commandHandler: CompanyCommandHandler;
|
||||
|
||||
constructor(companyRepository: ICompanyRepository) {
|
||||
appLogger.startup('Initializing CompanyCommandDispatcher...');
|
||||
this.commandHandler = new CompanyCommandHandler(companyRepository);
|
||||
appLogger.startup('CompanyCommandDispatcher initialized');
|
||||
}
|
||||
|
||||
async dispatch(command: any): Promise<any> {
|
||||
try {
|
||||
appLogger.info('Dispatching company command', {
|
||||
commandType: command.constructor.name,
|
||||
action: 'dispatch_company_command'
|
||||
});
|
||||
|
||||
switch (command.constructor) {
|
||||
case CreateCompanyCommand:
|
||||
return await this.commandHandler.handleCreate(command);
|
||||
|
||||
case UpdateCompanyCommand:
|
||||
return await this.commandHandler.handleUpdate(command);
|
||||
|
||||
case DeleteCompanyCommand:
|
||||
return await this.commandHandler.handleDelete(command);
|
||||
|
||||
default:
|
||||
const errorMessage = `Unknown command type: ${command.constructor.name}`;
|
||||
appLogger.errorEvent('Unknown company command type', 'dispatch_error', {
|
||||
commandType: command.constructor.name
|
||||
});
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error dispatching company command', 'dispatch_error', {
|
||||
error: error.message,
|
||||
commandType: command.constructor.name
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { ICompanyRepository } from '../../../Repositories/interfaces/ICompanyRepository';
|
||||
import { CompanyResponseDto } from '../../../Database/dto/company.dto';
|
||||
import {
|
||||
CreateCompanyCommand,
|
||||
UpdateCompanyCommand,
|
||||
DeleteCompanyCommand
|
||||
} from './CompanyCommand';
|
||||
import { appLogger } from '../../../utils/logger';
|
||||
|
||||
export class CompanyCommandHandler {
|
||||
constructor(private readonly companyRepository: ICompanyRepository) {
|
||||
appLogger.info('CompanyCommandHandler initialized');
|
||||
}
|
||||
|
||||
// Handle create company
|
||||
async handleCreate(command: CreateCompanyCommand): Promise<CompanyResponseDto> {
|
||||
try {
|
||||
const { companyCreateDto } = command;
|
||||
|
||||
appLogger.info('Creating company', {
|
||||
name: companyCreateDto.Name,
|
||||
contactEmail: companyCreateDto.ContactEmail,
|
||||
action: 'create_company'
|
||||
});
|
||||
|
||||
// Validate required fields
|
||||
if (!companyCreateDto.Name || !companyCreateDto.ContactFirstName ||
|
||||
!companyCreateDto.ContactLastName || !companyCreateDto.ContactEmail ||
|
||||
!companyCreateDto.FirstAPI || !companyCreateDto.TokenAPI) {
|
||||
throw new Error('All company fields are required: Name, ContactFirstName, ContactLastName, ContactEmail, FirstAPI, TokenAPI');
|
||||
}
|
||||
|
||||
const result = await this.companyRepository.create(companyCreateDto);
|
||||
|
||||
appLogger.info('Company created successfully', {
|
||||
companyId: result.CompanyId,
|
||||
name: result.Name,
|
||||
contactEmail: result.ContactEmail
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error creating company', 'company_creation', {
|
||||
error: error.message,
|
||||
companyData: {
|
||||
name: command.companyCreateDto?.Name,
|
||||
contactEmail: command.companyCreateDto?.ContactEmail
|
||||
}
|
||||
});
|
||||
throw new Error(`Failed to create company: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle update company
|
||||
async handleUpdate(command: UpdateCompanyCommand): Promise<CompanyResponseDto | null> {
|
||||
try {
|
||||
const { id, companyUpdateDto } = command;
|
||||
|
||||
appLogger.info('Updating company', {
|
||||
companyId: id,
|
||||
updateFields: Object.keys(companyUpdateDto),
|
||||
action: 'update_company'
|
||||
});
|
||||
|
||||
const result = await this.companyRepository.update(id, companyUpdateDto);
|
||||
|
||||
if (!result) {
|
||||
appLogger.warn('Company not found for update', {
|
||||
companyId: id
|
||||
});
|
||||
throw new Error(`Company with ID ${id} not found`);
|
||||
}
|
||||
|
||||
appLogger.info('Company updated successfully', {
|
||||
companyId: result.CompanyId,
|
||||
name: result.Name,
|
||||
updatedFields: Object.keys(companyUpdateDto)
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error updating company', 'company_update', {
|
||||
error: error.message,
|
||||
companyId: command.id,
|
||||
updateData: command.companyUpdateDto
|
||||
});
|
||||
throw new Error(`Failed to update company: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle delete company
|
||||
async handleDelete(command: DeleteCompanyCommand): Promise<boolean> {
|
||||
try {
|
||||
const { id } = command;
|
||||
|
||||
appLogger.info('Deleting company', {
|
||||
companyId: id,
|
||||
action: 'delete_company'
|
||||
});
|
||||
|
||||
const result = await this.companyRepository.deleteById(id);
|
||||
|
||||
if (!result) {
|
||||
appLogger.warn('Company not found for deletion', {
|
||||
companyId: id
|
||||
});
|
||||
throw new Error(`Company with ID ${id} not found`);
|
||||
}
|
||||
|
||||
appLogger.info('Company deleted successfully', {
|
||||
companyId: id
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error deleting company', 'company_deletion', {
|
||||
error: error.message,
|
||||
companyId: command.id
|
||||
});
|
||||
throw new Error(`Failed to delete company: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Company Query Classes
|
||||
|
||||
// Get company by ID
|
||||
export class GetCompanyByIdQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Get company by name
|
||||
export class GetCompanyByNameQuery {
|
||||
constructor(public readonly name: string) {}
|
||||
}
|
||||
|
||||
// Get all companies
|
||||
export class GetAllCompaniesQuery {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
// Get basic company by ID
|
||||
export class GetBasicCompanyByIdQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Get all basic companies
|
||||
export class GetAllBasicCompaniesQuery {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ICompanyRepository } from '../../../Repositories/interfaces/ICompanyRepository';
|
||||
import { CompanyQueryHandler } from './CompanyQueryHandler';
|
||||
import {
|
||||
GetCompanyByIdQuery,
|
||||
GetCompanyByNameQuery,
|
||||
GetAllCompaniesQuery,
|
||||
GetBasicCompanyByIdQuery,
|
||||
GetAllBasicCompaniesQuery
|
||||
} from './CompanyQuery';
|
||||
import { appLogger } from '../../../utils/logger';
|
||||
|
||||
export class CompanyQueryDispatcher {
|
||||
private queryHandler: CompanyQueryHandler;
|
||||
|
||||
constructor(companyRepository: ICompanyRepository) {
|
||||
appLogger.startup('Initializing CompanyQueryDispatcher...');
|
||||
this.queryHandler = new CompanyQueryHandler(companyRepository);
|
||||
appLogger.startup('CompanyQueryDispatcher initialized');
|
||||
}
|
||||
|
||||
async dispatch(query: any): Promise<any> {
|
||||
try {
|
||||
appLogger.info('Dispatching company query', {
|
||||
queryType: query.constructor.name,
|
||||
action: 'dispatch_company_query'
|
||||
});
|
||||
|
||||
switch (query.constructor) {
|
||||
case GetCompanyByIdQuery:
|
||||
return await this.queryHandler.handleGetById(query);
|
||||
|
||||
case GetCompanyByNameQuery:
|
||||
return await this.queryHandler.handleGetByName(query);
|
||||
|
||||
case GetAllCompaniesQuery:
|
||||
return await this.queryHandler.handleGetAll(query);
|
||||
|
||||
case GetBasicCompanyByIdQuery:
|
||||
return await this.queryHandler.handleGetBasicById(query);
|
||||
|
||||
case GetAllBasicCompaniesQuery:
|
||||
return await this.queryHandler.handleGetAllBasic(query);
|
||||
|
||||
default:
|
||||
const errorMessage = `Unknown query type: ${query.constructor.name}`;
|
||||
appLogger.errorEvent('Unknown company query type', 'dispatch_error', {
|
||||
queryType: query.constructor.name
|
||||
});
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error dispatching company query', 'dispatch_error', {
|
||||
error: error.message,
|
||||
queryType: query.constructor.name
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import { ICompanyRepository } from '../../../Repositories/interfaces/ICompanyRepository';
|
||||
import { CompanyResponseDto, CompanyBasicDto } from '../../../Database/dto/company.dto';
|
||||
import {
|
||||
GetCompanyByIdQuery,
|
||||
GetCompanyByNameQuery,
|
||||
GetAllCompaniesQuery,
|
||||
GetBasicCompanyByIdQuery,
|
||||
GetAllBasicCompaniesQuery
|
||||
} from './CompanyQuery';
|
||||
import { appLogger } from '../../../utils/logger';
|
||||
|
||||
export class CompanyQueryHandler {
|
||||
constructor(private readonly companyRepository: ICompanyRepository) {
|
||||
appLogger.info('CompanyQueryHandler initialized');
|
||||
}
|
||||
|
||||
// Get company by ID
|
||||
async handleGetById(query: GetCompanyByIdQuery): Promise<CompanyResponseDto | null> {
|
||||
try {
|
||||
appLogger.info('Getting company by ID', {
|
||||
companyId: query.id,
|
||||
action: 'get_company_by_id'
|
||||
});
|
||||
|
||||
// Validate ID
|
||||
if (!query.id || query.id <= 0) {
|
||||
throw new Error('Valid company ID is required');
|
||||
}
|
||||
|
||||
const result = await this.companyRepository.findById(query.id);
|
||||
|
||||
if (!result) {
|
||||
appLogger.warn('Company not found', {
|
||||
companyId: query.id
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
appLogger.info('Company found successfully', {
|
||||
companyId: result.CompanyId,
|
||||
name: result.Name
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error getting company by ID', 'company_query', {
|
||||
error: error.message,
|
||||
companyId: query.id
|
||||
});
|
||||
throw new Error(`Failed to get company by ID: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get company by name
|
||||
async handleGetByName(query: GetCompanyByNameQuery): Promise<CompanyResponseDto | null> {
|
||||
try {
|
||||
appLogger.info('Getting company by name', {
|
||||
companyName: query.name,
|
||||
action: 'get_company_by_name'
|
||||
});
|
||||
|
||||
// Validate name
|
||||
if (!query.name || query.name.trim() === '') {
|
||||
throw new Error('Valid company name is required');
|
||||
}
|
||||
|
||||
const result = await this.companyRepository.findByName(query.name.trim());
|
||||
|
||||
if (!result) {
|
||||
appLogger.warn('Company not found by name', {
|
||||
companyName: query.name
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
appLogger.info('Company found by name', {
|
||||
companyId: result.CompanyId,
|
||||
name: result.Name
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error getting company by name', 'company_query', {
|
||||
error: error.message,
|
||||
companyName: query.name
|
||||
});
|
||||
throw new Error(`Failed to get company by name: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all companies
|
||||
async handleGetAll(query: GetAllCompaniesQuery): Promise<CompanyResponseDto[]> {
|
||||
try {
|
||||
appLogger.info('Getting all companies', {
|
||||
action: 'get_all_companies'
|
||||
});
|
||||
|
||||
const result = await this.companyRepository.findAll();
|
||||
|
||||
appLogger.info('Companies retrieved successfully', {
|
||||
count: result.length
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error getting all companies', 'company_query', {
|
||||
error: error.message
|
||||
});
|
||||
throw new Error(`Failed to get all companies: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get basic company by ID
|
||||
async handleGetBasicById(query: GetBasicCompanyByIdQuery): Promise<CompanyBasicDto | null> {
|
||||
try {
|
||||
appLogger.info('Getting basic company by ID', {
|
||||
companyId: query.id,
|
||||
action: 'get_basic_company_by_id'
|
||||
});
|
||||
|
||||
// Validate ID
|
||||
if (!query.id || query.id <= 0) {
|
||||
throw new Error('Valid company ID is required');
|
||||
}
|
||||
|
||||
const result = await this.companyRepository.findBasicById(query.id);
|
||||
|
||||
if (!result) {
|
||||
appLogger.warn('Basic company not found', {
|
||||
companyId: query.id
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
appLogger.info('Basic company found successfully', {
|
||||
companyId: result.CompanyId,
|
||||
name: result.Name
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error getting basic company by ID', 'company_query', {
|
||||
error: error.message,
|
||||
companyId: query.id
|
||||
});
|
||||
throw new Error(`Failed to get basic company by ID: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all basic companies
|
||||
async handleGetAllBasic(query: GetAllBasicCompaniesQuery): Promise<CompanyBasicDto[]> {
|
||||
try {
|
||||
appLogger.info('Getting all basic companies', {
|
||||
action: 'get_all_basic_companies'
|
||||
});
|
||||
|
||||
const result = await this.companyRepository.findAllBasic();
|
||||
|
||||
appLogger.info('Basic companies retrieved successfully', {
|
||||
count: result.length
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error: any) {
|
||||
appLogger.errorEvent('Error getting all basic companies', 'company_query', {
|
||||
error: error.message
|
||||
});
|
||||
throw new Error(`Failed to get all basic companies: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { UserCreateDto, UserUpdateDto } from '../../../Database/dto/user.dto';
|
||||
|
||||
// Create user command
|
||||
export class CreateUserCommand {
|
||||
constructor(public readonly userCreateDto: UserCreateDto) {}
|
||||
}
|
||||
|
||||
// Update user command
|
||||
export class UpdateUserCommand {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly userUpdateDto: UserUpdateDto
|
||||
) {}
|
||||
}
|
||||
|
||||
// Delete user command
|
||||
export class DeleteUserCommand {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Delete multiple users command
|
||||
export class DeleteUsersCommand {
|
||||
constructor(public readonly ids: number[]) {}
|
||||
}
|
||||
|
||||
// Create multiple users command
|
||||
export class CreateUsersCommand {
|
||||
constructor(public readonly userCreateDtos: UserCreateDto[]) {}
|
||||
}
|
||||
|
||||
// Update user password command
|
||||
export class UpdateUserPasswordCommand {
|
||||
constructor(
|
||||
public readonly userId: number,
|
||||
public readonly currentPassword: string,
|
||||
public readonly newPassword: string
|
||||
) {}
|
||||
}
|
||||
|
||||
// Reset user password command (admin only)
|
||||
export class ResetUserPasswordCommand {
|
||||
constructor(
|
||||
public readonly userId: number,
|
||||
public readonly newPassword: string
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { UserCommandHandler } from './UserCommandHandler';
|
||||
import { IUserRepository } from '../../../Repositories/interfaces/IUserRepository';
|
||||
import {
|
||||
CreateUserCommand,
|
||||
UpdateUserCommand,
|
||||
DeleteUserCommand,
|
||||
DeleteUsersCommand,
|
||||
CreateUsersCommand,
|
||||
UpdateUserPasswordCommand,
|
||||
ResetUserPasswordCommand
|
||||
} from './UserCommand';
|
||||
|
||||
export class UserCommandDispatcher {
|
||||
private commandHandler: UserCommandHandler;
|
||||
|
||||
constructor(userRepository: IUserRepository) {
|
||||
this.commandHandler = new UserCommandHandler(userRepository);
|
||||
}
|
||||
|
||||
async dispatch(command: any): Promise<any> {
|
||||
switch (command.constructor) {
|
||||
case CreateUserCommand:
|
||||
return await this.commandHandler.handleCreate(command);
|
||||
|
||||
case UpdateUserCommand:
|
||||
return await this.commandHandler.handleUpdate(command);
|
||||
|
||||
case DeleteUserCommand:
|
||||
return await this.commandHandler.handleDelete(command);
|
||||
|
||||
case DeleteUsersCommand:
|
||||
return await this.commandHandler.handleDeleteUsers(command);
|
||||
|
||||
case CreateUsersCommand:
|
||||
return await this.commandHandler.handleCreateUsers(command);
|
||||
|
||||
case UpdateUserPasswordCommand:
|
||||
return await this.commandHandler.handleUpdatePassword(command);
|
||||
|
||||
case ResetUserPasswordCommand:
|
||||
return await this.commandHandler.handleResetPassword(command);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown command type: ${command.constructor.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { IUserRepository } from '../../../Repositories/interfaces/IUserRepository';
|
||||
import { UserResponseDto, UsersListResponseDto, UserCreateDto, UserUpdateDto } from '../../../Database/dto/user.dto';
|
||||
import { hashPassword, comparePasswords } from '../../../middlewares/security';
|
||||
import {
|
||||
CreateUserCommand,
|
||||
UpdateUserCommand,
|
||||
DeleteUserCommand,
|
||||
DeleteUsersCommand,
|
||||
CreateUsersCommand,
|
||||
UpdateUserPasswordCommand,
|
||||
ResetUserPasswordCommand
|
||||
} from './UserCommand';
|
||||
|
||||
export class UserCommandHandler {
|
||||
constructor(private readonly userRepository: IUserRepository) {}
|
||||
|
||||
// Create user
|
||||
async handleCreate(command: CreateUserCommand): Promise<UserResponseDto> {
|
||||
// Check if username already exists
|
||||
const usernameExists = await this.userRepository.usernameExists(command.userCreateDto.username);
|
||||
if (usernameExists) {
|
||||
throw new Error('Username already exists');
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
const emailExists = await this.userRepository.emailExists(command.userCreateDto.email);
|
||||
if (emailExists) {
|
||||
throw new Error('Email already exists');
|
||||
}
|
||||
|
||||
console.log('Creating user with DTO:', command.userCreateDto);
|
||||
|
||||
// Hash password using security middleware
|
||||
const hashedPassword = await hashPassword(command.userCreateDto.password);
|
||||
console.log('Hashed password:', hashedPassword);
|
||||
|
||||
const userWithHashedPassword = new UserCreateDto(
|
||||
command.userCreateDto.username,
|
||||
command.userCreateDto.FirstName,
|
||||
command.userCreateDto.LastName,
|
||||
command.userCreateDto.email,
|
||||
hashedPassword,
|
||||
command.userCreateDto.CompanyId // Pass the optional CompanyId
|
||||
);
|
||||
|
||||
console.log('Creating user with hashed password:', userWithHashedPassword);
|
||||
return await this.userRepository.create(userWithHashedPassword);
|
||||
}
|
||||
|
||||
// Update user
|
||||
async handleUpdate(command: UpdateUserCommand): Promise<UserResponseDto | null> {
|
||||
// Check if user exists
|
||||
const userExists = await this.userRepository.exists(command.id);
|
||||
if (!userExists) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// If updating username, check if it's already taken by another user
|
||||
if (command.userUpdateDto.username) {
|
||||
const existingUser = await this.userRepository.findByUsername(command.userUpdateDto.username);
|
||||
if (existingUser && existingUser.id !== command.id) {
|
||||
throw new Error('Username already exists');
|
||||
}
|
||||
}
|
||||
|
||||
// If updating email, check if it's already taken by another user
|
||||
if (command.userUpdateDto.email) {
|
||||
const existingUser = await this.userRepository.findByEmail(command.userUpdateDto.email);
|
||||
if (existingUser && existingUser.id !== command.id) {
|
||||
throw new Error('Email already exists');
|
||||
}
|
||||
}
|
||||
|
||||
// Create new DTO with hashed password if password is being updated
|
||||
let updateDto = command.userUpdateDto;
|
||||
if (command.userUpdateDto.password) {
|
||||
const hashedPassword = await hashPassword(command.userUpdateDto.password);
|
||||
updateDto = new UserUpdateDto({
|
||||
...command.userUpdateDto,
|
||||
password: hashedPassword
|
||||
});
|
||||
}
|
||||
|
||||
return await this.userRepository.update(command.id, updateDto);
|
||||
}
|
||||
|
||||
// Delete user
|
||||
async handleDelete(command: DeleteUserCommand): Promise<boolean> {
|
||||
const userExists = await this.userRepository.exists(command.id);
|
||||
if (!userExists) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
return await this.userRepository.deleteById(command.id);
|
||||
}
|
||||
|
||||
// Delete multiple users
|
||||
async handleDeleteUsers(command: DeleteUsersCommand): Promise<boolean> {
|
||||
if (command.ids.length === 0) {
|
||||
throw new Error('No user IDs provided');
|
||||
}
|
||||
|
||||
// Check if all users exist
|
||||
for (const id of command.ids) {
|
||||
const userExists = await this.userRepository.exists(id);
|
||||
if (!userExists) {
|
||||
throw new Error(`User with ID ${id} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
return await this.userRepository.deleteByIds(command.ids);
|
||||
}
|
||||
|
||||
// Create multiple users
|
||||
async handleCreateUsers(command: CreateUsersCommand): Promise<UsersListResponseDto> {
|
||||
if (command.userCreateDtos.length === 0) {
|
||||
throw new Error('No users provided');
|
||||
}
|
||||
|
||||
// Check for duplicate usernames and emails within the batch
|
||||
const usernames = command.userCreateDtos.map(dto => dto.username);
|
||||
const emails = command.userCreateDtos.map(dto => dto.email);
|
||||
|
||||
const uniqueUsernames = new Set(usernames);
|
||||
const uniqueEmails = new Set(emails);
|
||||
|
||||
if (uniqueUsernames.size !== usernames.length) {
|
||||
throw new Error('Duplicate usernames in batch');
|
||||
}
|
||||
|
||||
if (uniqueEmails.size !== emails.length) {
|
||||
throw new Error('Duplicate emails in batch');
|
||||
}
|
||||
|
||||
// Check if any usernames or emails already exist
|
||||
for (const dto of command.userCreateDtos) {
|
||||
const usernameExists = await this.userRepository.usernameExists(dto.username);
|
||||
if (usernameExists) {
|
||||
throw new Error(`Username ${dto.username} already exists`);
|
||||
}
|
||||
|
||||
const emailExists = await this.userRepository.emailExists(dto.email);
|
||||
if (emailExists) {
|
||||
throw new Error(`Email ${dto.email} already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
// Hash all passwords using security middleware
|
||||
const usersWithHashedPasswords = await Promise.all(
|
||||
command.userCreateDtos.map(async (dto) => {
|
||||
const hashedPassword = await hashPassword(dto.password);
|
||||
return new UserCreateDto(
|
||||
dto.username,
|
||||
dto.FirstName,
|
||||
dto.LastName,
|
||||
dto.email,
|
||||
hashedPassword
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
return await this.userRepository.createMany(usersWithHashedPasswords);
|
||||
}
|
||||
|
||||
// Update user password (with current password verification)
|
||||
async handleUpdatePassword(command: UpdateUserPasswordCommand): Promise<UserResponseDto | null> {
|
||||
// Get raw user entity for password verification
|
||||
const user = await this.userRepository.findRawById(command.userId);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Verify current password using security middleware
|
||||
const isCurrentPasswordValid = await comparePasswords(command.currentPassword, user.password);
|
||||
if (!isCurrentPasswordValid) {
|
||||
throw new Error('Current password is incorrect');
|
||||
}
|
||||
|
||||
// Hash new password using security middleware
|
||||
const hashedNewPassword = await hashPassword(command.newPassword);
|
||||
|
||||
const updateDto = new UserUpdateDto({
|
||||
password: hashedNewPassword
|
||||
});
|
||||
|
||||
return await this.userRepository.update(command.userId, updateDto);
|
||||
}
|
||||
|
||||
// Reset user password (admin only - no current password verification)
|
||||
async handleResetPassword(command: ResetUserPasswordCommand): Promise<UserResponseDto | null> {
|
||||
const userExists = await this.userRepository.exists(command.userId);
|
||||
if (!userExists) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Hash new password using security middleware
|
||||
const hashedNewPassword = await hashPassword(command.newPassword);
|
||||
|
||||
const updateDto = new UserUpdateDto({
|
||||
password: hashedNewPassword
|
||||
});
|
||||
|
||||
return await this.userRepository.update(command.userId, updateDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { UserBasicDto, UserResponseDto, UsersListResponseDto } from '../../../Database/dto/user.dto';
|
||||
|
||||
// Get user by ID
|
||||
export class GetUserByIdQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Get user by username
|
||||
export class GetUserByUsernameQuery {
|
||||
constructor(public readonly username: string) {}
|
||||
}
|
||||
|
||||
// Get user by email
|
||||
export class GetUserByEmailQuery {
|
||||
constructor(public readonly email: string) {}
|
||||
}
|
||||
|
||||
// Get all users
|
||||
export class GetAllUsersQuery {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
// Get users by company
|
||||
// export class GetUsersByCompanyQuery {
|
||||
// constructor(public readonly companyId: number) {}
|
||||
// }
|
||||
|
||||
// Get users by company token
|
||||
// export class GetUsersByCompanyTokenQuery {
|
||||
// constructor(public readonly companyToken: string) {}
|
||||
// }
|
||||
|
||||
// Search users by partial username
|
||||
export class SearchUsersByUsernameQuery {
|
||||
constructor(public readonly partialUsername: string) {}
|
||||
}
|
||||
|
||||
// Search users by partial name
|
||||
// export class SearchUsersByNameQuery {
|
||||
// constructor(public readonly partialName: string) {}
|
||||
// }
|
||||
|
||||
// Get users with pagination
|
||||
// export class GetUsersWithPaginationQuery {
|
||||
// constructor(
|
||||
// public readonly page: number,
|
||||
// public readonly limit: number
|
||||
// ) {}
|
||||
// }
|
||||
|
||||
// Get basic user info by ID
|
||||
export class GetUserBasicByIdQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Get all users basic info
|
||||
export class GetAllUsersBasicQuery {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
// Get user with company relation
|
||||
export class GetUserWithCompanyQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Get user with question banks relation
|
||||
export class GetUserWithQuestionBanksQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Get user with all relations
|
||||
// export class GetUserWithAllRelationsQuery {
|
||||
// constructor(public readonly id: number) {}
|
||||
// }
|
||||
|
||||
// Get users count
|
||||
// export class GetUsersCountQuery {
|
||||
// constructor() {}
|
||||
// }
|
||||
|
||||
// Get users count by company
|
||||
// export class GetUsersCountByCompanyQuery {
|
||||
// constructor(public readonly companyId: number) {}
|
||||
// }
|
||||
|
||||
// Get users by date range
|
||||
// export class GetUsersByDateRangeQuery {
|
||||
// constructor(
|
||||
// public readonly startDate: Date,
|
||||
// public readonly endDate: Date
|
||||
// ) {}
|
||||
// }
|
||||
|
||||
// Check if user exists
|
||||
export class UserExistsQuery {
|
||||
constructor(public readonly id: number) {}
|
||||
}
|
||||
|
||||
// Check if username exists
|
||||
export class UsernameExistsQuery {
|
||||
constructor(public readonly username: string) {}
|
||||
}
|
||||
|
||||
// Check if email exists
|
||||
export class EmailExistsQuery {
|
||||
constructor(public readonly email: string) {}
|
||||
}
|
||||
|
||||
// Get raw user by username (for authentication)
|
||||
export class GetRawUserByUsernameQuery {
|
||||
constructor(public readonly username: string) {}
|
||||
}
|
||||
|
||||
// Get raw user by email (for authentication)
|
||||
export class GetRawUserByEmailQuery {
|
||||
constructor(public readonly email: string) {}
|
||||
}
|
||||
|
||||
// Get raw user by ID (for authentication)
|
||||
// export class GetRawUserByIdQuery {
|
||||
// constructor(public readonly id: number) {}
|
||||
// }
|
||||
@@ -0,0 +1,111 @@
|
||||
import { UserQueryHandler } from './UserQueryHandler';
|
||||
import { IUserRepository } from '../../../Repositories/interfaces/IUserRepository';
|
||||
import {
|
||||
GetUserByIdQuery,
|
||||
GetUserByUsernameQuery,
|
||||
GetUserByEmailQuery,
|
||||
GetAllUsersQuery,
|
||||
// GetUsersByCompanyQuery,
|
||||
// GetUsersByCompanyTokenQuery,
|
||||
SearchUsersByUsernameQuery,
|
||||
// SearchUsersByNameQuery,
|
||||
// GetUsersWithPaginationQuery,
|
||||
GetUserBasicByIdQuery,
|
||||
GetAllUsersBasicQuery,
|
||||
GetUserWithCompanyQuery,
|
||||
GetUserWithQuestionBanksQuery,
|
||||
// GetUserWithAllRelationsQuery,
|
||||
// GetUsersCountQuery,
|
||||
// GetUsersCountByCompanyQuery,
|
||||
// GetUsersByDateRangeQuery,
|
||||
UserExistsQuery,
|
||||
UsernameExistsQuery,
|
||||
EmailExistsQuery,
|
||||
GetRawUserByUsernameQuery,
|
||||
GetRawUserByEmailQuery,
|
||||
// GetRawUserByIdQuery
|
||||
} from './UserQuery';
|
||||
|
||||
export class UserQueryDispatcher {
|
||||
private queryHandler: UserQueryHandler;
|
||||
|
||||
constructor(userRepository: IUserRepository) {
|
||||
this.queryHandler = new UserQueryHandler(userRepository);
|
||||
}
|
||||
|
||||
async dispatch(query: any): Promise<any> {
|
||||
switch (query.constructor) {
|
||||
case GetUserByIdQuery:
|
||||
return await this.queryHandler.handle(query);
|
||||
|
||||
// case GetUserByUsernameQuery:
|
||||
// return await this.queryHandler.handleGetByUsername(query);
|
||||
|
||||
case GetUserByEmailQuery:
|
||||
return await this.queryHandler.handleGetByEmail(query);
|
||||
|
||||
case GetAllUsersQuery:
|
||||
return await this.queryHandler.handleGetAll(query);
|
||||
|
||||
// case GetUsersByCompanyQuery:
|
||||
// return await this.queryHandler.handleGetByCompany(query);
|
||||
|
||||
// case GetUsersByCompanyTokenQuery:
|
||||
// return await this.queryHandler.handleGetByCompanyToken(query);
|
||||
|
||||
// case SearchUsersByUsernameQuery:
|
||||
// return await this.queryHandler.handleSearchByUsername(query);
|
||||
|
||||
// case SearchUsersByNameQuery:
|
||||
// return await this.queryHandler.handleSearchByName(query);
|
||||
|
||||
// case GetUsersWithPaginationQuery:
|
||||
// return await this.queryHandler.handleGetWithPagination(query);
|
||||
|
||||
// case GetUserBasicByIdQuery:
|
||||
// return await this.queryHandler.handleGetBasicById(query);
|
||||
|
||||
case GetAllUsersBasicQuery:
|
||||
return await this.queryHandler.handleGetAllBasic(query);
|
||||
|
||||
case GetUserWithCompanyQuery:
|
||||
return await this.queryHandler.handleGetWithCompany(query);
|
||||
|
||||
case GetUserWithQuestionBanksQuery:
|
||||
return await this.queryHandler.handleGetWithQuestionBanks(query);
|
||||
|
||||
// case GetUserWithAllRelationsQuery:
|
||||
// return await this.queryHandler.handleGetWithAllRelations(query);
|
||||
|
||||
// case GetUsersCountQuery:
|
||||
// return await this.queryHandler.handleGetCount(query);
|
||||
|
||||
// case GetUsersCountByCompanyQuery:
|
||||
// return await this.queryHandler.handleGetCountByCompany(query);
|
||||
|
||||
// case GetUsersByDateRangeQuery:
|
||||
// return await this.queryHandler.handleGetByDateRange(query);
|
||||
|
||||
// case UserExistsQuery:
|
||||
// return await this.queryHandler.handleUserExists(query);
|
||||
|
||||
case UsernameExistsQuery:
|
||||
return await this.queryHandler.handleUsernameExists(query);
|
||||
|
||||
case EmailExistsQuery:
|
||||
return await this.queryHandler.handleEmailExists(query);
|
||||
|
||||
case GetRawUserByUsernameQuery:
|
||||
return await this.queryHandler.handleGetRawByUsername(query);
|
||||
|
||||
case GetRawUserByEmailQuery:
|
||||
return await this.queryHandler.handleGetRawByEmail(query);
|
||||
|
||||
// case GetRawUserByIdQuery:
|
||||
// return await this.queryHandler.handleGetRawById(query);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown query type: ${query.constructor.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { IUserRepository } from '../../../Repositories/interfaces/IUserRepository';
|
||||
import { UserBasicDto, UserResponseDto, UsersListResponseDto } from '../../../Database/dto/user.dto';
|
||||
import { User } from '../../../Database/entities/user.entity';
|
||||
import {
|
||||
GetUserByIdQuery,
|
||||
GetUserByUsernameQuery,
|
||||
GetUserByEmailQuery,
|
||||
GetAllUsersQuery,
|
||||
// GetUsersByCompanyQuery,
|
||||
// GetUsersByCompanyTokenQuery,
|
||||
SearchUsersByUsernameQuery,
|
||||
// SearchUsersByNameQuery,
|
||||
// GetUsersWithPaginationQuery,
|
||||
GetUserBasicByIdQuery,
|
||||
GetAllUsersBasicQuery,
|
||||
GetUserWithCompanyQuery,
|
||||
GetUserWithQuestionBanksQuery,
|
||||
// GetUserWithAllRelationsQuery,
|
||||
// GetUsersCountQuery,
|
||||
// GetUsersCountByCompanyQuery,
|
||||
// GetUsersByDateRangeQuery,
|
||||
UserExistsQuery,
|
||||
UsernameExistsQuery,
|
||||
EmailExistsQuery,
|
||||
GetRawUserByUsernameQuery,
|
||||
GetRawUserByEmailQuery,
|
||||
// GetRawUserByIdQuery
|
||||
} from './UserQuery';
|
||||
|
||||
export class UserQueryHandler {
|
||||
constructor(private readonly userRepository: IUserRepository) {}
|
||||
|
||||
// Get user by ID
|
||||
async handle(query: GetUserByIdQuery): Promise<UserResponseDto | null> {
|
||||
return await this.userRepository.findById(query.id);
|
||||
}
|
||||
|
||||
// Get user by username
|
||||
// async handleGetByUsername(query: GetUserByUsernameQuery): Promise<UserResponseDto | null> {
|
||||
// return await this.userRepository.findByUsername(query.username);
|
||||
// }
|
||||
|
||||
// Get user by email
|
||||
async handleGetByEmail(query: GetUserByEmailQuery): Promise<UserResponseDto | null> {
|
||||
return await this.userRepository.findByEmail(query.email);
|
||||
}
|
||||
|
||||
// Get all users
|
||||
async handleGetAll(query: GetAllUsersQuery): Promise<UsersListResponseDto> {
|
||||
return await this.userRepository.findAll();
|
||||
}
|
||||
|
||||
// Get users by company
|
||||
// async handleGetByCompany(query: GetUsersByCompanyQuery): Promise<UsersListResponseDto> {
|
||||
// return await this.userRepository.findByCompanyId(query.companyId);
|
||||
// }
|
||||
|
||||
// Get users by company token
|
||||
// async handleGetByCompanyToken(query: GetUsersByCompanyTokenQuery): Promise<UsersListResponseDto> {
|
||||
// return await this.userRepository.findByCompanyToken(query.companyToken);
|
||||
// }
|
||||
|
||||
// Search users by partial username
|
||||
// async handleSearchByUsername(query: SearchUsersByUsernameQuery): Promise<UsersListResponseDto> {
|
||||
// return await this.userRepository.findByPartialUsername(query.partialUsername);
|
||||
// }
|
||||
|
||||
// Search users by partial name
|
||||
// async handleSearchByName(query: SearchUsersByNameQuery): Promise<UsersListResponseDto> {
|
||||
// return await this.userRepository.findByPartialName(query.partialName);
|
||||
// }
|
||||
|
||||
// Get users with pagination
|
||||
// async handleGetWithPagination(query: GetUsersWithPaginationQuery): Promise<{ users: UsersListResponseDto, total: number }> {
|
||||
// const skip = (query.page - 1) * query.limit;
|
||||
// return await this.userRepository.findWithPagination(skip, query.limit);
|
||||
// }
|
||||
|
||||
// Get basic user info by ID
|
||||
// async handleGetBasicById(query: GetUserBasicByIdQuery): Promise<UserBasicDto | null> {
|
||||
// return await this.userRepository.findBasicById(query.id);
|
||||
// }
|
||||
|
||||
// Get all users basic info
|
||||
async handleGetAllBasic(query: GetAllUsersBasicQuery): Promise<UserBasicDto[]> {
|
||||
return await this.userRepository.findAllBasic();
|
||||
}
|
||||
|
||||
// Get user with company relation
|
||||
async handleGetWithCompany(query: GetUserWithCompanyQuery): Promise<UserResponseDto | null> {
|
||||
return await this.userRepository.findWithCompany(query.id);
|
||||
}
|
||||
|
||||
// Get user with question banks relation
|
||||
async handleGetWithQuestionBanks(query: GetUserWithQuestionBanksQuery): Promise<UserResponseDto | null> {
|
||||
return await this.userRepository.findWithQuestionBanks(query.id);
|
||||
}
|
||||
|
||||
// Get user with all relations
|
||||
// async handleGetWithAllRelations(query: GetUserWithAllRelationsQuery): Promise<UserResponseDto | null> {
|
||||
// return await this.userRepository.findWithAllRelations(query.id);
|
||||
// }
|
||||
|
||||
// Get users count
|
||||
// async handleGetCount(query: GetUsersCountQuery): Promise<number> {
|
||||
// return await this.userRepository.count();
|
||||
// }
|
||||
|
||||
// Get users count by company
|
||||
// async handleGetCountByCompany(query: GetUsersCountByCompanyQuery): Promise<number> {
|
||||
// return await this.userRepository.countByCompany(query.companyId);
|
||||
// }
|
||||
|
||||
// Get users by date range
|
||||
// async handleGetByDateRange(query: GetUsersByDateRangeQuery): Promise<UsersListResponseDto> {
|
||||
// return await this.userRepository.findByDateRange(query.startDate, query.endDate);
|
||||
// }
|
||||
|
||||
// Check if user exists
|
||||
async handleUserExists(query: UserExistsQuery): Promise<boolean> {
|
||||
return await this.userRepository.exists(query.id);
|
||||
}
|
||||
|
||||
// Check if username exists
|
||||
async handleUsernameExists(query: UsernameExistsQuery): Promise<boolean> {
|
||||
return await this.userRepository.usernameExists(query.username);
|
||||
}
|
||||
|
||||
// Check if email exists
|
||||
async handleEmailExists(query: EmailExistsQuery): Promise<boolean> {
|
||||
return await this.userRepository.emailExists(query.email);
|
||||
}
|
||||
|
||||
// Get raw user by username (for password verification)
|
||||
async handleGetRawByUsername(query: GetRawUserByUsernameQuery): Promise<User | null> {
|
||||
return await this.userRepository.findRawByUsername(query.username);
|
||||
}
|
||||
|
||||
// Get raw user by email (for password verification)
|
||||
async handleGetRawByEmail(query: GetRawUserByEmailQuery): Promise<User | null> {
|
||||
return await this.userRepository.findRawByEmail(query.email);
|
||||
}
|
||||
|
||||
// Get raw user by ID (for password verification)
|
||||
// async handleGetRawById(query: GetRawUserByIdQuery): Promise<User | null> {
|
||||
// return await this.userRepository.findRawById(query.id);
|
||||
// }
|
||||
}
|
||||
@@ -1,23 +1,206 @@
|
||||
console.log(process.env.DATABASE_URL_1 as string);
|
||||
import express from 'express';
|
||||
import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { mainRouter } from './router';
|
||||
import { AppDataSourceOne } from './DB/Datasource';
|
||||
import express, { Application, Request, Response, NextFunction } from 'express';
|
||||
import cors from 'cors';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import helmet from 'helmet';
|
||||
import compression from 'compression';
|
||||
import dotenv from 'dotenv';
|
||||
import { AppDataSource } from '../ormconfig';
|
||||
import { authErrorHandler } from './middlewares/authErrorHandler';
|
||||
import createUserRouter from './Routers/UserRouter';
|
||||
import { appLogger, initializeMinIOBucket } from './utils/logger';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
// Initialize the database connection
|
||||
AppDataSourceOne.initialize();
|
||||
class App {
|
||||
public readonly app: Application = express();
|
||||
private readonly port: number = parseInt(process.env.PORT || '3000');
|
||||
|
||||
app.use(cors({
|
||||
origin: process.env.FRONTEND_URL,
|
||||
credentials: true
|
||||
}));
|
||||
app.use(cookieParser());
|
||||
constructor() {
|
||||
// Initialize application asynchronously
|
||||
this.initializeApplication().catch((error) => {
|
||||
appLogger.errorEvent('Application startup failed', 'startup', { error });
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
app.use('/', mainRouter);
|
||||
private async initializeApplication(): Promise<void> {
|
||||
appLogger.startup('🚀 Starting SerpentRace Backend');
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Server is running on port 3000');
|
||||
});
|
||||
try {
|
||||
// Initialize core services
|
||||
await this.initializeMinIO();
|
||||
await this.initializeDatabase();
|
||||
|
||||
// Setup application
|
||||
this.setupMiddlewares();
|
||||
this.setupRoutes();
|
||||
this.setupErrorHandling();
|
||||
|
||||
appLogger.startup('✅ Application ready');
|
||||
} catch (error) {
|
||||
appLogger.errorEvent('Initialization failed', 'startup', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeMinIO(): Promise<void> {
|
||||
try {
|
||||
await initializeMinIOBucket();
|
||||
appLogger.startup('MinIO initialized');
|
||||
} catch (error) {
|
||||
appLogger.errorEvent('MinIO failed', 'minio', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeDatabase(): Promise<void> {
|
||||
try {
|
||||
await AppDataSource.initialize();
|
||||
appLogger.startup('Database connected');
|
||||
} catch (error) {
|
||||
appLogger.errorEvent('Database failed', 'database', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private setupMiddlewares(): void {
|
||||
// Security
|
||||
this.app.use(helmet({
|
||||
crossOriginEmbedderPolicy: false,
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// CORS
|
||||
this.app.use(cors({
|
||||
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
}));
|
||||
|
||||
// Body parsing
|
||||
this.app.use(express.json({ limit: '10mb' }));
|
||||
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
this.app.use(cookieParser());
|
||||
this.app.use(compression());
|
||||
appLogger.startup('Middlewares loaded');
|
||||
}
|
||||
|
||||
private setupRoutes(): void {
|
||||
|
||||
// User routes - Mount on both paths for compatibility
|
||||
const userRouter = createUserRouter(AppDataSource);
|
||||
|
||||
this.app.use('/user', userRouter);
|
||||
|
||||
// 404 handler
|
||||
this.app.use('*', (req: Request, res: Response) => {
|
||||
appLogger.security('Route not found', 'low', {
|
||||
url: req.originalUrl,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
});
|
||||
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'Route not found'
|
||||
});
|
||||
});
|
||||
|
||||
appLogger.startup('Routes configured');
|
||||
}
|
||||
|
||||
private setupErrorHandling(): void {
|
||||
// Auth error handler
|
||||
this.app.use(authErrorHandler);
|
||||
|
||||
// General error handler
|
||||
this.app.use((err: any, req: Request, res: Response, next: NextFunction) => {
|
||||
appLogger.errorEvent('Application error', 'express', {
|
||||
message: err.message,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
ip: req.ip
|
||||
});
|
||||
|
||||
res.status(err.status || 500).json({
|
||||
success: false,
|
||||
message: err.message || 'Internal Server Error',
|
||||
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
|
||||
});
|
||||
});
|
||||
|
||||
// Process error handlers (set only once)
|
||||
if (!process.listenerCount('unhandledRejection')) {
|
||||
process.on('unhandledRejection', (reason: any) => {
|
||||
appLogger.errorEvent('Unhandled rejection', 'process', { reason });
|
||||
this.shutdown('Unhandled Rejection');
|
||||
});
|
||||
}
|
||||
|
||||
if (!process.listenerCount('uncaughtException')) {
|
||||
process.on('uncaughtException', (error: Error) => {
|
||||
appLogger.errorEvent('Uncaught exception', 'process', { error: error.message });
|
||||
this.shutdown('Uncaught Exception');
|
||||
});
|
||||
}
|
||||
|
||||
if (!process.listenerCount('SIGTERM')) {
|
||||
process.on('SIGTERM', () => this.shutdown('SIGTERM'));
|
||||
}
|
||||
|
||||
if (!process.listenerCount('SIGINT')) {
|
||||
process.on('SIGINT', () => this.shutdown('SIGINT'));
|
||||
}
|
||||
|
||||
appLogger.startup('Error handling configured');
|
||||
}
|
||||
|
||||
private async shutdown(signal: string): Promise<void> {
|
||||
appLogger.system(`Shutting down (${signal})`);
|
||||
|
||||
try {
|
||||
if (AppDataSource.isInitialized) {
|
||||
await AppDataSource.destroy();
|
||||
}
|
||||
appLogger.system('Shutdown complete');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
appLogger.errorEvent('Shutdown error', 'shutdown', { error });
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public listen(): void {
|
||||
this.app.listen(this.port, () => {
|
||||
appLogger.startup(`🎉 Server running on port ${this.port}`, {
|
||||
port: this.port,
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
urls: {
|
||||
users: `http://localhost:${this.port}/api/user`,
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`
|
||||
🚀 SerpentRace Backend Server
|
||||
📡 Port: ${this.port}
|
||||
🔗 Users: http://localhost:${this.port}/user
|
||||
`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create and start application
|
||||
const app = new App();
|
||||
app.listen();
|
||||
|
||||
export default app.app;
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export function auth(req: Request, res: Response, next: NextFunction): void {
|
||||
// Read token from cookie named 'jwt'
|
||||
const token = req.cookies.jwt;
|
||||
|
||||
if (!token) {
|
||||
res.status(401).json({ message: "No token provided" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || "secret");
|
||||
|
||||
if (typeof decoded === "string") {
|
||||
res.status(401).json({ message: "Invalid token payload" });
|
||||
return;
|
||||
}
|
||||
|
||||
req.user = decoded;
|
||||
|
||||
// Check if expiring soon & refresh if needed
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (decoded.exp && decoded.exp - now < 60 * 5) {
|
||||
const { iat, exp, ...payload } = decoded;
|
||||
const newToken = createToken(payload);
|
||||
res.cookie("jwt", newToken, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "none",
|
||||
maxAge: 60 * 60 * 1000, // 1 hour
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: "Invalid token" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function createToken(user: any): string {
|
||||
return jwt.sign({ id: user.id, username: user.username }, process.env.JWT_SECRET || 'secret', {
|
||||
expiresIn: '1h',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { clearAuthCookie } from './authentication';
|
||||
|
||||
// Middleware to handle authentication errors globally
|
||||
export function authErrorHandler(err: any, req: Request, res: Response, next: NextFunction): void {
|
||||
// Check if it's an authentication-related error
|
||||
if (err.name === 'UnauthorizedError' || err.status === 401) {
|
||||
clearAuthCookie(res);
|
||||
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Authentication failed',
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass other errors to the next error handler
|
||||
next(err);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { UserBasicDto } from '../Database/dto/user.dto';
|
||||
|
||||
// Extend Request interface to include user
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: UserBasicDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to clear JWT cookie (auto-logout)
|
||||
function clearAuthCookie(res: Response): void {
|
||||
res.clearCookie("jwt", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: process.env.NODE_ENV === 'production' ? "none" : "lax",
|
||||
});
|
||||
}
|
||||
|
||||
export function auth(req: Request, res: Response, next: NextFunction): void {
|
||||
// Read token from cookie named 'jwt'
|
||||
const token = req.cookies.jwt;
|
||||
|
||||
if (!token) {
|
||||
clearAuthCookie(res); // Clear any existing invalid cookie
|
||||
res.status(401).json({
|
||||
message: "No token provided",
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET || "secret");
|
||||
|
||||
if (typeof decoded === "string") {
|
||||
clearAuthCookie(res); // Clear invalid token cookie
|
||||
res.status(401).json({
|
||||
message: "Invalid token payload",
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Create UserBasicDto from token payload
|
||||
const userDto = new UserBasicDto(
|
||||
decoded.id,
|
||||
decoded.username,
|
||||
decoded.CompanyId
|
||||
);
|
||||
|
||||
req.user = userDto;
|
||||
|
||||
// Check if expiring soon & refresh if needed
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (decoded.exp && decoded.exp - now < 60 * 5) {
|
||||
const { iat, exp, ...payload } = decoded;
|
||||
const newToken = createToken(userDto);
|
||||
res.cookie("jwt", newToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: process.env.NODE_ENV === 'production' ? "none" : "lax",
|
||||
maxAge: 60 * 60 * 1000, // 1 hour
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
// Clear the invalid/expired token cookie
|
||||
clearAuthCookie(res);
|
||||
|
||||
// Determine the specific error type
|
||||
let message = "Invalid token";
|
||||
if (err instanceof jwt.TokenExpiredError) {
|
||||
message = "Token expired";
|
||||
} else if (err instanceof jwt.JsonWebTokenError) {
|
||||
message = "Invalid token";
|
||||
} else if (err instanceof jwt.NotBeforeError) {
|
||||
message = "Token not active";
|
||||
}
|
||||
|
||||
res.status(401).json({
|
||||
message,
|
||||
autoLogout: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function createToken(user: UserBasicDto): string {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
CompanyId: user.CompanyId
|
||||
},
|
||||
process.env.JWT_SECRET || 'secret',
|
||||
{
|
||||
expiresIn: '1h',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to create token from user response data
|
||||
export function createTokenFromUserResponse(userData: {
|
||||
id: number;
|
||||
username: string;
|
||||
CompanyId?: number;
|
||||
}): string {
|
||||
const userDto = new UserBasicDto(
|
||||
userData.id,
|
||||
userData.username,
|
||||
userData.CompanyId
|
||||
);
|
||||
|
||||
return createToken(userDto);
|
||||
}
|
||||
|
||||
// Export the clearAuthCookie function for use in other parts of the app
|
||||
export { clearAuthCookie };
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
//hash password
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//collection of all routes
|
||||
import { Router } from 'express';
|
||||
import { userRouter } from '../User/UserRouter';
|
||||
// import { companyRouter } from '../User/CompanyRouter';
|
||||
|
||||
const router = Router();
|
||||
// Use the user router for user-related routes
|
||||
router.use('/user', userRouter);
|
||||
// Use the company router for company-related routes
|
||||
// router.use('/company', companyRouter);
|
||||
// Add more routers as needed
|
||||
// Export the main router
|
||||
export { router as mainRouter };
|
||||
@@ -0,0 +1,313 @@
|
||||
import winston from 'winston';
|
||||
import AWS from 'aws-sdk';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
// MinIO Configuration
|
||||
const minioConfig = {
|
||||
endpoint: process.env.MINIO_ENDPOINT || 'http://127.0.0.1:9000',
|
||||
accessKeyId: process.env.MINIO_ACCESS_KEY || 'minioadmin',
|
||||
secretAccessKey: process.env.MINIO_SECRET_KEY || 'minioadmin',
|
||||
bucket: process.env.MINIO_BUCKET || 'logs',
|
||||
region: 'us-east-1'
|
||||
};
|
||||
|
||||
// Initialize S3 client for MinIO (single instance)
|
||||
const s3Client = new AWS.S3({
|
||||
endpoint: minioConfig.endpoint,
|
||||
accessKeyId: minioConfig.accessKeyId,
|
||||
secretAccessKey: minioConfig.secretAccessKey,
|
||||
s3ForcePathStyle: true,
|
||||
signatureVersion: 'v4',
|
||||
region: minioConfig.region
|
||||
});
|
||||
|
||||
// Ensure logs directory exists (single initialization)
|
||||
const logsDir = path.join(process.cwd(), 'logs');
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Logging formats (single definition)
|
||||
const simpleFormat = winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.printf(({ timestamp, level, message, category, ...meta }) => {
|
||||
let output = `${timestamp} [${level.toUpperCase()}]`;
|
||||
if (category) output += ` [${category}]`;
|
||||
output += `: ${message}`;
|
||||
if (Object.keys(meta).length > 0) {
|
||||
output += ` ${JSON.stringify(meta)}`;
|
||||
}
|
||||
return output;
|
||||
})
|
||||
);
|
||||
|
||||
const fileFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
// Create Winston logger (single instance)
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: fileFormat,
|
||||
defaultMeta: {
|
||||
service: 'serpentrace-backend',
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
},
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: path.join(logsDir, 'error.log'),
|
||||
level: 'error',
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(logsDir, 'app.log'),
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Add console transport for development (single initialization)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: simpleFormat
|
||||
}));
|
||||
}
|
||||
|
||||
// S3 upload utility (single definition)
|
||||
const uploadLogToS3 = async (logData: string, filename: string): Promise<void> => {
|
||||
try {
|
||||
const key = `serpentrace-backend/${new Date().toISOString().split('T')[0]}/${filename}`;
|
||||
|
||||
await s3Client.upload({
|
||||
Bucket: minioConfig.bucket,
|
||||
Key: key,
|
||||
Body: logData,
|
||||
ContentType: 'text/plain'
|
||||
}).promise();
|
||||
console.log(`✅ Log uploaded to MinIO: ${key}`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to upload log to MinIO:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize MinIO bucket (single function)
|
||||
const initializeMinIOBucket = async (): Promise<void> => {
|
||||
try {
|
||||
await s3Client.headBucket({ Bucket: minioConfig.bucket }).promise();
|
||||
console.log(`✅ MinIO bucket '${minioConfig.bucket}' is accessible`);
|
||||
} catch (error: any) {
|
||||
if (error.statusCode === 404) {
|
||||
try {
|
||||
await s3Client.createBucket({ Bucket: minioConfig.bucket }).promise();
|
||||
console.log(`✅ Created MinIO bucket '${minioConfig.bucket}'`);
|
||||
} catch (createError) {
|
||||
console.error(`❌ Failed to create MinIO bucket:`, createError);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Enhanced application logger with event categories
|
||||
export class AppLogger {
|
||||
private winston: winston.Logger;
|
||||
|
||||
constructor(winstonLogger: winston.Logger) {
|
||||
this.winston = winstonLogger;
|
||||
}
|
||||
|
||||
// Standard log levels
|
||||
error(message: string, meta?: any): void {
|
||||
this.winston.error(message, meta);
|
||||
}
|
||||
|
||||
warn(message: string, meta?: any): void {
|
||||
this.winston.warn(message, meta);
|
||||
}
|
||||
|
||||
info(message: string, meta?: any): void {
|
||||
this.winston.info(message, meta);
|
||||
}
|
||||
|
||||
debug(message: string, meta?: any): void {
|
||||
this.winston.debug(message, meta);
|
||||
}
|
||||
|
||||
// STARTUP EVENTS
|
||||
startup(message: string, meta?: any): void {
|
||||
this.winston.info(message, {
|
||||
category: 'STARTUP',
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// SYSTEM EVENTS
|
||||
system(message: string, meta?: any): void {
|
||||
this.winston.info(message, {
|
||||
category: 'SYSTEM',
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// DATABASE EVENTS
|
||||
database(operation: string, table?: string, duration?: number, meta?: any): void {
|
||||
this.winston.info(`Database: ${operation}`, {
|
||||
category: 'DATABASE',
|
||||
operation,
|
||||
table,
|
||||
duration: duration ? `${duration}ms` : undefined,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// AUTHENTICATION EVENTS
|
||||
auth(action: string, success: boolean, meta?: any): void {
|
||||
this.winston.info(`Auth: ${action}`, {
|
||||
category: 'AUTH',
|
||||
action,
|
||||
success,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// API REQUEST EVENTS
|
||||
request(req: any, res: any, duration?: number): void {
|
||||
this.winston.info(`Request: ${req.method} ${req.originalUrl || req.url}`, {
|
||||
category: 'API',
|
||||
method: req.method,
|
||||
url: req.originalUrl || req.url,
|
||||
statusCode: res.statusCode,
|
||||
ip: req.ip,
|
||||
duration: duration ? `${duration}ms` : undefined,
|
||||
userId: req.user?.id
|
||||
});
|
||||
}
|
||||
|
||||
// SECURITY EVENTS
|
||||
security(event: string, severity: 'low' | 'medium' | 'high' | 'critical', meta?: any): void {
|
||||
const logLevel = severity === 'critical' || severity === 'high' ? 'error' : 'warn';
|
||||
this[logLevel](`Security: ${event}`, {
|
||||
category: 'SECURITY',
|
||||
event,
|
||||
severity,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// BUSINESS LOGIC EVENTS
|
||||
business(action: string, entity: string, meta?: any): void {
|
||||
this.winston.info(`Business: ${action} ${entity}`, {
|
||||
category: 'BUSINESS',
|
||||
action,
|
||||
entity,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// ERROR EVENTS
|
||||
errorEvent(error: string, context: string, meta?: any): void {
|
||||
this.winston.error(`Error in ${context}: ${error}`, {
|
||||
category: 'ERROR',
|
||||
context,
|
||||
error,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// PERFORMANCE EVENTS
|
||||
performance(metric: string, value: number, unit: string, meta?: any): void {
|
||||
this.winston.info(`Performance: ${metric}`, {
|
||||
category: 'PERFORMANCE',
|
||||
metric,
|
||||
value,
|
||||
unit,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// CONFIGURATION EVENTS
|
||||
config(component: string, status: 'loaded' | 'failed' | 'updated', meta?: any): void {
|
||||
this.winston.info(`Config: ${component} ${status}`, {
|
||||
category: 'CONFIG',
|
||||
component,
|
||||
status,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// MIDDLEWARE EVENTS
|
||||
middleware(name: string, action: string, meta?: any): void {
|
||||
this.winston.info(`Middleware: ${name} ${action}`, {
|
||||
category: 'MIDDLEWARE',
|
||||
name,
|
||||
action,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// ROUTE EVENTS
|
||||
route(path: string, method: string, action: string, meta?: any): void {
|
||||
this.winston.info(`Route: ${method} ${path} ${action}`, {
|
||||
category: 'ROUTE',
|
||||
path,
|
||||
method,
|
||||
action,
|
||||
...meta
|
||||
});
|
||||
}
|
||||
|
||||
// Upload current log file to MinIO
|
||||
async uploadLogs(): Promise<void> {
|
||||
try {
|
||||
const logFile = path.join(logsDir, 'app.log');
|
||||
if (fs.existsSync(logFile)) {
|
||||
const logData = fs.readFileSync(logFile, 'utf8');
|
||||
const filename = `app-${Date.now()}.log`;
|
||||
await uploadLogToS3(logData, filename);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.error('Failed to upload logs', { error: errorMessage });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the app logger instance (single instance)
|
||||
export const appLogger = new AppLogger(logger);
|
||||
|
||||
// Setup periodic log upload (single initialization)
|
||||
let uploadInterval: NodeJS.Timeout | null = null;
|
||||
if (process.env.NODE_ENV === 'production' && !uploadInterval) {
|
||||
uploadInterval = setInterval(() => {
|
||||
appLogger.uploadLogs();
|
||||
}, 60 * 60 * 1000); // 1 hour
|
||||
}
|
||||
|
||||
// Graceful shutdown handlers (single initialization)
|
||||
let shutdownHandlersSet = false;
|
||||
if (!shutdownHandlersSet) {
|
||||
shutdownHandlersSet = true;
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
appLogger.system('Application shutting down via SIGTERM');
|
||||
appLogger.uploadLogs().finally(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
appLogger.system('Application interrupted via SIGINT');
|
||||
appLogger.uploadLogs().finally(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Export utilities
|
||||
export { logger, initializeMinIOBucket, uploadLogToS3 };
|
||||
export default appLogger;
|
||||
Reference in New Issue
Block a user