chore: untrack api/dist + tsbuildinfo build artifacts

api/dist/ (468 files, 11MB) and admin/tsconfig.tsbuildinfo were committed
before being added to .gitignore — the rule had no effect on the existing
tracked copies. Untrack them now so future Docker rebuilds stop showing
spurious diffs. Files stay on disk; rebuild regenerates everything.

Also add *.tsbuildinfo to .gitignore so future tsc incremental caches stay
out of git.
This commit is contained in:
bunker-admin 2026-04-16 16:54:55 -06:00
parent 3a528d9a49
commit 5082fe7b76
470 changed files with 3 additions and 50057 deletions

3
.gitignore vendored
View File

@ -70,6 +70,9 @@ core.*
# API compiled output (generated by tsc, baked into Docker images)
/api/dist/
# TypeScript incremental build cache (machine-specific)
*.tsbuildinfo
# Control Panel runtime data (managed deployments + backups)
/changemaker-control-panel/instances/
/changemaker-control-panel/backups/

File diff suppressed because one or more lines are too long

View File

@ -1,6 +0,0 @@
import { PrismaClient } from '@prisma/client';
declare const prisma: PrismaClient<{
log: ("error" | "query" | "warn")[];
}, "error" | "query" | "warn", import("@prisma/client/runtime/library").DefaultArgs>;
export { prisma };
//# sourceMappingURL=database.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/config/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,QAAA,MAAM,MAAM;;oFAEV,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC"}

View File

