Merge remote-tracking branch 'origin/main' into backend_complete

This commit is contained in:
2025-09-21 03:49:22 +02:00
1039 changed files with 80635 additions and 19 deletions
+6
View File
@@ -10,7 +10,10 @@ import chatRouter from './routers/chatRouter';
import contactRouter from './routers/contactRouter';
import adminRouter from './routers/adminRouter';
import deckImportExportRouter from './routers/deckImportExportRouter';
<<<<<<< HEAD
import gameRouter from './routers/gameRouter';
=======
>>>>>>> origin/main
import { LoggingService, logStartup, logConnection, logError, logRequest } from '../Application/Services/Logger';
import { WebSocketService } from '../Application/Services/WebSocketService';
import { setupSwagger } from './swagger/swaggerUiSetup';
@@ -132,7 +135,10 @@ app.use('/api/chats', chatRouter);
app.use('/api/contacts', contactRouter);
app.use('/api/admin', adminRouter);
app.use('/api/deck-import-export', deckImportExportRouter);
<<<<<<< HEAD
app.use('/api/games', gameRouter);
=======
>>>>>>> origin/main
// Global error handler (must be after routes)
app.use(loggingService.errorLoggingMiddleware());
@@ -107,6 +107,41 @@ router.get('/users/page/:from/:to', adminRequired, async (req: Request, res: Res
}
});
<<<<<<< HEAD
=======
// Get users by page (admin only) - RECOMMENDED
router.get('/users/page/:from/:to', adminRequired, async (req: Request, res: Response) => {
try {
const from = parseInt(req.params.from);
const to = parseInt(req.params.to);
const includeDeleted = req.query.includeDeleted === 'true';
if (isNaN(from) || isNaN(to) || from < 0 || to < from) {
return res.status(400).json({ error: 'Invalid page parameters. "from" and "to" must be valid numbers with to >= from >= 0' });
}
logRequest('Admin get users by page endpoint accessed', req, res, { from, to, includeDeleted });
const result = includeDeleted
? await container.userRepository.findByPageIncludingDeleted(from, to)
: await container.userRepository.findByPage(from, to);
logRequest('Admin users page retrieved successfully', req, res, {
from,
to,
count: result.users.length,
total: result.totalCount,
includeDeleted
});
res.json(result);
} catch (error) {
logError('Admin get users by page endpoint error', error as Error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
>>>>>>> origin/main
// Get user by ID including soft-deleted ones
router.get('/users/:userId',
adminRequired,
@@ -141,6 +176,7 @@ router.get('/users/:userId',
});
// Search users including soft-deleted ones
<<<<<<< HEAD
// router.get('/users/search/:searchTerm',
// adminRequired,
// ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }),
@@ -167,6 +203,34 @@ router.get('/users/:userId',
// res.status(500).json({ error: 'Internal server error' });
// }
// });
=======
router.get('/users/search/:searchTerm',
adminRequired,
ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }),
async (req: Request, res: Response) => {
try {
const { searchTerm } = req.params;
const includeDeleted = req.query.includeDeleted === 'true';
logRequest('Admin search users endpoint accessed', req, res, { searchTerm, includeDeleted });
const users = includeDeleted
? await container.userRepository.searchIncludingDeleted(searchTerm)
: await container.userRepository.search(searchTerm);
logRequest('Admin user search completed', req, res, {
searchTerm,
resultCount: Array.isArray(users) ? users.length : (users.totalCount || 0),
includeDeleted
});
res.json(users);
} catch (error) {
logError('Admin search users endpoint error', error as Error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
>>>>>>> origin/main
// Update any user (admin only)
router.patch('/users/:userId',
@@ -358,6 +422,7 @@ router.get('/decks/search/:searchTerm', adminRequired, async (req: Request, res:
}
});
<<<<<<< HEAD
//modify deck (admin only)
router.patch('/decks/:id', adminRequired, async (req: Request, res: Response) => {
try {
@@ -382,6 +447,8 @@ router.patch('/decks/:id', adminRequired, async (req: Request, res: Response) =>
}
});
=======
>>>>>>> origin/main
// Hard delete deck (admin only)
router.delete('/decks/:id/hard', adminRequired, async (req: Request, res: Response) => {
try {
@@ -60,7 +60,11 @@ deckRouter.post('/', authRequired, async (req, res) => {
try {
const userId = (req as any).user.userId;
logRequest('Create deck endpoint accessed', req, res, { name: req.body.name, userId });
<<<<<<< HEAD
req.body.userid = userId; // Set userId in request body
=======
>>>>>>> origin/main
const result = await container.createDeckCommandHandler.execute(req.body);
logRequest('Deck created successfully', req, res, { deckId: result.id, name: req.body.name, userId });
@@ -140,7 +144,11 @@ deckRouter.get('/:id', authRequired, async (req, res) => {
}
});
<<<<<<< HEAD
deckRouter.patch('/:id', authRequired, async (req, res) => {
=======
deckRouter.put('/:id', authRequired, async (req, res) => {
>>>>>>> origin/main
try {
const deckId = req.params.id;
const userId = (req as any).user.userId;
@@ -164,10 +172,13 @@ deckRouter.patch('/:id', authRequired, async (req, res) => {
if (error instanceof Error && error.message.includes('validation')) {
return res.status(400).json({ error: 'Invalid input data', details: error.message });
}
<<<<<<< HEAD
if (error instanceof Error && error.message.includes('admin')) {
return res.status(403).json({ error: 'Forbidden: ' + error.message });
}
=======
>>>>>>> origin/main
res.status(500).json({ error: 'Internal server error' });
}
@@ -32,7 +32,11 @@ userRouter.post('/login',
logAuth('User login successful', result.user.id, { username: result.user.username }, req, res);
res.json(result);
} else {
<<<<<<< HEAD
throw new Error(`Login failed: ${result}`);
=======
return ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
>>>>>>> origin/main
}
} catch (error) {
@@ -48,9 +52,12 @@ userRouter.post('/login',
if (error.message.includes('not verified')) {
return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
}
<<<<<<< HEAD
if (error.message.includes('restriction')) {
return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
}
=======
>>>>>>> origin/main
if (error.message.includes('deactivated')) {
return ErrorResponseService.sendUnauthorized(res, 'Account has been deactivated');
}
@@ -87,8 +94,12 @@ userRouter.post('/create',
res.status(201).json(result);
} catch (error) {
<<<<<<< HEAD
// Don't log here since CreateUserCommandHandler already logs system errors
// Only log validation/user input errors at router level
=======
logError('Create user endpoint error', error as Error, req, res);
>>>>>>> origin/main
if (error instanceof Error) {
if (error.message.includes('already exists')) {
@@ -97,10 +108,13 @@ userRouter.post('/create',
if (error.message.includes('validation')) {
return ErrorResponseService.sendBadRequest(res, error.message);
}
<<<<<<< HEAD
// Log unexpected errors that weren't handled by the command handler
if (!error.message.includes('Failed to create user')) {
logError('Unexpected create user endpoint error', error as Error, req, res);
}
=======
>>>>>>> origin/main
}
return ErrorResponseService.sendInternalServerError(res);
@@ -173,6 +187,7 @@ userRouter.patch('/profile', authRequired, async (req, res) => {
}
});
<<<<<<< HEAD
//Soft delete user (current user)
userRouter.delete('/profile', authRequired, async (req, res) => {
try {
@@ -310,4 +325,6 @@ userRouter.post('/reset-password',
}
});
=======
>>>>>>> origin/main
export default userRouter;
@@ -1,5 +1,8 @@
import swaggerJSDoc from 'swagger-jsdoc';
<<<<<<< HEAD
import path from 'path';
=======
>>>>>>> origin/main
export const swaggerOptions = {
definition: {
@@ -19,12 +22,17 @@ export const swaggerOptions = {
},
servers: [
{
<<<<<<< HEAD
url: 'http://localhost:3001',
description: 'Local development server'
},
{
url: 'http://localhost:3000',
description: 'Local development server (alt)'
=======
url: 'http://localhost:3000',
description: 'Local development server'
>>>>>>> origin/main
},
{
url: 'https://api.serpentrace.com',
@@ -66,6 +74,7 @@ export const swaggerOptions = {
{
name: 'Deck Import/Export',
description: 'Import and export deck functionality'
<<<<<<< HEAD
},
{
name: 'Games',
@@ -90,11 +99,17 @@ export const swaggerOptions = {
{
name: 'Admin - Contacts',
description: 'Admin contact management operations'
=======
>>>>>>> origin/main
}
]
},
apis: [
<<<<<<< HEAD
'./src/Api/swagger/swaggerDefinitionsFixed.ts'
=======
'./src/Api/swagger/swaggerDefinitions.ts'
>>>>>>> origin/main
],
};
@@ -1,6 +1,14 @@
/**
* @swagger
* components:
<<<<<<< HEAD
=======
* securitySchemes:
* bearerAuth:
* type: http
* scheme: bearer
* bearerFormat: JWT
>>>>>>> origin/main
* schemas:
* User:
* type: object
@@ -100,6 +108,7 @@
* type: string
* format: email
*
<<<<<<< HEAD
* ForgotPasswordRequest:
* type: object
* required:
@@ -131,6 +140,8 @@
* message:
* type: string
*
=======
>>>>>>> origin/main
* Organization:
* type: object
* properties:
@@ -325,6 +336,7 @@
* chatId:
* type: string
*
<<<<<<< HEAD
* Game:
* type: object
* properties:
@@ -353,6 +365,8 @@
* type: string
* format: date-time
*
=======
>>>>>>> origin/main
* Error:
* type: object
* properties:
@@ -363,6 +377,7 @@
* format: date-time
* details:
* type: string
<<<<<<< HEAD
*/
/**
* @swagger
@@ -392,6 +407,34 @@
* schema:
* $ref: '#/components/schemas/Error'
*
=======
*
* paths:
* /api/users/login:
* post:
* tags: [Users]
* summary: User login
* description: Authenticate user and return JWT token
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LoginRequest'
* responses:
* 200:
* description: Login successful
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LoginResponse'
* 401:
* description: Invalid credentials
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
>>>>>>> origin/main
*
* /api/users/create:
* post:
@@ -1454,6 +1497,7 @@
* application/json:
* schema:
* $ref: '#/components/schemas/Contact'
<<<<<<< HEAD
*
* /api/games/start:
* post:
@@ -1611,6 +1655,8 @@
* description: Game already started or not ready to start
* 500:
* description: Internal server error
=======
>>>>>>> origin/main
*/
export {};
@@ -21,6 +21,10 @@ export class UserMapper {
fname: user.fname,
lname: user.lname,
code: user.token,
<<<<<<< HEAD
=======
type: user.type,
>>>>>>> origin/main
phone: user.phone,
state: user.state,
};
@@ -24,6 +24,10 @@ export interface DetailUserDto {
fname: string;
lname: string;
code: string | null;
<<<<<<< HEAD
=======
type: string;
>>>>>>> origin/main
phone: string | null;
state: number;
}
@@ -1,6 +1,9 @@
export interface UpdateDeckCommand {
id: string;
<<<<<<< HEAD
userstate?: number;
=======
>>>>>>> origin/main
name?: string;
type?: number;
userid?: string;
@@ -2,13 +2,17 @@ import { IDeckRepository } from '../../../Domain/IRepository/IDeckRepository';
import { UpdateDeckCommand } from './UpdateDeckCommand';
import { ShortDeckDto } from '../../DTOs/DeckDto';
import { DeckMapper } from '../../DTOs/Mappers/DeckMapper';
<<<<<<< HEAD
import { DeckAggregate } from '../../../Domain/Deck/DeckAggregate';
import { logError } from '../../Services/Logger';
=======
>>>>>>> origin/main
export class UpdateDeckCommandHandler {
constructor(private readonly deckRepo: IDeckRepository) {}
async execute(cmd: UpdateDeckCommand): Promise<ShortDeckDto | null> {
<<<<<<< HEAD
if(cmd.state !== undefined && cmd.userstate!==1) {
throw new Error('Only admin users can change deck state');
}
@@ -46,5 +50,10 @@ export class UpdateDeckCommandHandler {
logError(`Error updating deck: ${cmd.id}`, error);
throw error;
}
=======
const updated = await this.deckRepo.update(cmd.id, { ...cmd });
if (!updated) return null;
return DeckMapper.toShortDto(updated);
>>>>>>> origin/main
}
}
@@ -1,14 +1,25 @@
import { IDeckRepository } from '../../../Domain/IRepository/IDeckRepository';
import { GetDeckByIdQuery } from './GetDeckByIdQuery';
<<<<<<< HEAD
import { DetailDeckDto } from '../../DTOs/DeckDto';
=======
import { ShortDeckDto } from '../../DTOs/DeckDto';
>>>>>>> origin/main
import { DeckMapper } from '../../DTOs/Mappers/DeckMapper';
export class GetDeckByIdQueryHandler {
constructor(private readonly deckRepo: IDeckRepository) {}
<<<<<<< HEAD
async execute(query: GetDeckByIdQuery): Promise<DetailDeckDto | null> {
const deck = await this.deckRepo.findById(query.id);
if (!deck) return null;
return DeckMapper.toDetailDto(deck);
=======
async execute(query: GetDeckByIdQuery): Promise<ShortDeckDto | null> {
const deck = await this.deckRepo.findById(query.id);
if (!deck) return null;
return DeckMapper.toShortDto(deck);
>>>>>>> origin/main
}
}
@@ -49,6 +49,7 @@ export class GeneralSearchService implements IGeneralSearchService {
};
}
<<<<<<< HEAD
// Ensure limit is at least 1 to prevent database issues
const effectiveLimit = Math.max(limit || 20, 1);
const effectiveOffset = Math.max(offset || 0, 0);
@@ -57,6 +58,12 @@ export class GeneralSearchService implements IGeneralSearchService {
const { users, totalCount } = await this.userRepo.search(query.trim(), effectiveLimit, effectiveOffset);
const results = users.map(user => UserMapper.toShortDto(user));
const hasMore = (effectiveOffset + effectiveLimit) < totalCount;
=======
try {
const { users, totalCount } = await this.userRepo.search(query.trim(), limit, offset);
const results = users.map(user => UserMapper.toShortDto(user));
const hasMore = (offset + limit) < totalCount;
>>>>>>> origin/main
return {
results,
@@ -109,6 +116,7 @@ export class GeneralSearchService implements IGeneralSearchService {
};
}
<<<<<<< HEAD
// Ensure limit is at least 1 to prevent database issues
const effectiveLimit = Math.max(limit || 20, 1);
const effectiveOffset = Math.max(offset || 0, 0);
@@ -128,6 +136,19 @@ export class GeneralSearchService implements IGeneralSearchService {
} catch (error) {
throw new Error('Failed to search decks');
}
=======
const { decks, totalCount } = await this.deckRepo.search(query.trim(), limit, offset);
const results = decks.map(deck => DeckMapper.toShortDto(deck));
const hasMore = (offset + limit) < totalCount;
return {
results,
totalCount,
hasMore,
searchQuery: query,
searchType: 'decks'
};
>>>>>>> origin/main
}
async searchByType(
@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from 'express';
import { JWTService } from './JWTService';
<<<<<<< HEAD
import { RedisService } from './RedisService';
import { logAuth, logWarning } from './Logger';
@@ -143,4 +144,60 @@ export async function adminRequired(req: Request, res: Response, next: NextFunct
logWarning('Admin authentication middleware error', { error: (error as Error).message }, req);
return res.status(500).json({ error: 'Internal server error' });
}
=======
import { logAuth, logWarning } from './Logger';
export const jwtService = new JWTService();
export function authRequired(req: Request, res: Response, next: NextFunction) {
const payload = jwtService.verify(req);
if (!payload) {
logAuth('Authentication failed - No valid token', undefined, {
ip: req.ip,
userAgent: req.get ? req.get('User-Agent') : 'unknown',
path: req.path
}, req);
return res.status(401).json({ error: 'Unauthorized' });
}
logAuth('Authentication successful', payload.userId, {
authLevel: payload.authLevel,
orgId: payload.orgId
}, req);
const refreshed = jwtService.refreshIfNeeded(payload, res);
if (refreshed) {
logAuth('Token refreshed', payload.userId, undefined, req);
}
(req as any).user = payload;
next();
}
export function adminRequired(req: Request, res: Response, next: NextFunction) {
const payload = jwtService.verify(req);
if (!payload || payload.authLevel !== 1) {
logWarning('Admin access denied', {
hasPayload: !!payload,
authLevel: payload?.authLevel,
userId: payload?.userId,
ip: req.ip,
path: req.path
}, req);
return res.status(403).json({ error: 'Forbidden' });
}
logAuth('Admin authentication successful', payload.userId, {
authLevel: payload.authLevel,
orgId: payload.orgId
}, req);
const refreshed = jwtService.refreshIfNeeded(payload, res);
if (refreshed) {
logAuth('Admin token refreshed', payload.userId, undefined, req);
}
(req as any).user = payload;
next();
>>>>>>> origin/main
}
@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Test1755691733404 implements MigrationInterface {
name = 'Test1755691733404'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "Users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "orgid" uuid, "username" character varying(100) NOT NULL, "password" character varying(255) NOT NULL, "email" character varying(255) NOT NULL, "fname" character varying(100) NOT NULL, "lname" character varying(100) NOT NULL, "code" character varying(50), "type" character varying(50) NOT NULL, "phone" character varying(20), "state" integer NOT NULL DEFAULT '0', "regdate" TIMESTAMP NOT NULL DEFAULT now(), "updatedate" TIMESTAMP NOT NULL DEFAULT now(), "Orglogindate" TIMESTAMP, CONSTRAINT "UQ_ffc81a3b97dcbf8e320d5106c0d" UNIQUE ("username"), CONSTRAINT "UQ_3c3ab3f49a87e6ddb607f3c4945" UNIQUE ("email"), CONSTRAINT "PK_16d4f7d636df336db11d87413e3" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "Organizations" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "contactfname" character varying(100) NOT NULL, "contactlname" character varying(100) NOT NULL, "contactphone" character varying(20) NOT NULL, "contactemail" character varying(255) NOT NULL, "state" integer NOT NULL DEFAULT '0', "regdate" TIMESTAMP NOT NULL DEFAULT now(), "updatedate" TIMESTAMP NOT NULL DEFAULT now(), "url" character varying(500), "userinorg" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_e0690a31419f6666194423526f2" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "Decks" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "type" integer NOT NULL, "user_id" uuid NOT NULL, "creation_date" TIMESTAMP NOT NULL DEFAULT now(), "cards" json NOT NULL, "played_number" integer NOT NULL DEFAULT '0', "ctype" integer NOT NULL DEFAULT '0', "update_date" TIMESTAMP NOT NULL DEFAULT now(), "state" integer NOT NULL DEFAULT '0', "organization_id" uuid, CONSTRAINT "PK_001f26cb3ec39c1f25269943473" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "Chats" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "users" uuid array NOT NULL, "messages" json NOT NULL, "updateDate" TIMESTAMP NOT NULL DEFAULT now(), "state" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_64c36c2b8d86a0d5de4cf64de8d" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "Decks" ADD CONSTRAINT "FK_06ee28f90d68543a03b14aebe13" FOREIGN KEY ("organization_id") REFERENCES "Organizations"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Decks" DROP CONSTRAINT "FK_06ee28f90d68543a03b14aebe13"`);
await queryRunner.query(`DROP TABLE "Chats"`);
await queryRunner.query(`DROP TABLE "Decks"`);
await queryRunner.query(`DROP TABLE "Organizations"`);
await queryRunner.query(`DROP TABLE "Users"`);
}
}
@@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddEmailVerificationFields1755706019351 implements MigrationInterface {
name = 'AddEmailVerificationFields1755706019351'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Users" DROP COLUMN "code"`);
await queryRunner.query(`ALTER TABLE "Users" ADD "token" character varying(255)`);
await queryRunner.query(`ALTER TABLE "Users" ADD "TokenExpires" TIMESTAMP`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Users" DROP COLUMN "TokenExpires"`);
await queryRunner.query(`ALTER TABLE "Users" DROP COLUMN "token"`);
await queryRunner.query(`ALTER TABLE "Users" ADD "code" character varying(50)`);
}
}
@@ -0,0 +1,30 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddChatMessagingSystem1755817306222 implements MigrationInterface {
name = 'AddChatMessagingSystem1755817306222'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "ChatArchives" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "chatId" uuid NOT NULL, "archivedMessages" json NOT NULL, "archivedAt" TIMESTAMP NOT NULL, "createDate" TIMESTAMP NOT NULL DEFAULT now(), "chatType" character varying(50) NOT NULL, "chatName" character varying(255), "gameId" uuid, "participants" uuid array NOT NULL, CONSTRAINT "PK_fe62979fc2061d7afe278d3f14e" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "type" character varying(50) NOT NULL DEFAULT 'direct'`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "name" character varying(255)`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "gameId" uuid`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "createdBy" uuid`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "lastActivity" TIMESTAMP`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "createDate" TIMESTAMP NOT NULL DEFAULT now()`);
await queryRunner.query(`ALTER TABLE "Chats" ADD "archiveDate" TIMESTAMP`);
await queryRunner.query(`ALTER TABLE "Chats" ALTER COLUMN "messages" SET DEFAULT '[]'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Chats" ALTER COLUMN "messages" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "archiveDate"`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "createDate"`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "lastActivity"`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "createdBy"`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "gameId"`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "name"`);
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "type"`);
await queryRunner.query(`DROP TABLE "ChatArchives"`);
}
}
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class CreateContactTable1755855028839 implements MigrationInterface {
name = 'CreateContactTable1755855028839'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "Contacts" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "email" character varying(255) NOT NULL, "userid" uuid, "type" integer NOT NULL, "txt" text NOT NULL, "state" integer NOT NULL DEFAULT '0', "createDate" TIMESTAMP NOT NULL DEFAULT now(), "updateDate" TIMESTAMP NOT NULL DEFAULT now(), "adminResponse" text, "responseDate" TIMESTAMP, "respondedBy" uuid, CONSTRAINT "PK_68782cec65c8eef577c62958273" PRIMARY KEY ("id"))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "Contacts"`);
}
}
@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class AddMaxOrganizationalDecksToOrganization1692712800000 implements MigrationInterface {
name = 'AddMaxOrganizationalDecksToOrganization1692712800000';
public async up(queryRunner: QueryRunner): Promise<void> {
// Add maxOrganizationalDecks column to Organizations table
await queryRunner.addColumn('Organizations', new TableColumn({
name: 'maxOrganizationalDecks',
type: 'int',
isNullable: true, // No default - set by admin
comment: 'Maximum number of organizational decks a premium user can create in this organization'
}));
// Add performance indexes for deck filtering queries
await queryRunner.query(`CREATE INDEX "IDX_DECK_USER_STATE_CTYPE" ON "Decks" ("user_id", "state", "ctype")`);
await queryRunner.query(`CREATE INDEX "IDX_DECK_ORG_CTYPE_STATE" ON "Decks" ("organization_id", "ctype", "state")`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Drop indexes
await queryRunner.query(`DROP INDEX "IDX_DECK_ORG_CTYPE_STATE"`);
await queryRunner.query(`DROP INDEX "IDX_DECK_USER_STATE_CTYPE"`);
// Remove maxOrganizationalDecks column
await queryRunner.dropColumn('Organizations', 'maxOrganizationalDecks');
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Test1755691732089 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddEmailVerificationFields1755706017175 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class FixEmailVerificationFields1755706055220 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}