Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled

Complete rewrite from Express to NestJS with enterprise-grade features:

## Backend Improvements
- Migrated from Express to NestJS 11.0.1 with TypeScript
- Implemented Prisma ORM 7.3.0 for type-safe database access
- Added CASL authorization system replacing role-based guards
- Created global exception filters with structured logging
- Implemented Auth0 JWT authentication with Passport.js
- Added vehicle management with conflict detection
- Enhanced event scheduling with driver/vehicle assignment
- Comprehensive error handling and logging

## Frontend Improvements
- Upgraded to React 19.2.0 with Vite 7.2.4
- Implemented CASL-based permission system
- Added AbilityContext for declarative permissions
- Created ErrorHandler utility for consistent error messages
- Enhanced API client with request/response logging
- Added War Room (Command Center) dashboard
- Created VIP Schedule view with complete itineraries
- Implemented Vehicle Management UI
- Added mock data generators for testing (288 events across 20 VIPs)

## New Features
- Vehicle fleet management (types, capacity, status tracking)
- Complete 3-day Jamboree schedule generation
- Individual VIP schedule pages with PDF export (planned)
- Real-time War Room dashboard with auto-refresh
- Permission-based navigation filtering
- First user auto-approval as administrator

## Documentation
- Created CASL_AUTHORIZATION.md (comprehensive guide)
- Created ERROR_HANDLING.md (error handling patterns)
- Updated CLAUDE.md with new architecture
- Added migration guides and best practices

## Technical Debt Resolved
- Removed custom authentication in favor of Auth0
- Replaced role checks with CASL abilities
- Standardized error responses across API
- Implemented proper TypeScript typing
- Added comprehensive logging

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 08:50:25 +01:00
parent 8ace1ab2c1
commit 868f7efc23
351 changed files with 44997 additions and 6276 deletions

View File

@@ -0,0 +1,4 @@
import { Pool } from 'pg';
declare const pool: Pool;
export default pool;
//# sourceMappingURL=database.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/config/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAK1B,QAAA,MAAM,IAAI,MAKR,CAAC;AAWH,eAAe,IAAI,CAAC"}

View File