@ -1,10 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.prisma = void 0;
const client_1 = require("@prisma/client");
const env_1 = require("./env");
const prisma = new client_1.PrismaClient({
log: env_1.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
exports.prisma = prisma;
//# sourceMappingURL=database.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/config/database.ts"],"names":[],"mappings":";;;AAAA,2CAA8C;AAC9C,+BAA4B;AAE5B,MAAM,MAAM,GAAG,IAAI,qBAAY,CAAC;IAC9B,GAAG,EAAE,SAAG,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;CAC7E,CAAC,CAAC;AAEM,wBAAM"}

View File

@ -1,582 +0,0 @@
import { z } from 'zod';
declare const envSchema: z.ZodObject<{
NODE_ENV: z.ZodDefault<z.ZodEnum<["development", "production", "test"]>>;
PORT: z.ZodDefault<z.ZodNumber>;
API_URL: z.ZodDefault<z.ZodString>;
ADMIN_URL: z.ZodDefault<z.ZodString>;
DOMAIN: z.ZodDefault<z.ZodString>;
INSTANCE_LABEL: z.ZodDefault<z.ZodString>;
BUNKER_OPS_ENABLED: z.ZodDefault<z.ZodString>;
BUNKER_OPS_REMOTE_WRITE_URL: z.ZodDefault<z.ZodString>;
DATABASE_URL: z.ZodString;
REDIS_URL: z.ZodDefault<z.ZodString>;
JWT_ACCESS_SECRET: z.ZodString;
JWT_REFRESH_SECRET: z.ZodString;
JWT_INVITE_SECRET: z.ZodString;
JWT_ACCESS_EXPIRY: z.ZodDefault<z.ZodString>;
JWT_REFRESH_EXPIRY: z.ZodDefault<z.ZodString>;
ENCRYPTION_KEY: z.ZodOptional<z.ZodString>;
INITIAL_ADMIN_EMAIL: z.ZodDefault<z.ZodString>;
INITIAL_ADMIN_PASSWORD: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
SMTP_HOST: z.ZodDefault<z.ZodString>;
SMTP_PORT: z.ZodDefault<z.ZodNumber>;
SMTP_USER: z.ZodDefault<z.ZodString>;
SMTP_PASS: z.ZodDefault<z.ZodString>;
SMTP_FROM: z.ZodDefault<z.ZodString>;
SMTP_FROM_NAME: z.ZodDefault<z.ZodString>;
EMAIL_TEST_MODE: z.ZodDefault<z.ZodString>;
TEST_EMAIL_RECIPIENT: z.ZodDefault<z.ZodString>;
LISTMONK_URL: z.ZodDefault<z.ZodString>;
LISTMONK_ADMIN_USER: z.ZodDefault<z.ZodString>;
LISTMONK_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
LISTMONK_SYNC_ENABLED: z.ZodDefault<z.ZodString>;
LISTMONK_WEBHOOK_SECRET: z.ZodDefault<z.ZodString>;
LISTMONK_PROXY_PORT: z.ZodDefault<z.ZodNumber>;
REPRESENT_API_URL: z.ZodDefault<z.ZodString>;
CORS_ORIGINS: z.ZodDefault<z.ZodString>;
RATE_LIMIT_WINDOW_MS: z.ZodDefault<z.ZodNumber>;
RATE_LIMIT_MAX: z.ZodDefault<z.ZodNumber>;
MAPBOX_API_KEY: z.ZodOptional<z.ZodString>;
GEOCODING_RATE_LIMIT_MS: z.ZodDefault<z.ZodNumber>;
GEOCODING_CACHE_ENABLED: z.ZodDefault<z.ZodString>;
GEOCODING_CACHE_TTL_HOURS: z.ZodDefault<z.ZodNumber>;
GOOGLE_MAPS_API_KEY: z.ZodOptional<z.ZodString>;
GOOGLE_MAPS_ENABLED: z.ZodDefault<z.ZodString>;
GEOCODING_PARALLEL_ENABLED: z.ZodDefault<z.ZodString>;
GEOCODING_BATCH_SIZE: z.ZodDefault<z.ZodNumber>;
BULK_GEOCODE_ENABLED: z.ZodDefault<z.ZodString>;
BULK_GEOCODE_MAX_BATCH: z.ZodDefault<z.ZodNumber>;
NOCODB_URL: z.ZodDefault<z.ZodString>;
NOCODB_PORT: z.ZodDefault<z.ZodNumber>;
NOCODB_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
N8N_URL: z.ZodDefault<z.ZodString>;
N8N_PORT: z.ZodDefault<z.ZodNumber>;
N8N_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
GITEA_URL: z.ZodDefault<z.ZodString>;
GITEA_PORT: z.ZodDefault<z.ZodNumber>;
GITEA_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
MAILHOG_URL: z.ZodDefault<z.ZodString>;
MAILHOG_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
MINI_QR_URL: z.ZodDefault<z.ZodString>;
MINI_QR_PORT: z.ZodDefault<z.ZodNumber>;
MINI_QR_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
EXCALIDRAW_URL: z.ZodDefault<z.ZodString>;
EXCALIDRAW_PORT: z.ZodDefault<z.ZodNumber>;
EXCALIDRAW_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
HOMEPAGE_URL: z.ZodDefault<z.ZodString>;
HOMEPAGE_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
VAULTWARDEN_URL: z.ZodDefault<z.ZodString>;
VAULTWARDEN_ADMIN_TOKEN: z.ZodDefault<z.ZodString>;
VAULTWARDEN_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
ROCKETCHAT_URL: z.ZodDefault<z.ZodString>;
ROCKETCHAT_ADMIN_USER: z.ZodDefault<z.ZodString>;
ROCKETCHAT_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
ROCKETCHAT_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
ENABLE_CHAT: z.ZodDefault<z.ZodString>;
GANCIO_URL: z.ZodDefault<z.ZodString>;
GANCIO_PORT: z.ZodDefault<z.ZodNumber>;
GANCIO_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
GANCIO_ADMIN_USER: z.ZodDefault<z.ZodString>;
GANCIO_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
GANCIO_SYNC_ENABLED: z.ZodDefault<z.ZodString>;
ENABLE_MEET: z.ZodDefault<z.ZodString>;
JITSI_APP_ID: z.ZodDefault<z.ZodString>;
JITSI_APP_SECRET: z.ZodDefault<z.ZodString>;
JITSI_URL: z.ZodDefault<z.ZodString>;
JITSI_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
PANGOLIN_API_URL: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
PANGOLIN_API_KEY: z.ZodDefault<z.ZodString>;
PANGOLIN_ORG_ID: z.ZodDefault<z.ZodString>;
PANGOLIN_SITE_ID: z.ZodDefault<z.ZodString>;
PANGOLIN_ENDPOINT: z.ZodDefault<z.ZodString>;
PANGOLIN_NEWT_ID: z.ZodDefault<z.ZodString>;
PANGOLIN_NEWT_SECRET: z.ZodDefault<z.ZodString>;
NAR_DATA_DIR: z.ZodDefault<z.ZodString>;
OVERPASS_API_URL: z.ZodDefault<z.ZodString>;
OVERPASS_MIN_DELAY_MS: z.ZodDefault<z.ZodNumber>;
AREA_IMPORT_MAX_GRID_POINTS: z.ZodDefault<z.ZodNumber>;
ENABLE_PAYMENTS: z.ZodDefault<z.ZodString>;
ENABLE_MEDIA_FEATURES: z.ZodDefault<z.ZodString>;
MEDIA_API_PORT: z.ZodDefault<z.ZodNumber>;
MEDIA_API_PUBLIC_URL: z.ZodDefault<z.ZodString>;
MEDIA_ROOT: z.ZodDefault<z.ZodString>;
MEDIA_UPLOADS: z.ZodDefault<z.ZodString>;
MAX_UPLOAD_SIZE_GB: z.ZodDefault<z.ZodNumber>;
GITEA_REGISTRY: z.ZodDefault<z.ZodString>;
GITEA_REGISTRY_USER: z.ZodDefault<z.ZodString>;
GITEA_REGISTRY_PASS: z.ZodDefault<z.ZodString>;
GITEA_COMMENTS_ENABLED: z.ZodDefault<z.ZodString>;
GITEA_API_TOKEN: z.ZodDefault<z.ZodString>;
GITEA_COMMENTS_REPO_OWNER: z.ZodDefault<z.ZodString>;
GITEA_COMMENTS_REPO_NAME: z.ZodDefault<z.ZodString>;
GITEA_OAUTH_CLIENT_ID: z.ZodDefault<z.ZodString>;
GITEA_OAUTH_CLIENT_SECRET: z.ZodDefault<z.ZodString>;
ENABLE_SMS: z.ZodDefault<z.ZodString>;
TERMUX_API_URL: z.ZodDefault<z.ZodString>;
TERMUX_API_KEY: z.ZodDefault<z.ZodString>;
SMS_DELAY_BETWEEN_MS: z.ZodDefault<z.ZodNumber>;
SMS_MAX_RETRIES: z.ZodDefault<z.ZodNumber>;
SMS_RESPONSE_SYNC_INTERVAL_MS: z.ZodDefault<z.ZodNumber>;
SMS_DEVICE_MONITOR_INTERVAL_MS: z.ZodDefault<z.ZodNumber>;
CODE_SERVER_URL: z.ZodDefault<z.ZodString>;
CODE_SERVER_PORT: z.ZodDefault<z.ZodNumber>;
MKDOCS_PREVIEW_URL: z.ZodDefault<z.ZodString>;
MKDOCS_PORT: z.ZodDefault<z.ZodNumber>;
MKDOCS_DOCS_PATH: z.ZodDefault<z.ZodString>;
MKDOCS_CONFIG_PATH: z.ZodDefault<z.ZodString>;
MKDOCS_CONTAINER_NAME: z.ZodDefault<z.ZodString>;
MKDOCS_SITE_SERVER_URL: z.ZodDefault<z.ZodString>;
MKDOCS_SITE_SERVER_PORT: z.ZodDefault<z.ZodNumber>;
PROMETHEUS_URL: z.ZodDefault<z.ZodString>;
PROMETHEUS_PORT: z.ZodDefault<z.ZodNumber>;
GRAFANA_URL: z.ZodDefault<z.ZodString>;
GRAFANA_PORT: z.ZodDefault<z.ZodNumber>;
GRAFANA_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
ALERTMANAGER_URL: z.ZodDefault<z.ZodString>;
ALERTMANAGER_PORT: z.ZodDefault<z.ZodNumber>;
ALERTMANAGER_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
CADVISOR_URL: z.ZodDefault<z.ZodString>;
CADVISOR_PORT: z.ZodDefault<z.ZodNumber>;
NODE_EXPORTER_URL: z.ZodDefault<z.ZodString>;
NODE_EXPORTER_PORT: z.ZodDefault<z.ZodNumber>;
REDIS_EXPORTER_URL: z.ZodDefault<z.ZodString>;
REDIS_EXPORTER_PORT: z.ZodDefault<z.ZodNumber>;
GOTIFY_URL: z.ZodDefault<z.ZodString>;
GOTIFY_PORT: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
NODE_ENV: "development" | "production" | "test";
PORT: number;
API_URL: string;
ADMIN_URL: string;
DOMAIN: string;
INSTANCE_LABEL: string;
BUNKER_OPS_ENABLED: string;
BUNKER_OPS_REMOTE_WRITE_URL: string;
DATABASE_URL: string;
REDIS_URL: string;
JWT_ACCESS_SECRET: string;
JWT_REFRESH_SECRET: string;
JWT_INVITE_SECRET: string;
JWT_ACCESS_EXPIRY: string;
JWT_REFRESH_EXPIRY: string;
INITIAL_ADMIN_EMAIL: string;
INITIAL_ADMIN_PASSWORD: string;
SMTP_HOST: string;
SMTP_PORT: number;
SMTP_USER: string;
SMTP_PASS: string;
SMTP_FROM: string;
SMTP_FROM_NAME: string;
EMAIL_TEST_MODE: string;
TEST_EMAIL_RECIPIENT: string;
LISTMONK_URL: string;
LISTMONK_ADMIN_USER: string;
LISTMONK_ADMIN_PASSWORD: string;
LISTMONK_SYNC_ENABLED: string;
LISTMONK_WEBHOOK_SECRET: string;
LISTMONK_PROXY_PORT: number;
REPRESENT_API_URL: string;
CORS_ORIGINS: string;
RATE_LIMIT_WINDOW_MS: number;
RATE_LIMIT_MAX: number;
GEOCODING_RATE_LIMIT_MS: number;
GEOCODING_CACHE_ENABLED: string;
GEOCODING_CACHE_TTL_HOURS: number;
GOOGLE_MAPS_ENABLED: string;
GEOCODING_PARALLEL_ENABLED: string;
GEOCODING_BATCH_SIZE: number;
BULK_GEOCODE_ENABLED: string;
BULK_GEOCODE_MAX_BATCH: number;
NOCODB_URL: string;
NOCODB_PORT: number;
NOCODB_EMBED_PORT: number;
N8N_URL: string;
N8N_PORT: number;
N8N_EMBED_PORT: number;
GITEA_URL: string;
GITEA_PORT: number;
GITEA_EMBED_PORT: number;
MAILHOG_URL: string;
MAILHOG_EMBED_PORT: number;
MINI_QR_URL: string;
MINI_QR_PORT: number;
MINI_QR_EMBED_PORT: number;
EXCALIDRAW_URL: string;
EXCALIDRAW_PORT: number;
EXCALIDRAW_EMBED_PORT: number;
HOMEPAGE_URL: string;
HOMEPAGE_EMBED_PORT: number;
VAULTWARDEN_URL: string;
VAULTWARDEN_ADMIN_TOKEN: string;
VAULTWARDEN_EMBED_PORT: number;
ROCKETCHAT_URL: string;
ROCKETCHAT_ADMIN_USER: string;
ROCKETCHAT_ADMIN_PASSWORD: string;
ROCKETCHAT_EMBED_PORT: number;
ENABLE_CHAT: string;
GANCIO_URL: string;
GANCIO_PORT: number;
GANCIO_EMBED_PORT: number;
GANCIO_ADMIN_USER: string;
GANCIO_ADMIN_PASSWORD: string;
GANCIO_SYNC_ENABLED: string;
ENABLE_MEET: string;
JITSI_APP_ID: string;
JITSI_APP_SECRET: string;
JITSI_URL: string;
JITSI_EMBED_PORT: number;
PANGOLIN_API_URL: string;
PANGOLIN_API_KEY: string;
PANGOLIN_ORG_ID: string;
PANGOLIN_SITE_ID: string;
PANGOLIN_ENDPOINT: string;
PANGOLIN_NEWT_ID: string;
PANGOLIN_NEWT_SECRET: string;
NAR_DATA_DIR: string;
OVERPASS_API_URL: string;
OVERPASS_MIN_DELAY_MS: number;
AREA_IMPORT_MAX_GRID_POINTS: number;
ENABLE_PAYMENTS: string;
ENABLE_MEDIA_FEATURES: string;
MEDIA_API_PORT: number;
MEDIA_API_PUBLIC_URL: string;
MEDIA_ROOT: string;
MEDIA_UPLOADS: string;
MAX_UPLOAD_SIZE_GB: number;
GITEA_REGISTRY: string;
GITEA_REGISTRY_USER: string;
GITEA_REGISTRY_PASS: string;
GITEA_COMMENTS_ENABLED: string;
GITEA_API_TOKEN: string;
GITEA_COMMENTS_REPO_OWNER: string;
GITEA_COMMENTS_REPO_NAME: string;
GITEA_OAUTH_CLIENT_ID: string;
GITEA_OAUTH_CLIENT_SECRET: string;
ENABLE_SMS: string;
TERMUX_API_URL: string;
TERMUX_API_KEY: string;
SMS_DELAY_BETWEEN_MS: number;
SMS_MAX_RETRIES: number;
SMS_RESPONSE_SYNC_INTERVAL_MS: number;
SMS_DEVICE_MONITOR_INTERVAL_MS: number;
CODE_SERVER_URL: string;
CODE_SERVER_PORT: number;
MKDOCS_PREVIEW_URL: string;
MKDOCS_PORT: number;
MKDOCS_DOCS_PATH: string;
MKDOCS_CONFIG_PATH: string;
MKDOCS_CONTAINER_NAME: string;
MKDOCS_SITE_SERVER_URL: string;
MKDOCS_SITE_SERVER_PORT: number;
PROMETHEUS_URL: string;
PROMETHEUS_PORT: number;
GRAFANA_URL: string;
GRAFANA_PORT: number;
GRAFANA_EMBED_PORT: number;
ALERTMANAGER_URL: string;
ALERTMANAGER_PORT: number;
ALERTMANAGER_EMBED_PORT: number;
CADVISOR_URL: string;
CADVISOR_PORT: number;
NODE_EXPORTER_URL: string;
NODE_EXPORTER_PORT: number;
REDIS_EXPORTER_URL: string;
REDIS_EXPORTER_PORT: number;
GOTIFY_URL: string;
GOTIFY_PORT: number;
ENCRYPTION_KEY?: string | undefined;
MAPBOX_API_KEY?: string | undefined;
GOOGLE_MAPS_API_KEY?: string | undefined;
}, {
DATABASE_URL: string;
JWT_ACCESS_SECRET: string;
JWT_REFRESH_SECRET: string;
JWT_INVITE_SECRET: string;
NODE_ENV?: "development" | "production" | "test" | undefined;
PORT?: number | undefined;
API_URL?: string | undefined;
ADMIN_URL?: string | undefined;
DOMAIN?: string | undefined;
INSTANCE_LABEL?: string | undefined;
BUNKER_OPS_ENABLED?: string | undefined;
BUNKER_OPS_REMOTE_WRITE_URL?: string | undefined;
REDIS_URL?: string | undefined;
JWT_ACCESS_EXPIRY?: string | undefined;
JWT_REFRESH_EXPIRY?: string | undefined;
ENCRYPTION_KEY?: string | undefined;
INITIAL_ADMIN_EMAIL?: string | undefined;
INITIAL_ADMIN_PASSWORD?: string | undefined;
SMTP_HOST?: string | undefined;
SMTP_PORT?: number | undefined;
SMTP_USER?: string | undefined;
SMTP_PASS?: string | undefined;
SMTP_FROM?: string | undefined;
SMTP_FROM_NAME?: string | undefined;
EMAIL_TEST_MODE?: string | undefined;
TEST_EMAIL_RECIPIENT?: string | undefined;
LISTMONK_URL?: string | undefined;
LISTMONK_ADMIN_USER?: string | undefined;
LISTMONK_ADMIN_PASSWORD?: string | undefined;
LISTMONK_SYNC_ENABLED?: string | undefined;
LISTMONK_WEBHOOK_SECRET?: string | undefined;
LISTMONK_PROXY_PORT?: number | undefined;
REPRESENT_API_URL?: string | undefined;
CORS_ORIGINS?: string | undefined;
RATE_LIMIT_WINDOW_MS?: number | undefined;
RATE_LIMIT_MAX?: number | undefined;
MAPBOX_API_KEY?: string | undefined;
GEOCODING_RATE_LIMIT_MS?: number | undefined;
GEOCODING_CACHE_ENABLED?: string | undefined;
GEOCODING_CACHE_TTL_HOURS?: number | undefined;
GOOGLE_MAPS_API_KEY?: string | undefined;
GOOGLE_MAPS_ENABLED?: string | undefined;
GEOCODING_PARALLEL_ENABLED?: string | undefined;
GEOCODING_BATCH_SIZE?: number | undefined;
BULK_GEOCODE_ENABLED?: string | undefined;
BULK_GEOCODE_MAX_BATCH?: number | undefined;
NOCODB_URL?: string | undefined;
NOCODB_PORT?: number | undefined;
NOCODB_EMBED_PORT?: number | undefined;
N8N_URL?: string | undefined;
N8N_PORT?: number | undefined;
N8N_EMBED_PORT?: number | undefined;
GITEA_URL?: string | undefined;
GITEA_PORT?: number | undefined;
GITEA_EMBED_PORT?: number | undefined;
MAILHOG_URL?: string | undefined;
MAILHOG_EMBED_PORT?: number | undefined;
MINI_QR_URL?: string | undefined;
MINI_QR_PORT?: number | undefined;
MINI_QR_EMBED_PORT?: number | undefined;
EXCALIDRAW_URL?: string | undefined;
EXCALIDRAW_PORT?: number | undefined;
EXCALIDRAW_EMBED_PORT?: number | undefined;
HOMEPAGE_URL?: string | undefined;
HOMEPAGE_EMBED_PORT?: number | undefined;
VAULTWARDEN_URL?: string | undefined;
VAULTWARDEN_ADMIN_TOKEN?: string | undefined;
VAULTWARDEN_EMBED_PORT?: number | undefined;
ROCKETCHAT_URL?: string | undefined;
ROCKETCHAT_ADMIN_USER?: string | undefined;
ROCKETCHAT_ADMIN_PASSWORD?: string | undefined;
ROCKETCHAT_EMBED_PORT?: number | undefined;
ENABLE_CHAT?: string | undefined;
GANCIO_URL?: string | undefined;
GANCIO_PORT?: number | undefined;
GANCIO_EMBED_PORT?: number | undefined;
GANCIO_ADMIN_USER?: string | undefined;
GANCIO_ADMIN_PASSWORD?: string | undefined;
GANCIO_SYNC_ENABLED?: string | undefined;
ENABLE_MEET?: string | undefined;
JITSI_APP_ID?: string | undefined;
JITSI_APP_SECRET?: string | undefined;
JITSI_URL?: string | undefined;
JITSI_EMBED_PORT?: number | undefined;
PANGOLIN_API_URL?: string | undefined;
PANGOLIN_API_KEY?: string | undefined;
PANGOLIN_ORG_ID?: string | undefined;
PANGOLIN_SITE_ID?: string | undefined;
PANGOLIN_ENDPOINT?: string | undefined;
PANGOLIN_NEWT_ID?: string | undefined;
PANGOLIN_NEWT_SECRET?: string | undefined;
NAR_DATA_DIR?: string | undefined;
OVERPASS_API_URL?: string | undefined;
OVERPASS_MIN_DELAY_MS?: number | undefined;
AREA_IMPORT_MAX_GRID_POINTS?: number | undefined;
ENABLE_PAYMENTS?: string | undefined;
ENABLE_MEDIA_FEATURES?: string | undefined;
MEDIA_API_PORT?: number | undefined;
MEDIA_API_PUBLIC_URL?: string | undefined;
MEDIA_ROOT?: string | undefined;
MEDIA_UPLOADS?: string | undefined;
MAX_UPLOAD_SIZE_GB?: number | undefined;
GITEA_REGISTRY?: string | undefined;
GITEA_REGISTRY_USER?: string | undefined;
GITEA_REGISTRY_PASS?: string | undefined;
GITEA_COMMENTS_ENABLED?: string | undefined;
GITEA_API_TOKEN?: string | undefined;
GITEA_COMMENTS_REPO_OWNER?: string | undefined;
GITEA_COMMENTS_REPO_NAME?: string | undefined;
GITEA_OAUTH_CLIENT_ID?: string | undefined;
GITEA_OAUTH_CLIENT_SECRET?: string | undefined;
ENABLE_SMS?: string | undefined;
TERMUX_API_URL?: string | undefined;
TERMUX_API_KEY?: string | undefined;
SMS_DELAY_BETWEEN_MS?: number | undefined;
SMS_MAX_RETRIES?: number | undefined;
SMS_RESPONSE_SYNC_INTERVAL_MS?: number | undefined;
SMS_DEVICE_MONITOR_INTERVAL_MS?: number | undefined;
CODE_SERVER_URL?: string | undefined;
CODE_SERVER_PORT?: number | undefined;
MKDOCS_PREVIEW_URL?: string | undefined;
MKDOCS_PORT?: number | undefined;
MKDOCS_DOCS_PATH?: string | undefined;
MKDOCS_CONFIG_PATH?: string | undefined;
MKDOCS_CONTAINER_NAME?: string | undefined;
MKDOCS_SITE_SERVER_URL?: string | undefined;
MKDOCS_SITE_SERVER_PORT?: number | undefined;
PROMETHEUS_URL?: string | undefined;
PROMETHEUS_PORT?: number | undefined;
GRAFANA_URL?: string | undefined;
GRAFANA_PORT?: number | undefined;
GRAFANA_EMBED_PORT?: number | undefined;
ALERTMANAGER_URL?: string | undefined;
ALERTMANAGER_PORT?: number | undefined;
ALERTMANAGER_EMBED_PORT?: number | undefined;
CADVISOR_URL?: string | undefined;
CADVISOR_PORT?: number | undefined;
NODE_EXPORTER_URL?: string | undefined;
NODE_EXPORTER_PORT?: number | undefined;
REDIS_EXPORTER_URL?: string | undefined;
REDIS_EXPORTER_PORT?: number | undefined;
GOTIFY_URL?: string | undefined;
GOTIFY_PORT?: number | undefined;
}>;
export type Env = z.infer<typeof envSchema>;
export declare const env: {
NODE_ENV: "development" | "production" | "test";
PORT: number;
API_URL: string;
ADMIN_URL: string;
DOMAIN: string;
INSTANCE_LABEL: string;
BUNKER_OPS_ENABLED: string;
BUNKER_OPS_REMOTE_WRITE_URL: string;
DATABASE_URL: string;
REDIS_URL: string;
JWT_ACCESS_SECRET: string;
JWT_REFRESH_SECRET: string;
JWT_INVITE_SECRET: string;
JWT_ACCESS_EXPIRY: string;
JWT_REFRESH_EXPIRY: string;
INITIAL_ADMIN_EMAIL: string;
INITIAL_ADMIN_PASSWORD: string;
SMTP_HOST: string;
SMTP_PORT: number;
SMTP_USER: string;
SMTP_PASS: string;
SMTP_FROM: string;
SMTP_FROM_NAME: string;
EMAIL_TEST_MODE: string;
TEST_EMAIL_RECIPIENT: string;
LISTMONK_URL: string;
LISTMONK_ADMIN_USER: string;
LISTMONK_ADMIN_PASSWORD: string;
LISTMONK_SYNC_ENABLED: string;
LISTMONK_WEBHOOK_SECRET: string;
LISTMONK_PROXY_PORT: number;
REPRESENT_API_URL: string;
CORS_ORIGINS: string;
RATE_LIMIT_WINDOW_MS: number;
RATE_LIMIT_MAX: number;
GEOCODING_RATE_LIMIT_MS: number;
GEOCODING_CACHE_ENABLED: string;
GEOCODING_CACHE_TTL_HOURS: number;
GOOGLE_MAPS_ENABLED: string;
GEOCODING_PARALLEL_ENABLED: string;
GEOCODING_BATCH_SIZE: number;
BULK_GEOCODE_ENABLED: string;
BULK_GEOCODE_MAX_BATCH: number;
NOCODB_URL: string;
NOCODB_PORT: number;
NOCODB_EMBED_PORT: number;
N8N_URL: string;
N8N_PORT: number;
N8N_EMBED_PORT: number;
GITEA_URL: string;
GITEA_PORT: number;
GITEA_EMBED_PORT: number;
MAILHOG_URL: string;
MAILHOG_EMBED_PORT: number;
MINI_QR_URL: string;
MINI_QR_PORT: number;
MINI_QR_EMBED_PORT: number;
EXCALIDRAW_URL: string;
EXCALIDRAW_PORT: number;
EXCALIDRAW_EMBED_PORT: number;
HOMEPAGE_URL: string;
HOMEPAGE_EMBED_PORT: number;
VAULTWARDEN_URL: string;
VAULTWARDEN_ADMIN_TOKEN: string;
VAULTWARDEN_EMBED_PORT: number;
ROCKETCHAT_URL: string;
ROCKETCHAT_ADMIN_USER: string;
ROCKETCHAT_ADMIN_PASSWORD: string;
ROCKETCHAT_EMBED_PORT: number;
ENABLE_CHAT: string;
GANCIO_URL: string;
GANCIO_PORT: number;
GANCIO_EMBED_PORT: number;
GANCIO_ADMIN_USER: string;
GANCIO_ADMIN_PASSWORD: string;
GANCIO_SYNC_ENABLED: string;
ENABLE_MEET: string;
JITSI_APP_ID: string;
JITSI_APP_SECRET: string;
JITSI_URL: string;
JITSI_EMBED_PORT: number;
PANGOLIN_API_URL: string;
PANGOLIN_API_KEY: string;
PANGOLIN_ORG_ID: string;
PANGOLIN_SITE_ID: string;
PANGOLIN_ENDPOINT: string;
PANGOLIN_NEWT_ID: string;
PANGOLIN_NEWT_SECRET: string;
NAR_DATA_DIR: string;
OVERPASS_API_URL: string;
OVERPASS_MIN_DELAY_MS: number;
AREA_IMPORT_MAX_GRID_POINTS: number;
ENABLE_PAYMENTS: string;
ENABLE_MEDIA_FEATURES: string;
MEDIA_API_PORT: number;
MEDIA_API_PUBLIC_URL: string;
MEDIA_ROOT: string;
MEDIA_UPLOADS: string;
MAX_UPLOAD_SIZE_GB: number;
GITEA_REGISTRY: string;
GITEA_REGISTRY_USER: string;
GITEA_REGISTRY_PASS: string;
GITEA_COMMENTS_ENABLED: string;
GITEA_API_TOKEN: string;
GITEA_COMMENTS_REPO_OWNER: string;
GITEA_COMMENTS_REPO_NAME: string;
GITEA_OAUTH_CLIENT_ID: string;
GITEA_OAUTH_CLIENT_SECRET: string;
ENABLE_SMS: string;
TERMUX_API_URL: string;
TERMUX_API_KEY: string;
SMS_DELAY_BETWEEN_MS: number;
SMS_MAX_RETRIES: number;
SMS_RESPONSE_SYNC_INTERVAL_MS: number;
SMS_DEVICE_MONITOR_INTERVAL_MS: number;
CODE_SERVER_URL: string;
CODE_SERVER_PORT: number;
MKDOCS_PREVIEW_URL: string;
MKDOCS_PORT: number;
MKDOCS_DOCS_PATH: string;
MKDOCS_CONFIG_PATH: string;
MKDOCS_CONTAINER_NAME: string;
MKDOCS_SITE_SERVER_URL: string;
MKDOCS_SITE_SERVER_PORT: number;
PROMETHEUS_URL: string;
PROMETHEUS_PORT: number;
GRAFANA_URL: string;
GRAFANA_PORT: number;
GRAFANA_EMBED_PORT: number;
ALERTMANAGER_URL: string;
ALERTMANAGER_PORT: number;
ALERTMANAGER_EMBED_PORT: number;
CADVISOR_URL: string;
CADVISOR_PORT: number;
NODE_EXPORTER_URL: string;
NODE_EXPORTER_PORT: number;
REDIS_EXPORTER_URL: string;
REDIS_EXPORTER_PORT: number;
GOTIFY_URL: string;
GOTIFY_PORT: number;
ENCRYPTION_KEY?: string | undefined;
MAPBOX_API_KEY?: string | undefined;
GOOGLE_MAPS_API_KEY?: string | undefined;
};
export {};
//# sourceMappingURL=env.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2Nb,CAAC;AAEH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAY5C,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC"}

202
api/dist/config/env.js vendored
View File

@ -1,202 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.env = void 0;
const dotenv_1 = __importDefault(require("dotenv"));
const zod_1 = require("zod");
dotenv_1.default.config();
const envSchema = zod_1.z.object({
// Server
NODE_ENV: zod_1.z.enum(['development', 'production', 'test']).default('development'),
PORT: zod_1.z.coerce.number().default(4000),
API_URL: zod_1.z.string().default('http://localhost:4000'),
ADMIN_URL: zod_1.z.string().default('http://localhost:3000'),
DOMAIN: zod_1.z.string().default('cmlite.org'),
// Bunker Ops (Fleet Management)
INSTANCE_LABEL: zod_1.z.string().default(''),
BUNKER_OPS_ENABLED: zod_1.z.string().default('false'),
BUNKER_OPS_REMOTE_WRITE_URL: zod_1.z.string().default(''),
// Database
DATABASE_URL: zod_1.z.string(),
// Redis
REDIS_URL: zod_1.z.string().default('redis://redis-changemaker:6379'),
// JWT
JWT_ACCESS_SECRET: zod_1.z.string().min(32),
JWT_REFRESH_SECRET: zod_1.z.string().min(32),
JWT_INVITE_SECRET: zod_1.z.string().min(32),
JWT_ACCESS_EXPIRY: zod_1.z.string().default('15m'),
JWT_REFRESH_EXPIRY: zod_1.z.string().default('7d'),
// Encryption (for DB-stored secrets like SMTP password; falls back to JWT_ACCESS_SECRET)
ENCRYPTION_KEY: zod_1.z.string().min(32, 'ENCRYPTION_KEY must be at least 32 characters').optional(),
// Initial Super Admin (auto-created during database seeding)
INITIAL_ADMIN_EMAIL: zod_1.z.string().email().default('admin@cmlite.org'),
INITIAL_ADMIN_PASSWORD: zod_1.z.string().min(12).default('REQUIRED_STRONG_PASSWORD_CHANGE_THIS')
.refine((val) => val !== 'REQUIRED_STRONG_PASSWORD_CHANGE_THIS', { message: 'INITIAL_ADMIN_PASSWORD must be changed from the default placeholder value' }),
// SMTP
SMTP_HOST: zod_1.z.string().default('mailhog-changemaker'),
SMTP_PORT: zod_1.z.coerce.number().default(1025),
SMTP_USER: zod_1.z.string().default(''),
SMTP_PASS: zod_1.z.string().default(''),
SMTP_FROM: zod_1.z.string().default('noreply@cmlite.org'),
SMTP_FROM_NAME: zod_1.z.string().default('Changemaker Lite'),
EMAIL_TEST_MODE: zod_1.z.string().default('true'),
TEST_EMAIL_RECIPIENT: zod_1.z.string().default('admin@cmlite.org'),
// Listmonk
LISTMONK_URL: zod_1.z.string().default('http://listmonk-app:9000'),
LISTMONK_ADMIN_USER: zod_1.z.string().default('admin'),
LISTMONK_ADMIN_PASSWORD: zod_1.z.string().default(''),
LISTMONK_SYNC_ENABLED: zod_1.z.string().default('false'),
LISTMONK_WEBHOOK_SECRET: zod_1.z.string().default(''),
LISTMONK_PROXY_PORT: zod_1.z.coerce.number().default(9002),
// Represent API (Canadian electoral data)
REPRESENT_API_URL: zod_1.z.string().default('https://represent.opennorth.ca'),
// CORS
CORS_ORIGINS: zod_1.z.string().default('http://localhost:3000'),
// Rate Limiting
RATE_LIMIT_WINDOW_MS: zod_1.z.coerce.number().default(15 * 60 * 1000),
RATE_LIMIT_MAX: zod_1.z.coerce.number().default(500),
// Geocoding
MAPBOX_API_KEY: zod_1.z.string().optional(),
GEOCODING_RATE_LIMIT_MS: zod_1.z.coerce.number().default(1100),
GEOCODING_CACHE_ENABLED: zod_1.z.string().default('true'),
GEOCODING_CACHE_TTL_HOURS: zod_1.z.coerce.number().default(24),
// Phase 2: Performance & Accuracy
GOOGLE_MAPS_API_KEY: zod_1.z.string().optional(),
GOOGLE_MAPS_ENABLED: zod_1.z.string().default('false'),
GEOCODING_PARALLEL_ENABLED: zod_1.z.string().default('true'),
GEOCODING_BATCH_SIZE: zod_1.z.coerce.number().default(10),
// Bulk Re-Geocoding (Phase 3)
BULK_GEOCODE_ENABLED: zod_1.z.string().default('true'),
BULK_GEOCODE_MAX_BATCH: zod_1.z.coerce.number().default(5000),
// Platform Services (NocoDB, n8n, Gitea)
NOCODB_URL: zod_1.z.string().default('http://changemaker-v2-nocodb:8080'),
NOCODB_PORT: zod_1.z.coerce.number().default(8091),
NOCODB_EMBED_PORT: zod_1.z.coerce.number().default(8881),
N8N_URL: zod_1.z.string().default('http://n8n-changemaker:5678'),
N8N_PORT: zod_1.z.coerce.number().default(5678),
N8N_EMBED_PORT: zod_1.z.coerce.number().default(8882),
GITEA_URL: zod_1.z.string().default('http://gitea-changemaker:3000'),
GITEA_PORT: zod_1.z.coerce.number().default(3030),
GITEA_EMBED_PORT: zod_1.z.coerce.number().default(8883),
// MailHog (email testing UI)
MAILHOG_URL: zod_1.z.string().default('http://mailhog-changemaker:8025'),
MAILHOG_EMBED_PORT: zod_1.z.coerce.number().default(8884),
// Mini QR (QR code generator)
MINI_QR_URL: zod_1.z.string().default('http://mini-qr:8080'),
MINI_QR_PORT: zod_1.z.coerce.number().default(8089),
MINI_QR_EMBED_PORT: zod_1.z.coerce.number().default(8885),
// Excalidraw (collaborative whiteboard)
EXCALIDRAW_URL: zod_1.z.string().default('http://excalidraw-changemaker:80'),
EXCALIDRAW_PORT: zod_1.z.coerce.number().default(8090),
EXCALIDRAW_EMBED_PORT: zod_1.z.coerce.number().default(8886),
// Homepage (service dashboard)
HOMEPAGE_URL: zod_1.z.string().default('http://homepage-changemaker:3000'),
HOMEPAGE_EMBED_PORT: zod_1.z.coerce.number().default(8887),
// Vaultwarden (password manager)
VAULTWARDEN_URL: zod_1.z.string().default('http://vaultwarden-changemaker:80'),
VAULTWARDEN_ADMIN_TOKEN: zod_1.z.string().default(''),
VAULTWARDEN_EMBED_PORT: zod_1.z.coerce.number().default(8890),
// Rocket.Chat (team chat)
ROCKETCHAT_URL: zod_1.z.string().default('http://rocketchat-changemaker:3000'),
ROCKETCHAT_ADMIN_USER: zod_1.z.string().default(''),
ROCKETCHAT_ADMIN_PASSWORD: zod_1.z.string().default(''),
ROCKETCHAT_EMBED_PORT: zod_1.z.coerce.number().default(8891),
ENABLE_CHAT: zod_1.z.string().default('false'),
// Gancio (event management)
GANCIO_URL: zod_1.z.string().default('http://gancio-changemaker:13120'),
GANCIO_PORT: zod_1.z.coerce.number().default(8092),
GANCIO_EMBED_PORT: zod_1.z.coerce.number().default(8892),
GANCIO_ADMIN_USER: zod_1.z.string().default('admin'),
GANCIO_ADMIN_PASSWORD: zod_1.z.string().default(''),
GANCIO_SYNC_ENABLED: zod_1.z.string().default('false'),
// Jitsi Meet (video conferencing)
ENABLE_MEET: zod_1.z.string().default('false'),
JITSI_APP_ID: zod_1.z.string().default('changemaker'),
JITSI_APP_SECRET: zod_1.z.string().default(''),
JITSI_URL: zod_1.z.string().default('http://jitsi-web-changemaker:80'),
JITSI_EMBED_PORT: zod_1.z.coerce.number().default(8893),
// Pangolin (tunnel / reverse proxy)
PANGOLIN_API_URL: zod_1.z.string()
.default('')
.refine((url) => !url || url.startsWith('https://'), { message: 'PANGOLIN_API_URL must use HTTPS for secure credential transmission' }),
PANGOLIN_API_KEY: zod_1.z.string().default(''),
PANGOLIN_ORG_ID: zod_1.z.string().default(''),
PANGOLIN_SITE_ID: zod_1.z.string().default(''),
PANGOLIN_ENDPOINT: zod_1.z.string().default(''),
PANGOLIN_NEWT_ID: zod_1.z.string().default(''),
PANGOLIN_NEWT_SECRET: zod_1.z.string().default(''),
// NAR (National Address Register)
NAR_DATA_DIR: zod_1.z.string().default('/data'),
// Overpass / Area Import
OVERPASS_API_URL: zod_1.z.string().default('https://overpass-api.de/api/interpreter'),
OVERPASS_MIN_DELAY_MS: zod_1.z.coerce.number().default(30000),
AREA_IMPORT_MAX_GRID_POINTS: zod_1.z.coerce.number().default(500),
// Payments (Stripe)
ENABLE_PAYMENTS: zod_1.z.string().default('false'),
// Media Management
ENABLE_MEDIA_FEATURES: zod_1.z.string().default('false'),
MEDIA_API_PORT: zod_1.z.coerce.number().default(4100),
MEDIA_API_PUBLIC_URL: zod_1.z.string().default('http://media-api:4100'),
MEDIA_ROOT: zod_1.z.string().default('/media/library'),
MEDIA_UPLOADS: zod_1.z.string().default('/media/uploads'),
MAX_UPLOAD_SIZE_GB: zod_1.z.coerce.number().default(10),
// Container Registry
GITEA_REGISTRY: zod_1.z.string().default('gitea.bnkops.com/admin'),
GITEA_REGISTRY_USER: zod_1.z.string().default(''),
GITEA_REGISTRY_PASS: zod_1.z.string().default(''),
// Gitea Docs Comments
GITEA_COMMENTS_ENABLED: zod_1.z.string().default('false'),
GITEA_API_TOKEN: zod_1.z.string().default(''),
GITEA_COMMENTS_REPO_OWNER: zod_1.z.string().default(''),
GITEA_COMMENTS_REPO_NAME: zod_1.z.string().default('docs-comments'),
GITEA_OAUTH_CLIENT_ID: zod_1.z.string().default(''),
GITEA_OAUTH_CLIENT_SECRET: zod_1.z.string().default(''),
// SMS Campaigns (Termux Android bridge)
ENABLE_SMS: zod_1.z.string().default('false'),
TERMUX_API_URL: zod_1.z.string().default('http://10.0.0.193:5001'),
TERMUX_API_KEY: zod_1.z.string().default(''),
SMS_DELAY_BETWEEN_MS: zod_1.z.coerce.number().default(3000),
SMS_MAX_RETRIES: zod_1.z.coerce.number().default(3),
SMS_RESPONSE_SYNC_INTERVAL_MS: zod_1.z.coerce.number().default(30000),
SMS_DEVICE_MONITOR_INTERVAL_MS: zod_1.z.coerce.number().default(30000),
// Docs / Code Server
CODE_SERVER_URL: zod_1.z.string().default('http://code-server-changemaker:8080'),
CODE_SERVER_PORT: zod_1.z.coerce.number().default(8888),
MKDOCS_PREVIEW_URL: zod_1.z.string().default('http://mkdocs-changemaker:8000'),
MKDOCS_PORT: zod_1.z.coerce.number().default(4003),
MKDOCS_DOCS_PATH: zod_1.z.string().default('/mkdocs/docs'),
MKDOCS_CONFIG_PATH: zod_1.z.string().default('/mkdocs/mkdocs.yml'),
MKDOCS_CONTAINER_NAME: zod_1.z.string().default('mkdocs-changemaker'),
MKDOCS_SITE_SERVER_URL: zod_1.z.string().default('http://mkdocs-site-server-changemaker:80'),
MKDOCS_SITE_SERVER_PORT: zod_1.z.coerce.number().default(4004),
// Monitoring Services (behind 'monitoring' profile)
PROMETHEUS_URL: zod_1.z.string().default('http://prometheus-changemaker:9090'),
PROMETHEUS_PORT: zod_1.z.coerce.number().default(9090),
GRAFANA_URL: zod_1.z.string().default('http://grafana-changemaker:3000'),
GRAFANA_PORT: zod_1.z.coerce.number().default(3005),
GRAFANA_EMBED_PORT: zod_1.z.coerce.number().default(8894),
ALERTMANAGER_URL: zod_1.z.string().default('http://alertmanager-changemaker:9093'),
ALERTMANAGER_PORT: zod_1.z.coerce.number().default(9093),
ALERTMANAGER_EMBED_PORT: zod_1.z.coerce.number().default(8895),
CADVISOR_URL: zod_1.z.string().default('http://cadvisor-changemaker:8080'),
CADVISOR_PORT: zod_1.z.coerce.number().default(8086),
NODE_EXPORTER_URL: zod_1.z.string().default('http://node-exporter-changemaker:9100'),
NODE_EXPORTER_PORT: zod_1.z.coerce.number().default(9100),
REDIS_EXPORTER_URL: zod_1.z.string().default('http://redis-exporter-changemaker:9121'),
REDIS_EXPORTER_PORT: zod_1.z.coerce.number().default(9121),
GOTIFY_URL: zod_1.z.string().default('http://gotify-changemaker:80'),
GOTIFY_PORT: zod_1.z.coerce.number().default(8889),
});
function validateEnv() {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('Invalid environment variables:');
console.error(result.error.flatten().fieldErrors);
process.exit(1);
}
return result.data;
}
exports.env = validateEnv();
//# sourceMappingURL=env.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
import Redis from 'ioredis';
export declare const redis: Redis;
//# sourceMappingURL=redis.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/config/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,SAAS,CAAC;AAI5B,eAAO,MAAM,KAAK,OAOhB,CAAC"}

View File

@ -1,24 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.redis = void 0;
const ioredis_1 = __importDefault(require("ioredis"));
const env_1 = require("./env");
const logger_1 = require("../utils/logger");
exports.redis = new ioredis_1.default(env_1.env.REDIS_URL, {
maxRetriesPerRequest: null,
enableReadyCheck: true,
retryStrategy(times) {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
exports.redis.on('connect', () => {
logger_1.logger.info('Redis connected');
});
exports.redis.on('error', (err) => {
logger_1.logger.error('Redis connection error:', err);
});
//# sourceMappingURL=redis.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/config/redis.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA4B;AAC5B,+BAA4B;AAC5B,4CAAyC;AAE5B,QAAA,KAAK,GAAG,IAAI,iBAAK,CAAC,SAAG,CAAC,SAAS,EAAE;IAC5C,oBAAoB,EAAE,IAAI;IAC1B,gBAAgB,EAAE,IAAI;IACtB,aAAa,CAAC,KAAK;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;CACF,CAAC,CAAC;AAEH,aAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,eAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,aAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;IACxB,eAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC"}

View File

@ -1,2 +0,0 @@
export {};
//# sourceMappingURL=media-server.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"media-server.d.ts","sourceRoot":"","sources":["../src/media-server.ts"],"names":[],"mappings":""}

View File

@ -1,172 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fastify_1 = __importDefault(require("fastify"));
const cors_1 = __importDefault(require("@fastify/cors"));
const multipart_1 = __importDefault(require("@fastify/multipart"));
const env_1 = require("./config/env");
const logger_1 = require("./utils/logger");
const videos_routes_1 = require("./modules/media/routes/videos.routes");
const video_streaming_routes_1 = require("./modules/media/routes/video-streaming.routes");
const reactions_routes_1 = require("./modules/media/routes/reactions.routes");
const public_routes_1 = require("./modules/media/routes/public.routes");
const chat_stream_routes_1 = require("./modules/media/routes/chat-stream.routes");
const comments_routes_1 = require("./modules/media/routes/comments.routes");
const upload_routes_1 = require("./modules/media/routes/upload.routes");
const video_actions_routes_1 = require("./modules/media/routes/video-actions.routes");
const video_schedule_routes_1 = require("./modules/media/routes/video-schedule.routes");
const video_tracking_routes_1 = require("./modules/media/routes/video-tracking.routes");
const comment_admin_routes_1 = require("./modules/media/routes/comment-admin.routes");
const chat_notifications_routes_1 = require("./modules/media/routes/chat-notifications.routes");
const chat_threads_routes_1 = require("./modules/media/routes/chat-threads.routes");
const user_profile_routes_1 = require("./modules/media/routes/user-profile.routes");
const shorts_routes_1 = require("./modules/media/routes/shorts.routes");
const upvote_routes_1 = require("./modules/media/routes/upvote.routes");
const video_schedule_queue_service_1 = require("./services/video-schedule-queue.service");
const video_fetch_queue_service_1 = require("./services/video-fetch-queue.service");
const fetch_routes_1 = require("./modules/media/routes/fetch.routes");
const playlists_public_routes_1 = require("./modules/media/routes/playlists-public.routes");
const playlists_user_routes_1 = require("./modules/media/routes/playlists-user.routes");
const playlists_admin_routes_1 = require("./modules/media/routes/playlists-admin.routes");
const photos_routes_1 = require("./modules/media/routes/photos.routes");
const photo_upload_routes_1 = require("./modules/media/routes/photo-upload.routes");
const photo_albums_routes_1 = require("./modules/media/routes/photo-albums.routes");
const photos_public_routes_1 = require("./modules/media/routes/photos-public.routes");
const photo_engagement_routes_1 = require("./modules/media/routes/photo-engagement.routes");
// Add BigInt serialization support for Prisma BigInt fields
// This converts BigInt values to strings when JSON.stringify() is called
BigInt.prototype.toJSON = function () {
return this.toString();
};
const fastify = (0, fastify_1.default)({
logger: {
level: env_1.env.NODE_ENV === 'production' ? 'info' : 'debug',
},
maxParamLength: 500,
trustProxy: true,
});
// Graceful shutdown handler
process.on('SIGTERM', async () => {
logger_1.logger.info('SIGTERM received, shutting down gracefully...');
await video_schedule_queue_service_1.videoScheduleQueueService.close();
await video_fetch_queue_service_1.videoFetchQueueService.close();
fastify.close(() => {
logger_1.logger.info('Media API server closed');
process.exit(0);
});
});
// Global error handlers
process.on('unhandledRejection', (reason, promise) => {
logger_1.logger.error('Unhandled Promise Rejection in Media API', { reason: JSON.stringify(reason), promise: JSON.stringify(promise) });
});
process.on('uncaughtException', (error) => {
logger_1.logger.error('Uncaught Exception in Media API', { error: error instanceof Error ? error.message : JSON.stringify(error) });
fastify.close(() => {
process.exit(1);
});
});
// Start server
const start = async () => {
try {
// CORS configuration — allow admin app + MkDocs docs site
const allowedOrigins = env_1.env.CORS_ORIGINS.split(',').map(o => o.trim());
// Auto-add MkDocs origins so video cards/players work in docs
const mkdocsOrigin = `http://localhost:${env_1.env.MKDOCS_PORT || 4003}`;
if (!allowedOrigins.includes(mkdocsOrigin)) {
allowedOrigins.push(mkdocsOrigin);
}
// Also allow the docs subdomain in production (docs.domain.org)
for (const origin of [...allowedOrigins]) {
const match = origin.match(/^(https?:\/\/)app\./);
if (match) {
const docsOrigin = origin.replace(/^(https?:\/\/)app\./, '$1docs.');
if (!allowedOrigins.includes(docsOrigin)) {
allowedOrigins.push(docsOrigin);
}
}
}
await fastify.register(cors_1.default, {
origin: (origin, cb) => {
// Allow requests with no origin (mobile apps, curl, etc.)
if (!origin) {
cb(null, true);
return;
}
// Check if origin is in allowed list
if (allowedOrigins.includes(origin)) {
cb(null, true);
}
else {
cb(new Error('CORS not allowed'), false);
}
},
credentials: true,
});
// Multipart support for file uploads (10GB limit)
await fastify.register(multipart_1.default, {
limits: {
fileSize: env_1.env.MAX_UPLOAD_SIZE_GB * 1024 * 1024 * 1024,
},
});
// Health check
fastify.get('/health', async () => {
return {
status: 'ok',
timestamp: new Date().toISOString(),
service: 'media-api'
};
});
// Register routes
await fastify.register(videos_routes_1.videosRoutes, { prefix: '/api/videos' });
await fastify.register(video_streaming_routes_1.videoStreamingRoutes, { prefix: '/api/videos' });
await fastify.register(upload_routes_1.uploadRoutes, { prefix: '/api/videos' });
await fastify.register(video_actions_routes_1.videoActionsRoutes, { prefix: '/api/videos' });
await fastify.register(video_schedule_routes_1.videoScheduleRoutes, { prefix: '/api/videos' });
await fastify.register(video_tracking_routes_1.videoTrackingRoutes, { prefix: '/api/track' });
await fastify.register(reactions_routes_1.reactionsRoutes, { prefix: '/api/reactions' });
await fastify.register(public_routes_1.publicRoutes, { prefix: '/api' });
await fastify.register(comments_routes_1.commentsRoutes, { prefix: '/api' });
await fastify.register(chat_stream_routes_1.chatStreamRoutes, { prefix: '/api' });
await fastify.register(comment_admin_routes_1.commentAdminRoutes, { prefix: '/api/media' });
await fastify.register(chat_notifications_routes_1.chatNotificationsRoutes, { prefix: '/api/media' });
await fastify.register(chat_threads_routes_1.chatThreadsRoutes, { prefix: '/api/media' });
await fastify.register(user_profile_routes_1.userProfileRoutes, { prefix: '/api/media' });
await fastify.register(fetch_routes_1.fetchRoutes, { prefix: '/api/videos' });
await fastify.register(shorts_routes_1.shortsRoutes, { prefix: '/api' });
await fastify.register(upvote_routes_1.upvoteRoutes, { prefix: '/api' });
await fastify.register(playlists_public_routes_1.playlistsPublicRoutes, { prefix: '/api/playlists' });
await fastify.register(playlists_user_routes_1.playlistsUserRoutes, { prefix: '/api/playlists' });
await fastify.register(playlists_admin_routes_1.playlistsAdminRoutes, { prefix: '/api/media' });
// Photo gallery routes
await fastify.register(photos_routes_1.photosRoutes, { prefix: '/api/photos' });
await fastify.register(photo_upload_routes_1.photoUploadRoutes, { prefix: '/api/photos' });
await fastify.register(photo_albums_routes_1.photoAlbumsRoutes, { prefix: '/api/albums' });
await fastify.register(photos_public_routes_1.photosPublicRoutes, { prefix: '/api' });
await fastify.register(photo_engagement_routes_1.photoEngagementRoutes, { prefix: '/api' });
// 404 handler for unmatched routes
fastify.setNotFoundHandler((_request, reply) => {
reply.status(404).send({ error: { message: 'Route not found', code: 'NOT_FOUND' } });
});
const port = env_1.env.MEDIA_API_PORT;
const host = '0.0.0.0';
await fastify.listen({ port, host });
logger_1.logger.info(`Media API listening on http://${host}:${port}`);
// Start video schedule queue worker
video_schedule_queue_service_1.videoScheduleQueueService.startWorker();
logger_1.logger.info('Video schedule queue worker initialized');
// Start video fetch queue worker
video_fetch_queue_service_1.videoFetchQueueService.startWorker();
logger_1.logger.info('Video fetch queue worker initialized');
if (env_1.env.ENABLE_MEDIA_FEATURES !== 'true') {
logger_1.logger.warn('Media features are disabled (ENABLE_MEDIA_FEATURES=false)');
}
}
catch (err) {
logger_1.logger.error('Media API startup error', { error: err instanceof Error ? err.message : JSON.stringify(err) });
process.exit(1);
}
};
start();
//# sourceMappingURL=media-server.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
import { Request, Response, NextFunction } from 'express';
export declare function authenticate(req: Request, _res: Response, next: NextFunction): void;
export declare function optionalAuth(req: Request, _res: Response, next: NextFunction): void;
//# sourceMappingURL=auth.middleware.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"auth.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAa1D,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAoB5E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAsB5E"}

View File

@ -1,52 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.authenticate = authenticate;
exports.optionalAuth = optionalAuth;
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const env_1 = require("../config/env");
const error_handler_1 = require("./error-handler");
function authenticate(req, _res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
throw new error_handler_1.AppError(401, 'Authentication required', 'AUTH_REQUIRED');
}
const token = header.slice(7);
try {
const payload = jsonwebtoken_1.default.verify(token, env_1.env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] });
req.user = {
id: payload.id,
email: payload.email,
role: payload.role,
roles: payload.roles || [payload.role], // Backwards compat: old JWTs without roles
};
next();
}
catch {
throw new error_handler_1.AppError(401, 'Invalid or expired token', 'INVALID_TOKEN');
}
}
function optionalAuth(req, _res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
next();
return;
}
const token = header.slice(7);
try {
const payload = jsonwebtoken_1.default.verify(token, env_1.env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] });
req.user = {
id: payload.id,
email: payload.email,
role: payload.role,
roles: payload.roles || [payload.role],
};
}
catch {
// Token invalid — continue without user
}
next();
}
//# sourceMappingURL=auth.middleware.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":";;;;;AAaA,oCAoBC;AAED,oCAsBC;AAxDD,gEAA+B;AAE/B,uCAAoC;AACpC,mDAA2C;AAS3C,SAAgB,YAAY,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAG,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAiB,CAAC;QACpG,GAAG,CAAC,IAAI,GAAG;YACT,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,2CAA2C;SACpF,CAAC;QACF,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,0BAA0B,EAAE,eAAe,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC;QACP,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAG,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAiB,CAAC;QACpG,GAAG,CAAC,IAAI,GAAG;YACT,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;SACvC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}

