Tonne of debugging - getting ready for the production builds
This commit is contained in:
50
api/Dockerfile.media
Normal file
50
api/Dockerfile.media
Normal file
@@ -0,0 +1,50 @@
|
||||
# Multi-stage Dockerfile for Media API (Fastify)
|
||||
# Uses Drizzle ORM for media tables + Prisma for user auth
|
||||
|
||||
FROM node:20-alpine AS base
|
||||
WORKDIR /app
|
||||
|
||||
# Install ffmpeg for video metadata extraction
|
||||
RUN apk add --no-cache ffmpeg
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
# Copy Prisma schema and generate client (needed for auth middleware)
|
||||
COPY prisma ./prisma
|
||||
RUN npx prisma generate
|
||||
|
||||
# Copy source code
|
||||
COPY src ./src
|
||||
COPY drizzle ./drizzle
|
||||
COPY drizzle.config.ts ./
|
||||
COPY tsconfig.json ./
|
||||
|
||||
# Development stage with hot reload
|
||||
FROM base AS development
|
||||
RUN npm install tsx --save-dev
|
||||
CMD ["npx", "tsx", "watch", "src/media-server.ts"]
|
||||
|
||||
# Build stage
|
||||
FROM base AS build
|
||||
RUN npm install typescript --save-dev
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine AS production
|
||||
WORKDIR /app
|
||||
|
||||
# Install ffmpeg for video metadata extraction
|
||||
RUN apk add --no-cache ffmpeg
|
||||
|
||||
# Copy built files and node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=build /app/prisma ./prisma
|
||||
|
||||
# Run as non-root user
|
||||
USER node
|
||||
|
||||
CMD ["node", "dist/media-server.js"]
|
||||
6
api/dist/config/database.d.ts
vendored
Normal file
6
api/dist/config/database.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
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
|
||||
1
api/dist/config/database.d.ts.map
vendored
Normal file
1
api/dist/config/database.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
10
api/dist/config/database.js
vendored
Normal file
10
api/dist/config/database.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"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
|
||||
1
api/dist/config/database.js.map
vendored
Normal file
1
api/dist/config/database.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
394
api/dist/config/env.d.ts
vendored
Normal file
394
api/dist/config/env.d.ts
vendored
Normal file
@@ -0,0 +1,394 @@
|
||||
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>;
|
||||
DATABASE_URL: z.ZodString;
|
||||
REDIS_URL: z.ZodDefault<z.ZodString>;
|
||||
JWT_ACCESS_SECRET: z.ZodString;
|
||||
JWT_REFRESH_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.ZodDefault<z.ZodString>;
|
||||
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_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>;
|
||||
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>;
|
||||
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>;
|
||||
PUBLIC_MEDIA_PORT: 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>;
|
||||
ALERTMANAGER_URL: z.ZodDefault<z.ZodString>;
|
||||
ALERTMANAGER_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;
|
||||
DATABASE_URL: string;
|
||||
REDIS_URL: string;
|
||||
JWT_ACCESS_SECRET: string;
|
||||
JWT_REFRESH_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_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;
|
||||
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;
|
||||
ENABLE_MEDIA_FEATURES: string;
|
||||
MEDIA_API_PORT: number;
|
||||
MEDIA_API_PUBLIC_URL: string;
|
||||
MEDIA_ROOT: string;
|
||||
MEDIA_UPLOADS: string;
|
||||
MAX_UPLOAD_SIZE_GB: number;
|
||||
PUBLIC_MEDIA_PORT: 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;
|
||||
ALERTMANAGER_URL: string;
|
||||
ALERTMANAGER_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;
|
||||
NODE_ENV?: "development" | "production" | "test" | undefined;
|
||||
PORT?: number | undefined;
|
||||
API_URL?: string | undefined;
|
||||
ADMIN_URL?: string | undefined;
|
||||
DOMAIN?: 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_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;
|
||||
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;
|
||||
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;
|
||||
PUBLIC_MEDIA_PORT?: 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;
|
||||
ALERTMANAGER_URL?: string | undefined;
|
||||
ALERTMANAGER_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;
|
||||
DATABASE_URL: string;
|
||||
REDIS_URL: string;
|
||||
JWT_ACCESS_SECRET: string;
|
||||
JWT_REFRESH_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_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;
|
||||
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;
|
||||
ENABLE_MEDIA_FEATURES: string;
|
||||
MEDIA_API_PORT: number;
|
||||
MEDIA_API_PUBLIC_URL: string;
|
||||
MEDIA_ROOT: string;
|
||||
MEDIA_UPLOADS: string;
|
||||
MAX_UPLOAD_SIZE_GB: number;
|
||||
PUBLIC_MEDIA_PORT: 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;
|
||||
ALERTMANAGER_URL: string;
|
||||
ALERTMANAGER_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
|
||||
1
api/dist/config/env.d.ts.map
vendored
Normal file
1
api/dist/config/env.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkJb,CAAC;AAEH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAY5C,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC"}
|
||||
143
api/dist/config/env.js
vendored
Normal file
143
api/dist/config/env.js
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
"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'),
|
||||
// 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_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().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'),
|
||||
// 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_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),
|
||||
// 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'),
|
||||
// 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),
|
||||
PUBLIC_MEDIA_PORT: zod_1.z.coerce.number().default(3100),
|
||||
// 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),
|
||||
ALERTMANAGER_URL: zod_1.z.string().default('http://alertmanager-changemaker:9093'),
|
||||
ALERTMANAGER_PORT: zod_1.z.coerce.number().default(9093),
|
||||
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
|
||||
1
api/dist/config/env.js.map
vendored
Normal file
1
api/dist/config/env.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
api/dist/config/redis.d.ts
vendored
Normal file
3
api/dist/config/redis.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import Redis from 'ioredis';
|
||||
export declare const redis: Redis;
|
||||
//# sourceMappingURL=redis.d.ts.map
|
||||
1
api/dist/config/redis.d.ts.map
vendored
Normal file
1
api/dist/config/redis.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
24
api/dist/config/redis.js
vendored
Normal file
24
api/dist/config/redis.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
"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
|
||||
1
api/dist/config/redis.js.map
vendored
Normal file
1
api/dist/config/redis.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
2
api/dist/media-server.d.ts
vendored
Normal file
2
api/dist/media-server.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=media-server.d.ts.map
|
||||
1
api/dist/media-server.d.ts.map
vendored
Normal file
1
api/dist/media-server.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"media-server.d.ts","sourceRoot":"","sources":["../src/media-server.ts"],"names":[],"mappings":""}
|
||||
117
api/dist/media-server.js
vendored
Normal file
117
api/dist/media-server.js
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
"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_media_routes_1 = require("./modules/media/routes/public-media.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 video_schedule_queue_service_1 = require("./services/video-schedule-queue.service");
|
||||
// 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();
|
||||
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
|
||||
const allowedOrigins = env_1.env.CORS_ORIGINS.split(',').map(o => o.trim());
|
||||
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_media_routes_1.publicMediaRoutes, { prefix: '/api/media' });
|
||||
await fastify.register(comments_routes_1.commentsRoutes, { prefix: '/api/media' });
|
||||
// TODO: Add more routes
|
||||
// await fastify.register(jobsRoutes, { prefix: '/api/jobs' });
|
||||
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');
|
||||
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
|
||||
1
api/dist/media-server.js.map
vendored
Normal file
1
api/dist/media-server.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"media-server.js","sourceRoot":"","sources":["../src/media-server.ts"],"names":[],"mappings":"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"}
|
||||
4
api/dist/middleware/auth.middleware.d.ts
vendored
Normal file
4
api/dist/middleware/auth.middleware.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
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
|
||||
1
api/dist/middleware/auth.middleware.d.ts.map
vendored
Normal file
1
api/dist/middleware/auth.middleware.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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;AAY1D,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAe5E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAiB5E"}
|
||||
42
api/dist/middleware/auth.middleware.js
vendored
Normal file
42
api/dist/middleware/auth.middleware.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
"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);
|
||||
req.user = { id: payload.id, email: payload.email, role: payload.role };
|
||||
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);
|
||||
req.user = { id: payload.id, email: payload.email, role: payload.role };
|
||||
}
|
||||
catch {
|
||||
// Token invalid — continue without user
|
||||
}
|
||||
next();
|
||||
}
|
||||
//# sourceMappingURL=auth.middleware.js.map
|
||||
1
api/dist/middleware/auth.middleware.js.map
vendored
Normal file
1
api/dist/middleware/auth.middleware.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":";;;;;AAYA,oCAeC;AAED,oCAiBC;AA7CD,gEAA+B;AAE/B,uCAAoC;AACpC,mDAA2C;AAQ3C,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,CAAiB,CAAC;QACzE,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;QACxE,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,CAAiB,CAAC;QACzE,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}
|
||||
8
api/dist/middleware/error-handler.d.ts
vendored
Normal file
8
api/dist/middleware/error-handler.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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
|
||||
1
api/dist/middleware/error-handler.d.ts.map
vendored
Normal file
1
api/dist/middleware/error-handler.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
52
api/dist/middleware/error-handler.js
vendored
Normal file
52
api/dist/middleware/error-handler.js
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
"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
|
||||
1
api/dist/middleware/error-handler.js.map
vendored
Normal file
1
api/dist/middleware/error-handler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
12
api/dist/middleware/rate-limit.d.ts
vendored
Normal file
12
api/dist/middleware/rate-limit.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
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 authRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const observabilityRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const healthMetricsRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
//# sourceMappingURL=rate-limit.d.ts.map
|
||||
1
api/dist/middleware/rate-limit.d.ts.map
vendored
Normal file
1
api/dist/middleware/rate-limit.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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,aAAa,sDAexB,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC"}
|
||||
187
api/dist/middleware/rate-limit.js
vendored
Normal file
187
api/dist/middleware/rate-limit.js
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
"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.observabilityRateLimit = exports.authRateLimit = 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.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.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
|
||||
1
api/dist/middleware/rate-limit.js.map
vendored
Normal file
1
api/dist/middleware/rate-limit.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"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"}
|
||||
5
api/dist/middleware/rbac.middleware.d.ts
vendored
Normal file
5
api/dist/middleware/rbac.middleware.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
1
api/dist/middleware/rbac.middleware.d.ts.map
vendored
Normal file
1
api/dist/middleware/rbac.middleware.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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,UAWzD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAU9E"}
|
||||
27
api/dist/middleware/rbac.middleware.js
vendored
Normal file
27
api/dist/middleware/rbac.middleware.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
"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');
|
||||
}
|
||||
if (!roles.includes(req.user.role)) {
|
||||
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');
|
||||
}
|
||||
if (req.user.role === 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
|
||||
1
api/dist/middleware/rbac.middleware.js.map
vendored
Normal file
1
api/dist/middleware/rbac.middleware.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"rbac.middleware.js","sourceRoot":"","sources":["../../src/middleware/rbac.middleware.ts"],"names":[],"mappings":";;AAIA,kCAYC;AAED,wCAUC;AA3BD,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,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,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,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAQ,CAAC,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,gDAAgD,EAAE,gBAAgB,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}
|
||||
4
api/dist/middleware/validate.d.ts
vendored
Normal file
4
api/dist/middleware/validate.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
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
|
||||
1
api/dist/middleware/validate.d.ts.map
vendored
Normal file
1
api/dist/middleware/validate.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
23
api/dist/middleware/validate.js
vendored
Normal file
23
api/dist/middleware/validate.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"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
|
||||
1
api/dist/middleware/validate.js.map
vendored
Normal file
1
api/dist/middleware/validate.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
3
api/dist/modules/auth/auth.routes.d.ts
vendored
Normal file
3
api/dist/modules/auth/auth.routes.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export { router as authRouter };
|
||||
//# sourceMappingURL=auth.routes.d.ts.map
|
||||
1
api/dist/modules/auth/auth.routes.d.ts.map
vendored
Normal file
1
api/dist/modules/auth/auth.routes.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.routes.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmGxB,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,CAAC"}
|
||||
116
api/dist/modules/auth/auth.routes.js
vendored
Normal file
116
api/dist/modules/auth/auth.routes.js
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
"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.authRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
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 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/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
|
||||
router.get('/me', auth_middleware_1.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { prisma } = await Promise.resolve().then(() => __importStar(require('../../config/database')));
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: req.user.id },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
phone: true,
|
||||
role: 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
|
||||
1
api/dist/modules/auth/auth.routes.js.map
vendored
Normal file
1
api/dist/modules/auth/auth.routes.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.routes.js","sourceRoot":"","sources":["../../../src/modules/auth/auth.routes.ts"],"names":[],"mappings":"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"}
|
||||
38
api/dist/modules/auth/auth.schemas.d.ts
vendored
Normal file
38
api/dist/modules/auth/auth.schemas.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
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>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
email: string;
|
||||
password: string;
|
||||
name?: string | undefined;
|
||||
phone?: string | undefined;
|
||||
}, {
|
||||
email: string;
|
||||
password: string;
|
||||
name?: string | undefined;
|
||||
phone?: 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
|
||||
1
api/dist/modules/auth/auth.schemas.d.ts.map
vendored
Normal file
1
api/dist/modules/auth/auth.schemas.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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;;;;;;;;;;;;;;;EAUzB,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"}
|
||||
23
api/dist/modules/auth/auth.schemas.js
vendored
Normal file
23
api/dist/modules/auth/auth.schemas.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"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(),
|
||||
// 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
|
||||
1
api/dist/modules/auth/auth.schemas.js.map
vendored
Normal file
1
api/dist/modules/auth/auth.schemas.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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,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"}
|
||||
86
api/dist/modules/auth/auth.service.d.ts
vendored
Normal file
86
api/dist/modules/auth/auth.service.d.ts
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
import { UserRole } from '@prisma/client';
|
||||
import type { RegisterInput } from './auth.schemas';
|
||||
interface TokenPair {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
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;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
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<{
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
user: {
|
||||
status: import(".prisma/client").$Enums.UserStatus;
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
phone: string | null;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
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;
|
||||
};
|
||||
}>;
|
||||
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;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
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: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: UserRole;
|
||||
}): string;
|
||||
generateRefreshToken(user: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: UserRole;
|
||||
}): Promise<string>;
|
||||
generateTokenPair(user: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: UserRole;
|
||||
}): Promise<TokenPair>;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=auth.service.d.ts.map
|
||||
1
api/dist/modules/auth/auth.service.d.ts.map
vendored
Normal file
1
api/dist/modules/auth/auth.service.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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;AAKtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAQpD,UAAU,SAAS;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,WAAW;iBACH,MAAM,YAAY,MAAM;qBAL9B,MAAM;sBACL,MAAM;;;;;;;;;;;;;;;;;;mBAwCC,aAAa;qBAzCrB,MAAM;sBACL,MAAM;;;;;;;;;;;;;;;;;;gCAgEc,MAAM;;;;;;;;;;;;;;;;;;;;yBAwDb,MAAM;8BAIP;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,GAAG,MAAM;+BAO/C;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;4BAqBlE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC;CAKjG,CAAC"}
|
||||
140
api/dist/modules/auth/auth.service.js
vendored
Normal file
140
api/dist/modules/auth/auth.service.js
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
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");
|
||||
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');
|
||||
}
|
||||
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() },
|
||||
});
|
||||
const tokens = await this.generateTokenPair(user);
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
return { user: userWithoutPassword, ...tokens };
|
||||
},
|
||||
async register(data) {
|
||||
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);
|
||||
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, // Always USER for public registration
|
||||
},
|
||||
});
|
||||
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);
|
||||
}
|
||||
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');
|
||||
}
|
||||
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 } });
|
||||
// Generate new token pair
|
||||
const accessToken = this.generateAccessToken(stored.user);
|
||||
const refreshPayload = {
|
||||
id: stored.user.id,
|
||||
email: stored.user.email,
|
||||
role: stored.user.role
|
||||
};
|
||||
const refreshToken = jsonwebtoken_1.default.sign(refreshPayload, env_1.env.JWT_REFRESH_SECRET, {
|
||||
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 payload = { id: user.id, email: user.email, role: user.role };
|
||||
return jsonwebtoken_1.default.sign(payload, env_1.env.JWT_ACCESS_SECRET, {
|
||||
expiresIn: env_1.env.JWT_ACCESS_EXPIRY,
|
||||
});
|
||||
},
|
||||
async generateRefreshToken(user) {
|
||||
const payload = { id: user.id, email: user.email, role: user.role };
|
||||
const token = jsonwebtoken_1.default.sign(payload, env_1.env.JWT_REFRESH_SECRET, {
|
||||
expiresIn: env_1.env.JWT_REFRESH_EXPIRY,
|
||||
});
|
||||
// Parse expiry to get a Date
|
||||
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
|
||||
1
api/dist/modules/auth/auth.service.js.map
vendored
Normal file
1
api/dist/modules/auth/auth.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
40
api/dist/modules/docs/docs-files.service.d.ts
vendored
Normal file
40
api/dist/modules/docs/docs-files.service.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
export declare const docsFilesService: {
|
||||
listTree: typeof listTree;
|
||||
readFileContent: typeof readFileContent;
|
||||
writeFileContent: typeof writeFileContent;
|
||||
createFile: typeof createFile;
|
||||
deleteFile: typeof deleteFile;
|
||||
renameFile: typeof renameFile;
|
||||
safeResolve: typeof safeResolve;
|
||||
isEditableFile: typeof isEditableFile;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=docs-files.service.d.ts.map
|
||||
1
api/dist/modules/docs/docs-files.service.d.ts.map
vendored
Normal file
1
api/dist/modules/docs/docs-files.service.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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;AAaD;;;GAGG;AACH,iBAAS,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAOjD;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;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;CAS5B,CAAC"}
|
||||
248
api/dist/modules/docs/docs-files.service.js
vendored
Normal file
248
api/dist/modules/docs/docs-files.service.js
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
"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 FILE_CACHE_TTL = 60 * 60; // 1 hour
|
||||
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);
|
||||
if (!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, FILE_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_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);
|
||||
}
|
||||
exports.docsFilesService = {
|
||||
listTree,
|
||||
readFileContent,
|
||||
writeFileContent,
|
||||
createFile,
|
||||
deleteFile,
|
||||
renameFile,
|
||||
safeResolve,
|
||||
isEditableFile,
|
||||
};
|
||||
//# sourceMappingURL=docs-files.service.js.map
|
||||
1
api/dist/modules/docs/docs-files.service.js.map
vendored
Normal file
1
api/dist/modules/docs/docs-files.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
api/dist/modules/docs/docs.routes.d.ts
vendored
Normal file
2
api/dist/modules/docs/docs.routes.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export declare const docsRouter: import("express-serve-static-core").Router;
|
||||
//# sourceMappingURL=docs.routes.d.ts.map
|
||||
1
api/dist/modules/docs/docs.routes.d.ts.map
vendored
Normal file
1
api/dist/modules/docs/docs.routes.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"docs.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/docs.routes.ts"],"names":[],"mappings":"AAiQA,eAAO,MAAM,UAAU,4CAAS,CAAC"}
|
||||
219
api/dist/modules/docs/docs.routes.js
vendored
Normal file
219
api/dist/modules/docs/docs.routes.js
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.docsRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const auth_middleware_1 = require("../../middleware/auth.middleware");
|
||||
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
|
||||
const env_1 = require("../../config/env");
|
||||
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 mkdocs_config_service_1 = require("./mkdocs-config.service");
|
||||
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
|
||||
router.get('/status', 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
|
||||
router.get('/config', 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
|
||||
router.get('/mkdocs-config', 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)('SUPER_ADMIN'), 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 (SUPER_ADMIN only)
|
||||
router.post('/build', (0, rbac_middleware_1.requireRole)('SUPER_ADMIN'), 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);
|
||||
}
|
||||
});
|
||||
// --- File Management Endpoints ---
|
||||
// GET /api/docs/files — list file tree
|
||||
router.get('/files', async (_req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'list' });
|
||||
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);
|
||||
}
|
||||
});
|
||||
// POST /api/docs/files/rename — rename/move file
|
||||
router.post('/files/rename', 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);
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (err) {
|
||||
handleFileError(err, res, next);
|
||||
}
|
||||
});
|
||||
// GET /api/docs/files/* — read file content
|
||||
router.get('/files/*', 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/*', 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);
|
||||
res.json({ success: true, path: filePath });
|
||||
}
|
||||
catch (err) {
|
||||
handleFileError(err, res, next);
|
||||
}
|
||||
});
|
||||
// POST /api/docs/files/* — create new file or folder
|
||||
router.post('/files/*', 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/*', 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);
|
||||
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
|
||||
1
api/dist/modules/docs/docs.routes.js.map
vendored
Normal file
1
api/dist/modules/docs/docs.routes.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
30
api/dist/modules/docs/mkdocs-config.service.d.ts
vendored
Normal file
30
api/dist/modules/docs/mkdocs-config.service.d.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
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>;
|
||||
/**
|
||||
* Execute `mkdocs build` inside the running MkDocs container via Docker Engine API.
|
||||
*/
|
||||
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
|
||||
1
api/dist/modules/docs/mkdocs-config.service.d.ts.map
vendored
Normal file
1
api/dist/modules/docs/mkdocs-config.service.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"mkdocs-config.service.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/mkdocs-config.service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,QAAQ,EAAE,MAAM,MAAM,CAAC;AAuB/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;AA8DD;;GAEG;AACH,iBAAe,YAAY,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgE7F;AAED,eAAO,MAAM,mBAAmB;;;;;;CAM/B,CAAC"}
|
||||
190
api/dist/modules/docs/mkdocs-config.service.js
vendored
Normal file
190
api/dist/modules/docs/mkdocs-config.service.js
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.mkdocsConfigService = void 0;
|
||||
const promises_1 = require("fs/promises");
|
||||
const http_1 = require("http");
|
||||
const env_1 = require("../../config/env");
|
||||
const logger_1 = require("../../utils/logger");
|
||||
const yaml_1 = require("yaml");
|
||||
const DOCKER_SOCKET = '/var/run/docker.sock';
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
/**
|
||||
* Make a request to the Docker Engine API over Unix socket.
|
||||
*/
|
||||
function dockerRequest(method, path, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
socketPath: DOCKER_SOCKET,
|
||||
path,
|
||||
method,
|
||||
headers: body
|
||||
? { 'Content-Type': 'application/json' }
|
||||
: undefined,
|
||||
};
|
||||
const req = (0, http_1.request)(options, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (chunk) => chunks.push(chunk));
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
statusCode: res.statusCode || 0,
|
||||
body: Buffer.concat(chunks).toString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (body) {
|
||||
req.write(JSON.stringify(body));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Read raw output from a Docker exec start stream.
|
||||
* Docker multiplexes stdout/stderr with 8-byte headers.
|
||||
*/
|
||||
function demuxDockerStream(raw) {
|
||||
const lines = [];
|
||||
let offset = 0;
|
||||
while (offset < raw.length) {
|
||||
if (offset + 8 > raw.length)
|
||||
break;
|
||||
// byte 0: stream type (1=stdout, 2=stderr)
|
||||
const size = raw.readUInt32BE(offset + 4);
|
||||
offset += 8;
|
||||
if (offset + size > raw.length) {
|
||||
lines.push(raw.subarray(offset).toString('utf-8'));
|
||||
break;
|
||||
}
|
||||
lines.push(raw.subarray(offset, offset + size).toString('utf-8'));
|
||||
offset += size;
|
||||
}
|
||||
return lines.join('');
|
||||
}
|
||||
/**
|
||||
* Execute `mkdocs build` inside the running MkDocs container via Docker Engine API.
|
||||
*/
|
||||
async function triggerBuild() {
|
||||
const containerName = env_1.env.MKDOCS_CONTAINER_NAME;
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
// 1. Create exec instance
|
||||
const execCreate = await dockerRequest('POST', `/containers/${containerName}/exec`, {
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Cmd: ['mkdocs', 'build', '--clean'],
|
||||
});
|
||||
if (execCreate.statusCode !== 201) {
|
||||
throw new Error(`Failed to create exec: ${execCreate.body}`);
|
||||
}
|
||||
const { Id: execId } = JSON.parse(execCreate.body);
|
||||
// 2. Start exec and collect output
|
||||
const execOutput = await new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
socketPath: DOCKER_SOCKET,
|
||||
path: `/exec/${execId}/start`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
const req = (0, http_1.request)(options, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (chunk) => chunks.push(chunk));
|
||||
res.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(JSON.stringify({ Detach: false, Tty: false }));
|
||||
req.end();
|
||||
});
|
||||
const output = demuxDockerStream(execOutput);
|
||||
// 3. Check exit code
|
||||
const execInspect = await dockerRequest('GET', `/exec/${execId}/json`);
|
||||
const inspectData = JSON.parse(execInspect.body);
|
||||
const exitCode = inspectData.ExitCode ?? -1;
|
||||
const duration = Date.now() - startTime;
|
||||
return {
|
||||
success: exitCode === 0,
|
||||
output: output || '(no output)',
|
||||
duration,
|
||||
};
|
||||
}
|
||||
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
|
||||
1
api/dist/modules/docs/mkdocs-config.service.js.map
vendored
Normal file
1
api/dist/modules/docs/mkdocs-config.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
api/dist/modules/email-templates/email-templates-admin.routes.d.ts
vendored
Normal file
3
api/dist/modules/email-templates/email-templates-admin.routes.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=email-templates-admin.routes.d.ts.map
|
||||
1
api/dist/modules/email-templates/email-templates-admin.routes.d.ts.map
vendored
Normal file
1
api/dist/modules/email-templates/email-templates-admin.routes.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"email-templates-admin.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates-admin.routes.ts"],"names":[],"mappings":"AAqBA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8UxB,eAAe,MAAM,CAAC"}
|
||||
277
api/dist/modules/email-templates/email-templates-admin.routes.js
vendored
Normal file
277
api/dist/modules/email-templates/email-templates-admin.routes.js
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
"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 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 router = (0, express_1.Router)();
|
||||
// All email template routes require authentication
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
// All routes require admin role (SUPER_ADMIN, INFLUENCE_ADMIN, or MAP_ADMIN)
|
||||
const requireAdminRole = (0, rbac_middleware_1.requireRole)(client_1.UserRole.SUPER_ADMIN, client_1.UserRole.INFLUENCE_ADMIN, client_1.UserRole.MAP_ADMIN);
|
||||
/**
|
||||
* List email templates
|
||||
* GET /email-templates
|
||||
*/
|
||||
router.get('/', requireAdminRole, (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', requireAdminRole, 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('/', requireAdminRole, (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', requireAdminRole, (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', requireAdminRole, 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', requireAdminRole, 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', requireAdminRole, 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', requireAdminRole, (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', requireAdminRole, (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', requireAdminRole, (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', requireAdminRole, 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 {
|
||||
// This is a placeholder - the actual seeding is done via the script
|
||||
// But we keep this endpoint for manual triggering if needed
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
const result = await execAsync('npx tsx src/scripts/seed-email-templates.ts', {
|
||||
cwd: '/app',
|
||||
});
|
||||
logger_1.logger.info('Email templates seeded via API');
|
||||
res.json({ success: true, output: result.stdout });
|
||||
}
|
||||
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
|
||||
1
api/dist/modules/email-templates/email-templates-admin.routes.js.map
vendored
Normal file
1
api/dist/modules/email-templates/email-templates-admin.routes.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
324
api/dist/modules/email-templates/email-templates.schemas.d.ts
vendored
Normal file
324
api/dist/modules/email-templates/email-templates.schemas.d.ts
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
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;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
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;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
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";
|
||||
}>>;
|
||||
isActive: z.ZodOptional<z.ZodBoolean>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
limit: number;
|
||||
page: number;
|
||||
search?: string | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
}, {
|
||||
search?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
page?: number | undefined;
|
||||
isActive?: boolean | 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";
|
||||
}>;
|
||||
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;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
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;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
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";
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
description?: string | undefined;
|
||||
variables?: {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
description?: string | undefined;
|
||||
sampleValue?: string | undefined;
|
||||
}[] | undefined;
|
||||
}, {
|
||||
name: string;
|
||||
category: "INFLUENCE" | "MAP" | "SYSTEM";
|
||||
key: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
description?: string | undefined;
|
||||
isActive?: boolean | 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";
|
||||
}>>;
|
||||
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;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
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;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
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" | undefined;
|
||||
description?: string | null | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
subjectLine?: string | undefined;
|
||||
htmlContent?: string | undefined;
|
||||
textContent?: string | undefined;
|
||||
variables?: {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
description?: string | undefined;
|
||||
sampleValue?: string | undefined;
|
||||
}[] | undefined;
|
||||
}, {
|
||||
name?: string | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
description?: string | null | undefined;
|
||||
isActive?: boolean | 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
|
||||
1
api/dist/modules/email-templates/email-templates.schemas.d.ts.map
vendored
Normal file
1
api/dist/modules/email-templates/email-templates.schemas.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
73
api/dist/modules/email-templates/email-templates.schemas.js
vendored
Normal file
73
api/dist/modules/email-templates/email-templates.schemas.js
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
"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
|
||||
1
api/dist/modules/email-templates/email-templates.schemas.js.map
vendored
Normal file
1
api/dist/modules/email-templates/email-templates.schemas.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"email-templates.schemas.js","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates.schemas.ts"],"names":[],"mappings":"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"}
|
||||
266
api/dist/modules/email-templates/email-templates.service.d.ts
vendored
Normal file
266
api/dist/modules/email-templates/email-templates.service.d.ts
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
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;
|
||||
};
|
||||
variables: {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
}[];
|
||||
updatedBy: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
} | null;
|
||||
createdBy: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
};
|
||||
} & {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
}>;
|
||||
/**
|
||||
* Get template by key
|
||||
*/
|
||||
getByKey(key: string): Promise<({
|
||||
variables: {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
}[];
|
||||
} & {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
}) | null>;
|
||||
/**
|
||||
* Create a new email template
|
||||
*/
|
||||
create(data: CreateEmailTemplateDto, userId: string): Promise<{
|
||||
variables: {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
}[];
|
||||
} & {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
}>;
|
||||
/**
|
||||
* Update an email template
|
||||
*/
|
||||
update(id: string, data: UpdateEmailTemplateDto, userId: string): Promise<{
|
||||
variables: {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
}[];
|
||||
} & {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
}>;
|
||||
/**
|
||||
* 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;
|
||||
createdByUserId: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: 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;
|
||||
createdByUserId: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: 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;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
}>;
|
||||
/**
|
||||
* 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;
|
||||
recipientEmail: string;
|
||||
sentAt: Date;
|
||||
templateId: string;
|
||||
testData: Prisma.JsonValue;
|
||||
errorMessage: string | null;
|
||||
messageId: string | null;
|
||||
sentByUserId: string;
|
||||
})[]>;
|
||||
}
|
||||
export declare const emailTemplatesService: EmailTemplatesService;
|
||||
export {};
|
||||
//# sourceMappingURL=email-templates.service.d.ts.map
|
||||
1
api/dist/modules/email-templates/email-templates.service.d.ts.map
vendored
Normal file
1
api/dist/modules/email-templates/email-templates.service.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
467
api/dist/modules/email-templates/email-templates.service.js
vendored
Normal file
467
api/dist/modules/email-templates/email-templates.service.js
vendored
Normal file
@@ -0,0 +1,467 @@
|
||||
"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
|
||||
1
api/dist/modules/email-templates/email-templates.service.js.map
vendored
Normal file
1
api/dist/modules/email-templates/email-templates.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4
api/dist/modules/influence/campaign-emails/campaign-emails.routes.d.ts
vendored
Normal file
4
api/dist/modules/influence/campaign-emails/campaign-emails.routes.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
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
|
||||
1
api/dist/modules/influence/campaign-emails/campaign-emails.routes.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaign-emails/campaign-emails.routes.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaign-emails.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.routes.ts"],"names":[],"mappings":"AAgBA,QAAA,MAAM,YAAY,4CAAW,CAAC;AAqC9B,QAAA,MAAM,WAAW,4CAAW,CAAC;AAiC7B,OAAO,EAAE,YAAY,IAAI,0BAA0B,EAAE,WAAW,IAAI,yBAAyB,EAAE,CAAC"}
|
||||
67
api/dist/modules/influence/campaign-emails/campaign-emails.routes.js
vendored
Normal file
67
api/dist/modules/influence/campaign-emails/campaign-emails.routes.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.campaignEmailsAdminRouter = exports.campaignEmailsPublicRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const client_1 = require("@prisma/client");
|
||||
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 ADMIN_ROLES = [client_1.UserRole.SUPER_ADMIN, client_1.UserRole.INFLUENCE_ADMIN, client_1.UserRole.MAP_ADMIN];
|
||||
// --- 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)(...ADMIN_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
|
||||
1
api/dist/modules/influence/campaign-emails/campaign-emails.routes.js.map
vendored
Normal file
1
api/dist/modules/influence/campaign-emails/campaign-emails.routes.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaign-emails.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.routes.ts"],"names":[],"mappings":"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"}
|
||||
116
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.d.ts
vendored
Normal file
116
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.d.ts
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
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
|
||||
1
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
39
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.js
vendored
Normal file
39
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
"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
|
||||
1
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.js.map
vendored
Normal file
1
api/dist/modules/influence/campaign-emails/campaign-emails.schemas.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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"}
|
||||
36
api/dist/modules/influence/campaign-emails/campaign-emails.service.d.ts
vendored
Normal file
36
api/dist/modules/influence/campaign-emails/campaign-emails.service.d.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
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;
|
||||
subject: string;
|
||||
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
|
||||
1
api/dist/modules/influence/campaign-emails/campaign-emails.service.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaign-emails/campaign-emails.service.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaign-emails.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEnH,eAAO,MAAM,qBAAqB;oBACV,MAAM,QAAQ,sBAAsB,aAAa,MAAM;;;;;sBAwFrD,MAAM,QAAQ,gBAAgB,aAAa,MAAM;;;;;+BAgDxC,MAAM,WAAW,uBAAuB;;;;;;;;;;;;;;;;;;;;;yBA0C9C,MAAM;CAqClC,CAAC"}
|
||||
206
api/dist/modules/influence/campaign-emails/campaign-emails.service.js
vendored
Normal file
206
api/dist/modules/influence/campaign-emails/campaign-emails.service.js
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
"use strict";
|
||||
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");
|
||||
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);
|
||||
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,
|
||||
},
|
||||
});
|
||||
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
|
||||
1
api/dist/modules/influence/campaign-emails/campaign-emails.service.js.map
vendored
Normal file
1
api/dist/modules/influence/campaign-emails/campaign-emails.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
api/dist/modules/influence/campaigns/campaigns-public.routes.d.ts
vendored
Normal file
3
api/dist/modules/influence/campaigns/campaigns-public.routes.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export { router as campaignPublicRouter };
|
||||
//# sourceMappingURL=campaigns-public.routes.d.ts.map
|
||||
1
api/dist/modules/influence/campaigns/campaigns-public.routes.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns-public.routes.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaigns-public.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns-public.routes.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA6BxB,OAAO,EAAE,MAAM,IAAI,oBAAoB,EAAE,CAAC"}
|
||||
29
api/dist/modules/influence/campaigns/campaigns-public.routes.js
vendored
Normal file
29
api/dist/modules/influence/campaigns/campaigns-public.routes.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"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 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);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=campaigns-public.routes.js.map
|
||||
1
api/dist/modules/influence/campaigns/campaigns-public.routes.js.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns-public.routes.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaigns-public.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns-public.routes.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAClE,2DAAuD;AAEvD,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AA6BL,sCAAoB;AA3BvC,iEAAiE;AACjE,MAAM,CAAC,GAAG,CACR,SAAS,EACT,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,oCAAgB,CAAC,mBAAmB,EAAE,CAAC;QAC/D,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,GAAG,CACR,gBAAgB,EAChB,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAc,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,oCAAgB,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC/D,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC"}
|
||||
3
api/dist/modules/influence/campaigns/campaigns.routes.d.ts
vendored
Normal file
3
api/dist/modules/influence/campaigns/campaigns.routes.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export { router as campaignsRouter };
|
||||
//# sourceMappingURL=campaigns.routes.d.ts.map
|
||||
1
api/dist/modules/influence/campaigns/campaigns.routes.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns.routes.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaigns.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA6ExB,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,CAAC"}
|
||||
70
api/dist/modules/influence/campaigns/campaigns.routes.js
vendored
Normal file
70
api/dist/modules/influence/campaigns/campaigns.routes.js
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.campaignsRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const client_1 = require("@prisma/client");
|
||||
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 ADMIN_ROLES = [client_1.UserRole.SUPER_ADMIN, client_1.UserRole.INFLUENCE_ADMIN, client_1.UserRole.MAP_ADMIN];
|
||||
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)(...ADMIN_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
|
||||
1
api/dist/modules/influence/campaigns/campaigns.routes.js.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns.routes.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaigns.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAClE,2CAA0C;AAC1C,2DAAuD;AACvD,2DAAsG;AACtG,2DAAwD;AACxD,yEAAmE;AACnE,yEAAkE;AAElE,MAAM,WAAW,GAAe,CAAC,iBAAQ,CAAC,WAAW,EAAE,iBAAQ,CAAC,eAAe,EAAE,iBAAQ,CAAC,SAAS,CAAC,CAAC;AAErG,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AA6EL,iCAAe;AA3ElC,gEAAgE;AAChE,MAAM,CAAC,GAAG,CAAC,8BAAY,CAAC,CAAC;AACzB,MAAM,CAAC,GAAG,CAAC,IAAA,6BAAW,EAAC,GAAG,WAAW,CAAC,CAAC,CAAC;AAExC,8DAA8D;AAC9D,MAAM,CAAC,GAAG,CACR,GAAG,EACH,IAAA,mBAAQ,EAAC,uCAAmB,EAAE,OAAO,CAAC,EACtC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,oCAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,KAAY,EAAE,GAAG,CAAC,IAAK,CAAC,CAAC;QAC3E,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,+CAA+C;AAC/C,MAAM,CAAC,GAAG,CACR,MAAM,EACN,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAY,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,oCAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,wCAAwC;AACxC,MAAM,CAAC,IAAI,CACT,GAAG,EACH,IAAA,mBAAQ,EAAC,wCAAoB,CAAC,EAC9B,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,oCAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAK,CAAC,CAAC;QACpE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,2CAA2C;AAC3C,MAAM,CAAC,GAAG,CACR,MAAM,EACN,IAAA,mBAAQ,EAAC,wCAAoB,CAAC,EAC9B,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAY,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,oCAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7D,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,8CAA8C;AAC9C,MAAM,CAAC,MAAM,CACX,MAAM,EACN,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAY,CAAC;QACnC,MAAM,oCAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC"}
|
||||
163
api/dist/modules/influence/campaigns/campaigns.schemas.d.ts
vendored
Normal file
163
api/dist/modules/influence/campaigns/campaigns.schemas.d.ts
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
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>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
status: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED";
|
||||
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;
|
||||
callToAction?: string | undefined;
|
||||
coverPhoto?: string | undefined;
|
||||
}, {
|
||||
title: string;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
description?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
coverPhoto?: 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>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
title?: string | undefined;
|
||||
description?: string | null | undefined;
|
||||
emailSubject?: string | undefined;
|
||||
emailBody?: string | undefined;
|
||||
callToAction?: string | null | undefined;
|
||||
coverPhoto?: 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" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
title?: string | undefined;
|
||||
description?: string | null | undefined;
|
||||
emailSubject?: string | undefined;
|
||||
emailBody?: string | undefined;
|
||||
callToAction?: string | null | undefined;
|
||||
coverPhoto?: 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" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
search?: string | undefined;
|
||||
}, {
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | 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 type CreateCampaignInput = z.infer<typeof createCampaignSchema>;
|
||||
export type UpdateCampaignInput = z.infer<typeof updateCampaignSchema>;
|
||||
export type ListCampaignsInput = z.infer<typeof listCampaignsSchema>;
|
||||
//# sourceMappingURL=campaigns.schemas.d.ts.map
|
||||
1
api/dist/modules/influence/campaigns/campaigns.schemas.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns.schemas.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB/B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB/B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;EAK9B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;EAE3B,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"}
|
||||
53
api/dist/modules/influence/campaigns/campaigns.schemas.js
vendored
Normal file
53
api/dist/modules/influence/campaigns/campaigns.schemas.js
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
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'),
|
||||
description: zod_1.z.string().optional(),
|
||||
emailSubject: zod_1.z.string().min(1, 'Email subject is required'),
|
||||
emailBody: zod_1.z.string().min(1, 'Email body is required'),
|
||||
callToAction: zod_1.z.string().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().optional(),
|
||||
});
|
||||
exports.updateCampaignSchema = zod_1.z.object({
|
||||
title: zod_1.z.string().min(1).optional(),
|
||||
description: zod_1.z.string().nullable().optional(),
|
||||
emailSubject: zod_1.z.string().min(1).optional(),
|
||||
emailBody: zod_1.z.string().min(1).optional(),
|
||||
callToAction: zod_1.z.string().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().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),
|
||||
});
|
||||
//# sourceMappingURL=campaigns.schemas.js.map
|
||||
1
api/dist/modules/influence/campaigns/campaigns.schemas.js.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns.schemas.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaigns.schemas.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.schemas.ts"],"names":[],"mappings":"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"}
|
||||
230
api/dist/modules/influence/campaigns/campaigns.service.d.ts
vendored
Normal file
230
api/dist/modules/influence/campaigns/campaigns.service.d.ts
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
import { UserRole } from '@prisma/client';
|
||||
import type { CreateCampaignInput, UpdateCampaignInput, ListCampaignsInput } 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: string | 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;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: 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;
|
||||
createdByUserId: string | null;
|
||||
}>;
|
||||
delete(id: string): Promise<void>;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=campaigns.service.d.ts.map
|
||||
1
api/dist/modules/influence/campaigns/campaigns.service.d.ts.map
vendored
Normal file
1
api/dist/modules/influence/campaigns/campaigns.service.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"campaigns.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA8DxG,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CvC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAaF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAaV,mBAAmB,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAgCrC,MAAM,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA0CrB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAiBlB,MAAM;CAQxB,CAAC"}
|
||||
202
api/dist/modules/influence/campaigns/campaigns.service.js
vendored
Normal file
202
api/dist/modules/influence/campaigns/campaigns.service.js
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.campaignsService = void 0;
|
||||
const client_1 = require("@prisma/client");
|
||||
const database_1 = require("../../../config/database");
|
||||
const error_handler_1 = require("../../../middleware/error-handler");
|
||||
const campaignSelect = {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
description: true,
|
||||
emailSubject: true,
|
||||
emailBody: true,
|
||||
callToAction: true,
|
||||
coverPhoto: true,
|
||||
status: true,
|
||||
allowSmtpEmail: true,
|
||||
allowMailtoLink: true,
|
||||
collectUserInfo: true,
|
||||
showEmailCount: true,
|
||||
showCallCount: true,
|
||||
allowEmailEditing: true,
|
||||
allowCustomRecipients: true,
|
||||
showResponseWall: true,
|
||||
highlightCampaign: true,
|
||||
targetGovernmentLevels: true,
|
||||
createdByUserId: true,
|
||||
createdByUserEmail: true,
|
||||
createdByUserName: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: {
|
||||
select: {
|
||||
emails: true,
|
||||
responses: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
function generateSlug(title) {
|
||||
return title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 80);
|
||||
}
|
||||
async function resolveSlugCollision(slug, excludeId) {
|
||||
let candidate = slug;
|
||||
let suffix = 2;
|
||||
while (true) {
|
||||
const existing = await database_1.prisma.campaign.findUnique({
|
||||
where: { slug: candidate },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing || (excludeId && existing.id === excludeId)) {
|
||||
return candidate;
|
||||
}
|
||||
candidate = `${slug}-${suffix}`;
|
||||
suffix++;
|
||||
}
|
||||
}
|
||||
exports.campaignsService = {
|
||||
async findAll(filters, user) {
|
||||
const { page, limit, search, status } = filters;
|
||||
const skip = (page - 1) * limit;
|
||||
const where = {};
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ title: { contains: search, mode: 'insensitive' } },
|
||||
{ description: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
if (status)
|
||||
where.status = status;
|
||||
// Non-admin users only see their own campaigns
|
||||
const adminRoles = [client_1.UserRole.SUPER_ADMIN, client_1.UserRole.INFLUENCE_ADMIN, client_1.UserRole.MAP_ADMIN];
|
||||
if (user && !adminRoles.includes(user.role)) {
|
||||
where.createdByUserId = user.id;
|
||||
}
|
||||
const [campaigns, total] = await Promise.all([
|
||||
database_1.prisma.campaign.findMany({
|
||||
where,
|
||||
select: campaignSelect,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
database_1.prisma.campaign.count({ where }),
|
||||
]);
|
||||
return {
|
||||
campaigns,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
},
|
||||
async findById(id) {
|
||||
const campaign = await database_1.prisma.campaign.findUnique({
|
||||
where: { id },
|
||||
select: campaignSelect,
|
||||
});
|
||||
if (!campaign) {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
}
|
||||
return campaign;
|
||||
},
|
||||
async findBySlug(slug) {
|
||||
const campaign = await database_1.prisma.campaign.findUnique({
|
||||
where: { slug },
|
||||
select: campaignSelect,
|
||||
});
|
||||
if (!campaign) {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
}
|
||||
return campaign;
|
||||
},
|
||||
async create(data, user) {
|
||||
const baseSlug = generateSlug(data.title);
|
||||
const slug = await resolveSlugCollision(baseSlug);
|
||||
// Look up user name from DB
|
||||
const dbUser = await database_1.prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { name: true },
|
||||
});
|
||||
// If highlighting this campaign, unset any other highlighted campaign
|
||||
if (data.highlightCampaign) {
|
||||
await database_1.prisma.campaign.updateMany({
|
||||
where: { highlightCampaign: true },
|
||||
data: { highlightCampaign: false },
|
||||
});
|
||||
}
|
||||
const campaign = await database_1.prisma.campaign.create({
|
||||
data: {
|
||||
...data,
|
||||
slug,
|
||||
createdByUserId: user.id,
|
||||
createdByUserEmail: user.email,
|
||||
createdByUserName: dbUser?.name ?? null,
|
||||
},
|
||||
select: campaignSelect,
|
||||
});
|
||||
return campaign;
|
||||
},
|
||||
async update(id, data) {
|
||||
const existing = await database_1.prisma.campaign.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
}
|
||||
const updateData = { ...data };
|
||||
// Regenerate slug if title changes
|
||||
if (data.title && data.title !== existing.title) {
|
||||
const baseSlug = generateSlug(data.title);
|
||||
updateData.slug = await resolveSlugCollision(baseSlug, id);
|
||||
}
|
||||
// If highlighting this campaign, unset any other highlighted campaign
|
||||
if (data.highlightCampaign) {
|
||||
await database_1.prisma.campaign.updateMany({
|
||||
where: { highlightCampaign: true, id: { not: id } },
|
||||
data: { highlightCampaign: false },
|
||||
});
|
||||
}
|
||||
const campaign = await database_1.prisma.campaign.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
select: campaignSelect,
|
||||
});
|
||||
return campaign;
|
||||
},
|
||||
async findActiveCampaigns() {
|
||||
return database_1.prisma.campaign.findMany({
|
||||
where: { status: 'ACTIVE' },
|
||||
select: campaignSelect,
|
||||
orderBy: [
|
||||
{ highlightCampaign: 'desc' },
|
||||
{ createdAt: 'desc' },
|
||||
],
|
||||
});
|
||||
},
|
||||
async findBySlugPublic(slug) {
|
||||
const campaign = await database_1.prisma.campaign.findUnique({
|
||||
where: { slug },
|
||||
select: campaignSelect,
|
||||
});
|
||||
if (!campaign) {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
}
|
||||
if (campaign.status !== 'ACTIVE') {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
}
|
||||
return campaign;
|
||||
},
|
||||
async delete(id) {
|
||||
const existing = await database_1.prisma.campaign.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
}
|
||||
await database_1.prisma.campaign.delete({ where: { id } });
|
||||
},
|
||||
};
|
||||
//# sourceMappingURL=campaigns.service.js.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user