@@ -0,0 +1,23 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const pg_1 = require("pg");
const dotenv_1 = __importDefault(require("dotenv"));
dotenv_1.default.config();
const pool = new pg_1.Pool({
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:changeme@localhost:5432/vip_coordinator',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Test the connection
pool.on('connect', () => {
console.log('✅ Connected to PostgreSQL database');
});
pool.on('error', (err) => {
console.error('❌ PostgreSQL connection error:', err);
});
exports.default = pool;
//# sourceMappingURL=database.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/config/database.ts"],"names":[],"mappings":";;;;;AAAA,2BAA0B;AAC1B,oDAA4B;AAE5B,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,IAAI,GAAG,IAAI,SAAI,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,+DAA+D;IAC7G,GAAG,EAAE,EAAE;IACP,iBAAiB,EAAE,KAAK;IACxB,uBAAuB,EAAE,IAAI;CAC9B,CAAC,CAAC;AAEH,sBAAsB;AACtB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACtB,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;IACvB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,kBAAe,IAAI,CAAC"}

View File

@@ -0,0 +1,17 @@
declare class MockDatabase {
private users;
private vips;
private drivers;
private scheduleEvents;
private adminSettings;
constructor();
query(text: string, params?: any[]): Promise<any>;
connect(): Promise<{
query: (text: string, params?: any[]) => Promise<any>;
release: () => void;
}>;
end(): Promise<void>;
on(event: string, callback: Function): void;
}
export default MockDatabase;
//# sourceMappingURL=mockDatabase.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"mockDatabase.d.ts","sourceRoot":"","sources":["../../src/config/mockDatabase.ts"],"names":[],"mappings":"AAyBA,cAAM,YAAY;IAChB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,aAAa,CAAkC;;IA8BjD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAiGjD,OAAO;sBAjGK,MAAM,WAAW,GAAG,EAAE,KAAG,OAAO,CAAC,GAAG,CAAC;;;IAyGjD,GAAG;IAIT,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;CAKrC;AAED,eAAe,YAAY,CAAC"}

View File

@@ -0,0 +1,137 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class MockDatabase {
constructor() {
this.users = new Map();
this.vips = new Map();
this.drivers = new Map();
this.scheduleEvents = new Map();
this.adminSettings = new Map();
// Add a test admin user
const adminId = '1';
this.users.set(adminId, {
id: adminId,
email: 'admin@example.com',
name: 'Test Admin',
role: 'admin',
created_at: new Date(),
updated_at: new Date()
});
// Add some test VIPs
this.vips.set('1', {
id: '1',
name: 'John Doe',
organization: 'Test Org',
department: 'Office of Development',
transport_mode: 'flight',
expected_arrival: '2025-07-25 14:00',
needs_airport_pickup: true,
needs_venue_transport: true,
notes: 'Test VIP',
created_at: new Date(),
updated_at: new Date()
});
}
async query(text, params) {
console.log('Mock DB Query:', text.substring(0, 50) + '...');
// Handle user queries
if (text.includes('COUNT(*) FROM users')) {
return { rows: [{ count: this.users.size.toString() }] };
}
if (text.includes('SELECT * FROM users WHERE email')) {
const email = params?.[0];
const user = Array.from(this.users.values()).find(u => u.email === email);
return { rows: user ? [user] : [] };
}
if (text.includes('SELECT * FROM users WHERE id')) {
const id = params?.[0];
const user = this.users.get(id);
return { rows: user ? [user] : [] };
}
if (text.includes('SELECT * FROM users WHERE google_id')) {
const google_id = params?.[0];
const user = Array.from(this.users.values()).find(u => u.google_id === google_id);
return { rows: user ? [user] : [] };
}
if (text.includes('INSERT INTO users')) {
const id = Date.now().toString();
const user = {
id,
email: params?.[0],
name: params?.[1],
role: params?.[2] || 'coordinator',
google_id: params?.[4],
created_at: new Date(),
updated_at: new Date()
};
this.users.set(id, user);
return { rows: [user] };
}
// Handle VIP queries
if (text.includes('SELECT v.*') && text.includes('FROM vips')) {
const vips = Array.from(this.vips.values());
return {
rows: vips.map(v => ({
...v,
flights: []
}))
};
}
// Handle admin settings queries
if (text.includes('SELECT * FROM admin_settings')) {
const settings = Array.from(this.adminSettings.entries()).map(([key, value]) => ({
key,
value
}));
return { rows: settings };
}
// Handle drivers queries
if (text.includes('SELECT * FROM drivers')) {
const drivers = Array.from(this.drivers.values());
return { rows: drivers };
}
// Handle schedule events queries
if (text.includes('SELECT * FROM schedule_events')) {
const events = Array.from(this.scheduleEvents.values());
return { rows: events };
}
if (text.includes('INSERT INTO vips')) {
const id = Date.now().toString();
const vip = {
id,
name: params?.[0],
organization: params?.[1],
department: params?.[2] || 'Office of Development',
transport_mode: params?.[3] || 'flight',
expected_arrival: params?.[4],
needs_airport_pickup: params?.[5] !== false,
needs_venue_transport: params?.[6] !== false,
notes: params?.[7] || '',
created_at: new Date(),
updated_at: new Date()
};
this.vips.set(id, vip);
return { rows: [vip] };
}
// Default empty result
console.log('Unhandled query:', text);
return { rows: [] };
}
async connect() {
return {
query: this.query.bind(this),
release: () => { }
};
}
// Make compatible with pg Pool interface
async end() {
console.log('Mock database connection closed');
}
on(event, callback) {
if (event === 'connect') {
setTimeout(() => callback(), 100);
}
}
}
exports.default = MockDatabase;
//# sourceMappingURL=mockDatabase.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,292 @@
declare const redisClient: import("@redis/client").RedisClientType<{
graph: {
CONFIG_GET: typeof import("@redis/graph/dist/commands/CONFIG_GET");
configGet: typeof import("@redis/graph/dist/commands/CONFIG_GET");
CONFIG_SET: typeof import("@redis/graph/dist/commands/CONFIG_SET");
configSet: typeof import("@redis/graph/dist/commands/CONFIG_SET");
DELETE: typeof import("@redis/graph/dist/commands/DELETE");
delete: typeof import("@redis/graph/dist/commands/DELETE");
EXPLAIN: typeof import("@redis/graph/dist/commands/EXPLAIN");
explain: typeof import("@redis/graph/dist/commands/EXPLAIN");
LIST: typeof import("@redis/graph/dist/commands/LIST");
list: typeof import("@redis/graph/dist/commands/LIST");
PROFILE: typeof import("@redis/graph/dist/commands/PROFILE");
profile: typeof import("@redis/graph/dist/commands/PROFILE");
QUERY: typeof import("@redis/graph/dist/commands/QUERY");
query: typeof import("@redis/graph/dist/commands/QUERY");
RO_QUERY: typeof import("@redis/graph/dist/commands/RO_QUERY");
roQuery: typeof import("@redis/graph/dist/commands/RO_QUERY");
SLOWLOG: typeof import("@redis/graph/dist/commands/SLOWLOG");
slowLog: typeof import("@redis/graph/dist/commands/SLOWLOG");
};
json: {
ARRAPPEND: typeof import("@redis/json/dist/commands/ARRAPPEND");
arrAppend: typeof import("@redis/json/dist/commands/ARRAPPEND");
ARRINDEX: typeof import("@redis/json/dist/commands/ARRINDEX");
arrIndex: typeof import("@redis/json/dist/commands/ARRINDEX");
ARRINSERT: typeof import("@redis/json/dist/commands/ARRINSERT");
arrInsert: typeof import("@redis/json/dist/commands/ARRINSERT");
ARRLEN: typeof import("@redis/json/dist/commands/ARRLEN");
arrLen: typeof import("@redis/json/dist/commands/ARRLEN");
ARRPOP: typeof import("@redis/json/dist/commands/ARRPOP");
arrPop: typeof import("@redis/json/dist/commands/ARRPOP");
ARRTRIM: typeof import("@redis/json/dist/commands/ARRTRIM");
arrTrim: typeof import("@redis/json/dist/commands/ARRTRIM");
DEBUG_MEMORY: typeof import("@redis/json/dist/commands/DEBUG_MEMORY");
debugMemory: typeof import("@redis/json/dist/commands/DEBUG_MEMORY");
DEL: typeof import("@redis/json/dist/commands/DEL");
del: typeof import("@redis/json/dist/commands/DEL");
FORGET: typeof import("@redis/json/dist/commands/FORGET");
forget: typeof import("@redis/json/dist/commands/FORGET");
GET: typeof import("@redis/json/dist/commands/GET");
get: typeof import("@redis/json/dist/commands/GET");
MERGE: typeof import("@redis/json/dist/commands/MERGE");
merge: typeof import("@redis/json/dist/commands/MERGE");
MGET: typeof import("@redis/json/dist/commands/MGET");
mGet: typeof import("@redis/json/dist/commands/MGET");
MSET: typeof import("@redis/json/dist/commands/MSET");
mSet: typeof import("@redis/json/dist/commands/MSET");
NUMINCRBY: typeof import("@redis/json/dist/commands/NUMINCRBY");
numIncrBy: typeof import("@redis/json/dist/commands/NUMINCRBY");
NUMMULTBY: typeof import("@redis/json/dist/commands/NUMMULTBY");
numMultBy: typeof import("@redis/json/dist/commands/NUMMULTBY");
OBJKEYS: typeof import("@redis/json/dist/commands/OBJKEYS");
objKeys: typeof import("@redis/json/dist/commands/OBJKEYS");
OBJLEN: typeof import("@redis/json/dist/commands/OBJLEN");
objLen: typeof import("@redis/json/dist/commands/OBJLEN");
RESP: typeof import("@redis/json/dist/commands/RESP");
resp: typeof import("@redis/json/dist/commands/RESP");
SET: typeof import("@redis/json/dist/commands/SET");
set: typeof import("@redis/json/dist/commands/SET");
STRAPPEND: typeof import("@redis/json/dist/commands/STRAPPEND");
strAppend: typeof import("@redis/json/dist/commands/STRAPPEND");
STRLEN: typeof import("@redis/json/dist/commands/STRLEN");
strLen: typeof import("@redis/json/dist/commands/STRLEN");
TYPE: typeof import("@redis/json/dist/commands/TYPE");
type: typeof import("@redis/json/dist/commands/TYPE");
};
ft: {
_LIST: typeof import("@redis/search/dist/commands/_LIST");
_list: typeof import("@redis/search/dist/commands/_LIST");
ALTER: typeof import("@redis/search/dist/commands/ALTER");
alter: typeof import("@redis/search/dist/commands/ALTER");
AGGREGATE_WITHCURSOR: typeof import("@redis/search/dist/commands/AGGREGATE_WITHCURSOR");
aggregateWithCursor: typeof import("@redis/search/dist/commands/AGGREGATE_WITHCURSOR");
AGGREGATE: typeof import("@redis/search/dist/commands/AGGREGATE");
aggregate: typeof import("@redis/search/dist/commands/AGGREGATE");
ALIASADD: typeof import("@redis/search/dist/commands/ALIASADD");
aliasAdd: typeof import("@redis/search/dist/commands/ALIASADD");
ALIASDEL: typeof import("@redis/search/dist/commands/ALIASDEL");
aliasDel: typeof import("@redis/search/dist/commands/ALIASDEL");
ALIASUPDATE: typeof import("@redis/search/dist/commands/ALIASUPDATE");
aliasUpdate: typeof import("@redis/search/dist/commands/ALIASUPDATE");
CONFIG_GET: typeof import("@redis/search/dist/commands/CONFIG_GET");
configGet: typeof import("@redis/search/dist/commands/CONFIG_GET");
CONFIG_SET: typeof import("@redis/search/dist/commands/CONFIG_SET");
configSet: typeof import("@redis/search/dist/commands/CONFIG_SET");
CREATE: typeof import("@redis/search/dist/commands/CREATE");
create: typeof import("@redis/search/dist/commands/CREATE");
CURSOR_DEL: typeof import("@redis/search/dist/commands/CURSOR_DEL");
cursorDel: typeof import("@redis/search/dist/commands/CURSOR_DEL");
CURSOR_READ: typeof import("@redis/search/dist/commands/CURSOR_READ");
cursorRead: typeof import("@redis/search/dist/commands/CURSOR_READ");
DICTADD: typeof import("@redis/search/dist/commands/DICTADD");
dictAdd: typeof import("@redis/search/dist/commands/DICTADD");
DICTDEL: typeof import("@redis/search/dist/commands/DICTDEL");
dictDel: typeof import("@redis/search/dist/commands/DICTDEL");
DICTDUMP: typeof import("@redis/search/dist/commands/DICTDUMP");
dictDump: typeof import("@redis/search/dist/commands/DICTDUMP");
DROPINDEX: typeof import("@redis/search/dist/commands/DROPINDEX");
dropIndex: typeof import("@redis/search/dist/commands/DROPINDEX");
EXPLAIN: typeof import("@redis/search/dist/commands/EXPLAIN");
explain: typeof import("@redis/search/dist/commands/EXPLAIN");
EXPLAINCLI: typeof import("@redis/search/dist/commands/EXPLAINCLI");
explainCli: typeof import("@redis/search/dist/commands/EXPLAINCLI");
INFO: typeof import("@redis/search/dist/commands/INFO");
info: typeof import("@redis/search/dist/commands/INFO");
PROFILESEARCH: typeof import("@redis/search/dist/commands/PROFILE_SEARCH");
profileSearch: typeof import("@redis/search/dist/commands/PROFILE_SEARCH");
PROFILEAGGREGATE: typeof import("@redis/search/dist/commands/PROFILE_AGGREGATE");
profileAggregate: typeof import("@redis/search/dist/commands/PROFILE_AGGREGATE");
SEARCH: typeof import("@redis/search/dist/commands/SEARCH");
search: typeof import("@redis/search/dist/commands/SEARCH");
SEARCH_NOCONTENT: typeof import("@redis/search/dist/commands/SEARCH_NOCONTENT");
searchNoContent: typeof import("@redis/search/dist/commands/SEARCH_NOCONTENT");
SPELLCHECK: typeof import("@redis/search/dist/commands/SPELLCHECK");
spellCheck: typeof import("@redis/search/dist/commands/SPELLCHECK");
SUGADD: typeof import("@redis/search/dist/commands/SUGADD");
sugAdd: typeof import("@redis/search/dist/commands/SUGADD");
SUGDEL: typeof import("@redis/search/dist/commands/SUGDEL");
sugDel: typeof import("@redis/search/dist/commands/SUGDEL");
SUGGET_WITHPAYLOADS: typeof import("@redis/search/dist/commands/SUGGET_WITHPAYLOADS");
sugGetWithPayloads: typeof import("@redis/search/dist/commands/SUGGET_WITHPAYLOADS");
SUGGET_WITHSCORES_WITHPAYLOADS: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES_WITHPAYLOADS");
sugGetWithScoresWithPayloads: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES_WITHPAYLOADS");
SUGGET_WITHSCORES: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES");
sugGetWithScores: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES");
SUGGET: typeof import("@redis/search/dist/commands/SUGGET");
sugGet: typeof import("@redis/search/dist/commands/SUGGET");
SUGLEN: typeof import("@redis/search/dist/commands/SUGLEN");
sugLen: typeof import("@redis/search/dist/commands/SUGLEN");
SYNDUMP: typeof import("@redis/search/dist/commands/SYNDUMP");
synDump: typeof import("@redis/search/dist/commands/SYNDUMP");
SYNUPDATE: typeof import("@redis/search/dist/commands/SYNUPDATE");
synUpdate: typeof import("@redis/search/dist/commands/SYNUPDATE");
TAGVALS: typeof import("@redis/search/dist/commands/TAGVALS");
tagVals: typeof import("@redis/search/dist/commands/TAGVALS");
};
ts: {
ADD: typeof import("@redis/time-series/dist/commands/ADD");
add: typeof import("@redis/time-series/dist/commands/ADD");
ALTER: typeof import("@redis/time-series/dist/commands/ALTER");
alter: typeof import("@redis/time-series/dist/commands/ALTER");
CREATE: typeof import("@redis/time-series/dist/commands/CREATE");
create: typeof import("@redis/time-series/dist/commands/CREATE");
CREATERULE: typeof import("@redis/time-series/dist/commands/CREATERULE");
createRule: typeof import("@redis/time-series/dist/commands/CREATERULE");
DECRBY: typeof import("@redis/time-series/dist/commands/DECRBY");
decrBy: typeof import("@redis/time-series/dist/commands/DECRBY");
DEL: typeof import("@redis/time-series/dist/commands/DEL");
del: typeof import("@redis/time-series/dist/commands/DEL");
DELETERULE: typeof import("@redis/time-series/dist/commands/DELETERULE");
deleteRule: typeof import("@redis/time-series/dist/commands/DELETERULE");
GET: typeof import("@redis/time-series/dist/commands/GET");
get: typeof import("@redis/time-series/dist/commands/GET");
INCRBY: typeof import("@redis/time-series/dist/commands/INCRBY");
incrBy: typeof import("@redis/time-series/dist/commands/INCRBY");
INFO_DEBUG: typeof import("@redis/time-series/dist/commands/INFO_DEBUG");
infoDebug: typeof import("@redis/time-series/dist/commands/INFO_DEBUG");
INFO: typeof import("@redis/time-series/dist/commands/INFO");
info: typeof import("@redis/time-series/dist/commands/INFO");
MADD: typeof import("@redis/time-series/dist/commands/MADD");
mAdd: typeof import("@redis/time-series/dist/commands/MADD");
MGET: typeof import("@redis/time-series/dist/commands/MGET");
mGet: typeof import("@redis/time-series/dist/commands/MGET");
MGET_WITHLABELS: typeof import("@redis/time-series/dist/commands/MGET_WITHLABELS");
mGetWithLabels: typeof import("@redis/time-series/dist/commands/MGET_WITHLABELS");
QUERYINDEX: typeof import("@redis/time-series/dist/commands/QUERYINDEX");
queryIndex: typeof import("@redis/time-series/dist/commands/QUERYINDEX");
RANGE: typeof import("@redis/time-series/dist/commands/RANGE");
range: typeof import("@redis/time-series/dist/commands/RANGE");
REVRANGE: typeof import("@redis/time-series/dist/commands/REVRANGE");
revRange: typeof import("@redis/time-series/dist/commands/REVRANGE");
MRANGE: typeof import("@redis/time-series/dist/commands/MRANGE");
mRange: typeof import("@redis/time-series/dist/commands/MRANGE");
MRANGE_WITHLABELS: typeof import("@redis/time-series/dist/commands/MRANGE_WITHLABELS");
mRangeWithLabels: typeof import("@redis/time-series/dist/commands/MRANGE_WITHLABELS");
MREVRANGE: typeof import("@redis/time-series/dist/commands/MREVRANGE");
mRevRange: typeof import("@redis/time-series/dist/commands/MREVRANGE");
MREVRANGE_WITHLABELS: typeof import("@redis/time-series/dist/commands/MREVRANGE_WITHLABELS");
mRevRangeWithLabels: typeof import("@redis/time-series/dist/commands/MREVRANGE_WITHLABELS");
};
bf: {
ADD: typeof import("@redis/bloom/dist/commands/bloom/ADD");
add: typeof import("@redis/bloom/dist/commands/bloom/ADD");
CARD: typeof import("@redis/bloom/dist/commands/bloom/CARD");
card: typeof import("@redis/bloom/dist/commands/bloom/CARD");
EXISTS: typeof import("@redis/bloom/dist/commands/bloom/EXISTS");
exists: typeof import("@redis/bloom/dist/commands/bloom/EXISTS");
INFO: typeof import("@redis/bloom/dist/commands/bloom/INFO");
info: typeof import("@redis/bloom/dist/commands/bloom/INFO");
INSERT: typeof import("@redis/bloom/dist/commands/bloom/INSERT");
insert: typeof import("@redis/bloom/dist/commands/bloom/INSERT");
LOADCHUNK: typeof import("@redis/bloom/dist/commands/bloom/LOADCHUNK");
loadChunk: typeof import("@redis/bloom/dist/commands/bloom/LOADCHUNK");
MADD: typeof import("@redis/bloom/dist/commands/bloom/MADD");
mAdd: typeof import("@redis/bloom/dist/commands/bloom/MADD");
MEXISTS: typeof import("@redis/bloom/dist/commands/bloom/MEXISTS");
mExists: typeof import("@redis/bloom/dist/commands/bloom/MEXISTS");
RESERVE: typeof import("@redis/bloom/dist/commands/bloom/RESERVE");
reserve: typeof import("@redis/bloom/dist/commands/bloom/RESERVE");
SCANDUMP: typeof import("@redis/bloom/dist/commands/bloom/SCANDUMP");
scanDump: typeof import("@redis/bloom/dist/commands/bloom/SCANDUMP");
};
cms: {
INCRBY: typeof import("@redis/bloom/dist/commands/count-min-sketch/INCRBY");
incrBy: typeof import("@redis/bloom/dist/commands/count-min-sketch/INCRBY");
INFO: typeof import("@redis/bloom/dist/commands/count-min-sketch/INFO");
info: typeof import("@redis/bloom/dist/commands/count-min-sketch/INFO");
INITBYDIM: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYDIM");
initByDim: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYDIM");
INITBYPROB: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYPROB");
initByProb: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYPROB");
MERGE: typeof import("@redis/bloom/dist/commands/count-min-sketch/MERGE");
merge: typeof import("@redis/bloom/dist/commands/count-min-sketch/MERGE");
QUERY: typeof import("@redis/bloom/dist/commands/count-min-sketch/QUERY");
query: typeof import("@redis/bloom/dist/commands/count-min-sketch/QUERY");
};
cf: {
ADD: typeof import("@redis/bloom/dist/commands/cuckoo/ADD");
add: typeof import("@redis/bloom/dist/commands/cuckoo/ADD");
ADDNX: typeof import("@redis/bloom/dist/commands/cuckoo/ADDNX");
addNX: typeof import("@redis/bloom/dist/commands/cuckoo/ADDNX");
COUNT: typeof import("@redis/bloom/dist/commands/cuckoo/COUNT");
count: typeof import("@redis/bloom/dist/commands/cuckoo/COUNT");
DEL: typeof import("@redis/bloom/dist/commands/cuckoo/DEL");
del: typeof import("@redis/bloom/dist/commands/cuckoo/DEL");
EXISTS: typeof import("@redis/bloom/dist/commands/cuckoo/EXISTS");
exists: typeof import("@redis/bloom/dist/commands/cuckoo/EXISTS");
INFO: typeof import("@redis/bloom/dist/commands/cuckoo/INFO");
info: typeof import("@redis/bloom/dist/commands/cuckoo/INFO");
INSERT: typeof import("@redis/bloom/dist/commands/cuckoo/INSERT");
insert: typeof import("@redis/bloom/dist/commands/cuckoo/INSERT");
INSERTNX: typeof import("@redis/bloom/dist/commands/cuckoo/INSERTNX");
insertNX: typeof import("@redis/bloom/dist/commands/cuckoo/INSERTNX");
LOADCHUNK: typeof import("@redis/bloom/dist/commands/cuckoo/LOADCHUNK");
loadChunk: typeof import("@redis/bloom/dist/commands/cuckoo/LOADCHUNK");
RESERVE: typeof import("@redis/bloom/dist/commands/cuckoo/RESERVE");
reserve: typeof import("@redis/bloom/dist/commands/cuckoo/RESERVE");
SCANDUMP: typeof import("@redis/bloom/dist/commands/cuckoo/SCANDUMP");
scanDump: typeof import("@redis/bloom/dist/commands/cuckoo/SCANDUMP");
};
tDigest: {
ADD: typeof import("@redis/bloom/dist/commands/t-digest/ADD");
add: typeof import("@redis/bloom/dist/commands/t-digest/ADD");
BYRANK: typeof import("@redis/bloom/dist/commands/t-digest/BYRANK");
byRank: typeof import("@redis/bloom/dist/commands/t-digest/BYRANK");
BYREVRANK: typeof import("@redis/bloom/dist/commands/t-digest/BYREVRANK");
byRevRank: typeof import("@redis/bloom/dist/commands/t-digest/BYREVRANK");
CDF: typeof import("@redis/bloom/dist/commands/t-digest/CDF");
cdf: typeof import("@redis/bloom/dist/commands/t-digest/CDF");
CREATE: typeof import("@redis/bloom/dist/commands/t-digest/CREATE");
create: typeof import("@redis/bloom/dist/commands/t-digest/CREATE");
INFO: typeof import("@redis/bloom/dist/commands/t-digest/INFO");
info: typeof import("@redis/bloom/dist/commands/t-digest/INFO");
MAX: typeof import("@redis/bloom/dist/commands/t-digest/MAX");
max: typeof import("@redis/bloom/dist/commands/t-digest/MAX");
MERGE: typeof import("@redis/bloom/dist/commands/t-digest/MERGE");
merge: typeof import("@redis/bloom/dist/commands/t-digest/MERGE");
MIN: typeof import("@redis/bloom/dist/commands/t-digest/MIN");
min: typeof import("@redis/bloom/dist/commands/t-digest/MIN");
QUANTILE: typeof import("@redis/bloom/dist/commands/t-digest/QUANTILE");
quantile: typeof import("@redis/bloom/dist/commands/t-digest/QUANTILE");
RANK: typeof import("@redis/bloom/dist/commands/t-digest/RANK");
rank: typeof import("@redis/bloom/dist/commands/t-digest/RANK");
RESET: typeof import("@redis/bloom/dist/commands/t-digest/RESET");
reset: typeof import("@redis/bloom/dist/commands/t-digest/RESET");
REVRANK: typeof import("@redis/bloom/dist/commands/t-digest/REVRANK");
revRank: typeof import("@redis/bloom/dist/commands/t-digest/REVRANK");
TRIMMED_MEAN: typeof import("@redis/bloom/dist/commands/t-digest/TRIMMED_MEAN");
trimmedMean: typeof import("@redis/bloom/dist/commands/t-digest/TRIMMED_MEAN");
};
topK: {
ADD: typeof import("@redis/bloom/dist/commands/top-k/ADD");
add: typeof import("@redis/bloom/dist/commands/top-k/ADD");
COUNT: typeof import("@redis/bloom/dist/commands/top-k/COUNT");
count: typeof import("@redis/bloom/dist/commands/top-k/COUNT");
INCRBY: typeof import("@redis/bloom/dist/commands/top-k/INCRBY");
incrBy: typeof import("@redis/bloom/dist/commands/top-k/INCRBY");
INFO: typeof import("@redis/bloom/dist/commands/top-k/INFO");
info: typeof import("@redis/bloom/dist/commands/top-k/INFO");
LIST_WITHCOUNT: typeof import("@redis/bloom/dist/commands/top-k/LIST_WITHCOUNT");
listWithCount: typeof import("@redis/bloom/dist/commands/top-k/LIST_WITHCOUNT");
LIST: typeof import("@redis/bloom/dist/commands/top-k/LIST");
list: typeof import("@redis/bloom/dist/commands/top-k/LIST");
QUERY: typeof import("@redis/bloom/dist/commands/top-k/QUERY");
query: typeof import("@redis/bloom/dist/commands/top-k/QUERY");
RESERVE: typeof import("@redis/bloom/dist/commands/top-k/RESERVE");
reserve: typeof import("@redis/bloom/dist/commands/top-k/RESERVE");
};
} & import("redis").RedisModules, import("redis").RedisFunctions, import("redis").RedisScripts>;
export default redisClient;
//# sourceMappingURL=redis.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/config/redis.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FAEf,CAAC;AAeH,eAAe,WAAW,CAAC"}

View File

@@ -0,0 +1,23 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const redis_1 = require("redis");
const dotenv_1 = __importDefault(require("dotenv"));
dotenv_1.default.config();
const redisClient = (0, redis_1.createClient)({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
redisClient.on('connect', () => {
console.log('✅ Connected to Redis');
});
redisClient.on('error', (err) => {
console.error('❌ Redis connection error:', err);
});
// Connect to Redis
redisClient.connect().catch((err) => {
console.error('❌ Failed to connect to Redis:', err);
});
exports.default = redisClient;
//# sourceMappingURL=redis.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/config/redis.ts"],"names":[],"mappings":";;;;;AAAA,iCAAqC;AACrC,oDAA4B;AAE5B,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,WAAW,GAAG,IAAA,oBAAY,EAAC;IAC/B,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,wBAAwB;CACvD,CAAC,CAAC;AAEH,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IAC7B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;IACrC,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,mBAAmB;AACnB,WAAW,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACzC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,kBAAe,WAAW,CAAC"}

View File

@@ -0,0 +1,9 @@
import { User } from '../services/jwtKeyManager';
export { User } from '../services/jwtKeyManager';
export declare function generateToken(user: User): string;
export declare function verifyToken(token: string): User | null;
export declare function verifyGoogleToken(googleToken: string): Promise<any>;
export declare function getGoogleAuthUrl(): string;
export declare function exchangeCodeForTokens(code: string): Promise<any>;
export declare function getGoogleUserInfo(accessToken: string): Promise<any>;
//# sourceMappingURL=simpleAuth.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"simpleAuth.d.ts","sourceRoot":"","sources":["../../src/config/simpleAuth.ts"],"names":[],"mappings":"AAAA,OAAsB,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAKhE,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAEjD,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAEhD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtD;AAGD,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAWzE;AAGD,wBAAgB,gBAAgB,IAAI,MAAM,CAiCzC;AAGD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAgGtE;AAGD,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAsEzE"}

View File

@@ -0,0 +1,217 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.User = void 0;
exports.generateToken = generateToken;
exports.verifyToken = verifyToken;
exports.verifyGoogleToken = verifyGoogleToken;
exports.getGoogleAuthUrl = getGoogleAuthUrl;
exports.exchangeCodeForTokens = exchangeCodeForTokens;
exports.getGoogleUserInfo = getGoogleUserInfo;
const jwtKeyManager_1 = __importDefault(require("../services/jwtKeyManager"));
// JWT Key Manager now handles all token operations with automatic rotation
// No more static JWT_SECRET needed!
var jwtKeyManager_2 = require("../services/jwtKeyManager");
Object.defineProperty(exports, "User", { enumerable: true, get: function () { return jwtKeyManager_2.User; } });
function generateToken(user) {
return jwtKeyManager_1.default.generateToken(user);
}
function verifyToken(token) {
return jwtKeyManager_1.default.verifyToken(token);
}
// Simple Google OAuth2 client using fetch
async function verifyGoogleToken(googleToken) {
try {
const response = await fetch(`https://www.googleapis.com/oauth2/v1/userinfo?access_token=${googleToken}`);
if (!response.ok) {
throw new Error('Invalid Google token');
}
return await response.json();
}
catch (error) {
console.error('Error verifying Google token:', error);
return null;
}
}
// Get Google OAuth2 URL
function getGoogleAuthUrl() {
const clientId = process.env.GOOGLE_CLIENT_ID;
const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback';
console.log('🔗 Generating Google OAuth URL:', {
client_id_present: !!clientId,
redirect_uri: redirectUri,
environment: process.env.NODE_ENV || 'development'
});
if (!clientId) {
console.error('❌ GOOGLE_CLIENT_ID not configured');
throw new Error('GOOGLE_CLIENT_ID not configured');
}
if (!redirectUri.startsWith('http')) {
console.error('❌ Invalid redirect URI:', redirectUri);
throw new Error('GOOGLE_REDIRECT_URI must be a valid HTTP/HTTPS URL');
}
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: 'openid email profile',
access_type: 'offline',
prompt: 'consent'
});
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
console.log('✅ Google OAuth URL generated successfully');
return authUrl;
}
// Exchange authorization code for tokens
async function exchangeCodeForTokens(code) {
const clientId = process.env.GOOGLE_CLIENT_ID;
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback';
console.log('🔄 Exchanging OAuth code for tokens:', {
client_id_present: !!clientId,
client_secret_present: !!clientSecret,
redirect_uri: redirectUri,
code_length: code?.length || 0
});
if (!clientId || !clientSecret) {
console.error('❌ Google OAuth credentials not configured:', {
client_id: !!clientId,
client_secret: !!clientSecret
});
throw new Error('Google OAuth credentials not configured');
}
if (!code || code.length < 10) {
console.error('❌ Invalid authorization code:', { code_length: code?.length || 0 });
throw new Error('Invalid authorization code provided');
}
try {
const tokenUrl = 'https://oauth2.googleapis.com/token';
const requestBody = new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
});
console.log('📡 Making token exchange request to Google:', {
url: tokenUrl,
redirect_uri: redirectUri,
grant_type: 'authorization_code'
});
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
body: requestBody,
});
const responseText = await response.text();
console.log('📨 Token exchange response:', {
status: response.status,
ok: response.ok,
content_type: response.headers.get('content-type'),
response_length: responseText.length
});
if (!response.ok) {
console.error('❌ Token exchange failed:', {
status: response.status,
statusText: response.statusText,
response: responseText
});
throw new Error(`Failed to exchange code for tokens: ${response.status} ${response.statusText}`);
}
let tokenData;
try {
tokenData = JSON.parse(responseText);
}
catch (parseError) {
console.error('❌ Failed to parse token response:', { response: responseText });
throw new Error('Invalid JSON response from Google token endpoint');
}
if (!tokenData.access_token) {
console.error('❌ No access token in response:', tokenData);
throw new Error('No access token received from Google');
}
console.log('✅ Token exchange successful:', {
has_access_token: !!tokenData.access_token,
has_refresh_token: !!tokenData.refresh_token,
token_type: tokenData.token_type,
expires_in: tokenData.expires_in
});
return tokenData;
}
catch (error) {
console.error('❌ Error exchanging code for tokens:', {
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
throw error;
}
}
// Get user info from Google
async function getGoogleUserInfo(accessToken) {
console.log('👤 Getting user info from Google:', {
token_length: accessToken?.length || 0,
token_prefix: accessToken ? accessToken.substring(0, 10) + '...' : 'none'
});
if (!accessToken || accessToken.length < 10) {
console.error('❌ Invalid access token for user info request');
throw new Error('Invalid access token provided');
}
try {
const userInfoUrl = `https://www.googleapis.com/oauth2/v2/userinfo?access_token=${accessToken}`;
console.log('📡 Making user info request to Google');
const response = await fetch(userInfoUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${accessToken}`
}
});
const responseText = await response.text();
console.log('📨 User info response:', {
status: response.status,
ok: response.ok,
content_type: response.headers.get('content-type'),
response_length: responseText.length
});
if (!response.ok) {
console.error('❌ Failed to get user info:', {
status: response.status,
statusText: response.statusText,
response: responseText
});
throw new Error(`Failed to get user info: ${response.status} ${response.statusText}`);
}
let userData;
try {
userData = JSON.parse(responseText);
}
catch (parseError) {
console.error('❌ Failed to parse user info response:', { response: responseText });
throw new Error('Invalid JSON response from Google user info endpoint');
}
if (!userData.email) {
console.error('❌ No email in user info response:', userData);
throw new Error('No email address received from Google');
}
console.log('✅ User info retrieved successfully:', {
email: userData.email,
name: userData.name,
verified_email: userData.verified_email,
has_picture: !!userData.picture
});
return userData;
}
catch (error) {
console.error('❌ Error getting Google user info:', {
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
throw error;
}
}
//# sourceMappingURL=simpleAuth.js.map

File diff suppressed because one or more lines are too long