View File

@ -1,8 +0,0 @@
import { Request, Response, NextFunction } from 'express';
export declare class AppError extends Error {
statusCode: number;
code?: string | undefined;
constructor(statusCode: number, message: string, code?: string | undefined);
}
export declare function errorHandler(err: Error, _req: Request, res: Response, _next: NextFunction): void;
//# sourceMappingURL=error-handler.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,qBAAa,QAAS,SAAQ,KAAK;IAExB,UAAU,EAAE,MAAM;IAElB,IAAI,CAAC,EAAE,MAAM;gBAFb,UAAU,EAAE,MAAM,EACzB,OAAO,EAAE,MAAM,EACR,IAAI,CAAC,EAAE,MAAM,YAAA;CAKvB;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,KAAK,EACV,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,QAqCpB"}

View File

@ -1,52 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppError = void 0;
exports.errorHandler = errorHandler;
const zod_1 = require("zod");
const logger_1 = require("../utils/logger");
const env_1 = require("../config/env");
class AppError extends Error {
statusCode;
code;
constructor(statusCode, message, code) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.name = 'AppError';
}
}
exports.AppError = AppError;
function errorHandler(err, _req, res, _next) {
if (err instanceof AppError) {
res.status(err.statusCode).json({
error: {
message: err.message,
code: err.code,
},
});
return;
}
if (err instanceof zod_1.ZodError) {
const fieldErrors = err.flatten().fieldErrors;
const errorCount = Object.keys(fieldErrors).length;
res.status(400).json({
error: {
message: 'Validation error',
code: 'VALIDATION_ERROR',
// Only expose details in development
...(env_1.env.NODE_ENV === 'development' && { details: fieldErrors }),
// In production, only show count
...(env_1.env.NODE_ENV === 'production' && { fieldCount: errorCount }),
},
});
return;
}
logger_1.logger.error('Unhandled error:', err);
res.status(500).json({
error: {
message: env_1.env.NODE_ENV === 'production' ? 'Internal server error' : err.message,
code: 'INTERNAL_ERROR',
},
});
}
//# sourceMappingURL=error-handler.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":";;;AAgBA,oCAyCC;AAxDD,6BAA+B;AAC/B,4CAAyC;AACzC,uCAAoC;AAEpC,MAAa,QAAS,SAAQ,KAAK;IAExB;IAEA;IAHT,YACS,UAAkB,EACzB,OAAe,EACR,IAAa;QAEpB,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,eAAU,GAAV,UAAU,CAAQ;QAElB,SAAI,GAAJ,IAAI,CAAS;QAGpB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AATD,4BASC;AAED,SAAgB,YAAY,CAC1B,GAAU,EACV,IAAa,EACb,GAAa,EACb,KAAmB;IAEnB,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YAC9B,KAAK,EAAE;gBACL,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,GAAG,CAAC,IAAI;aACf;SACF,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,GAAG,YAAY,cAAQ,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC;QAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC;QAEnD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE;gBACL,OAAO,EAAE,kBAAkB;gBAC3B,IAAI,EAAE,kBAAkB;gBACxB,qCAAqC;gBACrC,GAAG,CAAC,SAAG,CAAC,QAAQ,KAAK,aAAa,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;gBAC/D,iCAAiC;gBACjC,GAAG,CAAC,SAAG,CAAC,QAAQ,KAAK,YAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;aACjE;SACF,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,eAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAEtC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,KAAK,EAAE;YACL,OAAO,EAAE,SAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO;YAC9E,IAAI,EAAE,gBAAgB;SACvB;KACF,CAAC,CAAC;AACL,CAAC"}

View File

@ -1,24 +0,0 @@
export declare const globalRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const emailRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const responseRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const shiftSignupRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const canvassVisitRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const canvassBulkVisitRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const gpsTrackingRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const canvassGeocodeRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const adTrackingRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const quickJoinRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const authRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const observabilityRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const docsAnalyticsRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const docsCommentAnonRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const docsCommentAuthRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const docsCommentFetchRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const profileViewRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const profileEditRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const profilePhotoRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const profilePasswordRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const eventSubmissionRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const errorReportRateLimit: import("express-rate-limit").RateLimitRequestHandler;
export declare const healthMetricsRateLimit: import("express-rate-limit").RateLimitRequestHandler;
//# sourceMappingURL=rate-limit.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,sDAe1B,CAAC;AAEH,eAAO,MAAM,cAAc,sDAezB,CAAC;AAEH,eAAO,MAAM,iBAAiB,sDAe5B,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,sDAehC,CAAC;AAEH,eAAO,MAAM,yBAAyB,sDAepC,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,sDAelC,CAAC;AAEH,eAAO,MAAM,mBAAmB,sDAe9B,CAAC;AAEH,eAAO,MAAM,kBAAkB,sDAe7B,CAAC;AAEH,eAAO,MAAM,aAAa,sDAexB,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,yBAAyB,sDAepC,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,sDAehC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC"}

View File

@ -1,379 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.healthMetricsRateLimit = exports.errorReportRateLimit = exports.eventSubmissionRateLimit = exports.profilePasswordRateLimit = exports.profilePhotoRateLimit = exports.profileEditRateLimit = exports.profileViewRateLimit = exports.docsCommentFetchRateLimit = exports.docsCommentAuthRateLimit = exports.docsCommentAnonRateLimit = exports.docsAnalyticsRateLimit = exports.observabilityRateLimit = exports.authRateLimit = exports.quickJoinRateLimit = exports.adTrackingRateLimit = exports.canvassGeocodeRateLimit = exports.gpsTrackingRateLimit = exports.canvassBulkVisitRateLimit = exports.canvassVisitRateLimit = exports.shiftSignupRateLimit = exports.responseRateLimit = exports.emailRateLimit = exports.globalRateLimit = void 0;
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const rate_limit_redis_1 = __importDefault(require("rate-limit-redis"));
const redis_1 = require("../config/redis");
const env_1 = require("../config/env");
exports.globalRateLimit = (0, express_rate_limit_1.default)({
windowMs: env_1.env.RATE_LIMIT_WINDOW_MS,
max: env_1.env.RATE_LIMIT_MAX,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:global:',
}),
message: {
error: {
message: 'Too many requests, please try again later',
code: 'RATE_LIMIT_EXCEEDED',
},
},
});
exports.emailRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 30,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:email:',
}),
message: {
error: {
message: 'Too many email requests, please try again later',
code: 'EMAIL_RATE_LIMIT_EXCEEDED',
},
},
});
exports.responseRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:response:',
}),
message: {
error: {
message: 'Too many response submissions, please try again later',
code: 'RESPONSE_RATE_LIMIT_EXCEEDED',
},
},
});
exports.shiftSignupRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:shift-signup:',
}),
message: {
error: {
message: 'Too many signup attempts, please try again later',
code: 'SHIFT_SIGNUP_RATE_LIMIT_EXCEEDED',
},
},
});
exports.canvassVisitRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 30,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:canvass-visit:',
}),
message: {
error: {
message: 'Too many visit recordings, please slow down',
code: 'CANVASS_VISIT_RATE_LIMIT_EXCEEDED',
},
},
});
exports.canvassBulkVisitRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 5, // Stricter limit for bulk operations
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:canvass-visit-bulk:',
}),
message: {
error: {
message: 'Too many bulk visit recordings, please slow down',
code: 'CANVASS_BULK_VISIT_RATE_LIMIT_EXCEEDED',
},
},
});
exports.gpsTrackingRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 6,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:gps-tracking:',
}),
message: {
error: {
message: 'Too many GPS tracking requests, please slow down',
code: 'GPS_TRACKING_RATE_LIMIT_EXCEEDED',
},
},
});
exports.canvassGeocodeRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 10,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:canvass-geocode:',
}),
message: {
error: {
message: 'Too many geocode requests, please slow down',
code: 'CANVASS_GEOCODE_RATE_LIMIT_EXCEEDED',
},
},
});
exports.adTrackingRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 60, // 60 events/min per IP (generous for scroll-heavy gallery pages)
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:ad-track:',
}),
message: {
error: {
message: 'Too many tracking requests',
code: 'RATE_LIMIT_EXCEEDED',
},
},
});
exports.quickJoinRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:quick-join:',
}),
message: {
error: {
message: 'Too many join attempts, please try again later',
code: 'QUICK_JOIN_RATE_LIMIT_EXCEEDED',
},
},
});
exports.authRateLimit = (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000,
max: 10, // Reduced from 20 to prevent brute force attacks
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:auth:',
}),
message: {
error: {
message: 'Too many authentication attempts, please try again later',
code: 'AUTH_RATE_LIMIT_EXCEEDED',
},
},
});
exports.observabilityRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 20, // 20 requests per minute (stricter than global 500/min)
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:observability:',
}),
message: {
error: {
message: 'Too many observability requests, please try again later',
code: 'OBSERVABILITY_RATE_LIMIT_EXCEEDED',
},
},
});
exports.docsAnalyticsRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 60, // 60 requests/min per IP
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:docs-analytics:',
}),
message: {
error: {
message: 'Too many tracking requests, please slow down',
code: 'DOCS_ANALYTICS_RATE_LIMIT_EXCEEDED',
},
},
});
exports.docsCommentAnonRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:docs-comment-anon:',
}),
message: {
error: {
message: 'Too many anonymous comments, please try again later',
code: 'DOCS_COMMENT_ANON_RATE_LIMIT_EXCEEDED',
},
},
});
exports.docsCommentAuthRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 30,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:docs-comment-auth:',
}),
message: {
error: {
message: 'Too many comments, please try again later',
code: 'DOCS_COMMENT_AUTH_RATE_LIMIT_EXCEEDED',
},
},
});
exports.docsCommentFetchRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 60,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:docs-comment-fetch:',
}),
message: {
error: {
message: 'Too many comment fetch requests, please slow down',
code: 'DOCS_COMMENT_FETCH_RATE_LIMIT_EXCEEDED',
},
},
});
exports.profileViewRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 60, // 60 requests per minute (shared across profile, photo, activity endpoints)
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:profile-view:',
}),
message: {
error: {
message: 'Too many profile requests, please try again later',
code: 'PROFILE_VIEW_RATE_LIMIT_EXCEEDED',
},
},
});
exports.profileEditRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 20,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:profile-edit:',
}),
message: {
error: {
message: 'Too many profile edit requests, please try again later',
code: 'PROFILE_EDIT_RATE_LIMIT_EXCEEDED',
},
},
});
exports.profilePhotoRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:profile-photo:',
}),
message: {
error: {
message: 'Too many photo uploads, please try again later',
code: 'PROFILE_PHOTO_RATE_LIMIT_EXCEEDED',
},
},
});
exports.profilePasswordRateLimit = (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Stricter than auth (10/15min) — profile passwords may be simpler
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:profile-password:',
}),
message: {
error: {
message: 'Too many password attempts, please try again later',
code: 'PROFILE_PASSWORD_RATE_LIMIT_EXCEEDED',
},
},
});
exports.eventSubmissionRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:event-submit:',
}),
message: {
error: {
message: 'Too many event submissions, please try again later',
code: 'EVENT_SUBMISSION_RATE_LIMIT_EXCEEDED',
},
},
});
exports.errorReportRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:error-report:',
}),
message: {
error: {
message: 'Too many error reports, please try again later',
code: 'ERROR_REPORT_RATE_LIMIT_EXCEEDED',
},
},
});
exports.healthMetricsRateLimit = (0, express_rate_limit_1.default)({
windowMs: 60 * 1000, // 1 minute
max: 30, // 30 requests per minute
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:health-metrics:',
}),
message: {
error: {
message: 'Too many health check requests, please try again later',
code: 'HEALTH_METRICS_RATE_LIMIT_EXCEEDED',
},
},
});
//# sourceMappingURL=rate-limit.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,5 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import { UserRole } from '@prisma/client';
export declare function requireRole(...roles: UserRole[]): (req: Request, _res: Response, next: NextFunction) => void;
export declare function requireNonTemp(req: Request, _res: Response, next: NextFunction): void;
//# sourceMappingURL=rbac.middleware.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"rbac.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/rbac.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,QAAQ,EAAE,IACtC,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,YAAY,UAqBzD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAY9E"}

View File

@ -1,36 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireRole = requireRole;
exports.requireNonTemp = requireNonTemp;
const client_1 = require("@prisma/client");
const error_handler_1 = require("./error-handler");
function requireRole(...roles) {
return (req, _res, next) => {
if (!req.user) {
throw new error_handler_1.AppError(401, 'Authentication required', 'AUTH_REQUIRED');
}
// Check multi-role array (falls back to single role via auth middleware)
const userRoles = req.user.roles || [req.user.role];
// SUPER_ADMIN bypasses all role checks
if (userRoles.includes(client_1.UserRole.SUPER_ADMIN)) {
return next();
}
const hasRole = userRoles.some(r => roles.includes(r));
if (!hasRole) {
throw new error_handler_1.AppError(403, 'Insufficient permissions', 'FORBIDDEN');
}
next();
};
}
function requireNonTemp(req, _res, next) {
if (!req.user) {
throw new error_handler_1.AppError(401, 'Authentication required', 'AUTH_REQUIRED');
}
const userRoles = req.user.roles || [req.user.role];
// User is "temp only" if their only role is TEMP
if (userRoles.length === 1 && userRoles[0] === client_1.UserRole.TEMP) {
throw new error_handler_1.AppError(403, 'Temporary accounts cannot access this resource', 'TEMP_FORBIDDEN');
}
next();
}
//# sourceMappingURL=rbac.middleware.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"rbac.middleware.js","sourceRoot":"","sources":["../../src/middleware/rbac.middleware.ts"],"names":[],"mappings":";;AAIA,kCAsBC;AAED,wCAYC;AAvCD,2CAA0C;AAC1C,mDAA2C;AAE3C,SAAgB,WAAW,CAAC,GAAG,KAAiB;IAC9C,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC;QACtE,CAAC;QAED,yEAAyE;QACzE,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpD,uCAAuC;QACvC,IAAI,SAAS,CAAC,QAAQ,CAAC,iBAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7C,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAEvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,0BAA0B,EAAE,WAAW,CAAC,CAAC;QACnE,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,cAAc,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC7E,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,iDAAiD;IACjD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,iBAAQ,CAAC,IAAI,EAAE,CAAC;QAC7D,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,gDAAgD,EAAE,gBAAgB,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}

View File

@ -1,4 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export declare function validate(schema: ZodSchema, source?: 'body' | 'query' | 'params'): (req: Request, _res: Response, next: NextFunction) => void;
//# sourceMappingURL=validate.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/middleware/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAY,MAAM,KAAK,CAAC;AAG1C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,GAAE,MAAM,GAAG,OAAO,GAAG,QAAiB,IAC9E,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,YAAY,UAczD"}

View File

@ -1,23 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validate = validate;
const zod_1 = require("zod");
const error_handler_1 = require("./error-handler");
function validate(schema, source = 'body') {
return (req, _res, next) => {
try {
const data = schema.parse(req[source]);
req[source] = data;
next();
}
catch (err) {
if (err instanceof zod_1.ZodError) {
// Sanitize validation errors - only expose field count, not detailed messages
const fieldCount = err.errors.length;
throw new error_handler_1.AppError(400, `Invalid request data: ${fieldCount} field(s) failed validation`, 'VALIDATION_ERROR');
}
throw err;
}
};
}
//# sourceMappingURL=validate.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/middleware/validate.ts"],"names":[],"mappings":";;AAIA,4BAeC;AAlBD,6BAA0C;AAC1C,mDAA2C;AAE3C,SAAgB,QAAQ,CAAC,MAAiB,EAAE,SAAsC,MAAM;IACtF,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YACvC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;YACnB,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,cAAQ,EAAE,CAAC;gBAC5B,8EAA8E;gBAC9E,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;gBACrC,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,UAAU,6BAA6B,EAAE,kBAAkB,CAAC,CAAC;YAChH,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}

View File

@ -1,3 +0,0 @@
declare const router: import("express-serve-static-core").Router;
export { router as authRouter };
//# sourceMappingURL=auth.routes.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"auth.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.routes.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmUxB,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,CAAC"}

View File

@ -1,279 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.authRouter = void 0;
const express_1 = require("express");
const zod_1 = require("zod");
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const client_1 = require("@prisma/client");
const auth_service_1 = require("./auth.service");
const auth_schemas_1 = require("./auth.schemas");
const validate_1 = require("../../middleware/validate");
const auth_middleware_1 = require("../../middleware/auth.middleware");
const rate_limit_1 = require("../../middleware/rate-limit");
const database_1 = require("../../config/database");
const verification_token_service_1 = require("../../services/verification-token.service");
const password_reset_token_service_1 = require("../../services/password-reset-token.service");
const email_service_1 = require("../../services/email.service");
const settings_service_1 = require("../settings/settings.service");
const env_1 = require("../../config/env");
const logger_1 = require("../../utils/logger");
const auth_rate_limits_1 = require("./auth.rate-limits");
const profile_service_1 = require("../people/profile.service");
const router = (0, express_1.Router)();
exports.authRouter = router;
// POST /api/auth/login
router.post('/login', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.loginSchema), async (req, res, next) => {
try {
const result = await auth_service_1.authService.login(req.body.email, req.body.password);
res.json(result);
}
catch (err) {
next(err);
}
});
// POST /api/auth/register
router.post('/register', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.registerSchema), async (req, res, next) => {
try {
const result = await auth_service_1.authService.register(req.body);
res.status(201).json(result);
}
catch (err) {
next(err);
}
});
// POST /api/auth/verify-email
const verifyEmailSchema = zod_1.z.object({ token: zod_1.z.string().min(1) });
router.post('/verify-email', rate_limit_1.authRateLimit, (0, validate_1.validate)(verifyEmailSchema), async (req, res, next) => {
try {
const { token } = req.body;
const result = await verification_token_service_1.verificationTokenService.verifyToken(token);
if (!result.valid || !result.userId) {
res.status(400).json({
error: { message: result.error || 'Invalid token', code: 'INVALID_TOKEN' },
});
return;
}
const settings = await settings_service_1.siteSettingsService.get();
const autoApprove = settings.autoApproveVerifiedUsers;
const newStatus = autoApprove ? client_1.UserStatus.ACTIVE : client_1.UserStatus.PENDING_APPROVAL;
await database_1.prisma.user.update({
where: { id: result.userId },
data: { emailVerified: true, status: newStatus },
});
// If not auto-approved, notify admins
if (!autoApprove) {
const user = await database_1.prisma.user.findUnique({ where: { id: result.userId } });
if (user) {
const admins = await database_1.prisma.user.findMany({
where: { role: client_1.UserRole.SUPER_ADMIN, status: client_1.UserStatus.ACTIVE },
select: { email: true },
});
if (admins.length > 0) {
await email_service_1.emailService.sendPendingApprovalNotification({
adminEmails: admins.map(a => a.email),
newUserEmail: user.email,
newUserName: user.name || '',
}).catch(err => logger_1.logger.error('Failed to send approval notification:', err));
}
}
}
res.json({
verified: true,
approved: autoApprove,
message: autoApprove
? 'Email verified. You can now log in.'
: 'Email verified. Your account is pending admin approval.',
});
}
catch (err) {
next(err);
}
});
// POST /api/auth/resend-verification
const resendVerificationSchema = zod_1.z.object({ email: zod_1.z.string().email() });
router.post('/resend-verification', (0, auth_rate_limits_1.createVerificationRateLimit)(), (0, validate_1.validate)(resendVerificationSchema), async (req, res, next) => {
try {
// Always return success to prevent user enumeration
res.json({ message: 'If your email is registered and pending verification, a new verification link has been sent.' });
// Send asynchronously (don't block response)
const user = await database_1.prisma.user.findUnique({ where: { email: req.body.email } });
if (user && user.status === client_1.UserStatus.PENDING_VERIFICATION) {
const token = await verification_token_service_1.verificationTokenService.createToken(user.id);
const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000';
const verificationUrl = `${adminUrl}/verify-email?token=${token}`;
await email_service_1.emailService.sendVerificationEmail({
recipientEmail: user.email,
recipientName: user.name || 'there',
verificationUrl,
}).catch(err => logger_1.logger.error('Failed to resend verification email:', err));
}
}
catch (err) {
next(err);
}
});
// POST /api/auth/forgot-password
const forgotPasswordSchema = zod_1.z.object({ email: zod_1.z.string().email() });
router.post('/forgot-password', (0, auth_rate_limits_1.createResetRateLimit)(), (0, validate_1.validate)(forgotPasswordSchema), async (req, res, next) => {
try {
// Always return success to prevent user enumeration
res.json({ message: 'If your email is registered, a password reset link has been sent.' });
// Send asynchronously
const user = await database_1.prisma.user.findUnique({ where: { email: req.body.email } });
if (user && user.status === client_1.UserStatus.ACTIVE) {
const token = await password_reset_token_service_1.passwordResetTokenService.createToken(user.id);
const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000';
const resetUrl = `${adminUrl}/reset-password?token=${token}`;
await email_service_1.emailService.sendPasswordResetEmail({
recipientEmail: user.email,
recipientName: user.name || 'there',
resetUrl,
}).catch(err => logger_1.logger.error('Failed to send password reset email:', err));
}
}
catch (err) {
next(err);
}
});
// POST /api/auth/reset-password
const resetPasswordSchema = zod_1.z.object({
token: zod_1.z.string().min(1),
password: zod_1.z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one digit'),
});
router.post('/reset-password', rate_limit_1.authRateLimit, (0, validate_1.validate)(resetPasswordSchema), async (req, res, next) => {
try {
const { token, password } = req.body;
const result = await password_reset_token_service_1.passwordResetTokenService.validateToken(token);
if (!result.valid || !result.userId) {
res.status(400).json({
error: { message: result.error || 'Invalid token', code: 'INVALID_TOKEN' },
});
return;
}
const hashedPassword = await bcryptjs_1.default.hash(password, 12);
// Update password, mark token used, invalidate all refresh tokens — all in one transaction
await database_1.prisma.$transaction(async (tx) => {
await tx.user.update({
where: { id: result.userId },
data: { password: hashedPassword },
});
await tx.refreshToken.deleteMany({ where: { userId: result.userId } });
await tx.passwordResetToken.update({
where: { token },
data: { usedAt: new Date() },
});
});
res.json({ message: 'Password has been reset. You can now log in with your new password.' });
}
catch (err) {
next(err);
}
});
// POST /api/auth/refresh
router.post('/refresh', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.refreshSchema), async (req, res, next) => {
try {
const result = await auth_service_1.authService.refreshTokens(req.body.refreshToken);
res.json(result);
}
catch (err) {
next(err);
}
});
// POST /api/auth/logout
router.post('/logout', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.refreshSchema), async (req, res, next) => {
try {
await auth_service_1.authService.logout(req.body.refreshToken);
res.json({ message: 'Logged out' });
}
catch (err) {
next(err);
}
});
// GET /api/auth/me/profile-token
router.get('/me/profile-token', auth_middleware_1.authenticate, async (req, res, next) => {
try {
const userId = req.user.id;
// Look up existing Contact linked to this user
let contact = await database_1.prisma.contact.findUnique({ where: { userId } });
// Auto-create Contact if none exists
if (!contact) {
const user = await database_1.prisma.user.findUnique({
where: { id: userId },
select: { name: true, email: true },
});
if (!user) {
res.status(401).json({ error: { message: 'Invalid token', code: 'INVALID_TOKEN' } });
return;
}
try {
contact = await database_1.prisma.contact.create({
data: {
displayName: user.name || user.email,
firstName: user.name?.split(' ')[0] || null,
lastName: user.name?.split(' ').slice(1).join(' ') || null,
email: user.email,
userId,
primarySource: 'USER',
},
});
}
catch (err) {
// Race condition: another request created the Contact — retry lookup
if (err.code === 'P2002') {
contact = await database_1.prisma.contact.findUnique({ where: { userId } });
}
if (!contact)
throw err;
}
}
// Generate profile token if Contact doesn't have one
if (!contact.profileToken) {
const result = await profile_service_1.profileService.generateProfileToken(contact.id);
res.json({ token: result.token });
return;
}
res.json({ token: contact.profileToken });
}
catch (err) {
next(err);
}
});
// GET /api/auth/me
router.get('/me', auth_middleware_1.authenticate, async (req, res, next) => {
try {
const user = await database_1.prisma.user.findUnique({
where: { id: req.user.id },
select: {
id: true,
email: true,
name: true,
phone: true,
role: true,
roles: true,
status: true,
permissions: true,
createdVia: true,
emailVerified: true,
lastLoginAt: true,
createdAt: true,
updatedAt: true,
},
});
if (!user) {
res.status(401).json({ error: { message: 'Invalid token', code: 'INVALID_TOKEN' } });
return;
}
res.json(user);
}
catch (err) {
next(err);
}
});
//# sourceMappingURL=auth.routes.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,41 +0,0 @@
import { z } from 'zod';
export declare const loginSchema: z.ZodObject<{
email: z.ZodString;
password: z.ZodString;
}, "strip", z.ZodTypeAny, {
email: string;
password: string;
}, {
email: string;
password: string;
}>;
export declare const registerSchema: z.ZodObject<{
email: z.ZodString;
password: z.ZodString;
name: z.ZodOptional<z.ZodString>;
phone: z.ZodOptional<z.ZodString>;
inviteCode: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
email: string;
password: string;
name?: string | undefined;
phone?: string | undefined;
inviteCode?: string | undefined;
}, {
email: string;
password: string;
name?: string | undefined;
phone?: string | undefined;
inviteCode?: string | undefined;
}>;
export declare const refreshSchema: z.ZodObject<{
refreshToken: z.ZodString;
}, "strip", z.ZodTypeAny, {
refreshToken: string;
}, {
refreshToken: string;
}>;
export type LoginInput = z.infer<typeof loginSchema>;
export type RegisterInput = z.infer<typeof registerSchema>;
export type RefreshInput = z.infer<typeof refreshSchema>;
//# sourceMappingURL=auth.schemas.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"auth.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,WAAW;;;;;;;;;EAGtB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAWzB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;EAExB,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AACrD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}

