5 Commits
develop ... dev

Author SHA1 Message Date
john kevin asprec
c09d3723c3 feat: implement admin authentication services and tenant impersonation logic 2026-06-16 22:33:02 +08:00
kevin-asprec
5cdbd36999 feat: add RUN_SEED support and support_ticket_attachments migration
- New migration: create support_ticket_attachments table
- Dockerfile: RUN_SEED=true env var triggers seed after migrations
2026-04-15 06:05:28 +08:00
kevin-asprec
88148e6fbb fix: compile shared package as CommonJS for NestJS compatibility 2026-04-13 16:53:04 +08:00
kevin-asprec
4e69e7738b merge: develop into main 2026-04-13 13:04:45 +08:00
09d7f4b922 Initial commit 2026-04-13 00:41:03 +00:00
7 changed files with 35 additions and 8 deletions

View File

@@ -39,4 +39,4 @@ COPY --from=builder /app/dist ./dist
ENV NODE_ENV=production ENV NODE_ENV=production
EXPOSE 3004 EXPOSE 3004
ENTRYPOINT ["dumb-init", "--"] ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "cd packages/admin-db && npx prisma migrate deploy && cd /app && node dist/main.js"] CMD ["sh", "-c", "cd packages/admin-db && npx prisma migrate deploy && if [ \"$RUN_SEED\" = \"true\" ]; then echo 'Seeding admin database...' && npx tsx prisma/seed.ts; fi && cd /app && node dist/main.js"]

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
# fiberops-api-admin

View File

@@ -0,0 +1,24 @@
-- CreateTable: support_ticket_attachments
CREATE TABLE "support_ticket_attachments" (
"id" TEXT NOT NULL,
"ticketId" TEXT NOT NULL,
"commentId" TEXT,
"fileName" TEXT NOT NULL,
"originalName" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"sizeBytes" INTEGER NOT NULL,
"uploadedBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "support_ticket_attachments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "support_ticket_attachments_ticketId_idx" ON "support_ticket_attachments"("ticketId");
CREATE INDEX "support_ticket_attachments_commentId_idx" ON "support_ticket_attachments"("commentId");
-- AddForeignKey
ALTER TABLE "support_ticket_attachments" ADD CONSTRAINT "support_ticket_attachments_ticketId_fkey"
FOREIGN KEY ("ticketId") REFERENCES "support_tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "support_ticket_attachments" ADD CONSTRAINT "support_ticket_attachments_commentId_fkey"
FOREIGN KEY ("commentId") REFERENCES "support_ticket_comments"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -2,8 +2,8 @@
"name": "@fiberops/shared", "name": "@fiberops/shared",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"main": "./src/index.ts", "main": "./dist/index.js",
"types": "./src/index.ts", "types": "./dist/index.d.ts",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"lint": "tsc --noEmit", "lint": "tsc --noEmit",

View File

@@ -1,15 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"module": "ESNext", "module": "CommonJS",
"moduleResolution": "bundler", "moduleResolution": "node",
"lib": ["ES2022"], "lib": ["ES2022"],
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true,
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",

View File

@@ -6,6 +6,7 @@ import {
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { randomUUID } from 'crypto';
import { AdminPrismaService } from '../prisma/admin-prisma.service'; import { AdminPrismaService } from '../prisma/admin-prisma.service';
@Injectable() @Injectable()
@@ -119,6 +120,7 @@ export class AuthService {
sub: adminId, sub: adminId,
email, email,
type: 'super_admin', type: 'super_admin',
jti: randomUUID(),
}); });
const refreshSecret = this.config.get<string>( const refreshSecret = this.config.get<string>(
@@ -131,7 +133,7 @@ export class AuthService {
); );
const refreshToken = this.jwt.sign( const refreshToken = this.jwt.sign(
{ sub: adminId, email, type: 'super_admin' }, { sub: adminId, email, type: 'super_admin', jti: randomUUID() },
{ {
secret: refreshSecret, secret: refreshSecret,
expiresIn: refreshExpiresIn as any, expiresIn: refreshExpiresIn as any,

View File

@@ -15,7 +15,7 @@ export class ImpersonateService {
private readonly audit: AuditService, private readonly audit: AuditService,
) { ) {
// Use the MAIN API's JWT secret so the token works with the tenant API // Use the MAIN API's JWT secret so the token works with the tenant API
this.mainJwtSecret = this.config.get<string>('JWT_SECRET', 'dev-jwt-secret-not-for-production'); this.mainJwtSecret = this.config.get<string>('MAIN_API_JWT_SECRET', 'your-secret-key');
} }
async startImpersonation(tenantId: string, adminId: string, adminName: string) { async startImpersonation(tenantId: string, adminId: string, adminName: string) {