View File

@ -1,24 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.refreshSchema = exports.registerSchema = exports.loginSchema = void 0;
const zod_1 = require("zod");
exports.loginSchema = zod_1.z.object({
email: zod_1.z.string().email(),
password: zod_1.z.string().min(1, 'Password is required'),
});
exports.registerSchema = zod_1.z.object({
email: zod_1.z.string().email(),
password: zod_1.z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one digit'),
name: zod_1.z.string().optional(),
phone: zod_1.z.string().optional(),
inviteCode: zod_1.z.string().max(20).optional(),
// Role removed from public registration - must be set server-side only
});
exports.refreshSchema = zod_1.z.object({
refreshToken: zod_1.z.string().min(1, 'Refresh token is required'),
});
//# sourceMappingURL=auth.schemas.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"auth.schemas.js","sourceRoot":"","sources":["../../../src/modules/auth/auth.schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAGX,QAAA,WAAW,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;IACzB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,sBAAsB,CAAC;CACpD,CAAC,CAAC;AAEU,QAAA,cAAc,GAAG,OAAC,CAAC,MAAM,CAAC;IACrC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;IACzB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;SACjB,GAAG,CAAC,EAAE,EAAE,yCAAyC,CAAC;SAClD,KAAK,CAAC,OAAO,EAAE,qDAAqD,CAAC;SACrE,KAAK,CAAC,OAAO,EAAE,qDAAqD,CAAC;SACrE,KAAK,CAAC,OAAO,EAAE,0CAA0C,CAAC;IAC7D,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,uEAAuE;CACxE,CAAC,CAAC;AAEU,QAAA,aAAa,GAAG,OAAC,CAAC,MAAM,CAAC;IACpC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,2BAA2B,CAAC;CAC7D,CAAC,CAAC"}

View File

@ -1,109 +0,0 @@
import { UserRole } from '@prisma/client';
import type { RegisterInput } from './auth.schemas';
export interface TokenPair {
accessToken: string;
refreshToken: string;
}
type UserForToken = {
id: string;
email: string;
role: UserRole;
roles?: unknown;
};
export declare const authService: {
login(email: string, password: string): Promise<{
accessToken: string;
refreshToken: string;
user: {
status: import(".prisma/client").$Enums.UserStatus;
id: string;
email: string;
name: string | null;
phone: string | null;
pronouns: string | null;
role: import(".prisma/client").$Enums.UserRole;
roles: import("@prisma/client/runtime/library").JsonValue;
permissions: import("@prisma/client/runtime/library").JsonValue | null;
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
expiresAt: Date | null;
expireDays: number | null;
lastLoginAt: Date | null;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}>;
register(data: RegisterInput): Promise<{
user: {
status: import(".prisma/client").$Enums.UserStatus;
id: string;
email: string;
name: string | null;
phone: string | null;
pronouns: string | null;
role: import(".prisma/client").$Enums.UserRole;
roles: import("@prisma/client/runtime/library").JsonValue;
permissions: import("@prisma/client/runtime/library").JsonValue | null;
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
expiresAt: Date | null;
expireDays: number | null;
lastLoginAt: Date | null;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
requiresVerification: boolean;
message: string;
} | {
accessToken: string;
refreshToken: string;
user: {
status: import(".prisma/client").$Enums.UserStatus;
id: string;
email: string;
name: string | null;
phone: string | null;
pronouns: string | null;
role: import(".prisma/client").$Enums.UserRole;
roles: import("@prisma/client/runtime/library").JsonValue;
permissions: import("@prisma/client/runtime/library").JsonValue | null;
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
expiresAt: Date | null;
expireDays: number | null;
lastLoginAt: Date | null;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
requiresVerification?: undefined;
message?: undefined;
}>;
refreshTokens(refreshToken: string): Promise<{
accessToken: string;
refreshToken: string;
user: {
status: import(".prisma/client").$Enums.UserStatus;
id: string;
email: string;
name: string | null;
phone: string | null;
pronouns: string | null;
role: import(".prisma/client").$Enums.UserRole;
roles: import("@prisma/client/runtime/library").JsonValue;
permissions: import("@prisma/client/runtime/library").JsonValue | null;
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
expiresAt: Date | null;
expireDays: number | null;
lastLoginAt: Date | null;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
};
}>;
logout(refreshToken: string): Promise<void>;
generateAccessToken(user: UserForToken): string;
generateRefreshToken(user: UserForToken): Promise<string>;
generateTokenPair(user: UserForToken): Promise<TokenPair>;
};
export {};
//# sourceMappingURL=auth.service.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAUtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AASpD,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAUnF,eAAO,MAAM,WAAW;iBACH,MAAM,YAAY,MAAM;qBAf9B,MAAM;sBACL,MAAM;;;;;;;;;;;;;;;;;;;;mBA+EC,aAAa;;;;;;;;;;;;;;;;;;;;;;qBAhFrB,MAAM;sBACL,MAAM;;;;;;;;;;;;;;;;;;;;;;gCAiLc,MAAM;;;;;;;;;;;;;;;;;;;;;;yBAsEb,MAAM;8BAIP,YAAY,GAAG,MAAM;+BAcd,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;4BA2BjC,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;CAKhE,CAAC"}

View File

@ -1,305 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.authService = void 0;
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const client_1 = require("@prisma/client");
const database_1 = require("../../config/database");
const env_1 = require("../../config/env");
const error_handler_1 = require("../../middleware/error-handler");
const metrics_1 = require("../../utils/metrics");
const settings_service_1 = require("../settings/settings.service");
const verification_token_service_1 = require("../../services/verification-token.service");
const email_service_1 = require("../../services/email.service");
const roles_1 = require("../../utils/roles");
const logger_1 = require("../../utils/logger");
/** Parse the roles JSON field into a UserRole[] array */
function parseRoles(user) {
if (Array.isArray(user.roles) && user.roles.length > 0) {
return user.roles;
}
return [user.role];
}
exports.authService = {
async login(email, password) {
const user = await database_1.prisma.user.findUnique({ where: { email } });
if (!user) {
(0, metrics_1.recordLoginAttempt)('failure');
throw new error_handler_1.AppError(401, 'Invalid email or password', 'INVALID_CREDENTIALS');
}
const valid = await bcryptjs_1.default.compare(password, user.password);
if (!valid) {
(0, metrics_1.recordLoginAttempt)('failure');
throw new error_handler_1.AppError(401, 'Invalid email or password', 'INVALID_CREDENTIALS');
}
// Status-specific errors
if (user.status === client_1.UserStatus.PENDING_VERIFICATION) {
(0, metrics_1.recordLoginAttempt)('failure');
throw new error_handler_1.AppError(403, 'Please verify your email address before logging in', 'EMAIL_NOT_VERIFIED');
}
if (user.status === client_1.UserStatus.PENDING_APPROVAL) {
(0, metrics_1.recordLoginAttempt)('failure');
throw new error_handler_1.AppError(403, 'Your account is pending admin approval', 'ACCOUNT_PENDING');
}
if (user.status !== client_1.UserStatus.ACTIVE) {
(0, metrics_1.recordLoginAttempt)('failure');
throw new error_handler_1.AppError(403, `Account is ${user.status.toLowerCase()}`, 'ACCOUNT_INACTIVE');
}
if (user.expiresAt && user.expiresAt < new Date()) {
(0, metrics_1.recordLoginAttempt)('failure');
throw new error_handler_1.AppError(403, 'Account has expired', 'ACCOUNT_EXPIRED');
}
(0, metrics_1.recordLoginAttempt)('success');
await database_1.prisma.user.update({
where: { id: user.id },
data: { lastLoginAt: new Date() },
});
// Fire-and-forget: log USER_LOGIN activity on linked Contact
database_1.prisma.contact.findFirst({
where: { userId: user.id, mergedIntoId: null },
}).then(async (contact) => {
if (contact) {
await database_1.prisma.contactActivity.create({
data: {
contactId: contact.id,
type: 'USER_LOGIN',
title: 'User logged in',
description: `Login from ${user.email}`,
},
});
}
}).catch(err => {
logger_1.logger.warn('Login activity logging failed:', err);
});
const tokens = await this.generateTokenPair(user);
const { password: _, ...userWithoutPassword } = user;
return { user: userWithoutPassword, ...tokens };
},
async register(data) {
// Check if public registration is enabled
const settings = await settings_service_1.siteSettingsService.get();
if (!settings.enablePublicRegistration) {
throw new error_handler_1.AppError(403, 'Public registration is currently disabled', 'REGISTRATION_DISABLED');
}
const existing = await database_1.prisma.user.findUnique({ where: { email: data.email } });
if (existing) {
throw new error_handler_1.AppError(409, 'Email already registered', 'EMAIL_EXISTS');
}
const hashedPassword = await bcryptjs_1.default.hash(data.password, 12);
// Determine if email verification is needed
const smtpReady = await email_service_1.emailService.isSmtpConfigured();
const requireVerification = settings.enableEmailVerification && smtpReady;
const user = await database_1.prisma.user.create({
data: {
email: data.email,
password: hashedPassword,
name: data.name,
phone: data.phone,
role: client_1.UserRole.USER,
roles: JSON.parse(JSON.stringify([client_1.UserRole.USER])),
status: requireVerification ? client_1.UserStatus.PENDING_VERIFICATION : client_1.UserStatus.ACTIVE,
emailVerified: !requireVerification,
createdVia: 'SELF_REGISTRATION',
},
});
// Fire-and-forget: process referral if invite code provided
if (data.inviteCode) {
Promise.resolve().then(() => __importStar(require('../social/referral.service'))).then(({ referralService }) => {
referralService.processRegistrationReferral(user.id, data.inviteCode).catch(err => {
logger_1.logger.warn('Referral processing failed:', err);
});
}).catch(() => { });
}
// Fire-and-forget: auto-link or create Contact if People feature is enabled
settings_service_1.siteSettingsService.get().then(async (s) => {
if (!s.enablePeople)
return;
// Check for existing Contact with matching email → link
const existingContact = await database_1.prisma.contact.findFirst({
where: { email: { equals: data.email, mode: 'insensitive' }, userId: null, mergedIntoId: null },
});
if (existingContact) {
await database_1.prisma.contact.update({ where: { id: existingContact.id }, data: { userId: user.id } });
logger_1.logger.info(`Auto-linked contact ${existingContact.id} to registered user ${user.id}`);
}
else {
// Create new Contact linked to the user
await database_1.prisma.contact.create({
data: {
displayName: data.name || data.email,
firstName: data.name?.split(' ')[0] || null,
lastName: data.name?.split(' ').slice(1).join(' ') || null,
email: data.email,
phone: data.phone || null,
primarySource: 'USER',
userId: user.id,
tags: [],
},
});
logger_1.logger.info(`Auto-created contact for registered user ${user.id}`);
}
}).catch(err => {
logger_1.logger.warn('Contact auto-creation on register failed:', err);
});
// If verification required, send email and don't issue tokens
if (requireVerification) {
const token = await verification_token_service_1.verificationTokenService.createToken(user.id);
const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000';
const verificationUrl = `${adminUrl}/verify-email?token=${token}`;
await email_service_1.emailService.sendVerificationEmail({
recipientEmail: user.email,
recipientName: user.name || 'there',
verificationUrl,
});
const { password: _, ...userWithoutPassword } = user;
return {
user: userWithoutPassword,
requiresVerification: true,
message: 'Please check your email to verify your account',
};
}
// No verification needed — issue tokens immediately
const tokens = await this.generateTokenPair(user);
const { password: _, ...userWithoutPassword } = user;
return { user: userWithoutPassword, ...tokens };
},
async refreshTokens(refreshToken) {
let payload;
try {
payload = jsonwebtoken_1.default.verify(refreshToken, env_1.env.JWT_REFRESH_SECRET, { algorithms: ['HS256'] });
}
catch {
throw new error_handler_1.AppError(401, 'Invalid refresh token', 'INVALID_REFRESH_TOKEN');
}
const stored = await database_1.prisma.refreshToken.findUnique({
where: { token: refreshToken },
include: { user: true },
});
if (!stored) {
throw new error_handler_1.AppError(401, 'Refresh token not found', 'INVALID_REFRESH_TOKEN');
}
// Check user status — banned/inactive users must not get new tokens
if (stored.user.status !== client_1.UserStatus.ACTIVE) {
await database_1.prisma.refreshToken.delete({ where: { id: stored.id } });
throw new error_handler_1.AppError(401, 'Account is not active', 'ACCOUNT_INACTIVE');
}
// Check account expiry
if (stored.user.expiresAt && stored.user.expiresAt < new Date()) {
await database_1.prisma.refreshToken.delete({ where: { id: stored.id } });
throw new error_handler_1.AppError(401, 'Account has expired', 'ACCOUNT_EXPIRED');
}
if (stored.expiresAt < new Date()) {
await database_1.prisma.refreshToken.delete({ where: { id: stored.id } });
throw new error_handler_1.AppError(401, 'Refresh token expired', 'REFRESH_TOKEN_EXPIRED');
}
// Rotate: delete old and create new atomically
const tokens = await database_1.prisma.$transaction(async (tx) => {
await tx.refreshToken.delete({ where: { id: stored.id } });
const userRoles = parseRoles(stored.user);
const accessToken = this.generateAccessToken(stored.user);
const refreshPayload = {
id: stored.user.id,
email: stored.user.email,
role: (0, roles_1.getPrimaryRole)(userRoles),
roles: userRoles,
};
const refreshToken = jsonwebtoken_1.default.sign(refreshPayload, env_1.env.JWT_REFRESH_SECRET, {
algorithm: 'HS256',
expiresIn: env_1.env.JWT_REFRESH_EXPIRY,
});
const decoded = jsonwebtoken_1.default.decode(refreshToken);
const expiresAt = new Date(decoded.exp * 1000);
await tx.refreshToken.create({
data: {
token: refreshToken,
userId: stored.user.id,
expiresAt,
},
});
return { accessToken, refreshToken };
});
const { password: _, ...userWithoutPassword } = stored.user;
return { user: userWithoutPassword, ...tokens };
},
async logout(refreshToken) {
await database_1.prisma.refreshToken.deleteMany({ where: { token: refreshToken } });
},
generateAccessToken(user) {
const userRoles = parseRoles(user);
const payload = {
id: user.id,
email: user.email,
role: (0, roles_1.getPrimaryRole)(userRoles),
roles: userRoles,
};
return jsonwebtoken_1.default.sign(payload, env_1.env.JWT_ACCESS_SECRET, {
algorithm: 'HS256',
expiresIn: env_1.env.JWT_ACCESS_EXPIRY,
});
},
async generateRefreshToken(user) {
const userRoles = parseRoles(user);
const payload = {
id: user.id,
email: user.email,
role: (0, roles_1.getPrimaryRole)(userRoles),
roles: userRoles,
};
const token = jsonwebtoken_1.default.sign(payload, env_1.env.JWT_REFRESH_SECRET, {
algorithm: 'HS256',
expiresIn: env_1.env.JWT_REFRESH_EXPIRY,
});
const decoded = jsonwebtoken_1.default.decode(token);
const expiresAt = new Date(decoded.exp * 1000);
await database_1.prisma.refreshToken.create({
data: {
token,
userId: user.id,
expiresAt,
},
});
return token;
},
async generateTokenPair(user) {
const accessToken = this.generateAccessToken(user);
const refreshToken = await this.generateRefreshToken(user);
return { accessToken, refreshToken };
},
};
//# sourceMappingURL=auth.service.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,54 +0,0 @@
export interface FileNode {
name: string;
path: string;
isDirectory: boolean;
children?: FileNode[];
}
/**
* Resolve and validate a relative path within MKDOCS_DOCS_PATH.
* Throws if the resolved path escapes the docs root.
*/
declare function safeResolve(relativePath: string): string;
export declare class PathTraversalError extends Error {
constructor();
}
export declare class FileNotFoundError extends Error {
constructor(filePath: string);
}
/**
* Recursively list the file tree under MKDOCS_DOCS_PATH.
* Cached in Redis with 1-hour TTL for root calls.
*/
declare function listTree(dir?: string, relBase?: string): Promise<FileNode[]>;
declare function readFileContent(relativePath: string): Promise<string>;
declare function writeFileContent(relativePath: string, content: string): Promise<void>;
declare function createFile(relativePath: string, content?: string, isDirectory?: boolean): Promise<void>;
declare function deleteFile(relativePath: string): Promise<void>;
declare function renameFile(fromPath: string, toPath: string): Promise<void>;
declare function isEditableFile(relativePath: string): boolean;
declare function uploadFile(relativePath: string, sourcePath: string): Promise<void>;
declare function invalidateTreeCache(): Promise<void>;
/**
* Flatten a FileNode tree into file-only entries, then filter by
* case-insensitive query match on name or path. Name matches score
* higher so they sort first.
*/
declare function searchFiles(query: string, limit?: number): Promise<{
name: string;
path: string;
}[]>;
export declare const docsFilesService: {
listTree: typeof listTree;
readFileContent: typeof readFileContent;
writeFileContent: typeof writeFileContent;
createFile: typeof createFile;
deleteFile: typeof deleteFile;
renameFile: typeof renameFile;
uploadFile: typeof uploadFile;
safeResolve: typeof safeResolve;
isEditableFile: typeof isEditableFile;
invalidateTreeCache: typeof invalidateTreeCache;
searchFiles: typeof searchFiles;
};
export {};
//# sourceMappingURL=docs-files.service.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"docs-files.service.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/docs-files.service.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAcD;;;GAGG;AACH,iBAAS,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;;CAK5C;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,QAAQ,EAAE,MAAM;CAI7B;AAED;;;GAGG;AACH,iBAAe,QAAQ,CAAC,GAAG,GAAE,MAAkB,EAAE,OAAO,GAAE,MAAW,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAiD1F;AAED,iBAAe,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmCpE;AAED,iBAAe,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpF;AAED,iBAAe,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BtG;AAED,iBAAe,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B7D;AAED,iBAAe,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE;AAED,iBAAS,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAGrD;AAOD,iBAAe,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjF;AAED,iBAAe,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMlD;AAED;;;;GAIG;AACH,iBAAe,WAAW,CACxB,KAAK,EAAE,MAAM,EACb,KAAK,SAAI,GACR,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAyB3C;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;CAY5B,CAAC"}

View File

@ -1,312 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.docsFilesService = exports.FileNotFoundError = exports.PathTraversalError = void 0;
const promises_1 = require("fs/promises");
const path_1 = require("path");
const crypto_1 = __importDefault(require("crypto"));
const env_1 = require("../../config/env");
const redis_1 = require("../../config/redis");
const logger_1 = require("../../utils/logger");
const metrics_1 = require("../../utils/metrics");
const DOCS_ROOT = (0, path_1.resolve)(env_1.env.MKDOCS_DOCS_PATH);
// Redis cache configuration
const CACHE_KEY_PREFIX = 'DOCS_CACHE:';
const TREE_CACHE_KEY = `${CACHE_KEY_PREFIX}tree`;
const TREE_CACHE_TTL = 30; // 30 seconds — short so external changes show quickly
const FILE_CONTENT_CACHE_TTL = 60 * 60; // 1 hour for file content
function hashFilePath(path) {
return crypto_1.default.createHash('sha256').update(path).digest('hex').substring(0, 16);
}
/**
* Resolve and validate a relative path within MKDOCS_DOCS_PATH.
* Throws if the resolved path escapes the docs root.
*/
function safeResolve(relativePath) {
const normalized = (0, path_1.normalize)(relativePath).replace(/^(\.\.(\/|\\|$))+/, '');
const resolved = (0, path_1.resolve)(DOCS_ROOT, normalized);
// Use DOCS_ROOT + sep to prevent prefix attacks (e.g., /mkdocs/docs-evil matching /mkdocs/docs)
if (resolved !== DOCS_ROOT && !resolved.startsWith(DOCS_ROOT + '/')) {
throw new PathTraversalError();
}
return resolved;
}
class PathTraversalError extends Error {
constructor() {
super('Path traversal not allowed');
this.name = 'PathTraversalError';
}
}
exports.PathTraversalError = PathTraversalError;
class FileNotFoundError extends Error {
constructor(filePath) {
super(`File not found: ${filePath}`);
this.name = 'FileNotFoundError';
}
}
exports.FileNotFoundError = FileNotFoundError;
/**
* Recursively list the file tree under MKDOCS_DOCS_PATH.
* Cached in Redis with 1-hour TTL for root calls.
*/
async function listTree(dir = DOCS_ROOT, relBase = '') {
// Try cache for root call only
if (dir === DOCS_ROOT && !relBase) {
try {
const cached = await redis_1.redis.get(TREE_CACHE_KEY);
if (cached) {
metrics_1.cm_docs_cache_hits.inc({ type: 'tree' });
return JSON.parse(cached);
}
metrics_1.cm_docs_cache_misses.inc({ type: 'tree' });
}
catch (err) {
logger_1.logger.warn('Failed to get cached docs tree:', err);
metrics_1.cm_docs_cache_misses.inc({ type: 'tree' });
}
}
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
const sorted = entries
.filter(e => !e.name.startsWith('.'))
.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory())
return -1;
if (!a.isDirectory() && b.isDirectory())
return 1;
return a.name.localeCompare(b.name);
});
// Parallel I/O instead of sequential for better performance
const nodes = await Promise.all(sorted.map(async (entry) => {
const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
const children = await listTree((0, path_1.join)(dir, entry.name), relPath);
return { name: entry.name, path: relPath, isDirectory: true, children };
}
else {
return { name: entry.name, path: relPath, isDirectory: false };
}
}));
// Cache root result
if (dir === DOCS_ROOT && !relBase) {
try {
await redis_1.redis.setex(TREE_CACHE_KEY, TREE_CACHE_TTL, JSON.stringify(nodes));
}
catch (err) {
logger_1.logger.warn('Failed to cache docs tree:', err);
}
}
return nodes;
}
async function readFileContent(relativePath) {
const cacheKey = `${CACHE_KEY_PREFIX}file:${hashFilePath(relativePath)}`;
// Try cache first
try {
const cached = await redis_1.redis.get(cacheKey);
if (cached) {
metrics_1.cm_docs_cache_hits.inc({ type: 'file' });
return cached;
}
metrics_1.cm_docs_cache_misses.inc({ type: 'file' });
}
catch (err) {
logger_1.logger.warn('Failed to get cached file content:', err);
metrics_1.cm_docs_cache_misses.inc({ type: 'file' });
}
// Read from disk
const fullPath = safeResolve(relativePath);
try {
const content = await (0, promises_1.readFile)(fullPath, 'utf-8');
// Cache the result
try {
await redis_1.redis.setex(cacheKey, FILE_CONTENT_CACHE_TTL, content);
}
catch (err) {
logger_1.logger.warn('Failed to cache file content:', err);
}
return content;
}
catch (err) {
if (err.code === 'ENOENT') {
throw new FileNotFoundError(relativePath);
}
throw err;
}
}
async function writeFileContent(relativePath, content) {
const fullPath = safeResolve(relativePath);
await (0, promises_1.mkdir)((0, path_1.dirname)(fullPath), { recursive: true });
await (0, promises_1.writeFile)(fullPath, content, 'utf-8');
// Invalidate file cache (content changed, structure unchanged)
const cacheKey = `${CACHE_KEY_PREFIX}file:${hashFilePath(relativePath)}`;
try {
await redis_1.redis.del(cacheKey);
}
catch (err) {
logger_1.logger.warn('Failed to invalidate file cache:', err);
}
}
async function createFile(relativePath, content, isDirectory) {
const fullPath = safeResolve(relativePath);
try {
await (0, promises_1.stat)(fullPath);
throw new Error(`Already exists: ${relativePath}`);
}
catch (err) {
if (err.code !== 'ENOENT') {
if (err.message?.startsWith('Already exists'))
throw err;
throw err;
}
}
if (isDirectory) {
await (0, promises_1.mkdir)(fullPath, { recursive: true });
}
else {
await (0, promises_1.mkdir)((0, path_1.dirname)(fullPath), { recursive: true });
await (0, promises_1.writeFile)(fullPath, content || '', 'utf-8');
}
// Invalidate tree cache (structure changed)
try {
await redis_1.redis.del(TREE_CACHE_KEY);
}
catch (err) {
logger_1.logger.warn('Failed to invalidate tree cache:', err);
}
}
async function deleteFile(relativePath) {
const fullPath = safeResolve(relativePath);
try {
const info = await (0, promises_1.stat)(fullPath);
if (info.isDirectory()) {
const entries = await (0, promises_1.readdir)(fullPath);
if (entries.length > 0) {
throw new Error('Directory is not empty');
}
await (0, promises_1.rm)(fullPath, { recursive: false });
}
else {
await (0, promises_1.rm)(fullPath);
}
}
catch (err) {
if (err.code === 'ENOENT') {
throw new FileNotFoundError(relativePath);
}
throw err;
}
// Invalidate both tree and file cache
const fileCacheKey = `${CACHE_KEY_PREFIX}file:${hashFilePath(relativePath)}`;
try {
await Promise.all([
redis_1.redis.del(TREE_CACHE_KEY),
redis_1.redis.del(fileCacheKey),
]);
}
catch (err) {
logger_1.logger.warn('Failed to invalidate caches on delete:', err);
}
}
async function renameFile(fromPath, toPath) {
const fullFrom = safeResolve(fromPath);
const fullTo = safeResolve(toPath);
try {
await (0, promises_1.stat)(fullFrom);
}
catch {
throw new FileNotFoundError(fromPath);
}
await (0, promises_1.mkdir)((0, path_1.dirname)(fullTo), { recursive: true });
await (0, promises_1.rename)(fullFrom, fullTo);
// Invalidate tree and both file paths
const fromCacheKey = `${CACHE_KEY_PREFIX}file:${hashFilePath(fromPath)}`;
const toCacheKey = `${CACHE_KEY_PREFIX}file:${hashFilePath(toPath)}`;
try {
await Promise.all([
redis_1.redis.del(TREE_CACHE_KEY),
redis_1.redis.del(fromCacheKey),
redis_1.redis.del(toCacheKey),
]);
}
catch (err) {
logger_1.logger.warn('Failed to invalidate caches on rename:', err);
}
}
function isEditableFile(relativePath) {
const ext = (0, path_1.extname)(relativePath).toLowerCase();
return ['.md', '.txt', '.yml', '.yaml', '.json', '.css', '.html', '.js'].includes(ext);
}
const ALLOWED_UPLOAD_EXTENSIONS = new Set([
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
'.pdf', '.zip',
]);
async function uploadFile(relativePath, sourcePath) {
const ext = (0, path_1.extname)(relativePath).toLowerCase();
if (!ALLOWED_UPLOAD_EXTENSIONS.has(ext)) {
throw new Error(`File type not allowed: ${ext}`);
}
const fullPath = safeResolve(relativePath);
await (0, promises_1.mkdir)((0, path_1.dirname)(fullPath), { recursive: true });
await (0, promises_1.copyFile)(sourcePath, fullPath);
// Invalidate tree cache (structure changed)
try {
await redis_1.redis.del(TREE_CACHE_KEY);
}
catch (err) {
logger_1.logger.warn('Failed to invalidate tree cache after upload:', err);
}
}
async function invalidateTreeCache() {
try {
await redis_1.redis.del(TREE_CACHE_KEY);
}
catch (err) {
logger_1.logger.warn('Failed to invalidate tree cache:', err);
}
}
/**
* Flatten a FileNode tree into file-only entries, then filter by
* case-insensitive query match on name or path. Name matches score
* higher so they sort first.
*/
async function searchFiles(query, limit = 5) {
const tree = await listTree();
const q = query.toLowerCase();
const matches = [];
function walk(nodes) {
for (const node of nodes) {
if (node.isDirectory) {
if (node.children)
walk(node.children);
}
else {
const nameLower = node.name.toLowerCase();
const pathLower = node.path.toLowerCase();
if (nameLower.includes(q)) {
matches.push({ name: node.name, path: node.path, score: 2 });
}
else if (pathLower.includes(q)) {
matches.push({ name: node.name, path: node.path, score: 1 });
}
}
}
}
walk(tree);
matches.sort((a, b) => b.score - a.score);
return matches.slice(0, limit).map(({ name, path }) => ({ name, path }));
}
exports.docsFilesService = {
listTree,
readFileContent,
writeFileContent,
createFile,
deleteFile,
renameFile,
uploadFile,
safeResolve,
isEditableFile,
invalidateTreeCache,
searchFiles,
};
//# sourceMappingURL=docs-files.service.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
export declare const docsRouter: import("express-serve-static-core").Router;
//# sourceMappingURL=docs.routes.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"docs.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/docs.routes.ts"],"names":[],"mappings":"AA4YA,eAAO,MAAM,UAAU,4CAAS,CAAC"}

View File

@ -1,336 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.docsRouter = void 0;
const express_1 = require("express");
const multer_1 = __importDefault(require("multer"));
const promises_1 = require("fs/promises");
const path_1 = require("path");
const auth_middleware_1 = require("../../middleware/auth.middleware");
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
const env_1 = require("../../config/env");
const roles_1 = require("../../utils/roles");
const logger_1 = require("../../utils/logger");
const health_check_1 = require("../../utils/health-check");
const metrics_1 = require("../../utils/metrics");
const docs_files_service_1 = require("./docs-files.service");
const docs_collab_service_1 = require("./docs-collab.service");
const mkdocs_config_service_1 = require("./mkdocs-config.service");
const header_builder_service_1 = require("./header-builder.service");
const header_builder_schemas_1 = require("./header-builder.schemas");
const router = (0, express_1.Router)();
router.use(auth_middleware_1.authenticate);
router.use(rbac_middleware_1.requireNonTemp);
// Removed duplicated isServiceOnline - now using shared utility from utils/health-check.ts
// GET /api/docs/status — check MkDocs and Code Server availability (content editors only)
router.get('/status', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
try {
const [mkdocsOnline, codeServerOnline, siteServerOnline] = await Promise.all([
(0, health_check_1.isServiceOnline)(env_1.env.MKDOCS_PREVIEW_URL),
(0, health_check_1.isServiceOnline)(env_1.env.CODE_SERVER_URL),
(0, health_check_1.isServiceOnline)(env_1.env.MKDOCS_SITE_SERVER_URL),
]);
res.json({
mkdocs: { online: mkdocsOnline, url: env_1.env.MKDOCS_PREVIEW_URL },
codeServer: { online: codeServerOnline, url: env_1.env.CODE_SERVER_URL },
siteServer: { online: siteServerOnline, url: env_1.env.MKDOCS_SITE_SERVER_URL },
});
}
catch (err) {
logger_1.logger.error('Failed to check docs status', err);
next(err);
}
});
// GET /api/docs/config — return public-facing port numbers for iframe URLs (content editors only)
router.get('/config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, _next) => {
res.json({
codeServerPort: env_1.env.CODE_SERVER_PORT,
mkdocsPort: env_1.env.MKDOCS_PORT,
mkdocsSitePort: env_1.env.MKDOCS_SITE_SERVER_PORT,
});
});
// --- MkDocs Config Endpoints ---
// GET /api/docs/mkdocs-config — read raw mkdocs.yml content (content editors only)
router.get('/mkdocs-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
try {
const content = await mkdocs_config_service_1.mkdocsConfigService.readConfig();
res.json({ content });
}
catch (err) {
logger_1.logger.error('Failed to read mkdocs config', err);
next(err);
}
});
// PUT /api/docs/mkdocs-config — validate + write mkdocs.yml (SUPER_ADMIN only)
router.put('/mkdocs-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
const { content } = req.body;
if (typeof content !== 'string') {
res.status(400).json({ error: { message: 'Content string required', code: 'VALIDATION_ERROR' } });
return;
}
await mkdocs_config_service_1.mkdocsConfigService.writeConfig(content);
res.json({ success: true });
}
catch (err) {
if (err.message?.startsWith('Invalid YAML')) {
res.status(400).json({ error: { message: err.message, code: 'VALIDATION_ERROR' } });
return;
}
logger_1.logger.error('Failed to write mkdocs config', err);
next(err);
}
});
// POST /api/docs/build — trigger mkdocs build in container
router.post('/build', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
try {
const result = await mkdocs_config_service_1.mkdocsConfigService.triggerBuild();
res.json(result);
}
catch (err) {
logger_1.logger.error('MkDocs build failed', err);
next(err);
}
});
// --- Header Builder ---
// GET /api/docs/header-config — read header nav bar config (content editors only)
router.get('/header-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
try {
const config = await header_builder_service_1.headerBuilderService.readConfig();
res.json(config);
}
catch (err) {
logger_1.logger.error('Failed to read header config', err);
next(err);
}
});
// PUT /api/docs/header-config — save header nav bar config + regenerate template
router.put('/header-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
const parsed = header_builder_schemas_1.headerConfigSchema.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({
error: { message: 'Invalid header config', code: 'VALIDATION_ERROR', details: parsed.error.flatten().fieldErrors },
});
return;
}
await header_builder_service_1.headerBuilderService.writeConfig(parsed.data);
// Invalidate docs file tree cache so the new main.html shows up
await docs_files_service_1.docsFilesService.invalidateTreeCache();
res.json({ success: true });
}
catch (err) {
logger_1.logger.error('Failed to save header config', err);
next(err);
}
});
// --- File Upload ---
const ALLOWED_UPLOAD_EXTENSIONS = new Set([
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
'.pdf', '.zip',
]);
const upload = (0, multer_1.default)({
storage: multer_1.default.diskStorage({}), // temp dir
limits: { fileSize: 20 * 1024 * 1024 }, // 20MB
fileFilter: (_req, file, cb) => {
const ext = (0, path_1.extname)(file.originalname).toLowerCase();
if (ALLOWED_UPLOAD_EXTENSIONS.has(ext)) {
cb(null, true);
}
else {
cb(new Error(`File type not allowed: ${ext}`));
}
},
});
// POST /api/docs/upload — upload binary file (image, pdf, etc.)
router.post('/upload', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), upload.single('file'), async (req, res, next) => {
const tempPath = req.file?.path;
try {
metrics_1.cm_docs_operations.inc({ operation: 'upload' });
if (!req.file) {
res.status(400).json({ error: { message: 'No file provided', code: 'VALIDATION_ERROR' } });
return;
}
const targetDir = req.body.path || '';
const fileName = (0, path_1.basename)(req.file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
const relativePath = targetDir ? `${targetDir}/${fileName}` : fileName;
await docs_files_service_1.docsFilesService.uploadFile(relativePath, req.file.path);
// Clean up temp file
try {
await (0, promises_1.rm)(req.file.path);
}
catch { /* ignore */ }
res.json({ success: true, path: relativePath });
}
catch (err) {
// Clean up temp file on error
if (tempPath) {
try {
await (0, promises_1.rm)(tempPath);
}
catch { /* ignore */ }
}
handleFileError(err, res, next);
}
});
// --- File Management Endpoints ---
// GET /api/docs/files — list file tree (content editors only)
router.get('/files', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
metrics_1.cm_docs_operations.inc({ operation: 'list' });
if (req.query['force'] === 'true') {
await docs_files_service_1.docsFilesService.invalidateTreeCache();
}
const tree = await docs_files_service_1.docsFilesService.listTree();
res.json(tree);
}
catch (err) {
logger_1.logger.error('Failed to list docs files', err);
next(err);
}
});
// GET /api/docs/files/search — search files by name/path (content editors only)
router.get('/files/search', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
const search = String(req.query['search'] ?? req.query['q'] ?? '').trim();
if (!search) {
res.json({ files: [] });
return;
}
const limit = Math.min(Math.max(Number(req.query['limit']) || 5, 1), 20);
const files = await docs_files_service_1.docsFilesService.searchFiles(search, limit);
res.json({ files });
}
catch (err) {
logger_1.logger.error('Failed to search docs files', err);
next(err);
}
});
// POST /api/docs/files/rename — rename/move file
router.post('/files/rename', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
metrics_1.cm_docs_operations.inc({ operation: 'rename' });
const { from, to } = req.body;
if (!from || !to) {
res.status(400).json({ error: { message: 'Both "from" and "to" paths are required', code: 'VALIDATION_ERROR' } });
return;
}
await docs_files_service_1.docsFilesService.renameFile(from, to);
// Invalidate old path's collaboration state
docs_collab_service_1.docsCollabService.invalidateDocument(from).catch(() => { });
res.json({ success: true });
}
catch (err) {
handleFileError(err, res, next);
}
});
// GET /api/docs/files/* — read file content (content editors only)
router.get('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
metrics_1.cm_docs_operations.inc({ operation: 'read' });
const filePath = extractWildcardPath(req);
if (!filePath) {
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
return;
}
const content = await docs_files_service_1.docsFilesService.readFileContent(filePath);
res.json({ path: filePath, content });
}
catch (err) {
handleFileError(err, res, next);
}
});
// PUT /api/docs/files/* — write/update file content
router.put('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
metrics_1.cm_docs_operations.inc({ operation: 'write' });
const filePath = extractWildcardPath(req);
if (!filePath) {
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
return;
}
const { content } = req.body;
if (typeof content !== 'string') {
res.status(400).json({ error: { message: 'Content string required', code: 'VALIDATION_ERROR' } });
return;
}
await docs_files_service_1.docsFilesService.writeFileContent(filePath, content);
// Invalidate collaboration state so next session starts fresh from disk
docs_collab_service_1.docsCollabService.invalidateDocument(filePath).catch(() => { });
res.json({ success: true, path: filePath });
}
catch (err) {
handleFileError(err, res, next);
}
});
// POST /api/docs/files/* — create new file or folder
router.post('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
metrics_1.cm_docs_operations.inc({ operation: 'create' });
const filePath = extractWildcardPath(req);
if (!filePath) {
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
return;
}
const { content, isDirectory } = req.body;
await docs_files_service_1.docsFilesService.createFile(filePath, content, isDirectory);
res.status(201).json({ success: true, path: filePath });
}
catch (err) {
handleFileError(err, res, next);
}
});
// DELETE /api/docs/files/* — delete file or empty folder
router.delete('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
try {
metrics_1.cm_docs_operations.inc({ operation: 'delete' });
const filePath = extractWildcardPath(req);
if (!filePath) {
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
return;
}
await docs_files_service_1.docsFilesService.deleteFile(filePath);
// Invalidate collaboration state for deleted file
docs_collab_service_1.docsCollabService.invalidateDocument(filePath).catch(() => { });
res.json({ success: true });
}
catch (err) {
handleFileError(err, res, next);
}
});
/**
* Extract the wildcard path from Express 5 req.params.
* Express 5 uses params[0] for * routes.
*/
function extractWildcardPath(req) {
// Express 5: req.params is array-like for * routes
const params = req.params;
const wildcardParam = params[0] || params['0'];
if (Array.isArray(wildcardParam))
return wildcardParam.join('/');
return wildcardParam || '';
}
function handleFileError(err, res, next) {
if (err instanceof docs_files_service_1.PathTraversalError) {
res.status(403).json({ error: { message: 'Path traversal not allowed', code: 'FORBIDDEN' } });
return;
}
if (err instanceof docs_files_service_1.FileNotFoundError) {
res.status(404).json({ error: { message: err.message, code: 'NOT_FOUND' } });
return;
}
if (err.message?.startsWith('Already exists')) {
res.status(409).json({ error: { message: err.message, code: 'CONFLICT' } });
return;
}
if (err.message === 'Directory is not empty') {
res.status(400).json({ error: { message: 'Directory is not empty', code: 'VALIDATION_ERROR' } });
return;
}
logger_1.logger.error('Docs file operation failed', err);
next(err);
}
exports.docsRouter = router;
//# sourceMappingURL=docs.routes.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,31 +0,0 @@
import { Document } from 'yaml';
/**
* Parse mkdocs.yml content with support for !!python/name: tags.
* Returns a yaml Document that preserves comments and formatting.
*/
declare function parseConfig(content: string): Document;
/**
* Validate that a string is valid YAML (with python tags support).
* Returns null if valid, error message if invalid.
*/
declare function validateYaml(content: string): string | null;
declare function readConfig(): Promise<string>;
declare function writeConfig(content: string): Promise<void>;
/**
* Trigger `mkdocs build --clean` via the build trigger HTTP server
* running inside the MkDocs container on port 8001.
*/
declare function triggerBuild(): Promise<{
success: boolean;
output: string;
duration: number;
}>;
export declare const mkdocsConfigService: {
readConfig: typeof readConfig;
writeConfig: typeof writeConfig;
validateYaml: typeof validateYaml;
parseConfig: typeof parseConfig;
triggerBuild: typeof triggerBuild;
};
export {};
//# sourceMappingURL=mkdocs-config.service.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"mkdocs-config.service.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/mkdocs-config.service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,QAAQ,EAAE,MAAM,MAAM,CAAC;AAqB/C;;;GAGG;AACH,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAS9C;AAED;;;GAGG;AACH,iBAAS,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWpD;AAED,iBAAe,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3C;AAED,iBAAe,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAezD;AAED;;;GAGG;AACH,iBAAe,YAAY,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB7F;AAED,eAAO,MAAM,mBAAmB;;;;;;CAM/B,CAAC"}

View File

@ -1,108 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mkdocsConfigService = void 0;
const promises_1 = require("fs/promises");
const env_1 = require("../../config/env");
const logger_1 = require("../../utils/logger");
const yaml_1 = require("yaml");
/**
* Custom YAML tag schema to preserve !!python/name: and !!python/object: tags.
* Without this, the yaml library would reject these custom tags.
*/
const pythonNameTag = {
identify: () => false,
tag: '!python/name',
collection: undefined,
resolve: (str) => str,
};
const pythonObjectTag = {
identify: () => false,
tag: '!python/object',
collection: undefined,
resolve: (str) => str,
};
/**
* Parse mkdocs.yml content with support for !!python/name: tags.
* Returns a yaml Document that preserves comments and formatting.
*/
function parseConfig(content) {
return (0, yaml_1.parseDocument)(content, {
customTags: (tags) => [
...tags,
pythonNameTag,
pythonObjectTag,
],
keepSourceTokens: true,
});
}
/**
* Validate that a string is valid YAML (with python tags support).
* Returns null if valid, error message if invalid.
*/
function validateYaml(content) {
try {
const doc = parseConfig(content);
const errors = doc.errors;
if (errors.length > 0) {
return errors.map(e => e.message).join('; ');
}
return null;
}
catch (err) {
return err.message;
}
}
async function readConfig() {
return (0, promises_1.readFile)(env_1.env.MKDOCS_CONFIG_PATH, 'utf-8');
}
async function writeConfig(content) {
// Validate YAML first
const error = validateYaml(content);
if (error) {
throw new Error(`Invalid YAML: ${error}`);
}
// Create backup
try {
await (0, promises_1.copyFile)(env_1.env.MKDOCS_CONFIG_PATH, `${env_1.env.MKDOCS_CONFIG_PATH}.bak`);
}
catch {
logger_1.logger.warn('Could not create backup of mkdocs.yml');
}
await (0, promises_1.writeFile)(env_1.env.MKDOCS_CONFIG_PATH, content, 'utf-8');
}
/**
* Trigger `mkdocs build --clean` via the build trigger HTTP server
* running inside the MkDocs container on port 8001.
*/
async function triggerBuild() {
const buildUrl = `${env_1.env.MKDOCS_PREVIEW_URL.replace(':8000', ':8001')}/build`;
const startTime = Date.now();
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 130_000);
const response = await fetch(buildUrl, {
method: 'POST',
signal: controller.signal,
});
clearTimeout(timeout);
const data = await response.json();
return data;
}
catch (err) {
const duration = Date.now() - startTime;
logger_1.logger.error('MkDocs build failed', err);
return {
success: false,
output: `Build error: ${err.message}`,
duration,
};
}
}
exports.mkdocsConfigService = {
readConfig,
writeConfig,
validateYaml,
parseConfig,
triggerBuild,
};
//# sourceMappingURL=mkdocs-config.service.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"mkdocs-config.service.js","sourceRoot":"","sources":["../../../src/modules/docs/mkdocs-config.service.ts"],"names":[],"mappings":";;;AAAA,0CAA4D;AAC5D,0CAAuC;AACvC,+CAA4C;AAC5C,+BAA+C;AAG/C;;;GAGG;AACH,MAAM,aAAa,GAAc;IAC/B,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK;IACrB,GAAG,EAAE,cAAc;IACnB,UAAU,EAAE,SAAkB;IAC9B,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG;CAC9B,CAAC;AAEF,MAAM,eAAe,GAAc;IACjC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK;IACrB,GAAG,EAAE,gBAAgB;IACrB,UAAU,EAAE,SAAkB;IAC9B,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG;CAC9B,CAAC;AAEF;;;GAGG;AACH,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,IAAA,oBAAa,EAAC,OAAO,EAAE;QAC5B,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;YACpB,GAAG,IAAI;YACP,aAAa;YACb,eAAe;SAChB;QACD,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,OAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QAC1B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAQ,GAAa,CAAC,OAAO,CAAC;IAChC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,OAAO,IAAA,mBAAQ,EAAC,SAAG,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,OAAe;IACxC,sBAAsB;IACtB,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,gBAAgB;IAChB,IAAI,CAAC;QACH,MAAM,IAAA,mBAAQ,EAAC,SAAG,CAAC,kBAAkB,EAAE,GAAG,SAAG,CAAC,kBAAkB,MAAM,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,eAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,IAAA,oBAAS,EAAC,SAAG,CAAC,kBAAkB,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,YAAY;IACzB,MAAM,QAAQ,GAAG,GAAG,SAAG,CAAC,kBAAkB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC;IAC7E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;QAE9D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YACrC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,CAAC;QAEtB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA4D,CAAC;QAC7F,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,eAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,gBAAiB,GAAa,CAAC,OAAO,EAAE;YAChD,QAAQ;SACT,CAAC;IACJ,CAAC;AACH,CAAC;AAEY,QAAA,mBAAmB,GAAG;IACjC,UAAU;IACV,WAAW;IACX,YAAY;IACZ,WAAW;IACX,YAAY;CACb,CAAC"}

View File

@ -1,3 +0,0 @@
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=email-templates-admin.routes.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"email-templates-admin.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates-admin.routes.ts"],"names":[],"mappings":"AAuBA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqUxB,eAAe,MAAM,CAAC"}

View File

@ -1,272 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const email_templates_service_1 = require("./email-templates.service");
const email_service_1 = require("../../services/email.service");
const validate_1 = require("../../middleware/validate");
const email_templates_schemas_1 = require("./email-templates.schemas");
const logger_1 = require("../../utils/logger");
const auth_middleware_1 = require("../../middleware/auth.middleware");
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
const client_1 = require("@prisma/client");
const roles_1 = require("../../utils/roles");
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const rate_limit_redis_1 = __importDefault(require("rate-limit-redis"));
const redis_1 = require("../../config/redis");
const seed_email_templates_1 = require("../../scripts/seed-email-templates");
const router = (0, express_1.Router)();
// All email template routes require authentication
router.use(auth_middleware_1.authenticate);
// All routes require broadcast admin role
const requireBroadcastRole = (0, rbac_middleware_1.requireRole)(...roles_1.BROADCAST_ROLES);
/**
* List email templates
* GET /email-templates
*/
router.get('/', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.listEmailTemplatesSchema, 'query'), async (req, res) => {
try {
const result = await email_templates_service_1.emailTemplatesService.list(req.query);
res.json(result);
}
catch (error) {
logger_1.logger.error('Error listing email templates:', error);
res.status(500).json({ error: 'Failed to list email templates' });
}
});
/**
* Get single email template
* GET /email-templates/:id
*/
router.get('/:id', requireBroadcastRole, async (req, res) => {
try {
const template = await email_templates_service_1.emailTemplatesService.getById(req.params.id);
res.json(template);
}
catch (error) {
if (error instanceof Error && error.message === 'Template not found') {
res.status(404).json({ error: 'Template not found' });
return;
}
logger_1.logger.error('Error getting email template:', error);
res.status(500).json({ error: 'Failed to get email template' });
}
});
/**
* Create email template
* POST /email-templates
*/
router.post('/', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.createEmailTemplateSchema), async (req, res) => {
try {
const template = await email_templates_service_1.emailTemplatesService.create(req.body, req.user.id);
res.status(201).json(template);
}
catch (error) {
if (error instanceof Error && error.message.includes('already exists')) {
res.status(409).json({ error: error.message });
return;
}
if (error instanceof Error && error.message.includes('validation failed')) {
res.status(400).json({ error: error.message });
return;
}
logger_1.logger.error('Error creating email template:', error);
res.status(500).json({ error: 'Failed to create email template' });
}
});
/**
* Update email template
* PUT /email-templates/:id
*/
router.put('/:id', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.updateEmailTemplateSchema), async (req, res) => {
try {
const template = await email_templates_service_1.emailTemplatesService.update(req.params.id, req.body, req.user.id);
// Clear cache so changes take effect immediately
email_service_1.emailService.clearDatabaseCache(template.key);
logger_1.logger.info(`Cleared template cache for: ${template.key}`);
res.json(template);
}
catch (error) {
if (error instanceof Error && error.message === 'Template not found') {
res.status(404).json({ error: 'Template not found' });
return;
}
if (error instanceof Error && error.message.includes('validation failed')) {
res.status(400).json({ error: error.message });
return;
}
logger_1.logger.error('Error updating email template:', error);
res.status(500).json({ error: 'Failed to update email template' });
}
});
/**
* Delete email template
* DELETE /email-templates/:id
*/
router.delete('/:id', requireBroadcastRole, async (req, res) => {
try {
// Fetch template before deleting to get the key
const template = await email_templates_service_1.emailTemplatesService.getById(req.params.id);
await email_templates_service_1.emailTemplatesService.delete(req.params.id);
// Clear cache for deleted template
email_service_1.emailService.clearDatabaseCache(template.key);
logger_1.logger.info(`Cleared template cache for deleted template: ${template.key}`);
res.status(204).send();
}
catch (error) {
if (error instanceof Error && error.message === 'Template not found') {
res.status(404).json({ error: 'Template not found' });
return;
}
if (error instanceof Error && error.message.includes('Cannot delete system templates')) {
res.status(403).json({ error: error.message });
return;
}
logger_1.logger.error('Error deleting email template:', error);
res.status(500).json({ error: 'Failed to delete email template' });
}
});
/**
* Get version history
* GET /email-templates/:id/versions
*/
router.get('/:id/versions', requireBroadcastRole, async (req, res) => {
try {
const versions = await email_templates_service_1.emailTemplatesService.getVersions(req.params.id);
res.json(versions);
}
catch (error) {
logger_1.logger.error('Error getting template versions:', error);
res.status(500).json({ error: 'Failed to get template versions' });
}
});
/**
* Get specific version
* GET /email-templates/:id/versions/:versionNumber
*/
router.get('/:id/versions/:versionNumber', requireBroadcastRole, async (req, res) => {
try {
const version = await email_templates_service_1.emailTemplatesService.getVersion(req.params.id, parseInt(req.params.versionNumber, 10));
res.json(version);
}
catch (error) {
if (error instanceof Error && error.message === 'Version not found') {
res.status(404).json({ error: 'Version not found' });
return;
}
logger_1.logger.error('Error getting template version:', error);
res.status(500).json({ error: 'Failed to get template version' });
}
});
/**
* Rollback to previous version
* POST /email-templates/:id/rollback
*/
router.post('/:id/rollback', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.rollbackToVersionSchema), async (req, res) => {
try {
const template = await email_templates_service_1.emailTemplatesService.rollbackToVersion(req.params.id, req.body, req.user.id);
res.json(template);
}
catch (error) {
if (error instanceof Error && (error.message === 'Template not found' || error.message === 'Version not found')) {
res.status(404).json({ error: error.message });
return;
}
logger_1.logger.error('Error rolling back template:', error);
res.status(500).json({ error: 'Failed to rollback template' });
}
});
/**
* Validate template syntax
* POST /email-templates/validate
*/
router.post('/validate', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.validateTemplateSchema), async (req, res) => {
try {
const result = email_templates_service_1.emailTemplatesService.validateTemplate(req.body);
res.json(result);
}
catch (error) {
logger_1.logger.error('Error validating template:', error);
res.status(500).json({ error: 'Failed to validate template' });
}
});
/**
* Send test email
* POST /email-templates/:id/test
* Rate limited to 10 per 15 minutes per user
*/
router.post('/:id/test', requireBroadcastRole, (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10,
standardHeaders: true,
legacyHeaders: false,
store: new rate_limit_redis_1.default({
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
prefix: 'rl:email-template-test:',
}),
keyGenerator: (req) => req.user.id,
}), (0, validate_1.validate)(email_templates_schemas_1.sendTestEmailSchema), async (req, res) => {
try {
const result = await email_templates_service_1.emailTemplatesService.sendTestEmail(req.params.id, req.body, req.user.id);
res.json(result);
}
catch (error) {
if (error instanceof Error && error.message === 'Template not found') {
res.status(404).json({ error: 'Template not found' });
return;
}
logger_1.logger.error('Error sending test email:', error);
res.status(500).json({ error: 'Failed to send test email' });
}
});
/**
* Get test logs for template
* GET /email-templates/:id/test-logs
*/
router.get('/:id/test-logs', requireBroadcastRole, async (req, res) => {
try {
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 10;
const logs = await email_templates_service_1.emailTemplatesService.getTestLogs(req.params.id, limit);
res.json(logs);
}
catch (error) {
logger_1.logger.error('Error getting test logs:', error);
res.status(500).json({ error: 'Failed to get test logs' });
}
});
/**
* Seed templates from filesystem (SUPER_ADMIN only)
* POST /email-templates/seed
*/
router.post('/seed', (0, rbac_middleware_1.requireRole)(client_1.UserRole.SUPER_ADMIN), async (req, res) => {
try {
await (0, seed_email_templates_1.seedEmailTemplates)();
logger_1.logger.info('Email templates seeded via API');
res.json({ success: true, message: 'Templates seeded successfully' });
}
catch (error) {
logger_1.logger.error('Error seeding templates:', error);
res.status(500).json({ error: 'Failed to seed templates' });
}
});
/**
* Clear template cache (SUPER_ADMIN only)
* POST /email-templates/clear-cache
* Body: { key?: string } - Optional template key to clear. If not provided, clears all.
*/
router.post('/clear-cache', (0, rbac_middleware_1.requireRole)(client_1.UserRole.SUPER_ADMIN), async (req, res) => {
try {
const { key } = req.body;
email_service_1.emailService.clearDatabaseCache(key);
logger_1.logger.info(`Template cache cleared${key ? ` for: ${key}` : ' (all)'}`);
res.json({ success: true, cleared: key || 'all' });
}
catch (error) {
logger_1.logger.error('Error clearing template cache:', error);
res.status(500).json({ error: 'Failed to clear template cache' });
}
});
exports.default = router;
//# sourceMappingURL=email-templates-admin.routes.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,327 +0,0 @@
import { z } from 'zod';
export declare const EmailTemplateVariableTypeSchema: z.ZodEnum<["TEXT", "VIDEO"]>;
export type EmailTemplateVariableTypeType = z.infer<typeof EmailTemplateVariableTypeSchema>;
export declare const emailTemplateVariableSchema: z.ZodEffects<z.ZodObject<{
key: z.ZodString;
label: z.ZodString;
description: z.ZodOptional<z.ZodString>;
type: z.ZodDefault<z.ZodEnum<["TEXT", "VIDEO"]>>;
videoId: z.ZodOptional<z.ZodNumber>;
isRequired: z.ZodDefault<z.ZodBoolean>;
isConditional: z.ZodDefault<z.ZodBoolean>;
sampleValue: z.ZodOptional<z.ZodString>;
sortOrder: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}, {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}>, {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}, {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}>;
export declare const listEmailTemplatesSchema: z.ZodObject<{
page: z.ZodDefault<z.ZodNumber>;
limit: z.ZodDefault<z.ZodNumber>;
search: z.ZodOptional<z.ZodString>;
category: z.ZodOptional<z.ZodNativeEnum<{
INFLUENCE: "INFLUENCE";
MAP: "MAP";
SYSTEM: "SYSTEM";
PAYMENT: "PAYMENT";
}>>;
isActive: z.ZodOptional<z.ZodBoolean>;
}, "strip", z.ZodTypeAny, {
limit: number;
page: number;
search?: string | undefined;
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
isActive?: boolean | undefined;
}, {
search?: string | undefined;
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
limit?: number | undefined;
isActive?: boolean | undefined;
page?: number | undefined;
}>;
export type ListEmailTemplatesDto = z.infer<typeof listEmailTemplatesSchema>;
export declare const createEmailTemplateSchema: z.ZodObject<{
key: z.ZodString;
name: z.ZodString;
description: z.ZodOptional<z.ZodString>;
category: z.ZodNativeEnum<{
INFLUENCE: "INFLUENCE";
MAP: "MAP";
SYSTEM: "SYSTEM";
PAYMENT: "PAYMENT";
}>;
subjectLine: z.ZodString;
htmlContent: z.ZodString;
textContent: z.ZodString;
isActive: z.ZodDefault<z.ZodBoolean>;
variables: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
key: z.ZodString;
label: z.ZodString;
description: z.ZodOptional<z.ZodString>;
type: z.ZodDefault<z.ZodEnum<["TEXT", "VIDEO"]>>;
videoId: z.ZodOptional<z.ZodNumber>;
isRequired: z.ZodDefault<z.ZodBoolean>;
isConditional: z.ZodDefault<z.ZodBoolean>;
sampleValue: z.ZodOptional<z.ZodString>;
sortOrder: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}, {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}>, {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}, {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}>, "many">>;
}, "strip", z.ZodTypeAny, {
name: string;
category: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT";
key: string;
isActive: boolean;
subjectLine: string;
htmlContent: string;
textContent: string;
description?: string | undefined;
variables?: {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}[] | undefined;
}, {
name: string;
category: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT";
key: string;
subjectLine: string;
htmlContent: string;
textContent: string;
isActive?: boolean | undefined;
description?: string | undefined;
variables?: {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}[] | undefined;
}>;
export type CreateEmailTemplateDto = z.infer<typeof createEmailTemplateSchema>;
export declare const updateEmailTemplateSchema: z.ZodObject<{
name: z.ZodOptional<z.ZodString>;
description: z.ZodNullable<z.ZodOptional<z.ZodString>>;
category: z.ZodOptional<z.ZodNativeEnum<{
INFLUENCE: "INFLUENCE";
MAP: "MAP";
SYSTEM: "SYSTEM";
PAYMENT: "PAYMENT";
}>>;
subjectLine: z.ZodOptional<z.ZodString>;
htmlContent: z.ZodOptional<z.ZodString>;
textContent: z.ZodOptional<z.ZodString>;
isActive: z.ZodOptional<z.ZodBoolean>;
variables: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
key: z.ZodString;
label: z.ZodString;
description: z.ZodOptional<z.ZodString>;
type: z.ZodDefault<z.ZodEnum<["TEXT", "VIDEO"]>>;
videoId: z.ZodOptional<z.ZodNumber>;
isRequired: z.ZodDefault<z.ZodBoolean>;
isConditional: z.ZodDefault<z.ZodBoolean>;
sampleValue: z.ZodOptional<z.ZodString>;
sortOrder: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}, {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}>, {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}, {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}>, "many">>;
}, "strip", z.ZodTypeAny, {
name?: string | undefined;
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
isActive?: boolean | undefined;
description?: string | null | undefined;
subjectLine?: string | undefined;
htmlContent?: string | undefined;
textContent?: string | undefined;
variables?: {
type: "VIDEO" | "TEXT";
key: string;
sortOrder: number;
label: string;
isRequired: boolean;
isConditional: boolean;
videoId?: number | undefined;
description?: string | undefined;
sampleValue?: string | undefined;
}[] | undefined;
}, {
name?: string | undefined;
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
isActive?: boolean | undefined;
description?: string | null | undefined;
subjectLine?: string | undefined;
htmlContent?: string | undefined;
textContent?: string | undefined;
variables?: {
key: string;
label: string;
type?: "VIDEO" | "TEXT" | undefined;
videoId?: number | undefined;
description?: string | undefined;
sortOrder?: number | undefined;
isRequired?: boolean | undefined;
isConditional?: boolean | undefined;
sampleValue?: string | undefined;
}[] | undefined;
}>;
export type UpdateEmailTemplateDto = z.infer<typeof updateEmailTemplateSchema>;
export declare const rollbackToVersionSchema: z.ZodObject<{
versionNumber: z.ZodNumber;
changeNotes: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
versionNumber: number;
changeNotes?: string | undefined;
}, {
versionNumber: number;
changeNotes?: string | undefined;
}>;
export type RollbackToVersionDto = z.infer<typeof rollbackToVersionSchema>;
export declare const validateTemplateSchema: z.ZodObject<{
htmlContent: z.ZodString;
textContent: z.ZodString;
subjectLine: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
htmlContent: string;
textContent: string;
subjectLine?: string | undefined;
}, {
htmlContent: string;
textContent: string;
subjectLine?: string | undefined;
}>;
export type ValidateTemplateDto = z.infer<typeof validateTemplateSchema>;
export declare const sendTestEmailSchema: z.ZodObject<{
recipientEmail: z.ZodString;
testData: z.ZodRecord<z.ZodString, z.ZodString>;
}, "strip", z.ZodTypeAny, {
recipientEmail: string;
testData: Record<string, string>;
}, {
recipientEmail: string;
testData: Record<string, string>;
}>;
export type SendTestEmailDto = z.infer<typeof sendTestEmailSchema>;
//# sourceMappingURL=email-templates.schemas.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"email-templates.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,+BAA+B,8BAA4B,CAAC;AACzE,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAG5F,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBvC,CAAC;AAGF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;EAMnC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAG7E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUpC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAG/E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASpC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAG/E,eAAO,MAAM,uBAAuB;;;;;;;;;EAGlC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAG3E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;EAIjC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAGzE,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}

View File

@ -1,73 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendTestEmailSchema = exports.validateTemplateSchema = exports.rollbackToVersionSchema = exports.updateEmailTemplateSchema = exports.createEmailTemplateSchema = exports.listEmailTemplatesSchema = exports.emailTemplateVariableSchema = exports.EmailTemplateVariableTypeSchema = void 0;
const zod_1 = require("zod");
const client_1 = require("@prisma/client");
// Variable type enum
exports.EmailTemplateVariableTypeSchema = zod_1.z.enum(['TEXT', 'VIDEO']);
// Variable schema
exports.emailTemplateVariableSchema = zod_1.z.object({
key: zod_1.z.string().regex(/^[A-Z_]+$/, 'Variable key must be uppercase letters and underscores'),
label: zod_1.z.string().min(1).max(100),
description: zod_1.z.string().max(500).optional(),
type: exports.EmailTemplateVariableTypeSchema.default('TEXT'),
videoId: zod_1.z.number().int().positive().optional(),
isRequired: zod_1.z.boolean().default(true),
isConditional: zod_1.z.boolean().default(false),
sampleValue: zod_1.z.string().max(1000).optional(),
sortOrder: zod_1.z.number().int().min(0).default(0),
}).refine((data) => {
// VIDEO type must have videoId
if (data.type === 'VIDEO' && !data.videoId) {
return false;
}
return true;
}, { message: 'VIDEO variables must have a videoId', path: ['videoId'] });
// List templates
exports.listEmailTemplatesSchema = zod_1.z.object({
page: zod_1.z.coerce.number().int().min(1).default(1),
limit: zod_1.z.coerce.number().int().min(1).max(100).default(20),
search: zod_1.z.string().max(200).optional(),
category: zod_1.z.nativeEnum(client_1.EmailTemplateCategory).optional(),
isActive: zod_1.z.coerce.boolean().optional(),
});
// Create template
exports.createEmailTemplateSchema = zod_1.z.object({
key: zod_1.z.string().regex(/^[a-z0-9-]+$/, 'Template key must be lowercase alphanumeric with hyphens').min(1).max(100),
name: zod_1.z.string().min(1).max(200),
description: zod_1.z.string().max(1000).optional(),
category: zod_1.z.nativeEnum(client_1.EmailTemplateCategory),
subjectLine: zod_1.z.string().min(1).max(500),
htmlContent: zod_1.z.string().min(1).max(100000, 'HTML content exceeds 100KB limit'),
textContent: zod_1.z.string().min(1).max(50000, 'Text content exceeds 50KB limit'),
isActive: zod_1.z.boolean().default(true),
variables: zod_1.z.array(exports.emailTemplateVariableSchema).optional(),
});
// Update template
exports.updateEmailTemplateSchema = zod_1.z.object({
name: zod_1.z.string().min(1).max(200).optional(),
description: zod_1.z.string().max(1000).optional().nullable(),
category: zod_1.z.nativeEnum(client_1.EmailTemplateCategory).optional(),
subjectLine: zod_1.z.string().min(1).max(500).optional(),
htmlContent: zod_1.z.string().min(1).max(100000, 'HTML content exceeds 100KB limit').optional(),
textContent: zod_1.z.string().min(1).max(50000, 'Text content exceeds 50KB limit').optional(),
isActive: zod_1.z.boolean().optional(),
variables: zod_1.z.array(exports.emailTemplateVariableSchema).optional(),
});
// Rollback to version
exports.rollbackToVersionSchema = zod_1.z.object({
versionNumber: zod_1.z.number().int().min(1),
changeNotes: zod_1.z.string().max(500).optional(),
});
// Validate template
exports.validateTemplateSchema = zod_1.z.object({
htmlContent: zod_1.z.string().min(1).max(100000),
textContent: zod_1.z.string().min(1).max(50000),
subjectLine: zod_1.z.string().min(1).max(500).optional(),
});
// Send test email
exports.sendTestEmailSchema = zod_1.z.object({
recipientEmail: zod_1.z.string().email('Invalid email address').max(255),
testData: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
});
//# sourceMappingURL=email-templates.schemas.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"email-templates.schemas.js","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates.schemas.ts"],"names":[],"mappings":"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"}

View File

@ -1,266 +0,0 @@
import { Prisma } from '@prisma/client';
import type { ListEmailTemplatesDto, CreateEmailTemplateDto, UpdateEmailTemplateDto, RollbackToVersionDto, ValidateTemplateDto, SendTestEmailDto } from './email-templates.schemas';
interface EmailTemplatesListResponse {
templates: any[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
interface ValidationResult {
valid: boolean;
errors: string[];
warnings?: string[];
extractedVariables?: string[];
}
export declare class EmailTemplatesService {
/**
* List email templates with pagination, search, and filters
*/
list(params: ListEmailTemplatesDto): Promise<EmailTemplatesListResponse>;
/**
* Get a single email template by ID
*/
getById(id: string): Promise<{
_count: {
versions: number;
testLogs: number;
};
createdBy: {
id: string;
email: string;
name: string | null;
};
variables: {
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
id: string;
videoId: number | null;
key: string;
description: string | null;
sortOrder: number;
label: string;
templateId: string;
isRequired: boolean;
isConditional: boolean;
sampleValue: string | null;
}[];
updatedBy: {
id: string;
email: string;
name: string | null;
} | null;
} & {
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
category: import(".prisma/client").$Enums.EmailTemplateCategory;
key: string;
isActive: boolean;
description: string | null;
subjectLine: string;
htmlContent: string;
textContent: string;
isSystem: boolean;
createdByUserId: string;
updatedByUserId: string | null;
}>;
/**
* Get template by key
*/
getByKey(key: string): Promise<({
variables: {
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
id: string;
videoId: number | null;
key: string;
description: string | null;
sortOrder: number;
label: string;
templateId: string;
isRequired: boolean;
isConditional: boolean;
sampleValue: string | null;
}[];
} & {
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
category: import(".prisma/client").$Enums.EmailTemplateCategory;
key: string;
isActive: boolean;
description: string | null;
subjectLine: string;
htmlContent: string;
textContent: string;
isSystem: boolean;
createdByUserId: string;
updatedByUserId: string | null;
}) | null>;
/**
* Create a new email template
*/
create(data: CreateEmailTemplateDto, userId: string): Promise<{
variables: {
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
id: string;
videoId: number | null;
key: string;
description: string | null;
sortOrder: number;
label: string;
templateId: string;
isRequired: boolean;
isConditional: boolean;
sampleValue: string | null;
}[];
} & {
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
category: import(".prisma/client").$Enums.EmailTemplateCategory;
key: string;
isActive: boolean;
description: string | null;
subjectLine: string;
htmlContent: string;
textContent: string;
isSystem: boolean;
createdByUserId: string;
updatedByUserId: string | null;
}>;
/**
* Update an email template
*/
update(id: string, data: UpdateEmailTemplateDto, userId: string): Promise<{
variables: {
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
id: string;
videoId: number | null;
key: string;
description: string | null;
sortOrder: number;
label: string;
templateId: string;
isRequired: boolean;
isConditional: boolean;
sampleValue: string | null;
}[];
} & {
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
category: import(".prisma/client").$Enums.EmailTemplateCategory;
key: string;
isActive: boolean;
description: string | null;
subjectLine: string;
htmlContent: string;
textContent: string;
isSystem: boolean;
createdByUserId: string;
updatedByUserId: string | null;
}>;
/**
* Delete an email template
*/
delete(id: string): Promise<void>;
/**
* Get version history for a template
*/
getVersions(templateId: string): Promise<({
createdBy: {
id: string;
email: string;
name: string | null;
};
} & {
id: string;
createdAt: Date;
subjectLine: string;
htmlContent: string;
textContent: string;
createdByUserId: string;
versionNumber: number;
changeNotes: string | null;
templateId: string;
})[]>;
/**
* Get a specific version
*/
getVersion(templateId: string, versionNumber: number): Promise<{
createdBy: {
id: string;
email: string;
name: string | null;
};
} & {
id: string;
createdAt: Date;
subjectLine: string;
htmlContent: string;
textContent: string;
createdByUserId: string;
versionNumber: number;
changeNotes: string | null;
templateId: string;
}>;
/**
* Rollback to a previous version
*/
rollbackToVersion(templateId: string, data: RollbackToVersionDto, userId: string): Promise<{
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
category: import(".prisma/client").$Enums.EmailTemplateCategory;
key: string;
isActive: boolean;
description: string | null;
subjectLine: string;
htmlContent: string;
textContent: string;
isSystem: boolean;
createdByUserId: string;
updatedByUserId: string | null;
}>;
/**
* Validate template syntax
*/
validateTemplate(data: ValidateTemplateDto): ValidationResult;
/**
* Send test email
*/
sendTestEmail(templateId: string, data: SendTestEmailDto, userId: string): Promise<{
success: boolean;
messageId: string | undefined;
}>;
/**
* Get test logs for a template
*/
getTestLogs(templateId: string, limit?: number): Promise<({
sentBy: {
id: string;
email: string;
name: string | null;
};
} & {
id: string;
success: boolean;
messageId: string | null;
recipientEmail: string;
sentAt: Date;
templateId: string;
testData: Prisma.JsonValue;
errorMessage: string | null;
sentByUserId: string;
})[]>;
}
export declare const emailTemplatesService: EmailTemplatesService;
export {};
//# sourceMappingURL=email-templates.service.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"email-templates.service.d.ts","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AAMnC,UAAU,0BAA0B;IAClC,SAAS,EAAE,GAAG,EAAE,CAAC;IACjB,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,UAAU,gBAAgB;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,qBAAa,qBAAqB;IAChC;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAuD9E;;OAEG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6BxB;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAa1B;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsEzD;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8ErE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM;IAiBvB;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM;;;;;;;;;;;;;;;;;IAcpC;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;;;;;;;;;;;;;;;;;IAsB1D;;OAEG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;IAgDtF;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,gBAAgB;IA2D7D;;OAEG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM;;;;IAmE9E;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;CAcjD;AAED,eAAO,MAAM,qBAAqB,uBAA8B,CAAC"}

View File

@ -1,467 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.emailTemplatesService = exports.EmailTemplatesService = void 0;
const client_1 = require("@prisma/client");
const logger_1 = require("../../utils/logger");
const email_service_1 = require("../../services/email.service");
const prisma = new client_1.PrismaClient();
class EmailTemplatesService {
/**
* List email templates with pagination, search, and filters
*/
async list(params) {
const { page, limit, search, category, isActive } = params;
const skip = (page - 1) * limit;
// Build where clause
const where = {
...(search && {
OR: [
{ key: { contains: search, mode: 'insensitive' } },
{ name: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
],
}),
...(category && { category }),
...(isActive !== undefined && { isActive }),
};
const [data, total] = await Promise.all([
prisma.emailTemplate.findMany({
where,
skip,
take: limit,
orderBy: [{ isSystem: 'desc' }, { category: 'asc' }, { name: 'asc' }],
include: {
variables: {
orderBy: { sortOrder: 'asc' },
},
_count: {
select: {
versions: true,
testLogs: true,
},
},
createdBy: {
select: { id: true, name: true, email: true },
},
updatedBy: {
select: { id: true, name: true, email: true },
},
},
}),
prisma.emailTemplate.count({ where }),
]);
return {
templates: data,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
};
}
/**
* Get a single email template by ID
*/
async getById(id) {
const template = await prisma.emailTemplate.findUnique({
where: { id },
include: {
variables: {
orderBy: { sortOrder: 'asc' },
},
_count: {
select: {
versions: true,
testLogs: true,
},
},
createdBy: {
select: { id: true, name: true, email: true },
},
updatedBy: {
select: { id: true, name: true, email: true },
},
},
});
if (!template) {
throw new Error('Template not found');
}
return template;
}
/**
* Get template by key
*/
async getByKey(key) {
const template = await prisma.emailTemplate.findUnique({
where: { key },
include: {
variables: {
orderBy: { sortOrder: 'asc' },
},
},
});
return template;
}
/**
* Create a new email template
*/
async create(data, userId) {
// Check for duplicate key
const existing = await prisma.emailTemplate.findUnique({
where: { key: data.key },
});
if (existing) {
throw new Error(`Template with key "${data.key}" already exists`);
}
// Validate template content
const validation = this.validateTemplate({
htmlContent: data.htmlContent,
textContent: data.textContent,
subjectLine: data.subjectLine,
});
if (!validation.valid) {
throw new Error(`Template validation failed: ${validation.errors.join(', ')}`);
}
// Create template with variables and initial version in transaction
const template = await prisma.$transaction(async (tx) => {
const newTemplate = await tx.emailTemplate.create({
data: {
key: data.key,
name: data.name,
description: data.description,
category: data.category,
subjectLine: data.subjectLine,
htmlContent: data.htmlContent,
textContent: data.textContent,
isSystem: false, // User-created templates are never system templates
isActive: data.isActive ?? true,
createdByUserId: userId,
variables: data.variables
? {
create: data.variables,
}
: undefined,
},
include: {
variables: true,
},
});
// Create initial version
await tx.emailTemplateVersion.create({
data: {
templateId: newTemplate.id,
versionNumber: 1,
subjectLine: data.subjectLine,
htmlContent: data.htmlContent,
textContent: data.textContent,
changeNotes: 'Initial version',
createdByUserId: userId,
},
});
return newTemplate;
});
logger_1.logger.info(`Email template created: ${template.key} by user ${userId}`);
// Clear email service cache for this template
email_service_1.emailService.clearDatabaseCache(template.key);
return template;
}
/**
* Update an email template
*/
async update(id, data, userId) {
const existing = await this.getById(id);
// Validate template content if provided
if (data.htmlContent || data.textContent) {
const validation = this.validateTemplate({
htmlContent: data.htmlContent ?? existing.htmlContent,
textContent: data.textContent ?? existing.textContent,
subjectLine: data.subjectLine ?? existing.subjectLine,
});
if (!validation.valid) {
throw new Error(`Template validation failed: ${validation.errors.join(', ')}`);
}
}
// Update template and create new version if content changed
const template = await prisma.$transaction(async (tx) => {
// Update template
const updated = await tx.emailTemplate.update({
where: { id },
data: {
...(data.name && { name: data.name }),
...(data.description !== undefined && { description: data.description }),
...(data.category && { category: data.category }),
...(data.subjectLine && { subjectLine: data.subjectLine }),
...(data.htmlContent && { htmlContent: data.htmlContent }),
...(data.textContent && { textContent: data.textContent }),
...(data.isActive !== undefined && { isActive: data.isActive }),
updatedByUserId: userId,
// Handle variables update
...(data.variables && {
variables: {
deleteMany: {},
create: data.variables,
},
}),
},
include: {
variables: true,
},
});
// Create new version if content changed
if (data.subjectLine || data.htmlContent || data.textContent) {
const latestVersion = await tx.emailTemplateVersion.findFirst({
where: { templateId: id },
orderBy: { versionNumber: 'desc' },
});
const nextVersionNumber = (latestVersion?.versionNumber ?? 0) + 1;
await tx.emailTemplateVersion.create({
data: {
templateId: id,
versionNumber: nextVersionNumber,
subjectLine: data.subjectLine ?? existing.subjectLine,
htmlContent: data.htmlContent ?? existing.htmlContent,
textContent: data.textContent ?? existing.textContent,
changeNotes: `Updated via admin interface`,
createdByUserId: userId,
},
});
logger_1.logger.info(`Created version ${nextVersionNumber} for template ${existing.key}`);
}
return updated;
});
logger_1.logger.info(`Email template updated: ${existing.key} by user ${userId}`);
// Clear email service cache
email_service_1.emailService.clearDatabaseCache(existing.key);
return template;
}
/**
* Delete an email template
*/
async delete(id) {
const existing = await this.getById(id);
if (existing.isSystem) {
throw new Error('Cannot delete system templates');
}
await prisma.emailTemplate.delete({
where: { id },
});
logger_1.logger.info(`Email template deleted: ${existing.key}`);
// Clear email service cache
email_service_1.emailService.clearDatabaseCache(existing.key);
}
/**
* Get version history for a template
*/
async getVersions(templateId) {
const versions = await prisma.emailTemplateVersion.findMany({
where: { templateId },
orderBy: { versionNumber: 'desc' },
include: {
createdBy: {
select: { id: true, name: true, email: true },
},
},
});
return versions;
}
/**
* Get a specific version
*/
async getVersion(templateId, versionNumber) {
const version = await prisma.emailTemplateVersion.findUnique({
where: {
templateId_versionNumber: {
templateId,
versionNumber,
},
},
include: {
createdBy: {
select: { id: true, name: true, email: true },
},
},
});
if (!version) {
throw new Error('Version not found');
}
return version;
}
/**
* Rollback to a previous version
*/
async rollbackToVersion(templateId, data, userId) {
const template = await this.getById(templateId);
const targetVersion = await this.getVersion(templateId, data.versionNumber);
// Create new version with content from target version
const result = await prisma.$transaction(async (tx) => {
const latestVersion = await tx.emailTemplateVersion.findFirst({
where: { templateId },
orderBy: { versionNumber: 'desc' },
});
const nextVersionNumber = (latestVersion?.versionNumber ?? 0) + 1;
// Update template to target version content
const updated = await tx.emailTemplate.update({
where: { id: templateId },
data: {
subjectLine: targetVersion.subjectLine,
htmlContent: targetVersion.htmlContent,
textContent: targetVersion.textContent,
updatedByUserId: userId,
},
});
// Create new version (rollback creates a new version, doesn't revert history)
await tx.emailTemplateVersion.create({
data: {
templateId,
versionNumber: nextVersionNumber,
subjectLine: targetVersion.subjectLine,
htmlContent: targetVersion.htmlContent,
textContent: targetVersion.textContent,
changeNotes: data.changeNotes ?? `Rolled back to version ${data.versionNumber}`,
createdByUserId: userId,
},
});
return updated;
});
logger_1.logger.info(`Template ${template.key} rolled back to version ${data.versionNumber} by user ${userId}`);
// Clear email service cache
email_service_1.emailService.clearDatabaseCache(template.key);
return result;
}
/**
* Validate template syntax
*/
validateTemplate(data) {
const errors = [];
const warnings = [];
const variables = new Set();
// Extract variables from content
const variableRegex = /\{\{([A-Z_]+)\}\}/g;
const conditionalRegex = /\{\{#if\s+([A-Z_]+)\}\}|\{\{\/if\}\}/g;
// Check HTML content
let match;
while ((match = variableRegex.exec(data.htmlContent)) !== null) {
variables.add(match[1]);
}
while ((match = conditionalRegex.exec(data.htmlContent)) !== null) {
if (match[1])
variables.add(match[1]);
}
// Check text content
while ((match = variableRegex.exec(data.textContent)) !== null) {
variables.add(match[1]);
}
while ((match = conditionalRegex.exec(data.textContent)) !== null) {
if (match[1])
variables.add(match[1]);
}
// Check subject line if provided
if (data.subjectLine) {
while ((match = variableRegex.exec(data.subjectLine)) !== null) {
variables.add(match[1]);
}
}
// Check for unmatched conditionals
const ifCount = (data.htmlContent.match(/\{\{#if/g) || []).length;
const endifCount = (data.htmlContent.match(/\{\{\/if\}\}/g) || []).length;
if (ifCount !== endifCount) {
errors.push('Unmatched {{#if}} conditional blocks in HTML content');
}
const ifCountText = (data.textContent.match(/\{\{#if/g) || []).length;
const endifCountText = (data.textContent.match(/\{\{\/if\}\}/g) || []).length;
if (ifCountText !== endifCountText) {
errors.push('Unmatched {{#if}} conditional blocks in text content');
}
// Warn if no variables found
if (variables.size === 0) {
warnings.push('No template variables found');
}
return {
valid: errors.length === 0,
errors,
warnings,
extractedVariables: Array.from(variables),
};
}
/**
* Send test email
*/
async sendTestEmail(templateId, data, userId) {
const template = await this.getById(templateId);
try {
// Process template with test data
let htmlContent = template.htmlContent;
let textContent = template.textContent;
let subjectLine = template.subjectLine;
// Replace variables
for (const [key, value] of Object.entries(data.testData)) {
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
htmlContent = htmlContent.replace(regex, value);
textContent = textContent.replace(regex, value);
subjectLine = subjectLine.replace(regex, value);
}
// Handle conditionals (simple implementation)
for (const [key, value] of Object.entries(data.testData)) {
const ifRegex = new RegExp(`\\{\\{#if\\s+${key}\\}\\}([\\s\\S]*?)\\{\\{\\/if\\}\\}`, 'g');
const shouldShow = value && value !== 'false' && value !== '0';
htmlContent = htmlContent.replace(ifRegex, shouldShow ? '$1' : '');
textContent = textContent.replace(ifRegex, shouldShow ? '$1' : '');
}
// Send email via email service
const result = await email_service_1.emailService.sendEmail({
to: data.recipientEmail,
subject: subjectLine,
text: textContent,
html: htmlContent,
});
// Log test email
await prisma.emailTemplateTestLog.create({
data: {
templateId,
recipientEmail: data.recipientEmail,
testData: data.testData,
success: true,
messageId: result.messageId,
sentByUserId: userId,
},
});
logger_1.logger.info(`Test email sent for template ${template.key} to ${data.recipientEmail}`);
return { success: true, messageId: result.messageId };
}
catch (error) {
// Log failed test
await prisma.emailTemplateTestLog.create({
data: {
templateId,
recipientEmail: data.recipientEmail,
testData: data.testData,
success: false,
errorMessage: error instanceof Error ? error.message : String(error),
sentByUserId: userId,
},
});
logger_1.logger.error(`Test email failed for template ${template.key}: ${error}`);
throw error;
}
}
/**
* Get test logs for a template
*/
async getTestLogs(templateId, limit = 10) {
const logs = await prisma.emailTemplateTestLog.findMany({
where: { templateId },
orderBy: { sentAt: 'desc' },
take: limit,
include: {
sentBy: {
select: { id: true, name: true, email: true },
},
},
});
return logs;
}
}
exports.EmailTemplatesService = EmailTemplatesService;
exports.emailTemplatesService = new EmailTemplatesService();
//# sourceMappingURL=email-templates.service.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
declare const publicRouter: import("express-serve-static-core").Router;
declare const adminRouter: import("express-serve-static-core").Router;
export { publicRouter as campaignEmailsPublicRouter, adminRouter as campaignEmailsAdminRouter };
//# sourceMappingURL=campaign-emails.routes.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaign-emails.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.routes.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,YAAY,4CAAW,CAAC;AAqC9B,QAAA,MAAM,WAAW,4CAAW,CAAC;AAiC7B,OAAO,EAAE,YAAY,IAAI,0BAA0B,EAAE,WAAW,IAAI,yBAAyB,EAAE,CAAC"}

View File

@ -1,66 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.campaignEmailsAdminRouter = exports.campaignEmailsPublicRouter = void 0;
const express_1 = require("express");
const campaign_emails_service_1 = require("./campaign-emails.service");
const campaign_emails_schemas_1 = require("./campaign-emails.schemas");
const validate_1 = require("../../../middleware/validate");
const auth_middleware_1 = require("../../../middleware/auth.middleware");
const rbac_middleware_1 = require("../../../middleware/rbac.middleware");
const rate_limit_1 = require("../../../middleware/rate-limit");
const roles_1 = require("../../../utils/roles");
// --- Public Routes (no auth) ---
const publicRouter = (0, express_1.Router)();
exports.campaignEmailsPublicRouter = publicRouter;
// POST /api/campaigns/:slug/send-email
publicRouter.post('/:slug/send-email', rate_limit_1.emailRateLimit, (0, validate_1.validate)(campaign_emails_schemas_1.sendCampaignEmailSchema), async (req, res, next) => {
try {
const slug = req.params.slug;
const senderIp = req.ip || req.socket.remoteAddress;
const result = await campaign_emails_service_1.campaignEmailsService.sendEmail(slug, req.body, senderIp);
res.status(201).json(result);
}
catch (err) {
next(err);
}
});
// POST /api/campaigns/:slug/track-mailto
publicRouter.post('/:slug/track-mailto', rate_limit_1.emailRateLimit, (0, validate_1.validate)(campaign_emails_schemas_1.trackMailtoSchema), async (req, res, next) => {
try {
const slug = req.params.slug;
const senderIp = req.ip || req.socket.remoteAddress;
const result = await campaign_emails_service_1.campaignEmailsService.trackMailto(slug, req.body, senderIp);
res.status(201).json(result);
}
catch (err) {
next(err);
}
});
// --- Admin Routes (auth required) ---
const adminRouter = (0, express_1.Router)();
exports.campaignEmailsAdminRouter = adminRouter;
adminRouter.use(auth_middleware_1.authenticate);
adminRouter.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
// GET /api/campaigns/:id/emails
adminRouter.get('/:id/emails', (0, validate_1.validate)(campaign_emails_schemas_1.listCampaignEmailsSchema, 'query'), async (req, res, next) => {
try {
const id = req.params.id;
const result = await campaign_emails_service_1.campaignEmailsService.listByCampaign(id, req.query);
res.json(result);
}
catch (err) {
next(err);
}
});
// GET /api/campaigns/:id/email-stats
adminRouter.get('/:id/email-stats', async (req, res, next) => {
try {
const id = req.params.id;
const stats = await campaign_emails_service_1.campaignEmailsService.getStats(id);
res.json(stats);
}
catch (err) {
next(err);
}
});
//# sourceMappingURL=campaign-emails.routes.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaign-emails.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.routes.ts"],"names":[],"mappings":"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"}

View File

@ -1,116 +0,0 @@
import { z } from 'zod';
export declare const sendCampaignEmailSchema: z.ZodObject<{
userEmail: z.ZodString;
userName: z.ZodString;
postalCode: z.ZodString;
recipientEmail: z.ZodString;
recipientName: z.ZodOptional<z.ZodString>;
recipientTitle: z.ZodOptional<z.ZodString>;
recipientLevel: z.ZodOptional<z.ZodNativeEnum<{
FEDERAL: "FEDERAL";
PROVINCIAL: "PROVINCIAL";
MUNICIPAL: "MUNICIPAL";
SCHOOL_BOARD: "SCHOOL_BOARD";
}>>;
emailMethod: z.ZodNativeEnum<{
SMTP: "SMTP";
MAILTO: "MAILTO";
}>;
customEmailSubject: z.ZodOptional<z.ZodString>;
customEmailBody: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
userEmail: string;
userName: string;
recipientEmail: string;
emailMethod: "SMTP" | "MAILTO";
postalCode: string;
recipientName?: string | undefined;
recipientTitle?: string | undefined;
recipientLevel?: "FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD" | undefined;
customEmailSubject?: string | undefined;
customEmailBody?: string | undefined;
}, {
userEmail: string;
userName: string;
recipientEmail: string;
emailMethod: "SMTP" | "MAILTO";
postalCode: string;
recipientName?: string | undefined;
recipientTitle?: string | undefined;
recipientLevel?: "FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD" | undefined;
customEmailSubject?: string | undefined;
customEmailBody?: string | undefined;
}>;
export declare const trackMailtoSchema: z.ZodObject<{
recipientEmail: z.ZodString;
recipientName: z.ZodOptional<z.ZodString>;
recipientTitle: z.ZodOptional<z.ZodString>;
recipientLevel: z.ZodOptional<z.ZodNativeEnum<{
FEDERAL: "FEDERAL";
PROVINCIAL: "PROVINCIAL";
MUNICIPAL: "MUNICIPAL";
SCHOOL_BOARD: "SCHOOL_BOARD";
}>>;
userEmail: z.ZodOptional<z.ZodString>;
userName: z.ZodOptional<z.ZodString>;
postalCode: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
recipientEmail: string;
userEmail?: string | undefined;
userName?: string | undefined;
recipientName?: string | undefined;
recipientTitle?: string | undefined;
recipientLevel?: "FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD" | undefined;
postalCode?: string | undefined;
}, {
recipientEmail: string;
userEmail?: string | undefined;
userName?: string | undefined;
recipientName?: string | undefined;
recipientTitle?: string | undefined;
recipientLevel?: "FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD" | undefined;
postalCode?: string | undefined;
}>;
export declare const listCampaignEmailsSchema: z.ZodObject<{
page: z.ZodDefault<z.ZodNumber>;
limit: z.ZodDefault<z.ZodNumber>;
status: z.ZodOptional<z.ZodNativeEnum<{
QUEUED: "QUEUED";
SENT: "SENT";
FAILED: "FAILED";
CLICKED: "CLICKED";
USER_INFO_CAPTURED: "USER_INFO_CAPTURED";
}>>;
emailMethod: z.ZodOptional<z.ZodNativeEnum<{
SMTP: "SMTP";
MAILTO: "MAILTO";
}>>;
}, "strip", z.ZodTypeAny, {
limit: number;
page: number;
status?: "QUEUED" | "SENT" | "FAILED" | "CLICKED" | "USER_INFO_CAPTURED" | undefined;
emailMethod?: "SMTP" | "MAILTO" | undefined;
}, {
status?: "QUEUED" | "SENT" | "FAILED" | "CLICKED" | "USER_INFO_CAPTURED" | undefined;
limit?: number | undefined;
page?: number | undefined;
emailMethod?: "SMTP" | "MAILTO" | undefined;
}>;
export declare const campaignSlugParamSchema: z.ZodObject<{
slug: z.ZodString;
}, "strip", z.ZodTypeAny, {
slug: string;
}, {
slug: string;
}>;
export declare const campaignIdParamSchema: z.ZodObject<{
id: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
}, {
id: string;
}>;
export type SendCampaignEmailInput = z.infer<typeof sendCampaignEmailSchema>;
export type TrackMailtoInput = z.infer<typeof trackMailtoSchema>;
export type ListCampaignEmailsInput = z.infer<typeof listCampaignEmailsSchema>;
//# sourceMappingURL=campaign-emails.schemas.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaign-emails.schemas.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWlC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQ5B,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;EAKnC,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;EAElC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;EAEhC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}

View File

@ -1,39 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.campaignIdParamSchema = exports.campaignSlugParamSchema = exports.listCampaignEmailsSchema = exports.trackMailtoSchema = exports.sendCampaignEmailSchema = void 0;
const zod_1 = require("zod");
const client_1 = require("@prisma/client");
exports.sendCampaignEmailSchema = zod_1.z.object({
userEmail: zod_1.z.string().email('Valid email is required'),
userName: zod_1.z.string().min(1, 'Name is required'),
postalCode: zod_1.z.string().min(1, 'Postal code is required'),
recipientEmail: zod_1.z.string().email('Valid recipient email is required'),
recipientName: zod_1.z.string().optional(),
recipientTitle: zod_1.z.string().optional(),
recipientLevel: zod_1.z.nativeEnum(client_1.GovernmentLevel).optional(),
emailMethod: zod_1.z.nativeEnum(client_1.EmailMethod),
customEmailSubject: zod_1.z.string().optional(),
customEmailBody: zod_1.z.string().optional(),
});
exports.trackMailtoSchema = zod_1.z.object({
recipientEmail: zod_1.z.string().email('Valid recipient email is required'),
recipientName: zod_1.z.string().optional(),
recipientTitle: zod_1.z.string().optional(),
recipientLevel: zod_1.z.nativeEnum(client_1.GovernmentLevel).optional(),
userEmail: zod_1.z.string().email().optional(),
userName: zod_1.z.string().optional(),
postalCode: zod_1.z.string().optional(),
});
exports.listCampaignEmailsSchema = zod_1.z.object({
page: zod_1.z.coerce.number().int().positive().default(1),
limit: zod_1.z.coerce.number().int().positive().max(100).default(20),
status: zod_1.z.nativeEnum(client_1.CampaignEmailStatus).optional(),
emailMethod: zod_1.z.nativeEnum(client_1.EmailMethod).optional(),
});
exports.campaignSlugParamSchema = zod_1.z.object({
slug: zod_1.z.string().min(1),
});
exports.campaignIdParamSchema = zod_1.z.object({
id: zod_1.z.string().min(1),
});
//# sourceMappingURL=campaign-emails.schemas.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaign-emails.schemas.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,2CAAmF;AAEtE,QAAA,uBAAuB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9C,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,yBAAyB,CAAC;IACtD,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAC/C,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC;IACxD,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mCAAmC,CAAC;IACrE,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,cAAc,EAAE,OAAC,CAAC,UAAU,CAAC,wBAAe,CAAC,CAAC,QAAQ,EAAE;IACxD,WAAW,EAAE,OAAC,CAAC,UAAU,CAAC,oBAAW,CAAC;IACtC,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAEU,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mCAAmC,CAAC;IACrE,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,cAAc,EAAE,OAAC,CAAC,UAAU,CAAC,wBAAe,CAAC,CAAC,QAAQ,EAAE;IACxD,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IACxC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEU,QAAA,wBAAwB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC/C,IAAI,EAAE,OAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,KAAK,EAAE,OAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9D,MAAM,EAAE,OAAC,CAAC,UAAU,CAAC,4BAAmB,CAAC,CAAC,QAAQ,EAAE;IACpD,WAAW,EAAE,OAAC,CAAC,UAAU,CAAC,oBAAW,CAAC,CAAC,QAAQ,EAAE;CAClD,CAAC,CAAC;AAEU,QAAA,uBAAuB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAEU,QAAA,qBAAqB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC5C,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACtB,CAAC,CAAC"}

View File

@ -1,36 +0,0 @@
import type { SendCampaignEmailInput, TrackMailtoInput, ListCampaignEmailsInput } from './campaign-emails.schemas';
export declare const campaignEmailsService: {
sendEmail(slug: string, data: SendCampaignEmailInput, senderIp?: string): Promise<{
id: string;
status: import(".prisma/client").$Enums.CampaignEmailStatus;
emailMethod: import(".prisma/client").$Enums.EmailMethod;
}>;
trackMailto(slug: string, data: TrackMailtoInput, senderIp?: string): Promise<{
id: string;
status: import(".prisma/client").$Enums.CampaignEmailStatus;
emailMethod: import(".prisma/client").$Enums.EmailMethod;
}>;
listByCampaign(campaignId: string, filters: ListCampaignEmailsInput): Promise<{
emails: {
status: import(".prisma/client").$Enums.CampaignEmailStatus;
id: string;
subject: string;
userEmail: string | null;
userName: string | null;
userPostalCode: string | null;
recipientEmail: string;
recipientName: string | null;
recipientLevel: import(".prisma/client").$Enums.GovernmentLevel | null;
emailMethod: import(".prisma/client").$Enums.EmailMethod;
sentAt: Date;
}[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}>;
getStats(campaignId: string): Promise<Record<string, number>>;
};
//# sourceMappingURL=campaign-emails.service.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaign-emails.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEnH,eAAO,MAAM,qBAAqB;oBACV,MAAM,QAAQ,sBAAsB,aAAa,MAAM;;;;;sBA+GrD,MAAM,QAAQ,gBAAgB,aAAa,MAAM;;;;;+BAwDxC,MAAM,WAAW,uBAAuB;;;;;;;;;;;;;;;;;;;;;yBA0C9C,MAAM;CAqClC,CAAC"}

View File

@ -1,268 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.campaignEmailsService = void 0;
const client_1 = require("@prisma/client");
const database_1 = require("../../../config/database");
const error_handler_1 = require("../../../middleware/error-handler");
const email_queue_service_1 = require("../../../services/email-queue.service");
const metrics_1 = require("../../../utils/metrics");
const crm_activity_1 = require("../../../utils/crm-activity");
const group_service_1 = require("../../social/group.service");
const achievements_service_1 = require("../../social/achievements.service");
exports.campaignEmailsService = {
async sendEmail(slug, data, senderIp) {
const campaign = await database_1.prisma.campaign.findUnique({
where: { slug },
select: {
id: true,
slug: true,
title: true,
status: true,
emailSubject: true,
emailBody: true,
allowSmtpEmail: true,
allowMailtoLink: true,
allowEmailEditing: true,
},
});
if (!campaign) {
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
}
if (campaign.status !== client_1.CampaignStatus.ACTIVE) {
throw new error_handler_1.AppError(400, 'Campaign is not active', 'CAMPAIGN_NOT_ACTIVE');
}
if (data.emailMethod === client_1.EmailMethod.SMTP && !campaign.allowSmtpEmail) {
throw new error_handler_1.AppError(400, 'SMTP email is not enabled for this campaign', 'SMTP_NOT_ALLOWED');
}
if (data.emailMethod === client_1.EmailMethod.MAILTO && !campaign.allowMailtoLink) {
throw new error_handler_1.AppError(400, 'Mailto link is not enabled for this campaign', 'MAILTO_NOT_ALLOWED');
}
// Determine subject and body (custom only if campaign allows editing)
const subject = (campaign.allowEmailEditing && data.customEmailSubject)
? data.customEmailSubject
: campaign.emailSubject;
const message = (campaign.allowEmailEditing && data.customEmailBody)
? data.customEmailBody
: campaign.emailBody;
const status = data.emailMethod === client_1.EmailMethod.SMTP
? client_1.CampaignEmailStatus.QUEUED
: client_1.CampaignEmailStatus.CLICKED;
const campaignEmail = await database_1.prisma.campaignEmail.create({
data: {
campaignId: campaign.id,
campaignSlug: campaign.slug,
userEmail: data.userEmail,
userName: data.userName,
userPostalCode: data.postalCode,
recipientEmail: data.recipientEmail,
recipientName: data.recipientName,
recipientTitle: data.recipientTitle,
recipientLevel: data.recipientLevel,
emailMethod: data.emailMethod,
subject,
message,
status,
senderIp,
},
});
if (data.emailMethod === client_1.EmailMethod.SMTP) {
await email_queue_service_1.emailQueueService.addCampaignEmail({
campaignEmailId: campaignEmail.id,
recipientEmail: data.recipientEmail,
recipientName: data.recipientName,
recipientLevel: data.recipientLevel,
userEmail: data.userEmail,
userName: data.userName,
postalCode: data.postalCode,
subject,
message,
campaignTitle: campaign.title,
});
}
(0, metrics_1.recordCampaignEmail)(campaign.id);
// CRM activity (fire-and-forget)
(0, crm_activity_1.recordCrmActivity)({
email: data.userEmail,
activityType: 'EMAIL_SENT',
title: `Sent campaign email: ${campaign.title}`,
metadata: { campaignId: campaign.id, campaignSlug: campaign.slug, recipientEmail: data.recipientEmail, emailMethod: data.emailMethod },
}).catch(() => { });
// Social group sync (fire-and-forget)
group_service_1.groupService.syncCampaignTeam(campaign.id).catch(() => { });
// Achievement check for registered users (fire-and-forget)
database_1.prisma.user.findUnique({ where: { email: data.userEmail }, select: { id: true } })
.then((user) => {
if (user)
achievements_service_1.achievementsService.checkAndUnlock(user.id, ['campaigns']).catch(() => { });
})
.catch(() => { });
// Fire-and-forget: check campaign milestones
Promise.resolve().then(() => __importStar(require('../../social/impact-stories.service'))).then(({ impactStoriesService }) => {
impactStoriesService.checkMilestones(campaign.id).catch(() => { });
}).catch(() => { });
return {
id: campaignEmail.id,
status: campaignEmail.status,
emailMethod: campaignEmail.emailMethod,
};
},
async trackMailto(slug, data, senderIp) {
const campaign = await database_1.prisma.campaign.findUnique({
where: { slug },
select: {
id: true,
slug: true,
title: true,
status: true,
emailSubject: true,
emailBody: true,
allowMailtoLink: true,
},
});
if (!campaign) {
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
}
if (campaign.status !== client_1.CampaignStatus.ACTIVE) {
throw new error_handler_1.AppError(400, 'Campaign is not active', 'CAMPAIGN_NOT_ACTIVE');
}
const campaignEmail = await database_1.prisma.campaignEmail.create({
data: {
campaignId: campaign.id,
campaignSlug: campaign.slug,
userEmail: data.userEmail,
userName: data.userName,
userPostalCode: data.postalCode,
recipientEmail: data.recipientEmail,
recipientName: data.recipientName,
recipientTitle: data.recipientTitle,
recipientLevel: data.recipientLevel,
emailMethod: client_1.EmailMethod.MAILTO,
subject: campaign.emailSubject,
message: campaign.emailBody,
status: client_1.CampaignEmailStatus.CLICKED,
senderIp,
},
});
// Social group sync (fire-and-forget)
group_service_1.groupService.syncCampaignTeam(campaign.id).catch(() => { });
// Fire-and-forget: check campaign milestones
Promise.resolve().then(() => __importStar(require('../../social/impact-stories.service'))).then(({ impactStoriesService }) => {
impactStoriesService.checkMilestones(campaign.id).catch(() => { });
}).catch(() => { });
return {
id: campaignEmail.id,
status: campaignEmail.status,
emailMethod: campaignEmail.emailMethod,
};
},
async listByCampaign(campaignId, filters) {
const { page, limit, status, emailMethod } = filters;
const skip = (page - 1) * limit;
const where = { campaignId };
if (status)
where.status = status;
if (emailMethod)
where.emailMethod = emailMethod;
const [emails, total] = await Promise.all([
database_1.prisma.campaignEmail.findMany({
where,
skip,
take: limit,
orderBy: { sentAt: 'desc' },
select: {
id: true,
userEmail: true,
userName: true,
userPostalCode: true,
recipientEmail: true,
recipientName: true,
recipientLevel: true,
emailMethod: true,
subject: true,
status: true,
sentAt: true,
},
}),
database_1.prisma.campaignEmail.count({ where }),
]);
return {
emails,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
};
},
async getStats(campaignId) {
const [totals, byMethod] = await Promise.all([
database_1.prisma.campaignEmail.groupBy({
by: ['status'],
where: { campaignId },
_count: true,
}),
database_1.prisma.campaignEmail.groupBy({
by: ['emailMethod'],
where: { campaignId },
_count: true,
}),
]);
const stats = {
total: 0,
queued: 0,
sent: 0,
failed: 0,
clicked: 0,
smtpCount: 0,
mailtoCount: 0,
};
for (const row of totals) {
stats.total += row._count;
const key = row.status.toLowerCase();
if (key in stats)
stats[key] = row._count;
}
for (const row of byMethod) {
if (row.emailMethod === client_1.EmailMethod.SMTP)
stats.smtpCount = row._count;
if (row.emailMethod === client_1.EmailMethod.MAILTO)
stats.mailtoCount = row._count;
}
return stats;
},
};
//# sourceMappingURL=campaign-emails.service.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
declare const router: import("express-serve-static-core").Router;
export { router as campaignPublicRouter };
//# sourceMappingURL=campaigns-public.routes.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaigns-public.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns-public.routes.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2HxB,OAAO,EAAE,MAAM,IAAI,oBAAoB,EAAE,CAAC"}

View File

@ -1,120 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.campaignPublicRouter = void 0;
const express_1 = require("express");
const campaigns_service_1 = require("./campaigns.service");
const database_1 = require("../../../config/database");
const redis_1 = require("../../../config/redis");
const router = (0, express_1.Router)();
exports.campaignPublicRouter = router;
// GET /api/campaigns/public — list all active campaigns (public)
router.get('/public', async (_req, res, next) => {
try {
const campaigns = await campaigns_service_1.campaignsService.findActiveCampaigns();
res.json(campaigns);
}
catch (err) {
next(err);
}
});
// GET /api/campaigns/:slug/details — public campaign data (ACTIVE only)
router.get('/:slug/details', async (req, res, next) => {
try {
const slug = req.params.slug;
const campaign = await campaigns_service_1.campaignsService.findBySlugPublic(slug);
res.json(campaign);
}
catch (err) {
next(err);
}
});
// GET /api/campaigns/:slug/related — related campaigns + upcoming shifts
router.get('/:slug/related', async (req, res, next) => {
try {
const slug = req.params.slug;
const cacheKey = `campaign:related:${slug}`;
// Check cache
try {
const cached = await redis_1.redis.get(cacheKey);
if (cached) {
res.json(JSON.parse(cached));
return;
}
}
catch { /* cache miss */ }
// Find current campaign
const campaign = await database_1.prisma.campaign.findFirst({
where: { slug, status: 'ACTIVE' },
select: { id: true, targetGovernmentLevels: true },
});
if (!campaign) {
res.json({ campaigns: [], shifts: [] });
return;
}
// Related campaigns: same gov levels, exclude current, limit 3
const relatedCampaigns = await database_1.prisma.campaign.findMany({
where: {
status: 'ACTIVE',
id: { not: campaign.id },
...(campaign.targetGovernmentLevels.length > 0 && {
targetGovernmentLevels: { hasSome: campaign.targetGovernmentLevels },
}),
},
select: {
id: true,
slug: true,
title: true,
description: true,
_count: { select: { emails: true } },
},
orderBy: { createdAt: 'desc' },
take: 3,
});
// Related shifts: upcoming, limit 3
const today = new Date();
today.setHours(0, 0, 0, 0);
const relatedShifts = await database_1.prisma.shift.findMany({
where: {
date: { gte: today },
status: 'OPEN',
},
select: {
id: true,
title: true,
date: true,
startTime: true,
location: true,
maxVolunteers: true,
_count: { select: { signups: true } },
},
orderBy: { date: 'asc' },
take: 3,
});
const result = {
campaigns: relatedCampaigns.map(c => ({
id: c.id,
slug: c.slug,
title: c.title,
description: c.description?.slice(0, 150) ?? null,
emailCount: c._count.emails,
})),
shifts: relatedShifts.map(s => ({
id: s.id,
title: s.title,
startTime: `${s.date.toISOString().split('T')[0]}T${s.startTime}:00`,
location: s.location,
currentVolunteers: s._count.signups,
maxVolunteers: s.maxVolunteers,
})),
};
try {
await redis_1.redis.setex(cacheKey, 300, JSON.stringify(result));
}
catch { /* non-critical */ }
res.json(result);
}
catch (err) {
next(err);
}
});
//# sourceMappingURL=campaigns-public.routes.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaigns-public.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns-public.routes.ts"],"names":[],"mappings":"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"}

View File

@ -1,3 +0,0 @@
declare const router: import("express-serve-static-core").Router;
export { router as campaignsRouter };
//# sourceMappingURL=campaigns.routes.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaigns.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA6ExB,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,CAAC"}

View File

@ -1,69 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.campaignsRouter = void 0;
const express_1 = require("express");
const campaigns_service_1 = require("./campaigns.service");
const campaigns_schemas_1 = require("./campaigns.schemas");
const validate_1 = require("../../../middleware/validate");
const auth_middleware_1 = require("../../../middleware/auth.middleware");
const rbac_middleware_1 = require("../../../middleware/rbac.middleware");
const roles_1 = require("../../../utils/roles");
const router = (0, express_1.Router)();
exports.campaignsRouter = router;
// All campaign admin routes require authentication + admin role
router.use(auth_middleware_1.authenticate);
router.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
// GET /api/campaigns — list campaigns with pagination/filters
router.get('/', (0, validate_1.validate)(campaigns_schemas_1.listCampaignsSchema, 'query'), async (req, res, next) => {
try {
const result = await campaigns_service_1.campaignsService.findAll(req.query, req.user);
res.json(result);
}
catch (err) {
next(err);
}
});
// GET /api/campaigns/:id — get single campaign
router.get('/:id', async (req, res, next) => {
try {
const id = req.params.id;
const campaign = await campaigns_service_1.campaignsService.findById(id);
res.json(campaign);
}
catch (err) {
next(err);
}
});
// POST /api/campaigns — create campaign
router.post('/', (0, validate_1.validate)(campaigns_schemas_1.createCampaignSchema), async (req, res, next) => {
try {
const campaign = await campaigns_service_1.campaignsService.create(req.body, req.user);
res.status(201).json(campaign);
}
catch (err) {
next(err);
}
});
// PUT /api/campaigns/:id — update campaign
router.put('/:id', (0, validate_1.validate)(campaigns_schemas_1.updateCampaignSchema), async (req, res, next) => {
try {
const id = req.params.id;
const campaign = await campaigns_service_1.campaignsService.update(id, req.body);
res.json(campaign);
}
catch (err) {
next(err);
}
});
// DELETE /api/campaigns/:id — delete campaign
router.delete('/:id', async (req, res, next) => {
try {
const id = req.params.id;
await campaigns_service_1.campaignsService.delete(id);
res.status(204).send();
}
catch (err) {
next(err);
}
});
//# sourceMappingURL=campaigns.routes.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaigns.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":"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"}

View File

@ -1,261 +0,0 @@
import { z } from 'zod';
export declare const createCampaignSchema: z.ZodObject<{
title: z.ZodString;
description: z.ZodOptional<z.ZodString>;
emailSubject: z.ZodString;
emailBody: z.ZodString;
callToAction: z.ZodOptional<z.ZodString>;
status: z.ZodDefault<z.ZodOptional<z.ZodNativeEnum<{
DRAFT: "DRAFT";
ACTIVE: "ACTIVE";
PAUSED: "PAUSED";
ARCHIVED: "ARCHIVED";
}>>>;
targetGovernmentLevels: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodNativeEnum<{
FEDERAL: "FEDERAL";
PROVINCIAL: "PROVINCIAL";
MUNICIPAL: "MUNICIPAL";
SCHOOL_BOARD: "SCHOOL_BOARD";
}>, "many">>>;
allowSmtpEmail: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
allowMailtoLink: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
collectUserInfo: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
showEmailCount: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
showCallCount: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
allowEmailEditing: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
allowCustomRecipients: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
showResponseWall: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
highlightCampaign: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
coverPhoto: z.ZodOptional<z.ZodString>;
coverVideoId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
}, "strip", z.ZodTypeAny, {
status: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED";
title: string;
emailSubject: string;
emailBody: string;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[];
description?: string | undefined;
coverPhoto?: string | undefined;
coverVideoId?: number | null | undefined;
callToAction?: string | undefined;
}, {
title: string;
emailSubject: string;
emailBody: string;
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
description?: string | undefined;
coverPhoto?: string | undefined;
coverVideoId?: number | null | undefined;
callToAction?: string | undefined;
allowSmtpEmail?: boolean | undefined;
allowMailtoLink?: boolean | undefined;
collectUserInfo?: boolean | undefined;
showEmailCount?: boolean | undefined;
showCallCount?: boolean | undefined;
allowEmailEditing?: boolean | undefined;
allowCustomRecipients?: boolean | undefined;
showResponseWall?: boolean | undefined;
highlightCampaign?: boolean | undefined;
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
}>;
export declare const updateCampaignSchema: z.ZodObject<{
title: z.ZodOptional<z.ZodString>;
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
emailSubject: z.ZodOptional<z.ZodString>;
emailBody: z.ZodOptional<z.ZodString>;
callToAction: z.ZodOptional<z.ZodNullable<z.ZodString>>;
status: z.ZodOptional<z.ZodNativeEnum<{
DRAFT: "DRAFT";
ACTIVE: "ACTIVE";
PAUSED: "PAUSED";
ARCHIVED: "ARCHIVED";
}>>;
targetGovernmentLevels: z.ZodOptional<z.ZodArray<z.ZodNativeEnum<{
FEDERAL: "FEDERAL";
PROVINCIAL: "PROVINCIAL";
MUNICIPAL: "MUNICIPAL";
SCHOOL_BOARD: "SCHOOL_BOARD";
}>, "many">>;
allowSmtpEmail: z.ZodOptional<z.ZodBoolean>;
allowMailtoLink: z.ZodOptional<z.ZodBoolean>;
collectUserInfo: z.ZodOptional<z.ZodBoolean>;
showEmailCount: z.ZodOptional<z.ZodBoolean>;
showCallCount: z.ZodOptional<z.ZodBoolean>;
allowEmailEditing: z.ZodOptional<z.ZodBoolean>;
allowCustomRecipients: z.ZodOptional<z.ZodBoolean>;
showResponseWall: z.ZodOptional<z.ZodBoolean>;
highlightCampaign: z.ZodOptional<z.ZodBoolean>;
coverPhoto: z.ZodOptional<z.ZodNullable<z.ZodString>>;
coverVideoId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
}, "strip", z.ZodTypeAny, {
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
title?: string | undefined;
description?: string | null | undefined;
coverPhoto?: string | null | undefined;
coverVideoId?: number | null | undefined;
emailSubject?: string | undefined;
emailBody?: string | undefined;
callToAction?: string | null | undefined;
allowSmtpEmail?: boolean | undefined;
allowMailtoLink?: boolean | undefined;
collectUserInfo?: boolean | undefined;
showEmailCount?: boolean | undefined;
showCallCount?: boolean | undefined;
allowEmailEditing?: boolean | undefined;
allowCustomRecipients?: boolean | undefined;
showResponseWall?: boolean | undefined;
highlightCampaign?: boolean | undefined;
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
}, {
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
title?: string | undefined;
description?: string | null | undefined;
coverPhoto?: string | null | undefined;
coverVideoId?: number | null | undefined;
emailSubject?: string | undefined;
emailBody?: string | undefined;
callToAction?: string | null | undefined;
allowSmtpEmail?: boolean | undefined;
allowMailtoLink?: boolean | undefined;
collectUserInfo?: boolean | undefined;
showEmailCount?: boolean | undefined;
showCallCount?: boolean | undefined;
allowEmailEditing?: boolean | undefined;
allowCustomRecipients?: boolean | undefined;
showResponseWall?: boolean | undefined;
highlightCampaign?: boolean | undefined;
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
}>;
export declare const listCampaignsSchema: z.ZodObject<{
page: z.ZodDefault<z.ZodNumber>;
limit: z.ZodDefault<z.ZodNumber>;
search: z.ZodOptional<z.ZodString>;
status: z.ZodOptional<z.ZodNativeEnum<{
DRAFT: "DRAFT";
ACTIVE: "ACTIVE";
PAUSED: "PAUSED";
ARCHIVED: "ARCHIVED";
}>>;
}, "strip", z.ZodTypeAny, {
limit: number;
page: number;
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
search?: string | undefined;
}, {
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
search?: string | undefined;
limit?: number | undefined;
page?: number | undefined;
}>;
export declare const campaignIdSchema: z.ZodObject<{
id: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
}, {
id: string;
}>;
export declare const createUserCampaignSchema: z.ZodObject<{
title: z.ZodString;
description: z.ZodOptional<z.ZodString>;
emailSubject: z.ZodString;
emailBody: z.ZodString;
callToAction: z.ZodOptional<z.ZodString>;
targetGovernmentLevels: z.ZodArray<z.ZodNativeEnum<{
FEDERAL: "FEDERAL";
PROVINCIAL: "PROVINCIAL";
MUNICIPAL: "MUNICIPAL";
SCHOOL_BOARD: "SCHOOL_BOARD";
}>, "many">;
}, "strip", z.ZodTypeAny, {
title: string;
emailSubject: string;
emailBody: string;
targetGovernmentLevels: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[];
description?: string | undefined;
callToAction?: string | undefined;
}, {
title: string;
emailSubject: string;
emailBody: string;
targetGovernmentLevels: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[];
description?: string | undefined;
callToAction?: string | undefined;
}>;
export declare const updateUserCampaignSchema: z.ZodObject<{
title: z.ZodOptional<z.ZodString>;
description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
emailSubject: z.ZodOptional<z.ZodString>;
emailBody: z.ZodOptional<z.ZodString>;
callToAction: z.ZodOptional<z.ZodOptional<z.ZodString>>;
targetGovernmentLevels: z.ZodOptional<z.ZodArray<z.ZodNativeEnum<{
FEDERAL: "FEDERAL";
PROVINCIAL: "PROVINCIAL";
MUNICIPAL: "MUNICIPAL";
SCHOOL_BOARD: "SCHOOL_BOARD";
}>, "many">>;
}, "strip", z.ZodTypeAny, {
title?: string | undefined;
description?: string | undefined;
emailSubject?: string | undefined;
emailBody?: string | undefined;
callToAction?: string | undefined;
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
}, {
title?: string | undefined;
description?: string | undefined;
emailSubject?: string | undefined;
emailBody?: string | undefined;
callToAction?: string | undefined;
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
}>;
export declare const moderateCampaignSchema: z.ZodObject<{
action: z.ZodEnum<["approve", "reject", "request_changes"]>;
reason: z.ZodOptional<z.ZodString>;
notes: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
action: "approve" | "reject" | "request_changes";
reason?: string | undefined;
notes?: string | undefined;
}, {
action: "approve" | "reject" | "request_changes";
reason?: string | undefined;
notes?: string | undefined;
}>;
export declare const listModerationQueueSchema: z.ZodObject<{
page: z.ZodDefault<z.ZodNumber>;
limit: z.ZodDefault<z.ZodNumber>;
search: z.ZodOptional<z.ZodString>;
moderationStatus: z.ZodOptional<z.ZodNativeEnum<{
PENDING_REVIEW: "PENDING_REVIEW";
APPROVED: "APPROVED";
REJECTED: "REJECTED";
CHANGES_REQUESTED: "CHANGES_REQUESTED";
}>>;
}, "strip", z.ZodTypeAny, {
limit: number;
page: number;
search?: string | undefined;
moderationStatus?: "APPROVED" | "PENDING_REVIEW" | "REJECTED" | "CHANGES_REQUESTED" | undefined;
}, {
search?: string | undefined;
limit?: number | undefined;
page?: number | undefined;
moderationStatus?: "APPROVED" | "PENDING_REVIEW" | "REJECTED" | "CHANGES_REQUESTED" | undefined;
}>;
export type CreateCampaignInput = z.infer<typeof createCampaignSchema>;
export type UpdateCampaignInput = z.infer<typeof updateCampaignSchema>;
export type ListCampaignsInput = z.infer<typeof listCampaignsSchema>;
export type CreateUserCampaignInput = z.infer<typeof createUserCampaignSchema>;
export type UpdateUserCampaignInput = z.infer<typeof updateUserCampaignSchema>;
export type ModerateCampaignInput = z.infer<typeof moderateCampaignSchema>;
export type ListModerationQueueInput = z.infer<typeof listModerationQueueSchema>;
//# sourceMappingURL=campaigns.schemas.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaigns.schemas.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;EAK9B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;EAE3B,CAAC;AAGH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;EAOnC,CAAC;AAGH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;EAAqC,CAAC;AAG3E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;EAIjC,CAAC;AAGH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;EAKpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACrE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC/E,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC/E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC3E,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}

View File

@ -1,79 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listModerationQueueSchema = exports.moderateCampaignSchema = exports.updateUserCampaignSchema = exports.createUserCampaignSchema = exports.campaignIdSchema = exports.listCampaignsSchema = exports.updateCampaignSchema = exports.createCampaignSchema = void 0;
const zod_1 = require("zod");
const client_1 = require("@prisma/client");
exports.createCampaignSchema = zod_1.z.object({
title: zod_1.z.string().min(1, 'Title is required').max(200),
description: zod_1.z.string().max(2000).optional(),
emailSubject: zod_1.z.string().min(1, 'Email subject is required').max(200),
emailBody: zod_1.z.string().min(1, 'Email body is required').max(10000),
callToAction: zod_1.z.string().max(500).optional(),
status: zod_1.z.nativeEnum(client_1.CampaignStatus).optional().default(client_1.CampaignStatus.DRAFT),
targetGovernmentLevels: zod_1.z.array(zod_1.z.nativeEnum(client_1.GovernmentLevel)).optional().default([]),
allowSmtpEmail: zod_1.z.boolean().optional().default(true),
allowMailtoLink: zod_1.z.boolean().optional().default(true),
collectUserInfo: zod_1.z.boolean().optional().default(true),
showEmailCount: zod_1.z.boolean().optional().default(true),
showCallCount: zod_1.z.boolean().optional().default(true),
allowEmailEditing: zod_1.z.boolean().optional().default(false),
allowCustomRecipients: zod_1.z.boolean().optional().default(false),
showResponseWall: zod_1.z.boolean().optional().default(false),
highlightCampaign: zod_1.z.boolean().optional().default(false),
coverPhoto: zod_1.z.string().url().max(500).optional(),
coverVideoId: zod_1.z.number().int().positive().nullable().optional(),
});
exports.updateCampaignSchema = zod_1.z.object({
title: zod_1.z.string().min(1).max(200).optional(),
description: zod_1.z.string().max(2000).nullable().optional(),
emailSubject: zod_1.z.string().min(1).max(200).optional(),
emailBody: zod_1.z.string().min(1).max(10000).optional(),
callToAction: zod_1.z.string().max(500).nullable().optional(),
status: zod_1.z.nativeEnum(client_1.CampaignStatus).optional(),
targetGovernmentLevels: zod_1.z.array(zod_1.z.nativeEnum(client_1.GovernmentLevel)).optional(),
allowSmtpEmail: zod_1.z.boolean().optional(),
allowMailtoLink: zod_1.z.boolean().optional(),
collectUserInfo: zod_1.z.boolean().optional(),
showEmailCount: zod_1.z.boolean().optional(),
showCallCount: zod_1.z.boolean().optional(),
allowEmailEditing: zod_1.z.boolean().optional(),
allowCustomRecipients: zod_1.z.boolean().optional(),
showResponseWall: zod_1.z.boolean().optional(),
highlightCampaign: zod_1.z.boolean().optional(),
coverPhoto: zod_1.z.string().url().max(500).nullable().optional(),
coverVideoId: zod_1.z.number().int().positive().nullable().optional(),
});
exports.listCampaignsSchema = zod_1.z.object({
page: zod_1.z.coerce.number().int().positive().default(1),
limit: zod_1.z.coerce.number().int().positive().max(100).default(20),
search: zod_1.z.string().optional(),
status: zod_1.z.nativeEnum(client_1.CampaignStatus).optional(),
});
exports.campaignIdSchema = zod_1.z.object({
id: zod_1.z.string().min(1),
});
// User-submitted campaign (restricted fields)
exports.createUserCampaignSchema = zod_1.z.object({
title: zod_1.z.string().min(3, 'Title must be at least 3 characters').max(200),
description: zod_1.z.string().max(2000).optional(),
emailSubject: zod_1.z.string().min(3, 'Email subject is required').max(200),
emailBody: zod_1.z.string().min(10, 'Email body must be at least 10 characters').max(5000),
callToAction: zod_1.z.string().max(500).optional(),
targetGovernmentLevels: zod_1.z.array(zod_1.z.nativeEnum(client_1.GovernmentLevel)).min(1, 'Select at least one government level'),
});
// Update own user campaign (same restricted fields)
exports.updateUserCampaignSchema = exports.createUserCampaignSchema.partial();
// Admin moderation action
exports.moderateCampaignSchema = zod_1.z.object({
action: zod_1.z.enum(['approve', 'reject', 'request_changes']),
reason: zod_1.z.string().max(2000).optional(),
notes: zod_1.z.string().max(2000).optional(),
});
// Moderation queue filters
exports.listModerationQueueSchema = zod_1.z.object({
page: zod_1.z.coerce.number().int().positive().default(1),
limit: zod_1.z.coerce.number().int().positive().max(100).default(20),
search: zod_1.z.string().optional(),
moderationStatus: zod_1.z.nativeEnum(client_1.CampaignModerationStatus).optional(),
});
//# sourceMappingURL=campaigns.schemas.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,467 +0,0 @@
import { UserRole } from '@prisma/client';
import type { CreateCampaignInput, UpdateCampaignInput, ListCampaignsInput, CreateUserCampaignInput, ModerateCampaignInput, ListModerationQueueInput } from './campaigns.schemas';
interface AuthUser {
id: string;
email: string;
role: UserRole;
}
export declare const campaignsService: {
findAll(filters: ListCampaignsInput, user?: AuthUser): Promise<{
campaigns: {
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}>;
findById(id: string): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
findBySlug(slug: string): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
create(data: CreateCampaignInput, user: AuthUser): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
update(id: string, data: UpdateCampaignInput): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
findActiveCampaigns(): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
}[]>;
findBySlugPublic(slug: string): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
}>;
delete(id: string): Promise<void>;
createUserCampaign(data: CreateUserCampaignInput, user: AuthUser): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
findUserCampaigns(userId: string): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}[]>;
updateUserCampaign(id: string, data: Partial<CreateUserCampaignInput>, user: AuthUser): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
findModerationQueue(filters: ListModerationQueueInput): Promise<{
campaigns: {
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}>;
getModerationStats(): Promise<{
total: number;
pending: number;
approved: number;
rejected: number;
changesRequested: number;
}>;
moderateCampaign(id: string, input: ModerateCampaignInput, reviewer: AuthUser): Promise<{
status: import(".prisma/client").$Enums.CampaignStatus;
id: string;
createdAt: Date;
updatedAt: Date;
_count: {
responses: number;
emails: number;
};
title: string;
description: string | null;
slug: string;
coverPhoto: string | null;
coverVideoId: number | null;
moderationNotes: string | null;
createdByUserId: string | null;
emailSubject: string;
emailBody: string;
callToAction: string | null;
allowSmtpEmail: boolean;
allowMailtoLink: boolean;
collectUserInfo: boolean;
showEmailCount: boolean;
showCallCount: boolean;
allowEmailEditing: boolean;
allowCustomRecipients: boolean;
showResponseWall: boolean;
highlightCampaign: boolean;
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
createdByUserEmail: string | null;
createdByUserName: string | null;
isUserGenerated: boolean;
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
reviewedByUserId: string | null;
reviewedAt: Date | null;
rejectionReason: string | null;
}>;
};
export {};
//# sourceMappingURL=campaigns.service.d.ts.map

View File

@ -1 +0,0 @@
{"version":3,"file":"campaigns.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,QAAQ,EAA4B,MAAM,gBAAgB,CAAC;AAI5E,OAAO,KAAK,EACV,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAC5D,uBAAuB,EAA2B,qBAAqB,EAAE,wBAAwB,EAClG,MAAM,qBAAqB,CAAC;AAiH7B,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,eAAO,MAAM,gBAAgB;qBACJ,kBAAkB,SAAS,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0CvC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAaF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAaV,mBAAmB,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAgCrC,MAAM,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA0CrB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAiBlB,MAAM;6BAWQ,uBAAuB,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAwCtC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAQT,MAAM,QAAQ,OAAO,CAAC,uBAAuB,CAAC,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCA2CxD,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA0ChC,MAAM,SAAS,qBAAqB,YAAY,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCpF,CAAC"}

Some files were not shown because too many files have changed in this diff Show More