Upgrade system finished
This commit is contained in:
@@ -25,10 +25,13 @@ RUN npm run build
|
||||
# Production stage
|
||||
FROM node:22-alpine AS production
|
||||
WORKDIR /app
|
||||
# Copy compiled output and manifests
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=build /app/package-lock.json* ./
|
||||
COPY --from=build /app/prisma ./prisma
|
||||
# Install production-only deps and regenerate Prisma client
|
||||
RUN npm ci --omit=dev && npx prisma generate
|
||||
COPY --from=build /app/docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh \
|
||||
&& mkdir -p /app/uploads && chown -R node:node /app/uploads
|
||||
|
||||
@@ -40,11 +40,15 @@ WORKDIR /app
|
||||
# Install ffmpeg for video metadata, vips-dev for sharp HEIC support, yt-dlp for video fetching
|
||||
RUN apk add --no-cache ffmpeg vips-dev python3 py3-pip && pip3 install --break-system-packages yt-dlp
|
||||
|
||||
# Copy built files and node_modules
|
||||
# Copy manifests and install production-only deps (no devDeps like typescript)
|
||||
COPY --from=build /app/package*.json ./
|
||||
RUN npm ci --omit=dev --ignore-scripts && \
|
||||
npm install --no-save @img/sharp-linuxmusl-x64
|
||||
|
||||
# Copy built output and schema
|
||||
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 npx prisma generate
|
||||
|
||||
# Run as non-root user
|
||||
USER node
|
||||
|
||||
198
api/dist/config/env.d.ts
vendored
198
api/dist/config/env.d.ts
vendored
@@ -5,15 +5,19 @@ declare const envSchema: z.ZodObject<{
|
||||
API_URL: z.ZodDefault<z.ZodString>;
|
||||
ADMIN_URL: z.ZodDefault<z.ZodString>;
|
||||
DOMAIN: z.ZodDefault<z.ZodString>;
|
||||
INSTANCE_LABEL: z.ZodDefault<z.ZodString>;
|
||||
BUNKER_OPS_ENABLED: z.ZodDefault<z.ZodString>;
|
||||
BUNKER_OPS_REMOTE_WRITE_URL: z.ZodDefault<z.ZodString>;
|
||||
DATABASE_URL: z.ZodString;
|
||||
REDIS_URL: z.ZodDefault<z.ZodString>;
|
||||
JWT_ACCESS_SECRET: z.ZodString;
|
||||
JWT_REFRESH_SECRET: z.ZodString;
|
||||
JWT_INVITE_SECRET: z.ZodString;
|
||||
JWT_ACCESS_EXPIRY: z.ZodDefault<z.ZodString>;
|
||||
JWT_REFRESH_EXPIRY: z.ZodDefault<z.ZodString>;
|
||||
ENCRYPTION_KEY: z.ZodOptional<z.ZodString>;
|
||||
INITIAL_ADMIN_EMAIL: z.ZodDefault<z.ZodString>;
|
||||
INITIAL_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
|
||||
INITIAL_ADMIN_PASSWORD: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
|
||||
SMTP_HOST: z.ZodDefault<z.ZodString>;
|
||||
SMTP_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
SMTP_USER: z.ZodDefault<z.ZodString>;
|
||||
@@ -26,6 +30,7 @@ declare const envSchema: z.ZodObject<{
|
||||
LISTMONK_ADMIN_USER: z.ZodDefault<z.ZodString>;
|
||||
LISTMONK_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
|
||||
LISTMONK_SYNC_ENABLED: z.ZodDefault<z.ZodString>;
|
||||
LISTMONK_WEBHOOK_SECRET: z.ZodDefault<z.ZodString>;
|
||||
LISTMONK_PROXY_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
REPRESENT_API_URL: z.ZodDefault<z.ZodString>;
|
||||
CORS_ORIGINS: z.ZodDefault<z.ZodString>;
|
||||
@@ -58,6 +63,27 @@ declare const envSchema: z.ZodObject<{
|
||||
EXCALIDRAW_URL: z.ZodDefault<z.ZodString>;
|
||||
EXCALIDRAW_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
EXCALIDRAW_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
HOMEPAGE_URL: z.ZodDefault<z.ZodString>;
|
||||
HOMEPAGE_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
VAULTWARDEN_URL: z.ZodDefault<z.ZodString>;
|
||||
VAULTWARDEN_ADMIN_TOKEN: z.ZodDefault<z.ZodString>;
|
||||
VAULTWARDEN_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
ROCKETCHAT_URL: z.ZodDefault<z.ZodString>;
|
||||
ROCKETCHAT_ADMIN_USER: z.ZodDefault<z.ZodString>;
|
||||
ROCKETCHAT_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
|
||||
ROCKETCHAT_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
ENABLE_CHAT: z.ZodDefault<z.ZodString>;
|
||||
GANCIO_URL: z.ZodDefault<z.ZodString>;
|
||||
GANCIO_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
GANCIO_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
GANCIO_ADMIN_USER: z.ZodDefault<z.ZodString>;
|
||||
GANCIO_ADMIN_PASSWORD: z.ZodDefault<z.ZodString>;
|
||||
GANCIO_SYNC_ENABLED: z.ZodDefault<z.ZodString>;
|
||||
ENABLE_MEET: z.ZodDefault<z.ZodString>;
|
||||
JITSI_APP_ID: z.ZodDefault<z.ZodString>;
|
||||
JITSI_APP_SECRET: z.ZodDefault<z.ZodString>;
|
||||
JITSI_URL: z.ZodDefault<z.ZodString>;
|
||||
JITSI_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
PANGOLIN_API_URL: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
|
||||
PANGOLIN_API_KEY: z.ZodDefault<z.ZodString>;
|
||||
PANGOLIN_ORG_ID: z.ZodDefault<z.ZodString>;
|
||||
@@ -66,13 +92,32 @@ declare const envSchema: z.ZodObject<{
|
||||
PANGOLIN_NEWT_ID: z.ZodDefault<z.ZodString>;
|
||||
PANGOLIN_NEWT_SECRET: z.ZodDefault<z.ZodString>;
|
||||
NAR_DATA_DIR: z.ZodDefault<z.ZodString>;
|
||||
OVERPASS_API_URL: z.ZodDefault<z.ZodString>;
|
||||
OVERPASS_MIN_DELAY_MS: z.ZodDefault<z.ZodNumber>;
|
||||
AREA_IMPORT_MAX_GRID_POINTS: z.ZodDefault<z.ZodNumber>;
|
||||
ENABLE_PAYMENTS: z.ZodDefault<z.ZodString>;
|
||||
ENABLE_MEDIA_FEATURES: z.ZodDefault<z.ZodString>;
|
||||
MEDIA_API_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
MEDIA_API_PUBLIC_URL: z.ZodDefault<z.ZodString>;
|
||||
MEDIA_ROOT: z.ZodDefault<z.ZodString>;
|
||||
MEDIA_UPLOADS: z.ZodDefault<z.ZodString>;
|
||||
MAX_UPLOAD_SIZE_GB: z.ZodDefault<z.ZodNumber>;
|
||||
PUBLIC_MEDIA_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
GITEA_REGISTRY: z.ZodDefault<z.ZodString>;
|
||||
GITEA_REGISTRY_USER: z.ZodDefault<z.ZodString>;
|
||||
GITEA_REGISTRY_PASS: z.ZodDefault<z.ZodString>;
|
||||
GITEA_COMMENTS_ENABLED: z.ZodDefault<z.ZodString>;
|
||||
GITEA_API_TOKEN: z.ZodDefault<z.ZodString>;
|
||||
GITEA_COMMENTS_REPO_OWNER: z.ZodDefault<z.ZodString>;
|
||||
GITEA_COMMENTS_REPO_NAME: z.ZodDefault<z.ZodString>;
|
||||
GITEA_OAUTH_CLIENT_ID: z.ZodDefault<z.ZodString>;
|
||||
GITEA_OAUTH_CLIENT_SECRET: z.ZodDefault<z.ZodString>;
|
||||
ENABLE_SMS: z.ZodDefault<z.ZodString>;
|
||||
TERMUX_API_URL: z.ZodDefault<z.ZodString>;
|
||||
TERMUX_API_KEY: z.ZodDefault<z.ZodString>;
|
||||
SMS_DELAY_BETWEEN_MS: z.ZodDefault<z.ZodNumber>;
|
||||
SMS_MAX_RETRIES: z.ZodDefault<z.ZodNumber>;
|
||||
SMS_RESPONSE_SYNC_INTERVAL_MS: z.ZodDefault<z.ZodNumber>;
|
||||
SMS_DEVICE_MONITOR_INTERVAL_MS: z.ZodDefault<z.ZodNumber>;
|
||||
CODE_SERVER_URL: z.ZodDefault<z.ZodString>;
|
||||
CODE_SERVER_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
MKDOCS_PREVIEW_URL: z.ZodDefault<z.ZodString>;
|
||||
@@ -86,8 +131,10 @@ declare const envSchema: z.ZodObject<{
|
||||
PROMETHEUS_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
GRAFANA_URL: z.ZodDefault<z.ZodString>;
|
||||
GRAFANA_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
GRAFANA_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
ALERTMANAGER_URL: z.ZodDefault<z.ZodString>;
|
||||
ALERTMANAGER_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
ALERTMANAGER_EMBED_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
CADVISOR_URL: z.ZodDefault<z.ZodString>;
|
||||
CADVISOR_PORT: z.ZodDefault<z.ZodNumber>;
|
||||
NODE_EXPORTER_URL: z.ZodDefault<z.ZodString>;
|
||||
@@ -102,10 +149,14 @@ declare const envSchema: z.ZodObject<{
|
||||
API_URL: string;
|
||||
ADMIN_URL: string;
|
||||
DOMAIN: string;
|
||||
INSTANCE_LABEL: string;
|
||||
BUNKER_OPS_ENABLED: string;
|
||||
BUNKER_OPS_REMOTE_WRITE_URL: string;
|
||||
DATABASE_URL: string;
|
||||
REDIS_URL: string;
|
||||
JWT_ACCESS_SECRET: string;
|
||||
JWT_REFRESH_SECRET: string;
|
||||
JWT_INVITE_SECRET: string;
|
||||
JWT_ACCESS_EXPIRY: string;
|
||||
JWT_REFRESH_EXPIRY: string;
|
||||
INITIAL_ADMIN_EMAIL: string;
|
||||
@@ -122,6 +173,7 @@ declare const envSchema: z.ZodObject<{
|
||||
LISTMONK_ADMIN_USER: string;
|
||||
LISTMONK_ADMIN_PASSWORD: string;
|
||||
LISTMONK_SYNC_ENABLED: string;
|
||||
LISTMONK_WEBHOOK_SECRET: string;
|
||||
LISTMONK_PROXY_PORT: number;
|
||||
REPRESENT_API_URL: string;
|
||||
CORS_ORIGINS: string;
|
||||
@@ -152,6 +204,27 @@ declare const envSchema: z.ZodObject<{
|
||||
EXCALIDRAW_URL: string;
|
||||
EXCALIDRAW_PORT: number;
|
||||
EXCALIDRAW_EMBED_PORT: number;
|
||||
HOMEPAGE_URL: string;
|
||||
HOMEPAGE_EMBED_PORT: number;
|
||||
VAULTWARDEN_URL: string;
|
||||
VAULTWARDEN_ADMIN_TOKEN: string;
|
||||
VAULTWARDEN_EMBED_PORT: number;
|
||||
ROCKETCHAT_URL: string;
|
||||
ROCKETCHAT_ADMIN_USER: string;
|
||||
ROCKETCHAT_ADMIN_PASSWORD: string;
|
||||
ROCKETCHAT_EMBED_PORT: number;
|
||||
ENABLE_CHAT: string;
|
||||
GANCIO_URL: string;
|
||||
GANCIO_PORT: number;
|
||||
GANCIO_EMBED_PORT: number;
|
||||
GANCIO_ADMIN_USER: string;
|
||||
GANCIO_ADMIN_PASSWORD: string;
|
||||
GANCIO_SYNC_ENABLED: string;
|
||||
ENABLE_MEET: string;
|
||||
JITSI_APP_ID: string;
|
||||
JITSI_APP_SECRET: string;
|
||||
JITSI_URL: string;
|
||||
JITSI_EMBED_PORT: number;
|
||||
PANGOLIN_API_URL: string;
|
||||
PANGOLIN_API_KEY: string;
|
||||
PANGOLIN_ORG_ID: string;
|
||||
@@ -160,13 +233,32 @@ declare const envSchema: z.ZodObject<{
|
||||
PANGOLIN_NEWT_ID: string;
|
||||
PANGOLIN_NEWT_SECRET: string;
|
||||
NAR_DATA_DIR: string;
|
||||
OVERPASS_API_URL: string;
|
||||
OVERPASS_MIN_DELAY_MS: number;
|
||||
AREA_IMPORT_MAX_GRID_POINTS: number;
|
||||
ENABLE_PAYMENTS: string;
|
||||
ENABLE_MEDIA_FEATURES: string;
|
||||
MEDIA_API_PORT: number;
|
||||
MEDIA_API_PUBLIC_URL: string;
|
||||
MEDIA_ROOT: string;
|
||||
MEDIA_UPLOADS: string;
|
||||
MAX_UPLOAD_SIZE_GB: number;
|
||||
PUBLIC_MEDIA_PORT: number;
|
||||
GITEA_REGISTRY: string;
|
||||
GITEA_REGISTRY_USER: string;
|
||||
GITEA_REGISTRY_PASS: string;
|
||||
GITEA_COMMENTS_ENABLED: string;
|
||||
GITEA_API_TOKEN: string;
|
||||
GITEA_COMMENTS_REPO_OWNER: string;
|
||||
GITEA_COMMENTS_REPO_NAME: string;
|
||||
GITEA_OAUTH_CLIENT_ID: string;
|
||||
GITEA_OAUTH_CLIENT_SECRET: string;
|
||||
ENABLE_SMS: string;
|
||||
TERMUX_API_URL: string;
|
||||
TERMUX_API_KEY: string;
|
||||
SMS_DELAY_BETWEEN_MS: number;
|
||||
SMS_MAX_RETRIES: number;
|
||||
SMS_RESPONSE_SYNC_INTERVAL_MS: number;
|
||||
SMS_DEVICE_MONITOR_INTERVAL_MS: number;
|
||||
CODE_SERVER_URL: string;
|
||||
CODE_SERVER_PORT: number;
|
||||
MKDOCS_PREVIEW_URL: string;
|
||||
@@ -180,8 +272,10 @@ declare const envSchema: z.ZodObject<{
|
||||
PROMETHEUS_PORT: number;
|
||||
GRAFANA_URL: string;
|
||||
GRAFANA_PORT: number;
|
||||
GRAFANA_EMBED_PORT: number;
|
||||
ALERTMANAGER_URL: string;
|
||||
ALERTMANAGER_PORT: number;
|
||||
ALERTMANAGER_EMBED_PORT: number;
|
||||
CADVISOR_URL: string;
|
||||
CADVISOR_PORT: number;
|
||||
NODE_EXPORTER_URL: string;
|
||||
@@ -197,11 +291,15 @@ declare const envSchema: z.ZodObject<{
|
||||
DATABASE_URL: string;
|
||||
JWT_ACCESS_SECRET: string;
|
||||
JWT_REFRESH_SECRET: string;
|
||||
JWT_INVITE_SECRET: string;
|
||||
NODE_ENV?: "development" | "production" | "test" | undefined;
|
||||
PORT?: number | undefined;
|
||||
API_URL?: string | undefined;
|
||||
ADMIN_URL?: string | undefined;
|
||||
DOMAIN?: string | undefined;
|
||||
INSTANCE_LABEL?: string | undefined;
|
||||
BUNKER_OPS_ENABLED?: string | undefined;
|
||||
BUNKER_OPS_REMOTE_WRITE_URL?: string | undefined;
|
||||
REDIS_URL?: string | undefined;
|
||||
JWT_ACCESS_EXPIRY?: string | undefined;
|
||||
JWT_REFRESH_EXPIRY?: string | undefined;
|
||||
@@ -220,6 +318,7 @@ declare const envSchema: z.ZodObject<{
|
||||
LISTMONK_ADMIN_USER?: string | undefined;
|
||||
LISTMONK_ADMIN_PASSWORD?: string | undefined;
|
||||
LISTMONK_SYNC_ENABLED?: string | undefined;
|
||||
LISTMONK_WEBHOOK_SECRET?: string | undefined;
|
||||
LISTMONK_PROXY_PORT?: number | undefined;
|
||||
REPRESENT_API_URL?: string | undefined;
|
||||
CORS_ORIGINS?: string | undefined;
|
||||
@@ -252,6 +351,27 @@ declare const envSchema: z.ZodObject<{
|
||||
EXCALIDRAW_URL?: string | undefined;
|
||||
EXCALIDRAW_PORT?: number | undefined;
|
||||
EXCALIDRAW_EMBED_PORT?: number | undefined;
|
||||
HOMEPAGE_URL?: string | undefined;
|
||||
HOMEPAGE_EMBED_PORT?: number | undefined;
|
||||
VAULTWARDEN_URL?: string | undefined;
|
||||
VAULTWARDEN_ADMIN_TOKEN?: string | undefined;
|
||||
VAULTWARDEN_EMBED_PORT?: number | undefined;
|
||||
ROCKETCHAT_URL?: string | undefined;
|
||||
ROCKETCHAT_ADMIN_USER?: string | undefined;
|
||||
ROCKETCHAT_ADMIN_PASSWORD?: string | undefined;
|
||||
ROCKETCHAT_EMBED_PORT?: number | undefined;
|
||||
ENABLE_CHAT?: string | undefined;
|
||||
GANCIO_URL?: string | undefined;
|
||||
GANCIO_PORT?: number | undefined;
|
||||
GANCIO_EMBED_PORT?: number | undefined;
|
||||
GANCIO_ADMIN_USER?: string | undefined;
|
||||
GANCIO_ADMIN_PASSWORD?: string | undefined;
|
||||
GANCIO_SYNC_ENABLED?: string | undefined;
|
||||
ENABLE_MEET?: string | undefined;
|
||||
JITSI_APP_ID?: string | undefined;
|
||||
JITSI_APP_SECRET?: string | undefined;
|
||||
JITSI_URL?: string | undefined;
|
||||
JITSI_EMBED_PORT?: number | undefined;
|
||||
PANGOLIN_API_URL?: string | undefined;
|
||||
PANGOLIN_API_KEY?: string | undefined;
|
||||
PANGOLIN_ORG_ID?: string | undefined;
|
||||
@@ -260,13 +380,32 @@ declare const envSchema: z.ZodObject<{
|
||||
PANGOLIN_NEWT_ID?: string | undefined;
|
||||
PANGOLIN_NEWT_SECRET?: string | undefined;
|
||||
NAR_DATA_DIR?: string | undefined;
|
||||
OVERPASS_API_URL?: string | undefined;
|
||||
OVERPASS_MIN_DELAY_MS?: number | undefined;
|
||||
AREA_IMPORT_MAX_GRID_POINTS?: number | undefined;
|
||||
ENABLE_PAYMENTS?: string | undefined;
|
||||
ENABLE_MEDIA_FEATURES?: string | undefined;
|
||||
MEDIA_API_PORT?: number | undefined;
|
||||
MEDIA_API_PUBLIC_URL?: string | undefined;
|
||||
MEDIA_ROOT?: string | undefined;
|
||||
MEDIA_UPLOADS?: string | undefined;
|
||||
MAX_UPLOAD_SIZE_GB?: number | undefined;
|
||||
PUBLIC_MEDIA_PORT?: number | undefined;
|
||||
GITEA_REGISTRY?: string | undefined;
|
||||
GITEA_REGISTRY_USER?: string | undefined;
|
||||
GITEA_REGISTRY_PASS?: string | undefined;
|
||||
GITEA_COMMENTS_ENABLED?: string | undefined;
|
||||
GITEA_API_TOKEN?: string | undefined;
|
||||
GITEA_COMMENTS_REPO_OWNER?: string | undefined;
|
||||
GITEA_COMMENTS_REPO_NAME?: string | undefined;
|
||||
GITEA_OAUTH_CLIENT_ID?: string | undefined;
|
||||
GITEA_OAUTH_CLIENT_SECRET?: string | undefined;
|
||||
ENABLE_SMS?: string | undefined;
|
||||
TERMUX_API_URL?: string | undefined;
|
||||
TERMUX_API_KEY?: string | undefined;
|
||||
SMS_DELAY_BETWEEN_MS?: number | undefined;
|
||||
SMS_MAX_RETRIES?: number | undefined;
|
||||
SMS_RESPONSE_SYNC_INTERVAL_MS?: number | undefined;
|
||||
SMS_DEVICE_MONITOR_INTERVAL_MS?: number | undefined;
|
||||
CODE_SERVER_URL?: string | undefined;
|
||||
CODE_SERVER_PORT?: number | undefined;
|
||||
MKDOCS_PREVIEW_URL?: string | undefined;
|
||||
@@ -280,8 +419,10 @@ declare const envSchema: z.ZodObject<{
|
||||
PROMETHEUS_PORT?: number | undefined;
|
||||
GRAFANA_URL?: string | undefined;
|
||||
GRAFANA_PORT?: number | undefined;
|
||||
GRAFANA_EMBED_PORT?: number | undefined;
|
||||
ALERTMANAGER_URL?: string | undefined;
|
||||
ALERTMANAGER_PORT?: number | undefined;
|
||||
ALERTMANAGER_EMBED_PORT?: number | undefined;
|
||||
CADVISOR_URL?: string | undefined;
|
||||
CADVISOR_PORT?: number | undefined;
|
||||
NODE_EXPORTER_URL?: string | undefined;
|
||||
@@ -298,10 +439,14 @@ export declare const env: {
|
||||
API_URL: string;
|
||||
ADMIN_URL: string;
|
||||
DOMAIN: string;
|
||||
INSTANCE_LABEL: string;
|
||||
BUNKER_OPS_ENABLED: string;
|
||||
BUNKER_OPS_REMOTE_WRITE_URL: string;
|
||||
DATABASE_URL: string;
|
||||
REDIS_URL: string;
|
||||
JWT_ACCESS_SECRET: string;
|
||||
JWT_REFRESH_SECRET: string;
|
||||
JWT_INVITE_SECRET: string;
|
||||
JWT_ACCESS_EXPIRY: string;
|
||||
JWT_REFRESH_EXPIRY: string;
|
||||
INITIAL_ADMIN_EMAIL: string;
|
||||
@@ -318,6 +463,7 @@ export declare const env: {
|
||||
LISTMONK_ADMIN_USER: string;
|
||||
LISTMONK_ADMIN_PASSWORD: string;
|
||||
LISTMONK_SYNC_ENABLED: string;
|
||||
LISTMONK_WEBHOOK_SECRET: string;
|
||||
LISTMONK_PROXY_PORT: number;
|
||||
REPRESENT_API_URL: string;
|
||||
CORS_ORIGINS: string;
|
||||
@@ -348,6 +494,27 @@ export declare const env: {
|
||||
EXCALIDRAW_URL: string;
|
||||
EXCALIDRAW_PORT: number;
|
||||
EXCALIDRAW_EMBED_PORT: number;
|
||||
HOMEPAGE_URL: string;
|
||||
HOMEPAGE_EMBED_PORT: number;
|
||||
VAULTWARDEN_URL: string;
|
||||
VAULTWARDEN_ADMIN_TOKEN: string;
|
||||
VAULTWARDEN_EMBED_PORT: number;
|
||||
ROCKETCHAT_URL: string;
|
||||
ROCKETCHAT_ADMIN_USER: string;
|
||||
ROCKETCHAT_ADMIN_PASSWORD: string;
|
||||
ROCKETCHAT_EMBED_PORT: number;
|
||||
ENABLE_CHAT: string;
|
||||
GANCIO_URL: string;
|
||||
GANCIO_PORT: number;
|
||||
GANCIO_EMBED_PORT: number;
|
||||
GANCIO_ADMIN_USER: string;
|
||||
GANCIO_ADMIN_PASSWORD: string;
|
||||
GANCIO_SYNC_ENABLED: string;
|
||||
ENABLE_MEET: string;
|
||||
JITSI_APP_ID: string;
|
||||
JITSI_APP_SECRET: string;
|
||||
JITSI_URL: string;
|
||||
JITSI_EMBED_PORT: number;
|
||||
PANGOLIN_API_URL: string;
|
||||
PANGOLIN_API_KEY: string;
|
||||
PANGOLIN_ORG_ID: string;
|
||||
@@ -356,13 +523,32 @@ export declare const env: {
|
||||
PANGOLIN_NEWT_ID: string;
|
||||
PANGOLIN_NEWT_SECRET: string;
|
||||
NAR_DATA_DIR: string;
|
||||
OVERPASS_API_URL: string;
|
||||
OVERPASS_MIN_DELAY_MS: number;
|
||||
AREA_IMPORT_MAX_GRID_POINTS: number;
|
||||
ENABLE_PAYMENTS: string;
|
||||
ENABLE_MEDIA_FEATURES: string;
|
||||
MEDIA_API_PORT: number;
|
||||
MEDIA_API_PUBLIC_URL: string;
|
||||
MEDIA_ROOT: string;
|
||||
MEDIA_UPLOADS: string;
|
||||
MAX_UPLOAD_SIZE_GB: number;
|
||||
PUBLIC_MEDIA_PORT: number;
|
||||
GITEA_REGISTRY: string;
|
||||
GITEA_REGISTRY_USER: string;
|
||||
GITEA_REGISTRY_PASS: string;
|
||||
GITEA_COMMENTS_ENABLED: string;
|
||||
GITEA_API_TOKEN: string;
|
||||
GITEA_COMMENTS_REPO_OWNER: string;
|
||||
GITEA_COMMENTS_REPO_NAME: string;
|
||||
GITEA_OAUTH_CLIENT_ID: string;
|
||||
GITEA_OAUTH_CLIENT_SECRET: string;
|
||||
ENABLE_SMS: string;
|
||||
TERMUX_API_URL: string;
|
||||
TERMUX_API_KEY: string;
|
||||
SMS_DELAY_BETWEEN_MS: number;
|
||||
SMS_MAX_RETRIES: number;
|
||||
SMS_RESPONSE_SYNC_INTERVAL_MS: number;
|
||||
SMS_DEVICE_MONITOR_INTERVAL_MS: number;
|
||||
CODE_SERVER_URL: string;
|
||||
CODE_SERVER_PORT: number;
|
||||
MKDOCS_PREVIEW_URL: string;
|
||||
@@ -376,8 +562,10 @@ export declare const env: {
|
||||
PROMETHEUS_PORT: number;
|
||||
GRAFANA_URL: string;
|
||||
GRAFANA_PORT: number;
|
||||
GRAFANA_EMBED_PORT: number;
|
||||
ALERTMANAGER_URL: string;
|
||||
ALERTMANAGER_PORT: number;
|
||||
ALERTMANAGER_EMBED_PORT: number;
|
||||
CADVISOR_URL: string;
|
||||
CADVISOR_PORT: number;
|
||||
NODE_EXPORTER_URL: string;
|
||||
|
||||
2
api/dist/config/env.d.ts.map
vendored
2
api/dist/config/env.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2Nb,CAAC;AAEH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAY5C,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC"}
|
||||
65
api/dist/config/env.js
vendored
65
api/dist/config/env.js
vendored
@@ -14,6 +14,10 @@ const envSchema = zod_1.z.object({
|
||||
API_URL: zod_1.z.string().default('http://localhost:4000'),
|
||||
ADMIN_URL: zod_1.z.string().default('http://localhost:3000'),
|
||||
DOMAIN: zod_1.z.string().default('cmlite.org'),
|
||||
// Bunker Ops (Fleet Management)
|
||||
INSTANCE_LABEL: zod_1.z.string().default(''),
|
||||
BUNKER_OPS_ENABLED: zod_1.z.string().default('false'),
|
||||
BUNKER_OPS_REMOTE_WRITE_URL: zod_1.z.string().default(''),
|
||||
// Database
|
||||
DATABASE_URL: zod_1.z.string(),
|
||||
// Redis
|
||||
@@ -21,13 +25,15 @@ const envSchema = zod_1.z.object({
|
||||
// JWT
|
||||
JWT_ACCESS_SECRET: zod_1.z.string().min(32),
|
||||
JWT_REFRESH_SECRET: zod_1.z.string().min(32),
|
||||
JWT_INVITE_SECRET: zod_1.z.string().min(32),
|
||||
JWT_ACCESS_EXPIRY: zod_1.z.string().default('15m'),
|
||||
JWT_REFRESH_EXPIRY: zod_1.z.string().default('7d'),
|
||||
// Encryption (for DB-stored secrets like SMTP password; falls back to JWT_ACCESS_SECRET)
|
||||
ENCRYPTION_KEY: zod_1.z.string().optional(),
|
||||
ENCRYPTION_KEY: zod_1.z.string().min(32, 'ENCRYPTION_KEY must be at least 32 characters').optional(),
|
||||
// Initial Super Admin (auto-created during database seeding)
|
||||
INITIAL_ADMIN_EMAIL: zod_1.z.string().email().default('admin@cmlite.org'),
|
||||
INITIAL_ADMIN_PASSWORD: zod_1.z.string().min(12).default('REQUIRED_STRONG_PASSWORD_CHANGE_THIS'),
|
||||
INITIAL_ADMIN_PASSWORD: zod_1.z.string().min(12).default('REQUIRED_STRONG_PASSWORD_CHANGE_THIS')
|
||||
.refine((val) => val !== 'REQUIRED_STRONG_PASSWORD_CHANGE_THIS', { message: 'INITIAL_ADMIN_PASSWORD must be changed from the default placeholder value' }),
|
||||
// SMTP
|
||||
SMTP_HOST: zod_1.z.string().default('mailhog-changemaker'),
|
||||
SMTP_PORT: zod_1.z.coerce.number().default(1025),
|
||||
@@ -42,6 +48,7 @@ const envSchema = zod_1.z.object({
|
||||
LISTMONK_ADMIN_USER: zod_1.z.string().default('admin'),
|
||||
LISTMONK_ADMIN_PASSWORD: zod_1.z.string().default(''),
|
||||
LISTMONK_SYNC_ENABLED: zod_1.z.string().default('false'),
|
||||
LISTMONK_WEBHOOK_SECRET: zod_1.z.string().default(''),
|
||||
LISTMONK_PROXY_PORT: zod_1.z.coerce.number().default(9002),
|
||||
// Represent API (Canadian electoral data)
|
||||
REPRESENT_API_URL: zod_1.z.string().default('https://represent.opennorth.ca'),
|
||||
@@ -84,6 +91,32 @@ const envSchema = zod_1.z.object({
|
||||
EXCALIDRAW_URL: zod_1.z.string().default('http://excalidraw-changemaker:80'),
|
||||
EXCALIDRAW_PORT: zod_1.z.coerce.number().default(8090),
|
||||
EXCALIDRAW_EMBED_PORT: zod_1.z.coerce.number().default(8886),
|
||||
// Homepage (service dashboard)
|
||||
HOMEPAGE_URL: zod_1.z.string().default('http://homepage-changemaker:3000'),
|
||||
HOMEPAGE_EMBED_PORT: zod_1.z.coerce.number().default(8887),
|
||||
// Vaultwarden (password manager)
|
||||
VAULTWARDEN_URL: zod_1.z.string().default('http://vaultwarden-changemaker:80'),
|
||||
VAULTWARDEN_ADMIN_TOKEN: zod_1.z.string().default(''),
|
||||
VAULTWARDEN_EMBED_PORT: zod_1.z.coerce.number().default(8890),
|
||||
// Rocket.Chat (team chat)
|
||||
ROCKETCHAT_URL: zod_1.z.string().default('http://rocketchat-changemaker:3000'),
|
||||
ROCKETCHAT_ADMIN_USER: zod_1.z.string().default(''),
|
||||
ROCKETCHAT_ADMIN_PASSWORD: zod_1.z.string().default(''),
|
||||
ROCKETCHAT_EMBED_PORT: zod_1.z.coerce.number().default(8891),
|
||||
ENABLE_CHAT: zod_1.z.string().default('false'),
|
||||
// Gancio (event management)
|
||||
GANCIO_URL: zod_1.z.string().default('http://gancio-changemaker:13120'),
|
||||
GANCIO_PORT: zod_1.z.coerce.number().default(8092),
|
||||
GANCIO_EMBED_PORT: zod_1.z.coerce.number().default(8892),
|
||||
GANCIO_ADMIN_USER: zod_1.z.string().default('admin'),
|
||||
GANCIO_ADMIN_PASSWORD: zod_1.z.string().default(''),
|
||||
GANCIO_SYNC_ENABLED: zod_1.z.string().default('false'),
|
||||
// Jitsi Meet (video conferencing)
|
||||
ENABLE_MEET: zod_1.z.string().default('false'),
|
||||
JITSI_APP_ID: zod_1.z.string().default('changemaker'),
|
||||
JITSI_APP_SECRET: zod_1.z.string().default(''),
|
||||
JITSI_URL: zod_1.z.string().default('http://jitsi-web-changemaker:80'),
|
||||
JITSI_EMBED_PORT: zod_1.z.coerce.number().default(8893),
|
||||
// Pangolin (tunnel / reverse proxy)
|
||||
PANGOLIN_API_URL: zod_1.z.string()
|
||||
.default('')
|
||||
@@ -96,6 +129,12 @@ const envSchema = zod_1.z.object({
|
||||
PANGOLIN_NEWT_SECRET: zod_1.z.string().default(''),
|
||||
// NAR (National Address Register)
|
||||
NAR_DATA_DIR: zod_1.z.string().default('/data'),
|
||||
// Overpass / Area Import
|
||||
OVERPASS_API_URL: zod_1.z.string().default('https://overpass-api.de/api/interpreter'),
|
||||
OVERPASS_MIN_DELAY_MS: zod_1.z.coerce.number().default(30000),
|
||||
AREA_IMPORT_MAX_GRID_POINTS: zod_1.z.coerce.number().default(500),
|
||||
// Payments (Stripe)
|
||||
ENABLE_PAYMENTS: zod_1.z.string().default('false'),
|
||||
// Media Management
|
||||
ENABLE_MEDIA_FEATURES: zod_1.z.string().default('false'),
|
||||
MEDIA_API_PORT: zod_1.z.coerce.number().default(4100),
|
||||
@@ -103,7 +142,25 @@ const envSchema = zod_1.z.object({
|
||||
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),
|
||||
// Container Registry
|
||||
GITEA_REGISTRY: zod_1.z.string().default('gitea.bnkops.com/admin'),
|
||||
GITEA_REGISTRY_USER: zod_1.z.string().default(''),
|
||||
GITEA_REGISTRY_PASS: zod_1.z.string().default(''),
|
||||
// Gitea Docs Comments
|
||||
GITEA_COMMENTS_ENABLED: zod_1.z.string().default('false'),
|
||||
GITEA_API_TOKEN: zod_1.z.string().default(''),
|
||||
GITEA_COMMENTS_REPO_OWNER: zod_1.z.string().default(''),
|
||||
GITEA_COMMENTS_REPO_NAME: zod_1.z.string().default('docs-comments'),
|
||||
GITEA_OAUTH_CLIENT_ID: zod_1.z.string().default(''),
|
||||
GITEA_OAUTH_CLIENT_SECRET: zod_1.z.string().default(''),
|
||||
// SMS Campaigns (Termux Android bridge)
|
||||
ENABLE_SMS: zod_1.z.string().default('false'),
|
||||
TERMUX_API_URL: zod_1.z.string().default('http://10.0.0.193:5001'),
|
||||
TERMUX_API_KEY: zod_1.z.string().default(''),
|
||||
SMS_DELAY_BETWEEN_MS: zod_1.z.coerce.number().default(3000),
|
||||
SMS_MAX_RETRIES: zod_1.z.coerce.number().default(3),
|
||||
SMS_RESPONSE_SYNC_INTERVAL_MS: zod_1.z.coerce.number().default(30000),
|
||||
SMS_DEVICE_MONITOR_INTERVAL_MS: zod_1.z.coerce.number().default(30000),
|
||||
// Docs / Code Server
|
||||
CODE_SERVER_URL: zod_1.z.string().default('http://code-server-changemaker:8080'),
|
||||
CODE_SERVER_PORT: zod_1.z.coerce.number().default(8888),
|
||||
@@ -119,8 +176,10 @@ const envSchema = zod_1.z.object({
|
||||
PROMETHEUS_PORT: zod_1.z.coerce.number().default(9090),
|
||||
GRAFANA_URL: zod_1.z.string().default('http://grafana-changemaker:3000'),
|
||||
GRAFANA_PORT: zod_1.z.coerce.number().default(3005),
|
||||
GRAFANA_EMBED_PORT: zod_1.z.coerce.number().default(8894),
|
||||
ALERTMANAGER_URL: zod_1.z.string().default('http://alertmanager-changemaker:9093'),
|
||||
ALERTMANAGER_PORT: zod_1.z.coerce.number().default(9093),
|
||||
ALERTMANAGER_EMBED_PORT: zod_1.z.coerce.number().default(8895),
|
||||
CADVISOR_URL: zod_1.z.string().default('http://cadvisor-changemaker:8080'),
|
||||
CADVISOR_PORT: zod_1.z.coerce.number().default(8086),
|
||||
NODE_EXPORTER_URL: zod_1.z.string().default('http://node-exporter-changemaker:9100'),
|
||||
|
||||
2
api/dist/config/env.js.map
vendored
2
api/dist/config/env.js.map
vendored
File diff suppressed because one or more lines are too long
67
api/dist/media-server.js
vendored
67
api/dist/media-server.js
vendored
@@ -11,13 +11,30 @@ 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 public_routes_1 = require("./modules/media/routes/public.routes");
|
||||
const chat_stream_routes_1 = require("./modules/media/routes/chat-stream.routes");
|
||||
const comments_routes_1 = require("./modules/media/routes/comments.routes");
|
||||
const upload_routes_1 = require("./modules/media/routes/upload.routes");
|
||||
const video_actions_routes_1 = require("./modules/media/routes/video-actions.routes");
|
||||
const video_schedule_routes_1 = require("./modules/media/routes/video-schedule.routes");
|
||||
const video_tracking_routes_1 = require("./modules/media/routes/video-tracking.routes");
|
||||
const comment_admin_routes_1 = require("./modules/media/routes/comment-admin.routes");
|
||||
const chat_notifications_routes_1 = require("./modules/media/routes/chat-notifications.routes");
|
||||
const chat_threads_routes_1 = require("./modules/media/routes/chat-threads.routes");
|
||||
const user_profile_routes_1 = require("./modules/media/routes/user-profile.routes");
|
||||
const shorts_routes_1 = require("./modules/media/routes/shorts.routes");
|
||||
const upvote_routes_1 = require("./modules/media/routes/upvote.routes");
|
||||
const video_schedule_queue_service_1 = require("./services/video-schedule-queue.service");
|
||||
const video_fetch_queue_service_1 = require("./services/video-fetch-queue.service");
|
||||
const fetch_routes_1 = require("./modules/media/routes/fetch.routes");
|
||||
const playlists_public_routes_1 = require("./modules/media/routes/playlists-public.routes");
|
||||
const playlists_user_routes_1 = require("./modules/media/routes/playlists-user.routes");
|
||||
const playlists_admin_routes_1 = require("./modules/media/routes/playlists-admin.routes");
|
||||
const photos_routes_1 = require("./modules/media/routes/photos.routes");
|
||||
const photo_upload_routes_1 = require("./modules/media/routes/photo-upload.routes");
|
||||
const photo_albums_routes_1 = require("./modules/media/routes/photo-albums.routes");
|
||||
const photos_public_routes_1 = require("./modules/media/routes/photos-public.routes");
|
||||
const photo_engagement_routes_1 = require("./modules/media/routes/photo-engagement.routes");
|
||||
// Add BigInt serialization support for Prisma BigInt fields
|
||||
// This converts BigInt values to strings when JSON.stringify() is called
|
||||
BigInt.prototype.toJSON = function () {
|
||||
@@ -34,6 +51,7 @@ const fastify = (0, fastify_1.default)({
|
||||
process.on('SIGTERM', async () => {
|
||||
logger_1.logger.info('SIGTERM received, shutting down gracefully...');
|
||||
await video_schedule_queue_service_1.videoScheduleQueueService.close();
|
||||
await video_fetch_queue_service_1.videoFetchQueueService.close();
|
||||
fastify.close(() => {
|
||||
logger_1.logger.info('Media API server closed');
|
||||
process.exit(0);
|
||||
@@ -52,8 +70,23 @@ process.on('uncaughtException', (error) => {
|
||||
// Start server
|
||||
const start = async () => {
|
||||
try {
|
||||
// CORS configuration
|
||||
// CORS configuration — allow admin app + MkDocs docs site
|
||||
const allowedOrigins = env_1.env.CORS_ORIGINS.split(',').map(o => o.trim());
|
||||
// Auto-add MkDocs origins so video cards/players work in docs
|
||||
const mkdocsOrigin = `http://localhost:${env_1.env.MKDOCS_PORT || 4003}`;
|
||||
if (!allowedOrigins.includes(mkdocsOrigin)) {
|
||||
allowedOrigins.push(mkdocsOrigin);
|
||||
}
|
||||
// Also allow the docs subdomain in production (docs.domain.org)
|
||||
for (const origin of [...allowedOrigins]) {
|
||||
const match = origin.match(/^(https?:\/\/)app\./);
|
||||
if (match) {
|
||||
const docsOrigin = origin.replace(/^(https?:\/\/)app\./, '$1docs.');
|
||||
if (!allowedOrigins.includes(docsOrigin)) {
|
||||
allowedOrigins.push(docsOrigin);
|
||||
}
|
||||
}
|
||||
}
|
||||
await fastify.register(cors_1.default, {
|
||||
origin: (origin, cb) => {
|
||||
// Allow requests with no origin (mobile apps, curl, etc.)
|
||||
@@ -93,10 +126,29 @@ const start = async () => {
|
||||
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' });
|
||||
await fastify.register(public_routes_1.publicRoutes, { prefix: '/api' });
|
||||
await fastify.register(comments_routes_1.commentsRoutes, { prefix: '/api' });
|
||||
await fastify.register(chat_stream_routes_1.chatStreamRoutes, { prefix: '/api' });
|
||||
await fastify.register(comment_admin_routes_1.commentAdminRoutes, { prefix: '/api/media' });
|
||||
await fastify.register(chat_notifications_routes_1.chatNotificationsRoutes, { prefix: '/api/media' });
|
||||
await fastify.register(chat_threads_routes_1.chatThreadsRoutes, { prefix: '/api/media' });
|
||||
await fastify.register(user_profile_routes_1.userProfileRoutes, { prefix: '/api/media' });
|
||||
await fastify.register(fetch_routes_1.fetchRoutes, { prefix: '/api/videos' });
|
||||
await fastify.register(shorts_routes_1.shortsRoutes, { prefix: '/api' });
|
||||
await fastify.register(upvote_routes_1.upvoteRoutes, { prefix: '/api' });
|
||||
await fastify.register(playlists_public_routes_1.playlistsPublicRoutes, { prefix: '/api/playlists' });
|
||||
await fastify.register(playlists_user_routes_1.playlistsUserRoutes, { prefix: '/api/playlists' });
|
||||
await fastify.register(playlists_admin_routes_1.playlistsAdminRoutes, { prefix: '/api/media' });
|
||||
// Photo gallery routes
|
||||
await fastify.register(photos_routes_1.photosRoutes, { prefix: '/api/photos' });
|
||||
await fastify.register(photo_upload_routes_1.photoUploadRoutes, { prefix: '/api/photos' });
|
||||
await fastify.register(photo_albums_routes_1.photoAlbumsRoutes, { prefix: '/api/albums' });
|
||||
await fastify.register(photos_public_routes_1.photosPublicRoutes, { prefix: '/api' });
|
||||
await fastify.register(photo_engagement_routes_1.photoEngagementRoutes, { prefix: '/api' });
|
||||
// 404 handler for unmatched routes
|
||||
fastify.setNotFoundHandler((_request, reply) => {
|
||||
reply.status(404).send({ error: { message: 'Route not found', code: 'NOT_FOUND' } });
|
||||
});
|
||||
const port = env_1.env.MEDIA_API_PORT;
|
||||
const host = '0.0.0.0';
|
||||
await fastify.listen({ port, host });
|
||||
@@ -104,6 +156,9 @@ const start = async () => {
|
||||
// Start video schedule queue worker
|
||||
video_schedule_queue_service_1.videoScheduleQueueService.startWorker();
|
||||
logger_1.logger.info('Video schedule queue worker initialized');
|
||||
// Start video fetch queue worker
|
||||
video_fetch_queue_service_1.videoFetchQueueService.startWorker();
|
||||
logger_1.logger.info('Video fetch queue worker initialized');
|
||||
if (env_1.env.ENABLE_MEDIA_FEATURES !== 'true') {
|
||||
logger_1.logger.warn('Media features are disabled (ENABLE_MEDIA_FEATURES=false)');
|
||||
}
|
||||
|
||||
2
api/dist/media-server.js.map
vendored
2
api/dist/media-server.js.map
vendored
File diff suppressed because one or more lines are too long
2
api/dist/middleware/auth.middleware.d.ts.map
vendored
2
api/dist/middleware/auth.middleware.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"auth.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAa1D,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAoB5E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAsB5E"}
|
||||
18
api/dist/middleware/auth.middleware.js
vendored
18
api/dist/middleware/auth.middleware.js
vendored
@@ -15,8 +15,13 @@ function authenticate(req, _res, next) {
|
||||
}
|
||||
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 };
|
||||
const payload = jsonwebtoken_1.default.verify(token, env_1.env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] });
|
||||
req.user = {
|
||||
id: payload.id,
|
||||
email: payload.email,
|
||||
role: payload.role,
|
||||
roles: payload.roles || [payload.role], // Backwards compat: old JWTs without roles
|
||||
};
|
||||
next();
|
||||
}
|
||||
catch {
|
||||
@@ -31,8 +36,13 @@ function optionalAuth(req, _res, next) {
|
||||
}
|
||||
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 };
|
||||
const payload = jsonwebtoken_1.default.verify(token, env_1.env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] });
|
||||
req.user = {
|
||||
id: payload.id,
|
||||
email: payload.email,
|
||||
role: payload.role,
|
||||
roles: payload.roles || [payload.role],
|
||||
};
|
||||
}
|
||||
catch {
|
||||
// Token invalid — continue without user
|
||||
|
||||
2
api/dist/middleware/auth.middleware.js.map
vendored
2
api/dist/middleware/auth.middleware.js.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":";;;;;AAaA,oCAoBC;AAED,oCAsBC;AAxDD,gEAA+B;AAE/B,uCAAoC;AACpC,mDAA2C;AAS3C,SAAgB,YAAY,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAG,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAiB,CAAC;QACpG,GAAG,CAAC,IAAI,GAAG;YACT,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,2CAA2C;SACpF,CAAC;QACF,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,0BAA0B,EAAE,eAAe,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC;QACP,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAG,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAiB,CAAC;QACpG,GAAG,CAAC,IAAI,GAAG;YACT,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;SACvC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}
|
||||
12
api/dist/middleware/rate-limit.d.ts
vendored
12
api/dist/middleware/rate-limit.d.ts
vendored
@@ -6,7 +6,19 @@ export declare const canvassVisitRateLimit: import("express-rate-limit").RateLim
|
||||
export declare const canvassBulkVisitRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const gpsTrackingRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const canvassGeocodeRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const adTrackingRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const quickJoinRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const authRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const observabilityRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const docsAnalyticsRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const docsCommentAnonRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const docsCommentAuthRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const docsCommentFetchRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const profileViewRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const profileEditRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const profilePhotoRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const profilePasswordRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const eventSubmissionRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const errorReportRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
export declare const healthMetricsRateLimit: import("express-rate-limit").RateLimitRequestHandler;
|
||||
//# sourceMappingURL=rate-limit.d.ts.map
|
||||
2
api/dist/middleware/rate-limit.d.ts.map
vendored
2
api/dist/middleware/rate-limit.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,sDAe1B,CAAC;AAEH,eAAO,MAAM,cAAc,sDAezB,CAAC;AAEH,eAAO,MAAM,iBAAiB,sDAe5B,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,sDAehC,CAAC;AAEH,eAAO,MAAM,yBAAyB,sDAepC,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,sDAelC,CAAC;AAEH,eAAO,MAAM,mBAAmB,sDAe9B,CAAC;AAEH,eAAO,MAAM,kBAAkB,sDAe7B,CAAC;AAEH,eAAO,MAAM,aAAa,sDAexB,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,yBAAyB,sDAepC,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,sDAehC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,wBAAwB,sDAenC,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAe/B,CAAC;AAEH,eAAO,MAAM,sBAAsB,sDAejC,CAAC"}
|
||||
194
api/dist/middleware/rate-limit.js
vendored
194
api/dist/middleware/rate-limit.js
vendored
@@ -3,7 +3,7 @@ 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;
|
||||
exports.healthMetricsRateLimit = exports.errorReportRateLimit = exports.eventSubmissionRateLimit = exports.profilePasswordRateLimit = exports.profilePhotoRateLimit = exports.profileEditRateLimit = exports.profileViewRateLimit = exports.docsCommentFetchRateLimit = exports.docsCommentAuthRateLimit = exports.docsCommentAnonRateLimit = exports.docsAnalyticsRateLimit = exports.observabilityRateLimit = exports.authRateLimit = exports.quickJoinRateLimit = exports.adTrackingRateLimit = exports.canvassGeocodeRateLimit = exports.gpsTrackingRateLimit = exports.canvassBulkVisitRateLimit = exports.canvassVisitRateLimit = exports.shiftSignupRateLimit = exports.responseRateLimit = exports.emailRateLimit = exports.globalRateLimit = void 0;
|
||||
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
||||
const rate_limit_redis_1 = __importDefault(require("rate-limit-redis"));
|
||||
const redis_1 = require("../config/redis");
|
||||
@@ -136,6 +136,38 @@ exports.canvassGeocodeRateLimit = (0, express_rate_limit_1.default)({
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.adTrackingRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 60, // 60 events/min per IP (generous for scroll-heavy gallery pages)
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:ad-track:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many tracking requests',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.quickJoinRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:quick-join:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many join attempts, please try again later',
|
||||
code: 'QUICK_JOIN_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.authRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 10, // Reduced from 20 to prevent brute force attacks
|
||||
@@ -168,6 +200,166 @@ exports.observabilityRateLimit = (0, express_rate_limit_1.default)({
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.docsAnalyticsRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 60, // 60 requests/min per IP
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:docs-analytics:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many tracking requests, please slow down',
|
||||
code: 'DOCS_ANALYTICS_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.docsCommentAnonRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:docs-comment-anon:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many anonymous comments, please try again later',
|
||||
code: 'DOCS_COMMENT_ANON_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.docsCommentAuthRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 30,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:docs-comment-auth:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many comments, please try again later',
|
||||
code: 'DOCS_COMMENT_AUTH_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.docsCommentFetchRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 60,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:docs-comment-fetch:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many comment fetch requests, please slow down',
|
||||
code: 'DOCS_COMMENT_FETCH_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.profileViewRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 60, // 60 requests per minute (shared across profile, photo, activity endpoints)
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:profile-view:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many profile requests, please try again later',
|
||||
code: 'PROFILE_VIEW_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.profileEditRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 20,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:profile-edit:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many profile edit requests, please try again later',
|
||||
code: 'PROFILE_EDIT_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.profilePhotoRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:profile-photo:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many photo uploads, please try again later',
|
||||
code: 'PROFILE_PHOTO_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.profilePasswordRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 5, // Stricter than auth (10/15min) — profile passwords may be simpler
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:profile-password:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many password attempts, please try again later',
|
||||
code: 'PROFILE_PASSWORD_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.eventSubmissionRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:event-submit:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many event submissions, please try again later',
|
||||
code: 'EVENT_SUBMISSION_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.errorReportRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
store: new rate_limit_redis_1.default({
|
||||
sendCommand: (command, ...args) => redis_1.redis.call(command, ...args),
|
||||
prefix: 'rl:error-report:',
|
||||
}),
|
||||
message: {
|
||||
error: {
|
||||
message: 'Too many error reports, please try again later',
|
||||
code: 'ERROR_REPORT_RATE_LIMIT_EXCEEDED',
|
||||
},
|
||||
},
|
||||
});
|
||||
exports.healthMetricsRateLimit = (0, express_rate_limit_1.default)({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 30, // 30 requests per minute
|
||||
|
||||
2
api/dist/middleware/rate-limit.js.map
vendored
2
api/dist/middleware/rate-limit.js.map
vendored
File diff suppressed because one or more lines are too long
2
api/dist/middleware/rbac.middleware.d.ts.map
vendored
2
api/dist/middleware/rbac.middleware.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"rbac.middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/rbac.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,QAAQ,EAAE,IACtC,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,YAAY,UAqBzD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,QAY9E"}
|
||||
13
api/dist/middleware/rbac.middleware.js
vendored
13
api/dist/middleware/rbac.middleware.js
vendored
@@ -9,7 +9,14 @@ function requireRole(...roles) {
|
||||
if (!req.user) {
|
||||
throw new error_handler_1.AppError(401, 'Authentication required', 'AUTH_REQUIRED');
|
||||
}
|
||||
if (!roles.includes(req.user.role)) {
|
||||
// Check multi-role array (falls back to single role via auth middleware)
|
||||
const userRoles = req.user.roles || [req.user.role];
|
||||
// SUPER_ADMIN bypasses all role checks
|
||||
if (userRoles.includes(client_1.UserRole.SUPER_ADMIN)) {
|
||||
return next();
|
||||
}
|
||||
const hasRole = userRoles.some(r => roles.includes(r));
|
||||
if (!hasRole) {
|
||||
throw new error_handler_1.AppError(403, 'Insufficient permissions', 'FORBIDDEN');
|
||||
}
|
||||
next();
|
||||
@@ -19,7 +26,9 @@ 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) {
|
||||
const userRoles = req.user.roles || [req.user.role];
|
||||
// User is "temp only" if their only role is TEMP
|
||||
if (userRoles.length === 1 && userRoles[0] === client_1.UserRole.TEMP) {
|
||||
throw new error_handler_1.AppError(403, 'Temporary accounts cannot access this resource', 'TEMP_FORBIDDEN');
|
||||
}
|
||||
next();
|
||||
|
||||
2
api/dist/middleware/rbac.middleware.js.map
vendored
2
api/dist/middleware/rbac.middleware.js.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"rbac.middleware.js","sourceRoot":"","sources":["../../src/middleware/rbac.middleware.ts"],"names":[],"mappings":";;AAIA,kCAsBC;AAED,wCAYC;AAvCD,2CAA0C;AAC1C,mDAA2C;AAE3C,SAAgB,WAAW,CAAC,GAAG,KAAiB;IAC9C,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC;QACtE,CAAC;QAED,yEAAyE;QACzE,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpD,uCAAuC;QACvC,IAAI,SAAS,CAAC,QAAQ,CAAC,iBAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7C,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAEvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,0BAA0B,EAAE,WAAW,CAAC,CAAC;QACnE,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,cAAc,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB;IAC7E,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,iDAAiD;IACjD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,iBAAQ,CAAC,IAAI,EAAE,CAAC;QAC7D,MAAM,IAAI,wBAAQ,CAAC,GAAG,EAAE,gDAAgD,EAAE,gBAAgB,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,EAAE,CAAC;AACT,CAAC"}
|
||||
2
api/dist/modules/auth/auth.routes.d.ts.map
vendored
2
api/dist/modules/auth/auth.routes.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"auth.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.routes.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmUxB,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,CAAC"}
|
||||
233
api/dist/modules/auth/auth.routes.js
vendored
233
api/dist/modules/auth/auth.routes.js
vendored
@@ -1,45 +1,27 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.authRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const zod_1 = require("zod");
|
||||
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
||||
const client_1 = require("@prisma/client");
|
||||
const auth_service_1 = require("./auth.service");
|
||||
const auth_schemas_1 = require("./auth.schemas");
|
||||
const validate_1 = require("../../middleware/validate");
|
||||
const auth_middleware_1 = require("../../middleware/auth.middleware");
|
||||
const rate_limit_1 = require("../../middleware/rate-limit");
|
||||
const database_1 = require("../../config/database");
|
||||
const verification_token_service_1 = require("../../services/verification-token.service");
|
||||
const password_reset_token_service_1 = require("../../services/password-reset-token.service");
|
||||
const email_service_1 = require("../../services/email.service");
|
||||
const settings_service_1 = require("../settings/settings.service");
|
||||
const env_1 = require("../../config/env");
|
||||
const logger_1 = require("../../utils/logger");
|
||||
const auth_rate_limits_1 = require("./auth.rate-limits");
|
||||
const profile_service_1 = require("../people/profile.service");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.authRouter = router;
|
||||
// POST /api/auth/login
|
||||
@@ -62,6 +44,138 @@ router.post('/register', rate_limit_1.authRateLimit, (0, validate_1.validate)(au
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// POST /api/auth/verify-email
|
||||
const verifyEmailSchema = zod_1.z.object({ token: zod_1.z.string().min(1) });
|
||||
router.post('/verify-email', rate_limit_1.authRateLimit, (0, validate_1.validate)(verifyEmailSchema), async (req, res, next) => {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
const result = await verification_token_service_1.verificationTokenService.verifyToken(token);
|
||||
if (!result.valid || !result.userId) {
|
||||
res.status(400).json({
|
||||
error: { message: result.error || 'Invalid token', code: 'INVALID_TOKEN' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
const settings = await settings_service_1.siteSettingsService.get();
|
||||
const autoApprove = settings.autoApproveVerifiedUsers;
|
||||
const newStatus = autoApprove ? client_1.UserStatus.ACTIVE : client_1.UserStatus.PENDING_APPROVAL;
|
||||
await database_1.prisma.user.update({
|
||||
where: { id: result.userId },
|
||||
data: { emailVerified: true, status: newStatus },
|
||||
});
|
||||
// If not auto-approved, notify admins
|
||||
if (!autoApprove) {
|
||||
const user = await database_1.prisma.user.findUnique({ where: { id: result.userId } });
|
||||
if (user) {
|
||||
const admins = await database_1.prisma.user.findMany({
|
||||
where: { role: client_1.UserRole.SUPER_ADMIN, status: client_1.UserStatus.ACTIVE },
|
||||
select: { email: true },
|
||||
});
|
||||
if (admins.length > 0) {
|
||||
await email_service_1.emailService.sendPendingApprovalNotification({
|
||||
adminEmails: admins.map(a => a.email),
|
||||
newUserEmail: user.email,
|
||||
newUserName: user.name || '',
|
||||
}).catch(err => logger_1.logger.error('Failed to send approval notification:', err));
|
||||
}
|
||||
}
|
||||
}
|
||||
res.json({
|
||||
verified: true,
|
||||
approved: autoApprove,
|
||||
message: autoApprove
|
||||
? 'Email verified. You can now log in.'
|
||||
: 'Email verified. Your account is pending admin approval.',
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// POST /api/auth/resend-verification
|
||||
const resendVerificationSchema = zod_1.z.object({ email: zod_1.z.string().email() });
|
||||
router.post('/resend-verification', (0, auth_rate_limits_1.createVerificationRateLimit)(), (0, validate_1.validate)(resendVerificationSchema), async (req, res, next) => {
|
||||
try {
|
||||
// Always return success to prevent user enumeration
|
||||
res.json({ message: 'If your email is registered and pending verification, a new verification link has been sent.' });
|
||||
// Send asynchronously (don't block response)
|
||||
const user = await database_1.prisma.user.findUnique({ where: { email: req.body.email } });
|
||||
if (user && user.status === client_1.UserStatus.PENDING_VERIFICATION) {
|
||||
const token = await verification_token_service_1.verificationTokenService.createToken(user.id);
|
||||
const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000';
|
||||
const verificationUrl = `${adminUrl}/verify-email?token=${token}`;
|
||||
await email_service_1.emailService.sendVerificationEmail({
|
||||
recipientEmail: user.email,
|
||||
recipientName: user.name || 'there',
|
||||
verificationUrl,
|
||||
}).catch(err => logger_1.logger.error('Failed to resend verification email:', err));
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// POST /api/auth/forgot-password
|
||||
const forgotPasswordSchema = zod_1.z.object({ email: zod_1.z.string().email() });
|
||||
router.post('/forgot-password', (0, auth_rate_limits_1.createResetRateLimit)(), (0, validate_1.validate)(forgotPasswordSchema), async (req, res, next) => {
|
||||
try {
|
||||
// Always return success to prevent user enumeration
|
||||
res.json({ message: 'If your email is registered, a password reset link has been sent.' });
|
||||
// Send asynchronously
|
||||
const user = await database_1.prisma.user.findUnique({ where: { email: req.body.email } });
|
||||
if (user && user.status === client_1.UserStatus.ACTIVE) {
|
||||
const token = await password_reset_token_service_1.passwordResetTokenService.createToken(user.id);
|
||||
const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000';
|
||||
const resetUrl = `${adminUrl}/reset-password?token=${token}`;
|
||||
await email_service_1.emailService.sendPasswordResetEmail({
|
||||
recipientEmail: user.email,
|
||||
recipientName: user.name || 'there',
|
||||
resetUrl,
|
||||
}).catch(err => logger_1.logger.error('Failed to send password reset email:', err));
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// POST /api/auth/reset-password
|
||||
const resetPasswordSchema = zod_1.z.object({
|
||||
token: zod_1.z.string().min(1),
|
||||
password: zod_1.z.string()
|
||||
.min(12, 'Password must be at least 12 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
|
||||
.regex(/[0-9]/, 'Password must contain at least one digit'),
|
||||
});
|
||||
router.post('/reset-password', rate_limit_1.authRateLimit, (0, validate_1.validate)(resetPasswordSchema), async (req, res, next) => {
|
||||
try {
|
||||
const { token, password } = req.body;
|
||||
const result = await password_reset_token_service_1.passwordResetTokenService.validateToken(token);
|
||||
if (!result.valid || !result.userId) {
|
||||
res.status(400).json({
|
||||
error: { message: result.error || 'Invalid token', code: 'INVALID_TOKEN' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
const hashedPassword = await bcryptjs_1.default.hash(password, 12);
|
||||
// Update password, mark token used, invalidate all refresh tokens — all in one transaction
|
||||
await database_1.prisma.$transaction(async (tx) => {
|
||||
await tx.user.update({
|
||||
where: { id: result.userId },
|
||||
data: { password: hashedPassword },
|
||||
});
|
||||
await tx.refreshToken.deleteMany({ where: { userId: result.userId } });
|
||||
await tx.passwordResetToken.update({
|
||||
where: { token },
|
||||
data: { usedAt: new Date() },
|
||||
});
|
||||
});
|
||||
res.json({ message: 'Password has been reset. You can now log in with your new password.' });
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// POST /api/auth/refresh
|
||||
router.post('/refresh', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth_schemas_1.refreshSchema), async (req, res, next) => {
|
||||
try {
|
||||
@@ -82,11 +196,59 @@ router.post('/logout', rate_limit_1.authRateLimit, (0, validate_1.validate)(auth
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/auth/me/profile-token
|
||||
router.get('/me/profile-token', auth_middleware_1.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
// Look up existing Contact linked to this user
|
||||
let contact = await database_1.prisma.contact.findUnique({ where: { userId } });
|
||||
// Auto-create Contact if none exists
|
||||
if (!contact) {
|
||||
const user = await database_1.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { name: true, email: true },
|
||||
});
|
||||
if (!user) {
|
||||
res.status(401).json({ error: { message: 'Invalid token', code: 'INVALID_TOKEN' } });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
contact = await database_1.prisma.contact.create({
|
||||
data: {
|
||||
displayName: user.name || user.email,
|
||||
firstName: user.name?.split(' ')[0] || null,
|
||||
lastName: user.name?.split(' ').slice(1).join(' ') || null,
|
||||
email: user.email,
|
||||
userId,
|
||||
primarySource: 'USER',
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
// Race condition: another request created the Contact — retry lookup
|
||||
if (err.code === 'P2002') {
|
||||
contact = await database_1.prisma.contact.findUnique({ where: { userId } });
|
||||
}
|
||||
if (!contact)
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// Generate profile token if Contact doesn't have one
|
||||
if (!contact.profileToken) {
|
||||
const result = await profile_service_1.profileService.generateProfileToken(contact.id);
|
||||
res.json({ token: result.token });
|
||||
return;
|
||||
}
|
||||
res.json({ token: contact.profileToken });
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/auth/me
|
||||
router.get('/me', auth_middleware_1.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { prisma } = await Promise.resolve().then(() => __importStar(require('../../config/database')));
|
||||
const user = await prisma.user.findUnique({
|
||||
const user = await database_1.prisma.user.findUnique({
|
||||
where: { id: req.user.id },
|
||||
select: {
|
||||
id: true,
|
||||
@@ -94,6 +256,7 @@ router.get('/me', auth_middleware_1.authenticate, async (req, res, next) => {
|
||||
name: true,
|
||||
phone: true,
|
||||
role: true,
|
||||
roles: true,
|
||||
status: true,
|
||||
permissions: true,
|
||||
createdVia: true,
|
||||
|
||||
2
api/dist/modules/auth/auth.routes.js.map
vendored
2
api/dist/modules/auth/auth.routes.js.map
vendored
File diff suppressed because one or more lines are too long
3
api/dist/modules/auth/auth.schemas.d.ts
vendored
3
api/dist/modules/auth/auth.schemas.d.ts
vendored
@@ -14,16 +14,19 @@ export declare const registerSchema: z.ZodObject<{
|
||||
password: z.ZodString;
|
||||
name: z.ZodOptional<z.ZodString>;
|
||||
phone: z.ZodOptional<z.ZodString>;
|
||||
inviteCode: z.ZodOptional<z.ZodString>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
email: string;
|
||||
password: string;
|
||||
name?: string | undefined;
|
||||
phone?: string | undefined;
|
||||
inviteCode?: string | undefined;
|
||||
}, {
|
||||
email: string;
|
||||
password: string;
|
||||
name?: string | undefined;
|
||||
phone?: string | undefined;
|
||||
inviteCode?: string | undefined;
|
||||
}>;
|
||||
export declare const refreshSchema: z.ZodObject<{
|
||||
refreshToken: z.ZodString;
|
||||
|
||||
2
api/dist/modules/auth/auth.schemas.d.ts.map
vendored
2
api/dist/modules/auth/auth.schemas.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"auth.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,WAAW;;;;;;;;;EAGtB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAWzB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;EAExB,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AACrD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
||||
1
api/dist/modules/auth/auth.schemas.js
vendored
1
api/dist/modules/auth/auth.schemas.js
vendored
@@ -15,6 +15,7 @@ exports.registerSchema = zod_1.z.object({
|
||||
.regex(/[0-9]/, 'Password must contain at least one digit'),
|
||||
name: zod_1.z.string().optional(),
|
||||
phone: zod_1.z.string().optional(),
|
||||
inviteCode: zod_1.z.string().max(20).optional(),
|
||||
// Role removed from public registration - must be set server-side only
|
||||
});
|
||||
exports.refreshSchema = zod_1.z.object({
|
||||
|
||||
2
api/dist/modules/auth/auth.schemas.js.map
vendored
2
api/dist/modules/auth/auth.schemas.js.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"auth.schemas.js","sourceRoot":"","sources":["../../../src/modules/auth/auth.schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAGX,QAAA,WAAW,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;IACzB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,sBAAsB,CAAC;CACpD,CAAC,CAAC;AAEU,QAAA,cAAc,GAAG,OAAC,CAAC,MAAM,CAAC;IACrC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;IACzB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;SACjB,GAAG,CAAC,EAAE,EAAE,yCAAyC,CAAC;SAClD,KAAK,CAAC,OAAO,EAAE,qDAAqD,CAAC;SACrE,KAAK,CAAC,OAAO,EAAE,qDAAqD,CAAC;SACrE,KAAK,CAAC,OAAO,EAAE,0CAA0C,CAAC;IAC7D,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,uEAAuE;CACxE,CAAC,CAAC;AAEU,QAAA,aAAa,GAAG,OAAC,CAAC,MAAM,CAAC;IACpC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,2BAA2B,CAAC;CAC7D,CAAC,CAAC"}
|
||||
59
api/dist/modules/auth/auth.service.d.ts
vendored
59
api/dist/modules/auth/auth.service.d.ts
vendored
@@ -1,9 +1,15 @@
|
||||
import { UserRole } from '@prisma/client';
|
||||
import type { RegisterInput } from './auth.schemas';
|
||||
interface TokenPair {
|
||||
export interface TokenPair {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
type UserForToken = {
|
||||
id: string;
|
||||
email: string;
|
||||
role: UserRole;
|
||||
roles?: unknown;
|
||||
};
|
||||
export declare const authService: {
|
||||
login(email: string, password: string): Promise<{
|
||||
accessToken: string;
|
||||
@@ -14,7 +20,9 @@ export declare const authService: {
|
||||
email: string;
|
||||
name: string | null;
|
||||
phone: string | null;
|
||||
pronouns: string | null;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
roles: import("@prisma/client/runtime/library").JsonValue;
|
||||
permissions: import("@prisma/client/runtime/library").JsonValue | null;
|
||||
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
|
||||
expiresAt: Date | null;
|
||||
@@ -26,15 +34,15 @@ export declare const authService: {
|
||||
};
|
||||
}>;
|
||||
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;
|
||||
pronouns: string | null;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
roles: import("@prisma/client/runtime/library").JsonValue;
|
||||
permissions: import("@prisma/client/runtime/library").JsonValue | null;
|
||||
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
|
||||
expiresAt: Date | null;
|
||||
@@ -44,6 +52,31 @@ export declare const authService: {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
requiresVerification: boolean;
|
||||
message: string;
|
||||
} | {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
user: {
|
||||
status: import(".prisma/client").$Enums.UserStatus;
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
phone: string | null;
|
||||
pronouns: string | null;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
roles: import("@prisma/client/runtime/library").JsonValue;
|
||||
permissions: import("@prisma/client/runtime/library").JsonValue | null;
|
||||
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
|
||||
expiresAt: Date | null;
|
||||
expireDays: number | null;
|
||||
lastLoginAt: Date | null;
|
||||
emailVerified: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
requiresVerification?: undefined;
|
||||
message?: undefined;
|
||||
}>;
|
||||
refreshTokens(refreshToken: string): Promise<{
|
||||
accessToken: string;
|
||||
@@ -54,7 +87,9 @@ export declare const authService: {
|
||||
email: string;
|
||||
name: string | null;
|
||||
phone: string | null;
|
||||
pronouns: string | null;
|
||||
role: import(".prisma/client").$Enums.UserRole;
|
||||
roles: import("@prisma/client/runtime/library").JsonValue;
|
||||
permissions: import("@prisma/client/runtime/library").JsonValue | null;
|
||||
createdVia: import(".prisma/client").$Enums.UserCreatedVia;
|
||||
expiresAt: Date | null;
|
||||
@@ -66,21 +101,9 @@ export declare const authService: {
|
||||
};
|
||||
}>;
|
||||
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>;
|
||||
generateAccessToken(user: UserForToken): string;
|
||||
generateRefreshToken(user: UserForToken): Promise<string>;
|
||||
generateTokenPair(user: UserForToken): Promise<TokenPair>;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=auth.service.d.ts.map
|
||||
2
api/dist/modules/auth/auth.service.d.ts.map
vendored
2
api/dist/modules/auth/auth.service.d.ts.map
vendored
@@ -1 +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"}
|
||||
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAUtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AASpD,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAUnF,eAAO,MAAM,WAAW;iBACH,MAAM,YAAY,MAAM;qBAf9B,MAAM;sBACL,MAAM;;;;;;;;;;;;;;;;;;;;mBA+EC,aAAa;;;;;;;;;;;;;;;;;;;;;;qBAhFrB,MAAM;sBACL,MAAM;;;;;;;;;;;;;;;;;;;;;;gCAiLc,MAAM;;;;;;;;;;;;;;;;;;;;;;yBAsEb,MAAM;8BAIP,YAAY,GAAG,MAAM;+BAcd,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;4BA2BjC,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;CAKhE,CAAC"}
|
||||
179
api/dist/modules/auth/auth.service.js
vendored
179
api/dist/modules/auth/auth.service.js
vendored
@@ -1,4 +1,37 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
@@ -11,6 +44,18 @@ const database_1 = require("../../config/database");
|
||||
const env_1 = require("../../config/env");
|
||||
const error_handler_1 = require("../../middleware/error-handler");
|
||||
const metrics_1 = require("../../utils/metrics");
|
||||
const settings_service_1 = require("../settings/settings.service");
|
||||
const verification_token_service_1 = require("../../services/verification-token.service");
|
||||
const email_service_1 = require("../../services/email.service");
|
||||
const roles_1 = require("../../utils/roles");
|
||||
const logger_1 = require("../../utils/logger");
|
||||
/** Parse the roles JSON field into a UserRole[] array */
|
||||
function parseRoles(user) {
|
||||
if (Array.isArray(user.roles) && user.roles.length > 0) {
|
||||
return user.roles;
|
||||
}
|
||||
return [user.role];
|
||||
}
|
||||
exports.authService = {
|
||||
async login(email, password) {
|
||||
const user = await database_1.prisma.user.findUnique({ where: { email } });
|
||||
@@ -23,6 +68,15 @@ exports.authService = {
|
||||
(0, metrics_1.recordLoginAttempt)('failure');
|
||||
throw new error_handler_1.AppError(401, 'Invalid email or password', 'INVALID_CREDENTIALS');
|
||||
}
|
||||
// Status-specific errors
|
||||
if (user.status === client_1.UserStatus.PENDING_VERIFICATION) {
|
||||
(0, metrics_1.recordLoginAttempt)('failure');
|
||||
throw new error_handler_1.AppError(403, 'Please verify your email address before logging in', 'EMAIL_NOT_VERIFIED');
|
||||
}
|
||||
if (user.status === client_1.UserStatus.PENDING_APPROVAL) {
|
||||
(0, metrics_1.recordLoginAttempt)('failure');
|
||||
throw new error_handler_1.AppError(403, 'Your account is pending admin approval', 'ACCOUNT_PENDING');
|
||||
}
|
||||
if (user.status !== client_1.UserStatus.ACTIVE) {
|
||||
(0, metrics_1.recordLoginAttempt)('failure');
|
||||
throw new error_handler_1.AppError(403, `Account is ${user.status.toLowerCase()}`, 'ACCOUNT_INACTIVE');
|
||||
@@ -36,25 +90,111 @@ exports.authService = {
|
||||
where: { id: user.id },
|
||||
data: { lastLoginAt: new Date() },
|
||||
});
|
||||
// Fire-and-forget: log USER_LOGIN activity on linked Contact
|
||||
database_1.prisma.contact.findFirst({
|
||||
where: { userId: user.id, mergedIntoId: null },
|
||||
}).then(async (contact) => {
|
||||
if (contact) {
|
||||
await database_1.prisma.contactActivity.create({
|
||||
data: {
|
||||
contactId: contact.id,
|
||||
type: 'USER_LOGIN',
|
||||
title: 'User logged in',
|
||||
description: `Login from ${user.email}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
logger_1.logger.warn('Login activity logging failed:', err);
|
||||
});
|
||||
const tokens = await this.generateTokenPair(user);
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
return { user: userWithoutPassword, ...tokens };
|
||||
},
|
||||
async register(data) {
|
||||
// Check if public registration is enabled
|
||||
const settings = await settings_service_1.siteSettingsService.get();
|
||||
if (!settings.enablePublicRegistration) {
|
||||
throw new error_handler_1.AppError(403, 'Public registration is currently disabled', 'REGISTRATION_DISABLED');
|
||||
}
|
||||
const existing = await database_1.prisma.user.findUnique({ where: { email: data.email } });
|
||||
if (existing) {
|
||||
throw new error_handler_1.AppError(409, 'Email already registered', 'EMAIL_EXISTS');
|
||||
}
|
||||
const hashedPassword = await bcryptjs_1.default.hash(data.password, 12);
|
||||
// Determine if email verification is needed
|
||||
const smtpReady = await email_service_1.emailService.isSmtpConfigured();
|
||||
const requireVerification = settings.enableEmailVerification && smtpReady;
|
||||
const user = await database_1.prisma.user.create({
|
||||
data: {
|
||||
email: data.email,
|
||||
password: hashedPassword,
|
||||
name: data.name,
|
||||
phone: data.phone,
|
||||
role: client_1.UserRole.USER, // Always USER for public registration
|
||||
role: client_1.UserRole.USER,
|
||||
roles: JSON.parse(JSON.stringify([client_1.UserRole.USER])),
|
||||
status: requireVerification ? client_1.UserStatus.PENDING_VERIFICATION : client_1.UserStatus.ACTIVE,
|
||||
emailVerified: !requireVerification,
|
||||
createdVia: 'SELF_REGISTRATION',
|
||||
},
|
||||
});
|
||||
// Fire-and-forget: process referral if invite code provided
|
||||
if (data.inviteCode) {
|
||||
Promise.resolve().then(() => __importStar(require('../social/referral.service'))).then(({ referralService }) => {
|
||||
referralService.processRegistrationReferral(user.id, data.inviteCode).catch(err => {
|
||||
logger_1.logger.warn('Referral processing failed:', err);
|
||||
});
|
||||
}).catch(() => { });
|
||||
}
|
||||
// Fire-and-forget: auto-link or create Contact if People feature is enabled
|
||||
settings_service_1.siteSettingsService.get().then(async (s) => {
|
||||
if (!s.enablePeople)
|
||||
return;
|
||||
// Check for existing Contact with matching email → link
|
||||
const existingContact = await database_1.prisma.contact.findFirst({
|
||||
where: { email: { equals: data.email, mode: 'insensitive' }, userId: null, mergedIntoId: null },
|
||||
});
|
||||
if (existingContact) {
|
||||
await database_1.prisma.contact.update({ where: { id: existingContact.id }, data: { userId: user.id } });
|
||||
logger_1.logger.info(`Auto-linked contact ${existingContact.id} to registered user ${user.id}`);
|
||||
}
|
||||
else {
|
||||
// Create new Contact linked to the user
|
||||
await database_1.prisma.contact.create({
|
||||
data: {
|
||||
displayName: data.name || data.email,
|
||||
firstName: data.name?.split(' ')[0] || null,
|
||||
lastName: data.name?.split(' ').slice(1).join(' ') || null,
|
||||
email: data.email,
|
||||
phone: data.phone || null,
|
||||
primarySource: 'USER',
|
||||
userId: user.id,
|
||||
tags: [],
|
||||
},
|
||||
});
|
||||
logger_1.logger.info(`Auto-created contact for registered user ${user.id}`);
|
||||
}
|
||||
}).catch(err => {
|
||||
logger_1.logger.warn('Contact auto-creation on register failed:', err);
|
||||
});
|
||||
// If verification required, send email and don't issue tokens
|
||||
if (requireVerification) {
|
||||
const token = await verification_token_service_1.verificationTokenService.createToken(user.id);
|
||||
const adminUrl = env_1.env.ADMIN_URL || 'http://localhost:3000';
|
||||
const verificationUrl = `${adminUrl}/verify-email?token=${token}`;
|
||||
await email_service_1.emailService.sendVerificationEmail({
|
||||
recipientEmail: user.email,
|
||||
recipientName: user.name || 'there',
|
||||
verificationUrl,
|
||||
});
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
return {
|
||||
user: userWithoutPassword,
|
||||
requiresVerification: true,
|
||||
message: 'Please check your email to verify your account',
|
||||
};
|
||||
}
|
||||
// No verification needed — issue tokens immediately
|
||||
const tokens = await this.generateTokenPair(user);
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
return { user: userWithoutPassword, ...tokens };
|
||||
@@ -62,7 +202,7 @@ exports.authService = {
|
||||
async refreshTokens(refreshToken) {
|
||||
let payload;
|
||||
try {
|
||||
payload = jsonwebtoken_1.default.verify(refreshToken, env_1.env.JWT_REFRESH_SECRET);
|
||||
payload = jsonwebtoken_1.default.verify(refreshToken, env_1.env.JWT_REFRESH_SECRET, { algorithms: ['HS256'] });
|
||||
}
|
||||
catch {
|
||||
throw new error_handler_1.AppError(401, 'Invalid refresh token', 'INVALID_REFRESH_TOKEN');
|
||||
@@ -74,6 +214,16 @@ exports.authService = {
|
||||
if (!stored) {
|
||||
throw new error_handler_1.AppError(401, 'Refresh token not found', 'INVALID_REFRESH_TOKEN');
|
||||
}
|
||||
// Check user status — banned/inactive users must not get new tokens
|
||||
if (stored.user.status !== client_1.UserStatus.ACTIVE) {
|
||||
await database_1.prisma.refreshToken.delete({ where: { id: stored.id } });
|
||||
throw new error_handler_1.AppError(401, 'Account is not active', 'ACCOUNT_INACTIVE');
|
||||
}
|
||||
// Check account expiry
|
||||
if (stored.user.expiresAt && stored.user.expiresAt < new Date()) {
|
||||
await database_1.prisma.refreshToken.delete({ where: { id: stored.id } });
|
||||
throw new error_handler_1.AppError(401, 'Account has expired', 'ACCOUNT_EXPIRED');
|
||||
}
|
||||
if (stored.expiresAt < new Date()) {
|
||||
await database_1.prisma.refreshToken.delete({ where: { id: stored.id } });
|
||||
throw new error_handler_1.AppError(401, 'Refresh token expired', 'REFRESH_TOKEN_EXPIRED');
|
||||
@@ -81,14 +231,16 @@ exports.authService = {
|
||||
// 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 userRoles = parseRoles(stored.user);
|
||||
const accessToken = this.generateAccessToken(stored.user);
|
||||
const refreshPayload = {
|
||||
id: stored.user.id,
|
||||
email: stored.user.email,
|
||||
role: stored.user.role
|
||||
role: (0, roles_1.getPrimaryRole)(userRoles),
|
||||
roles: userRoles,
|
||||
};
|
||||
const refreshToken = jsonwebtoken_1.default.sign(refreshPayload, env_1.env.JWT_REFRESH_SECRET, {
|
||||
algorithm: 'HS256',
|
||||
expiresIn: env_1.env.JWT_REFRESH_EXPIRY,
|
||||
});
|
||||
const decoded = jsonwebtoken_1.default.decode(refreshToken);
|
||||
@@ -109,17 +261,30 @@ exports.authService = {
|
||||
await database_1.prisma.refreshToken.deleteMany({ where: { token: refreshToken } });
|
||||
},
|
||||
generateAccessToken(user) {
|
||||
const payload = { id: user.id, email: user.email, role: user.role };
|
||||
const userRoles = parseRoles(user);
|
||||
const payload = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: (0, roles_1.getPrimaryRole)(userRoles),
|
||||
roles: userRoles,
|
||||
};
|
||||
return jsonwebtoken_1.default.sign(payload, env_1.env.JWT_ACCESS_SECRET, {
|
||||
algorithm: 'HS256',
|
||||
expiresIn: env_1.env.JWT_ACCESS_EXPIRY,
|
||||
});
|
||||
},
|
||||
async generateRefreshToken(user) {
|
||||
const payload = { id: user.id, email: user.email, role: user.role };
|
||||
const userRoles = parseRoles(user);
|
||||
const payload = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
role: (0, roles_1.getPrimaryRole)(userRoles),
|
||||
roles: userRoles,
|
||||
};
|
||||
const token = jsonwebtoken_1.default.sign(payload, env_1.env.JWT_REFRESH_SECRET, {
|
||||
algorithm: 'HS256',
|
||||
expiresIn: env_1.env.JWT_REFRESH_EXPIRY,
|
||||
});
|
||||
// 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({
|
||||
|
||||
2
api/dist/modules/auth/auth.service.js.map
vendored
2
api/dist/modules/auth/auth.service.js.map
vendored
File diff suppressed because one or more lines are too long
14
api/dist/modules/docs/docs-files.service.d.ts
vendored
14
api/dist/modules/docs/docs-files.service.d.ts
vendored
@@ -26,6 +26,17 @@ declare function createFile(relativePath: string, content?: string, isDirectory?
|
||||
declare function deleteFile(relativePath: string): Promise<void>;
|
||||
declare function renameFile(fromPath: string, toPath: string): Promise<void>;
|
||||
declare function isEditableFile(relativePath: string): boolean;
|
||||
declare function uploadFile(relativePath: string, sourcePath: string): Promise<void>;
|
||||
declare function invalidateTreeCache(): Promise<void>;
|
||||
/**
|
||||
* Flatten a FileNode tree into file-only entries, then filter by
|
||||
* case-insensitive query match on name or path. Name matches score
|
||||
* higher so they sort first.
|
||||
*/
|
||||
declare function searchFiles(query: string, limit?: number): Promise<{
|
||||
name: string;
|
||||
path: string;
|
||||
}[]>;
|
||||
export declare const docsFilesService: {
|
||||
listTree: typeof listTree;
|
||||
readFileContent: typeof readFileContent;
|
||||
@@ -33,8 +44,11 @@ export declare const docsFilesService: {
|
||||
createFile: typeof createFile;
|
||||
deleteFile: typeof deleteFile;
|
||||
renameFile: typeof renameFile;
|
||||
uploadFile: typeof uploadFile;
|
||||
safeResolve: typeof safeResolve;
|
||||
isEditableFile: typeof isEditableFile;
|
||||
invalidateTreeCache: typeof invalidateTreeCache;
|
||||
searchFiles: typeof searchFiles;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=docs-files.service.d.ts.map
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"docs-files.service.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/docs-files.service.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAcD;;;GAGG;AACH,iBAAS,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;;CAK5C;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,QAAQ,EAAE,MAAM;CAI7B;AAED;;;GAGG;AACH,iBAAe,QAAQ,CAAC,GAAG,GAAE,MAAkB,EAAE,OAAO,GAAE,MAAW,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAiD1F;AAED,iBAAe,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmCpE;AAED,iBAAe,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpF;AAED,iBAAe,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BtG;AAED,iBAAe,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B7D;AAED,iBAAe,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE;AAED,iBAAS,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAGrD;AAOD,iBAAe,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjF;AAED,iBAAe,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMlD;AAED;;;;GAIG;AACH,iBAAe,WAAW,CACxB,KAAK,EAAE,MAAM,EACb,KAAK,SAAI,GACR,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAyB3C;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;CAY5B,CAAC"}
|
||||
72
api/dist/modules/docs/docs-files.service.js
vendored
72
api/dist/modules/docs/docs-files.service.js
vendored
@@ -15,7 +15,8 @@ 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
|
||||
const TREE_CACHE_TTL = 30; // 30 seconds — short so external changes show quickly
|
||||
const FILE_CONTENT_CACHE_TTL = 60 * 60; // 1 hour for file content
|
||||
function hashFilePath(path) {
|
||||
return crypto_1.default.createHash('sha256').update(path).digest('hex').substring(0, 16);
|
||||
}
|
||||
@@ -26,7 +27,8 @@ function hashFilePath(path) {
|
||||
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)) {
|
||||
// Use DOCS_ROOT + sep to prevent prefix attacks (e.g., /mkdocs/docs-evil matching /mkdocs/docs)
|
||||
if (resolved !== DOCS_ROOT && !resolved.startsWith(DOCS_ROOT + '/')) {
|
||||
throw new PathTraversalError();
|
||||
}
|
||||
return resolved;
|
||||
@@ -89,7 +91,7 @@ async function listTree(dir = DOCS_ROOT, relBase = '') {
|
||||
// Cache root result
|
||||
if (dir === DOCS_ROOT && !relBase) {
|
||||
try {
|
||||
await redis_1.redis.setex(TREE_CACHE_KEY, FILE_CACHE_TTL, JSON.stringify(nodes));
|
||||
await redis_1.redis.setex(TREE_CACHE_KEY, TREE_CACHE_TTL, JSON.stringify(nodes));
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.warn('Failed to cache docs tree:', err);
|
||||
@@ -118,7 +120,7 @@ async function readFileContent(relativePath) {
|
||||
const content = await (0, promises_1.readFile)(fullPath, 'utf-8');
|
||||
// Cache the result
|
||||
try {
|
||||
await redis_1.redis.setex(cacheKey, FILE_CACHE_TTL, content);
|
||||
await redis_1.redis.setex(cacheKey, FILE_CONTENT_CACHE_TTL, content);
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.warn('Failed to cache file content:', err);
|
||||
@@ -235,6 +237,65 @@ function isEditableFile(relativePath) {
|
||||
const ext = (0, path_1.extname)(relativePath).toLowerCase();
|
||||
return ['.md', '.txt', '.yml', '.yaml', '.json', '.css', '.html', '.js'].includes(ext);
|
||||
}
|
||||
const ALLOWED_UPLOAD_EXTENSIONS = new Set([
|
||||
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
|
||||
'.pdf', '.zip',
|
||||
]);
|
||||
async function uploadFile(relativePath, sourcePath) {
|
||||
const ext = (0, path_1.extname)(relativePath).toLowerCase();
|
||||
if (!ALLOWED_UPLOAD_EXTENSIONS.has(ext)) {
|
||||
throw new Error(`File type not allowed: ${ext}`);
|
||||
}
|
||||
const fullPath = safeResolve(relativePath);
|
||||
await (0, promises_1.mkdir)((0, path_1.dirname)(fullPath), { recursive: true });
|
||||
await (0, promises_1.copyFile)(sourcePath, fullPath);
|
||||
// Invalidate tree cache (structure changed)
|
||||
try {
|
||||
await redis_1.redis.del(TREE_CACHE_KEY);
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.warn('Failed to invalidate tree cache after upload:', err);
|
||||
}
|
||||
}
|
||||
async function invalidateTreeCache() {
|
||||
try {
|
||||
await redis_1.redis.del(TREE_CACHE_KEY);
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.warn('Failed to invalidate tree cache:', err);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Flatten a FileNode tree into file-only entries, then filter by
|
||||
* case-insensitive query match on name or path. Name matches score
|
||||
* higher so they sort first.
|
||||
*/
|
||||
async function searchFiles(query, limit = 5) {
|
||||
const tree = await listTree();
|
||||
const q = query.toLowerCase();
|
||||
const matches = [];
|
||||
function walk(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.isDirectory) {
|
||||
if (node.children)
|
||||
walk(node.children);
|
||||
}
|
||||
else {
|
||||
const nameLower = node.name.toLowerCase();
|
||||
const pathLower = node.path.toLowerCase();
|
||||
if (nameLower.includes(q)) {
|
||||
matches.push({ name: node.name, path: node.path, score: 2 });
|
||||
}
|
||||
else if (pathLower.includes(q)) {
|
||||
matches.push({ name: node.name, path: node.path, score: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(tree);
|
||||
matches.sort((a, b) => b.score - a.score);
|
||||
return matches.slice(0, limit).map(({ name, path }) => ({ name, path }));
|
||||
}
|
||||
exports.docsFilesService = {
|
||||
listTree,
|
||||
readFileContent,
|
||||
@@ -242,7 +303,10 @@ exports.docsFilesService = {
|
||||
createFile,
|
||||
deleteFile,
|
||||
renameFile,
|
||||
uploadFile,
|
||||
safeResolve,
|
||||
isEditableFile,
|
||||
invalidateTreeCache,
|
||||
searchFiles,
|
||||
};
|
||||
//# sourceMappingURL=docs-files.service.js.map
|
||||
File diff suppressed because one or more lines are too long
2
api/dist/modules/docs/docs.routes.d.ts.map
vendored
2
api/dist/modules/docs/docs.routes.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"docs.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/docs.routes.ts"],"names":[],"mappings":"AAiQA,eAAO,MAAM,UAAU,4CAAS,CAAC"}
|
||||
{"version":3,"file":"docs.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/docs.routes.ts"],"names":[],"mappings":"AA4YA,eAAO,MAAM,UAAU,4CAAS,CAAC"}
|
||||
151
api/dist/modules/docs/docs.routes.js
vendored
151
api/dist/modules/docs/docs.routes.js
vendored
@@ -1,21 +1,31 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.docsRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const multer_1 = __importDefault(require("multer"));
|
||||
const promises_1 = require("fs/promises");
|
||||
const path_1 = require("path");
|
||||
const auth_middleware_1 = require("../../middleware/auth.middleware");
|
||||
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
|
||||
const env_1 = require("../../config/env");
|
||||
const roles_1 = require("../../utils/roles");
|
||||
const logger_1 = require("../../utils/logger");
|
||||
const health_check_1 = require("../../utils/health-check");
|
||||
const metrics_1 = require("../../utils/metrics");
|
||||
const docs_files_service_1 = require("./docs-files.service");
|
||||
const docs_collab_service_1 = require("./docs-collab.service");
|
||||
const mkdocs_config_service_1 = require("./mkdocs-config.service");
|
||||
const header_builder_service_1 = require("./header-builder.service");
|
||||
const header_builder_schemas_1 = require("./header-builder.schemas");
|
||||
const router = (0, express_1.Router)();
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
router.use(rbac_middleware_1.requireNonTemp);
|
||||
// Removed duplicated isServiceOnline - now using shared utility from utils/health-check.ts
|
||||
// GET /api/docs/status — check MkDocs and Code Server availability
|
||||
router.get('/status', async (_req, res, next) => {
|
||||
// GET /api/docs/status — check MkDocs and Code Server availability (content editors only)
|
||||
router.get('/status', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
|
||||
try {
|
||||
const [mkdocsOnline, codeServerOnline, siteServerOnline] = await Promise.all([
|
||||
(0, health_check_1.isServiceOnline)(env_1.env.MKDOCS_PREVIEW_URL),
|
||||
@@ -33,8 +43,8 @@ router.get('/status', async (_req, res, next) => {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/docs/config — return public-facing port numbers for iframe URLs
|
||||
router.get('/config', async (_req, res, _next) => {
|
||||
// GET /api/docs/config — return public-facing port numbers for iframe URLs (content editors only)
|
||||
router.get('/config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, _next) => {
|
||||
res.json({
|
||||
codeServerPort: env_1.env.CODE_SERVER_PORT,
|
||||
mkdocsPort: env_1.env.MKDOCS_PORT,
|
||||
@@ -42,8 +52,8 @@ router.get('/config', async (_req, res, _next) => {
|
||||
});
|
||||
});
|
||||
// --- MkDocs Config Endpoints ---
|
||||
// GET /api/docs/mkdocs-config — read raw mkdocs.yml content
|
||||
router.get('/mkdocs-config', async (_req, res, next) => {
|
||||
// GET /api/docs/mkdocs-config — read raw mkdocs.yml content (content editors only)
|
||||
router.get('/mkdocs-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
|
||||
try {
|
||||
const content = await mkdocs_config_service_1.mkdocsConfigService.readConfig();
|
||||
res.json({ content });
|
||||
@@ -54,7 +64,7 @@ router.get('/mkdocs-config', async (_req, res, next) => {
|
||||
}
|
||||
});
|
||||
// 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) => {
|
||||
router.put('/mkdocs-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
const { content } = req.body;
|
||||
if (typeof content !== 'string') {
|
||||
@@ -73,8 +83,8 @@ router.put('/mkdocs-config', (0, rbac_middleware_1.requireRole)('SUPER_ADMIN'),
|
||||
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) => {
|
||||
// POST /api/docs/build — trigger mkdocs build in container
|
||||
router.post('/build', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
|
||||
try {
|
||||
const result = await mkdocs_config_service_1.mkdocsConfigService.triggerBuild();
|
||||
res.json(result);
|
||||
@@ -84,11 +94,95 @@ router.post('/build', (0, rbac_middleware_1.requireRole)('SUPER_ADMIN'), async (
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// --- Header Builder ---
|
||||
// GET /api/docs/header-config — read header nav bar config (content editors only)
|
||||
router.get('/header-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (_req, res, next) => {
|
||||
try {
|
||||
const config = await header_builder_service_1.headerBuilderService.readConfig();
|
||||
res.json(config);
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.error('Failed to read header config', err);
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// PUT /api/docs/header-config — save header nav bar config + regenerate template
|
||||
router.put('/header-config', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
const parsed = header_builder_schemas_1.headerConfigSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({
|
||||
error: { message: 'Invalid header config', code: 'VALIDATION_ERROR', details: parsed.error.flatten().fieldErrors },
|
||||
});
|
||||
return;
|
||||
}
|
||||
await header_builder_service_1.headerBuilderService.writeConfig(parsed.data);
|
||||
// Invalidate docs file tree cache so the new main.html shows up
|
||||
await docs_files_service_1.docsFilesService.invalidateTreeCache();
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.error('Failed to save header config', err);
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// --- File Upload ---
|
||||
const ALLOWED_UPLOAD_EXTENSIONS = new Set([
|
||||
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
|
||||
'.pdf', '.zip',
|
||||
]);
|
||||
const upload = (0, multer_1.default)({
|
||||
storage: multer_1.default.diskStorage({}), // temp dir
|
||||
limits: { fileSize: 20 * 1024 * 1024 }, // 20MB
|
||||
fileFilter: (_req, file, cb) => {
|
||||
const ext = (0, path_1.extname)(file.originalname).toLowerCase();
|
||||
if (ALLOWED_UPLOAD_EXTENSIONS.has(ext)) {
|
||||
cb(null, true);
|
||||
}
|
||||
else {
|
||||
cb(new Error(`File type not allowed: ${ext}`));
|
||||
}
|
||||
},
|
||||
});
|
||||
// POST /api/docs/upload — upload binary file (image, pdf, etc.)
|
||||
router.post('/upload', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), upload.single('file'), async (req, res, next) => {
|
||||
const tempPath = req.file?.path;
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'upload' });
|
||||
if (!req.file) {
|
||||
res.status(400).json({ error: { message: 'No file provided', code: 'VALIDATION_ERROR' } });
|
||||
return;
|
||||
}
|
||||
const targetDir = req.body.path || '';
|
||||
const fileName = (0, path_1.basename)(req.file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
|
||||
const relativePath = targetDir ? `${targetDir}/${fileName}` : fileName;
|
||||
await docs_files_service_1.docsFilesService.uploadFile(relativePath, req.file.path);
|
||||
// Clean up temp file
|
||||
try {
|
||||
await (0, promises_1.rm)(req.file.path);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
res.json({ success: true, path: relativePath });
|
||||
}
|
||||
catch (err) {
|
||||
// Clean up temp file on error
|
||||
if (tempPath) {
|
||||
try {
|
||||
await (0, promises_1.rm)(tempPath);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
handleFileError(err, res, next);
|
||||
}
|
||||
});
|
||||
// --- File Management Endpoints ---
|
||||
// GET /api/docs/files — list file tree
|
||||
router.get('/files', async (_req, res, next) => {
|
||||
// GET /api/docs/files — list file tree (content editors only)
|
||||
router.get('/files', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'list' });
|
||||
if (req.query['force'] === 'true') {
|
||||
await docs_files_service_1.docsFilesService.invalidateTreeCache();
|
||||
}
|
||||
const tree = await docs_files_service_1.docsFilesService.listTree();
|
||||
res.json(tree);
|
||||
}
|
||||
@@ -97,8 +191,25 @@ router.get('/files', async (_req, res, next) => {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/docs/files/search — search files by name/path (content editors only)
|
||||
router.get('/files/search', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
const search = String(req.query['search'] ?? req.query['q'] ?? '').trim();
|
||||
if (!search) {
|
||||
res.json({ files: [] });
|
||||
return;
|
||||
}
|
||||
const limit = Math.min(Math.max(Number(req.query['limit']) || 5, 1), 20);
|
||||
const files = await docs_files_service_1.docsFilesService.searchFiles(search, limit);
|
||||
res.json({ files });
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.error('Failed to search docs files', err);
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// POST /api/docs/files/rename — rename/move file
|
||||
router.post('/files/rename', async (req, res, next) => {
|
||||
router.post('/files/rename', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'rename' });
|
||||
const { from, to } = req.body;
|
||||
@@ -107,14 +218,16 @@ router.post('/files/rename', async (req, res, next) => {
|
||||
return;
|
||||
}
|
||||
await docs_files_service_1.docsFilesService.renameFile(from, to);
|
||||
// Invalidate old path's collaboration state
|
||||
docs_collab_service_1.docsCollabService.invalidateDocument(from).catch(() => { });
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (err) {
|
||||
handleFileError(err, res, next);
|
||||
}
|
||||
});
|
||||
// GET /api/docs/files/* — read file content
|
||||
router.get('/files/*', async (req, res, next) => {
|
||||
// GET /api/docs/files/* — read file content (content editors only)
|
||||
router.get('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'read' });
|
||||
const filePath = extractWildcardPath(req);
|
||||
@@ -130,7 +243,7 @@ router.get('/files/*', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
// PUT /api/docs/files/* — write/update file content
|
||||
router.put('/files/*', async (req, res, next) => {
|
||||
router.put('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'write' });
|
||||
const filePath = extractWildcardPath(req);
|
||||
@@ -144,6 +257,8 @@ router.put('/files/*', async (req, res, next) => {
|
||||
return;
|
||||
}
|
||||
await docs_files_service_1.docsFilesService.writeFileContent(filePath, content);
|
||||
// Invalidate collaboration state so next session starts fresh from disk
|
||||
docs_collab_service_1.docsCollabService.invalidateDocument(filePath).catch(() => { });
|
||||
res.json({ success: true, path: filePath });
|
||||
}
|
||||
catch (err) {
|
||||
@@ -151,7 +266,7 @@ router.put('/files/*', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
// POST /api/docs/files/* — create new file or folder
|
||||
router.post('/files/*', async (req, res, next) => {
|
||||
router.post('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'create' });
|
||||
const filePath = extractWildcardPath(req);
|
||||
@@ -168,7 +283,7 @@ router.post('/files/*', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
// DELETE /api/docs/files/* — delete file or empty folder
|
||||
router.delete('/files/*', async (req, res, next) => {
|
||||
router.delete('/files/*', (0, rbac_middleware_1.requireRole)(...roles_1.CONTENT_ROLES), async (req, res, next) => {
|
||||
try {
|
||||
metrics_1.cm_docs_operations.inc({ operation: 'delete' });
|
||||
const filePath = extractWildcardPath(req);
|
||||
@@ -177,6 +292,8 @@ router.delete('/files/*', async (req, res, next) => {
|
||||
return;
|
||||
}
|
||||
await docs_files_service_1.docsFilesService.deleteFile(filePath);
|
||||
// Invalidate collaboration state for deleted file
|
||||
docs_collab_service_1.docsCollabService.invalidateDocument(filePath).catch(() => { });
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
2
api/dist/modules/docs/docs.routes.js.map
vendored
2
api/dist/modules/docs/docs.routes.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -12,7 +12,8 @@ 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.
|
||||
* Trigger `mkdocs build --clean` via the build trigger HTTP server
|
||||
* running inside the MkDocs container on port 8001.
|
||||
*/
|
||||
declare function triggerBuild(): Promise<{
|
||||
success: boolean;
|
||||
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"mkdocs-config.service.d.ts","sourceRoot":"","sources":["../../../src/modules/docs/mkdocs-config.service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,QAAQ,EAAE,MAAM,MAAM,CAAC;AAqB/C;;;GAGG;AACH,iBAAS,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAS9C;AAED;;;GAGG;AACH,iBAAS,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWpD;AAED,iBAAe,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3C;AAED,iBAAe,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAezD;AAED;;;GAGG;AACH,iBAAe,YAAY,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB7F;AAED,eAAO,MAAM,mBAAmB;;;;;;CAM/B,CAAC"}
|
||||
104
api/dist/modules/docs/mkdocs-config.service.js
vendored
104
api/dist/modules/docs/mkdocs-config.service.js
vendored
@@ -2,11 +2,9 @@
|
||||
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.
|
||||
@@ -73,102 +71,22 @@ async function writeConfig(content) {
|
||||
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.
|
||||
* Trigger `mkdocs build --clean` via the build trigger HTTP server
|
||||
* running inside the MkDocs container on port 8001.
|
||||
*/
|
||||
async function triggerBuild() {
|
||||
const containerName = env_1.env.MKDOCS_CONTAINER_NAME;
|
||||
const buildUrl = `${env_1.env.MKDOCS_PREVIEW_URL.replace(':8000', ':8001')}/build`;
|
||||
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'],
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 130_000);
|
||||
const response = await fetch(buildUrl, {
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
});
|
||||
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,
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
catch (err) {
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +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"}
|
||||
{"version":3,"file":"email-templates-admin.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/email-templates/email-templates-admin.routes.ts"],"names":[],"mappings":"AAuBA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqUxB,eAAe,MAAM,CAAC"}
|
||||
@@ -12,19 +12,21 @@ const logger_1 = require("../../utils/logger");
|
||||
const auth_middleware_1 = require("../../middleware/auth.middleware");
|
||||
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
|
||||
const client_1 = require("@prisma/client");
|
||||
const roles_1 = require("../../utils/roles");
|
||||
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
||||
const rate_limit_redis_1 = __importDefault(require("rate-limit-redis"));
|
||||
const redis_1 = require("../../config/redis");
|
||||
const seed_email_templates_1 = require("../../scripts/seed-email-templates");
|
||||
const router = (0, express_1.Router)();
|
||||
// All email template routes require authentication
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
// All routes require 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);
|
||||
// All routes require broadcast admin role
|
||||
const requireBroadcastRole = (0, rbac_middleware_1.requireRole)(...roles_1.BROADCAST_ROLES);
|
||||
/**
|
||||
* List email templates
|
||||
* GET /email-templates
|
||||
*/
|
||||
router.get('/', requireAdminRole, (0, validate_1.validate)(email_templates_schemas_1.listEmailTemplatesSchema, 'query'), async (req, res) => {
|
||||
router.get('/', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.listEmailTemplatesSchema, 'query'), async (req, res) => {
|
||||
try {
|
||||
const result = await email_templates_service_1.emailTemplatesService.list(req.query);
|
||||
res.json(result);
|
||||
@@ -38,7 +40,7 @@ router.get('/', requireAdminRole, (0, validate_1.validate)(email_templates_schem
|
||||
* Get single email template
|
||||
* GET /email-templates/:id
|
||||
*/
|
||||
router.get('/:id', requireAdminRole, async (req, res) => {
|
||||
router.get('/:id', requireBroadcastRole, async (req, res) => {
|
||||
try {
|
||||
const template = await email_templates_service_1.emailTemplatesService.getById(req.params.id);
|
||||
res.json(template);
|
||||
@@ -56,7 +58,7 @@ router.get('/:id', requireAdminRole, async (req, res) => {
|
||||
* Create email template
|
||||
* POST /email-templates
|
||||
*/
|
||||
router.post('/', requireAdminRole, (0, validate_1.validate)(email_templates_schemas_1.createEmailTemplateSchema), async (req, res) => {
|
||||
router.post('/', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.createEmailTemplateSchema), async (req, res) => {
|
||||
try {
|
||||
const template = await email_templates_service_1.emailTemplatesService.create(req.body, req.user.id);
|
||||
res.status(201).json(template);
|
||||
@@ -78,7 +80,7 @@ router.post('/', requireAdminRole, (0, validate_1.validate)(email_templates_sche
|
||||
* Update email template
|
||||
* PUT /email-templates/:id
|
||||
*/
|
||||
router.put('/:id', requireAdminRole, (0, validate_1.validate)(email_templates_schemas_1.updateEmailTemplateSchema), async (req, res) => {
|
||||
router.put('/:id', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.updateEmailTemplateSchema), async (req, res) => {
|
||||
try {
|
||||
const template = await email_templates_service_1.emailTemplatesService.update(req.params.id, req.body, req.user.id);
|
||||
// Clear cache so changes take effect immediately
|
||||
@@ -103,7 +105,7 @@ router.put('/:id', requireAdminRole, (0, validate_1.validate)(email_templates_sc
|
||||
* Delete email template
|
||||
* DELETE /email-templates/:id
|
||||
*/
|
||||
router.delete('/:id', requireAdminRole, async (req, res) => {
|
||||
router.delete('/:id', requireBroadcastRole, async (req, res) => {
|
||||
try {
|
||||
// Fetch template before deleting to get the key
|
||||
const template = await email_templates_service_1.emailTemplatesService.getById(req.params.id);
|
||||
@@ -130,7 +132,7 @@ router.delete('/:id', requireAdminRole, async (req, res) => {
|
||||
* Get version history
|
||||
* GET /email-templates/:id/versions
|
||||
*/
|
||||
router.get('/:id/versions', requireAdminRole, async (req, res) => {
|
||||
router.get('/:id/versions', requireBroadcastRole, async (req, res) => {
|
||||
try {
|
||||
const versions = await email_templates_service_1.emailTemplatesService.getVersions(req.params.id);
|
||||
res.json(versions);
|
||||
@@ -144,7 +146,7 @@ router.get('/:id/versions', requireAdminRole, async (req, res) => {
|
||||
* Get specific version
|
||||
* GET /email-templates/:id/versions/:versionNumber
|
||||
*/
|
||||
router.get('/:id/versions/:versionNumber', requireAdminRole, async (req, res) => {
|
||||
router.get('/:id/versions/:versionNumber', requireBroadcastRole, async (req, res) => {
|
||||
try {
|
||||
const version = await email_templates_service_1.emailTemplatesService.getVersion(req.params.id, parseInt(req.params.versionNumber, 10));
|
||||
res.json(version);
|
||||
@@ -162,7 +164,7 @@ router.get('/:id/versions/:versionNumber', requireAdminRole, async (req, res) =>
|
||||
* 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) => {
|
||||
router.post('/:id/rollback', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.rollbackToVersionSchema), async (req, res) => {
|
||||
try {
|
||||
const template = await email_templates_service_1.emailTemplatesService.rollbackToVersion(req.params.id, req.body, req.user.id);
|
||||
res.json(template);
|
||||
@@ -180,7 +182,7 @@ router.post('/:id/rollback', requireAdminRole, (0, validate_1.validate)(email_te
|
||||
* Validate template syntax
|
||||
* POST /email-templates/validate
|
||||
*/
|
||||
router.post('/validate', requireAdminRole, (0, validate_1.validate)(email_templates_schemas_1.validateTemplateSchema), async (req, res) => {
|
||||
router.post('/validate', requireBroadcastRole, (0, validate_1.validate)(email_templates_schemas_1.validateTemplateSchema), async (req, res) => {
|
||||
try {
|
||||
const result = email_templates_service_1.emailTemplatesService.validateTemplate(req.body);
|
||||
res.json(result);
|
||||
@@ -195,7 +197,7 @@ router.post('/validate', requireAdminRole, (0, validate_1.validate)(email_templa
|
||||
* 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)({
|
||||
router.post('/:id/test', requireBroadcastRole, (0, express_rate_limit_1.default)({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
@@ -223,7 +225,7 @@ router.post('/:id/test', requireAdminRole, (0, express_rate_limit_1.default)({
|
||||
* Get test logs for template
|
||||
* GET /email-templates/:id/test-logs
|
||||
*/
|
||||
router.get('/:id/test-logs', requireAdminRole, async (req, res) => {
|
||||
router.get('/:id/test-logs', requireBroadcastRole, async (req, res) => {
|
||||
try {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 10;
|
||||
const logs = await email_templates_service_1.emailTemplatesService.getTestLogs(req.params.id, limit);
|
||||
@@ -240,16 +242,9 @@ router.get('/:id/test-logs', requireAdminRole, async (req, res) => {
|
||||
*/
|
||||
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',
|
||||
});
|
||||
await (0, seed_email_templates_1.seedEmailTemplates)();
|
||||
logger_1.logger.info('Email templates seeded via API');
|
||||
res.json({ success: true, output: result.stdout });
|
||||
res.json({ success: true, message: 'Templates seeded successfully' });
|
||||
}
|
||||
catch (error) {
|
||||
logger_1.logger.error('Error seeding templates:', error);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -14,8 +14,8 @@ export declare const emailTemplateVariableSchema: z.ZodEffects<z.ZodObject<{
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -34,8 +34,8 @@ export declare const emailTemplateVariableSchema: z.ZodEffects<z.ZodObject<{
|
||||
}>, {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -60,20 +60,21 @@ export declare const listEmailTemplatesSchema: z.ZodObject<{
|
||||
INFLUENCE: "INFLUENCE";
|
||||
MAP: "MAP";
|
||||
SYSTEM: "SYSTEM";
|
||||
PAYMENT: "PAYMENT";
|
||||
}>>;
|
||||
isActive: z.ZodOptional<z.ZodBoolean>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
limit: number;
|
||||
page: number;
|
||||
search?: string | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
}, {
|
||||
search?: string | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
|
||||
limit?: number | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
page?: number | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
page?: number | undefined;
|
||||
}>;
|
||||
export type ListEmailTemplatesDto = z.infer<typeof listEmailTemplatesSchema>;
|
||||
export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
@@ -84,6 +85,7 @@ export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
INFLUENCE: "INFLUENCE";
|
||||
MAP: "MAP";
|
||||
SYSTEM: "SYSTEM";
|
||||
PAYMENT: "PAYMENT";
|
||||
}>;
|
||||
subjectLine: z.ZodString;
|
||||
htmlContent: z.ZodString;
|
||||
@@ -102,8 +104,8 @@ export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -122,8 +124,8 @@ export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
}>, {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -142,9 +144,9 @@ export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
}>, "many">>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
name: string;
|
||||
category: "INFLUENCE" | "MAP" | "SYSTEM";
|
||||
isActive: boolean;
|
||||
category: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT";
|
||||
key: string;
|
||||
isActive: boolean;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
@@ -152,8 +154,8 @@ export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
variables?: {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -162,13 +164,13 @@ export declare const createEmailTemplateSchema: z.ZodObject<{
|
||||
}[] | undefined;
|
||||
}, {
|
||||
name: string;
|
||||
category: "INFLUENCE" | "MAP" | "SYSTEM";
|
||||
category: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT";
|
||||
key: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
description?: string | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
description?: string | undefined;
|
||||
variables?: {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -189,6 +191,7 @@ export declare const updateEmailTemplateSchema: z.ZodObject<{
|
||||
INFLUENCE: "INFLUENCE";
|
||||
MAP: "MAP";
|
||||
SYSTEM: "SYSTEM";
|
||||
PAYMENT: "PAYMENT";
|
||||
}>>;
|
||||
subjectLine: z.ZodOptional<z.ZodString>;
|
||||
htmlContent: z.ZodOptional<z.ZodString>;
|
||||
@@ -207,8 +210,8 @@ export declare const updateEmailTemplateSchema: z.ZodObject<{
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -227,8 +230,8 @@ export declare const updateEmailTemplateSchema: z.ZodObject<{
|
||||
}>, {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -247,17 +250,17 @@ export declare const updateEmailTemplateSchema: z.ZodObject<{
|
||||
}>, "many">>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
name?: string | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
description?: string | null | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
description?: string | null | undefined;
|
||||
subjectLine?: string | undefined;
|
||||
htmlContent?: string | undefined;
|
||||
textContent?: string | undefined;
|
||||
variables?: {
|
||||
type: "VIDEO" | "TEXT";
|
||||
key: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
videoId?: number | undefined;
|
||||
@@ -266,9 +269,9 @@ export declare const updateEmailTemplateSchema: z.ZodObject<{
|
||||
}[] | undefined;
|
||||
}, {
|
||||
name?: string | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | undefined;
|
||||
description?: string | null | undefined;
|
||||
category?: "INFLUENCE" | "MAP" | "SYSTEM" | "PAYMENT" | undefined;
|
||||
isActive?: boolean | undefined;
|
||||
description?: string | null | undefined;
|
||||
subjectLine?: string | undefined;
|
||||
htmlContent?: string | undefined;
|
||||
textContent?: string | undefined;
|
||||
|
||||
@@ -1 +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"}
|
||||
{"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"}
|
||||
@@ -28,15 +28,20 @@ export declare class EmailTemplatesService {
|
||||
versions: number;
|
||||
testLogs: number;
|
||||
};
|
||||
createdBy: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
};
|
||||
variables: {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
description: string | null;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
templateId: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
@@ -46,26 +51,21 @@ export declare class EmailTemplatesService {
|
||||
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;
|
||||
isActive: boolean;
|
||||
description: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
}>;
|
||||
/**
|
||||
* Get template by key
|
||||
@@ -75,11 +75,11 @@ export declare class EmailTemplatesService {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
description: string | null;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
templateId: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
@@ -90,15 +90,15 @@ export declare class EmailTemplatesService {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
isActive: boolean;
|
||||
description: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
}) | null>;
|
||||
/**
|
||||
* Create a new email template
|
||||
@@ -108,11 +108,11 @@ export declare class EmailTemplatesService {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
description: string | null;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
templateId: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
@@ -123,15 +123,15 @@ export declare class EmailTemplatesService {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
isActive: boolean;
|
||||
description: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
}>;
|
||||
/**
|
||||
* Update an email template
|
||||
@@ -141,11 +141,11 @@ export declare class EmailTemplatesService {
|
||||
type: import(".prisma/client").$Enums.EmailTemplateVariableType;
|
||||
id: string;
|
||||
videoId: number | null;
|
||||
description: string | null;
|
||||
key: string;
|
||||
templateId: string;
|
||||
label: string;
|
||||
description: string | null;
|
||||
sortOrder: number;
|
||||
label: string;
|
||||
templateId: string;
|
||||
isRequired: boolean;
|
||||
isConditional: boolean;
|
||||
sampleValue: string | null;
|
||||
@@ -156,15 +156,15 @@ export declare class EmailTemplatesService {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
isActive: boolean;
|
||||
description: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
}>;
|
||||
/**
|
||||
* Delete an email template
|
||||
@@ -182,10 +182,10 @@ export declare class EmailTemplatesService {
|
||||
} & {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
createdByUserId: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
createdByUserId: string;
|
||||
versionNumber: number;
|
||||
changeNotes: string | null;
|
||||
templateId: string;
|
||||
@@ -202,10 +202,10 @@ export declare class EmailTemplatesService {
|
||||
} & {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
createdByUserId: string;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
createdByUserId: string;
|
||||
versionNumber: number;
|
||||
changeNotes: string | null;
|
||||
templateId: string;
|
||||
@@ -219,15 +219,15 @@ export declare class EmailTemplatesService {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
category: import(".prisma/client").$Enums.EmailTemplateCategory;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
isActive: boolean;
|
||||
description: string | null;
|
||||
subjectLine: string;
|
||||
htmlContent: string;
|
||||
textContent: string;
|
||||
isSystem: boolean;
|
||||
createdByUserId: string;
|
||||
updatedByUserId: string | null;
|
||||
}>;
|
||||
/**
|
||||
* Validate template syntax
|
||||
@@ -252,12 +252,12 @@ export declare class EmailTemplatesService {
|
||||
} & {
|
||||
id: string;
|
||||
success: boolean;
|
||||
messageId: string | null;
|
||||
recipientEmail: string;
|
||||
sentAt: Date;
|
||||
templateId: string;
|
||||
testData: Prisma.JsonValue;
|
||||
errorMessage: string | null;
|
||||
messageId: string | null;
|
||||
sentByUserId: string;
|
||||
})[]>;
|
||||
}
|
||||
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"campaign-emails.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.routes.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,YAAY,4CAAW,CAAC;AAqC9B,QAAA,MAAM,WAAW,4CAAW,CAAC;AAiC7B,OAAO,EAAE,YAAY,IAAI,0BAA0B,EAAE,WAAW,IAAI,yBAAyB,EAAE,CAAC"}
|
||||
@@ -2,14 +2,13 @@
|
||||
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];
|
||||
const roles_1 = require("../../../utils/roles");
|
||||
// --- Public Routes (no auth) ---
|
||||
const publicRouter = (0, express_1.Router)();
|
||||
exports.campaignEmailsPublicRouter = publicRouter;
|
||||
@@ -41,7 +40,7 @@ publicRouter.post('/:slug/track-mailto', rate_limit_1.emailRateLimit, (0, valida
|
||||
const adminRouter = (0, express_1.Router)();
|
||||
exports.campaignEmailsAdminRouter = adminRouter;
|
||||
adminRouter.use(auth_middleware_1.authenticate);
|
||||
adminRouter.use((0, rbac_middleware_1.requireRole)(...ADMIN_ROLES));
|
||||
adminRouter.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
|
||||
// GET /api/campaigns/:id/emails
|
||||
adminRouter.get('/:id/emails', (0, validate_1.validate)(campaign_emails_schemas_1.listCampaignEmailsSchema, 'query'), async (req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -1 +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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"}
|
||||
{"version":3,"file":"campaign-emails.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.routes.ts"],"names":[],"mappings":"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"}
|
||||
@@ -14,6 +14,7 @@ export declare const campaignEmailsService: {
|
||||
emails: {
|
||||
status: import(".prisma/client").$Enums.CampaignEmailStatus;
|
||||
id: string;
|
||||
subject: string;
|
||||
userEmail: string | null;
|
||||
userName: string | null;
|
||||
userPostalCode: string | null;
|
||||
@@ -21,7 +22,6 @@ export declare const campaignEmailsService: {
|
||||
recipientName: string | null;
|
||||
recipientLevel: import(".prisma/client").$Enums.GovernmentLevel | null;
|
||||
emailMethod: import(".prisma/client").$Enums.EmailMethod;
|
||||
subject: string;
|
||||
sentAt: Date;
|
||||
}[];
|
||||
pagination: {
|
||||
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"campaign-emails.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaign-emails/campaign-emails.service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEnH,eAAO,MAAM,qBAAqB;oBACV,MAAM,QAAQ,sBAAsB,aAAa,MAAM;;;;;sBA+GrD,MAAM,QAAQ,gBAAgB,aAAa,MAAM;;;;;+BAwDxC,MAAM,WAAW,uBAAuB;;;;;;;;;;;;;;;;;;;;;yBA0C9C,MAAM;CAqClC,CAAC"}
|
||||
@@ -1,4 +1,37 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.campaignEmailsService = void 0;
|
||||
const client_1 = require("@prisma/client");
|
||||
@@ -6,6 +39,9 @@ const database_1 = require("../../../config/database");
|
||||
const error_handler_1 = require("../../../middleware/error-handler");
|
||||
const email_queue_service_1 = require("../../../services/email-queue.service");
|
||||
const metrics_1 = require("../../../utils/metrics");
|
||||
const crm_activity_1 = require("../../../utils/crm-activity");
|
||||
const group_service_1 = require("../../social/group.service");
|
||||
const achievements_service_1 = require("../../social/achievements.service");
|
||||
exports.campaignEmailsService = {
|
||||
async sendEmail(slug, data, senderIp) {
|
||||
const campaign = await database_1.prisma.campaign.findUnique({
|
||||
@@ -77,6 +113,26 @@ exports.campaignEmailsService = {
|
||||
});
|
||||
}
|
||||
(0, metrics_1.recordCampaignEmail)(campaign.id);
|
||||
// CRM activity (fire-and-forget)
|
||||
(0, crm_activity_1.recordCrmActivity)({
|
||||
email: data.userEmail,
|
||||
activityType: 'EMAIL_SENT',
|
||||
title: `Sent campaign email: ${campaign.title}`,
|
||||
metadata: { campaignId: campaign.id, campaignSlug: campaign.slug, recipientEmail: data.recipientEmail, emailMethod: data.emailMethod },
|
||||
}).catch(() => { });
|
||||
// Social group sync (fire-and-forget)
|
||||
group_service_1.groupService.syncCampaignTeam(campaign.id).catch(() => { });
|
||||
// Achievement check for registered users (fire-and-forget)
|
||||
database_1.prisma.user.findUnique({ where: { email: data.userEmail }, select: { id: true } })
|
||||
.then((user) => {
|
||||
if (user)
|
||||
achievements_service_1.achievementsService.checkAndUnlock(user.id, ['campaigns']).catch(() => { });
|
||||
})
|
||||
.catch(() => { });
|
||||
// Fire-and-forget: check campaign milestones
|
||||
Promise.resolve().then(() => __importStar(require('../../social/impact-stories.service'))).then(({ impactStoriesService }) => {
|
||||
impactStoriesService.checkMilestones(campaign.id).catch(() => { });
|
||||
}).catch(() => { });
|
||||
return {
|
||||
id: campaignEmail.id,
|
||||
status: campaignEmail.status,
|
||||
@@ -120,6 +176,12 @@ exports.campaignEmailsService = {
|
||||
senderIp,
|
||||
},
|
||||
});
|
||||
// Social group sync (fire-and-forget)
|
||||
group_service_1.groupService.syncCampaignTeam(campaign.id).catch(() => { });
|
||||
// Fire-and-forget: check campaign milestones
|
||||
Promise.resolve().then(() => __importStar(require('../../social/impact-stories.service'))).then(({ impactStoriesService }) => {
|
||||
impactStoriesService.checkMilestones(campaign.id).catch(() => { });
|
||||
}).catch(() => { });
|
||||
return {
|
||||
id: campaignEmail.id,
|
||||
status: campaignEmail.status,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +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"}
|
||||
{"version":3,"file":"campaigns-public.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns-public.routes.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2HxB,OAAO,EAAE,MAAM,IAAI,oBAAoB,EAAE,CAAC"}
|
||||
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.campaignPublicRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const campaigns_service_1 = require("./campaigns.service");
|
||||
const database_1 = require("../../../config/database");
|
||||
const redis_1 = require("../../../config/redis");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.campaignPublicRouter = router;
|
||||
// GET /api/campaigns/public — list all active campaigns (public)
|
||||
@@ -26,4 +28,93 @@ router.get('/:slug/details', async (req, res, next) => {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/campaigns/:slug/related — related campaigns + upcoming shifts
|
||||
router.get('/:slug/related', async (req, res, next) => {
|
||||
try {
|
||||
const slug = req.params.slug;
|
||||
const cacheKey = `campaign:related:${slug}`;
|
||||
// Check cache
|
||||
try {
|
||||
const cached = await redis_1.redis.get(cacheKey);
|
||||
if (cached) {
|
||||
res.json(JSON.parse(cached));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch { /* cache miss */ }
|
||||
// Find current campaign
|
||||
const campaign = await database_1.prisma.campaign.findFirst({
|
||||
where: { slug, status: 'ACTIVE' },
|
||||
select: { id: true, targetGovernmentLevels: true },
|
||||
});
|
||||
if (!campaign) {
|
||||
res.json({ campaigns: [], shifts: [] });
|
||||
return;
|
||||
}
|
||||
// Related campaigns: same gov levels, exclude current, limit 3
|
||||
const relatedCampaigns = await database_1.prisma.campaign.findMany({
|
||||
where: {
|
||||
status: 'ACTIVE',
|
||||
id: { not: campaign.id },
|
||||
...(campaign.targetGovernmentLevels.length > 0 && {
|
||||
targetGovernmentLevels: { hasSome: campaign.targetGovernmentLevels },
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
description: true,
|
||||
_count: { select: { emails: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 3,
|
||||
});
|
||||
// Related shifts: upcoming, limit 3
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const relatedShifts = await database_1.prisma.shift.findMany({
|
||||
where: {
|
||||
date: { gte: today },
|
||||
status: 'OPEN',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
date: true,
|
||||
startTime: true,
|
||||
location: true,
|
||||
maxVolunteers: true,
|
||||
_count: { select: { signups: true } },
|
||||
},
|
||||
orderBy: { date: 'asc' },
|
||||
take: 3,
|
||||
});
|
||||
const result = {
|
||||
campaigns: relatedCampaigns.map(c => ({
|
||||
id: c.id,
|
||||
slug: c.slug,
|
||||
title: c.title,
|
||||
description: c.description?.slice(0, 150) ?? null,
|
||||
emailCount: c._count.emails,
|
||||
})),
|
||||
shifts: relatedShifts.map(s => ({
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
startTime: `${s.date.toISOString().split('T')[0]}T${s.startTime}:00`,
|
||||
location: s.location,
|
||||
currentVolunteers: s._count.signups,
|
||||
maxVolunteers: s.maxVolunteers,
|
||||
})),
|
||||
};
|
||||
try {
|
||||
await redis_1.redis.setex(cacheKey, 300, JSON.stringify(result));
|
||||
}
|
||||
catch { /* non-critical */ }
|
||||
res.json(result);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=campaigns-public.routes.js.map
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"campaigns-public.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns-public.routes.ts"],"names":[],"mappings":"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"}
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"campaigns.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA6ExB,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,CAAC"}
|
||||
@@ -2,18 +2,17 @@
|
||||
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 roles_1 = require("../../../utils/roles");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.campaignsRouter = router;
|
||||
// All campaign admin routes require authentication + admin role
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
router.use((0, rbac_middleware_1.requireRole)(...ADMIN_ROLES));
|
||||
router.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
|
||||
// GET /api/campaigns — list campaigns with pagination/filters
|
||||
router.get('/', (0, validate_1.validate)(campaigns_schemas_1.listCampaignsSchema, 'query'), async (req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"campaigns.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"campaigns.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.routes.ts"],"names":[],"mappings":"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"}
|
||||
@@ -27,8 +27,9 @@ export declare const createCampaignSchema: z.ZodObject<{
|
||||
showResponseWall: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
||||
highlightCampaign: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
||||
coverPhoto: z.ZodOptional<z.ZodString>;
|
||||
coverVideoId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
status: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED";
|
||||
status: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED";
|
||||
title: string;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
@@ -43,16 +44,18 @@ export declare const createCampaignSchema: z.ZodObject<{
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[];
|
||||
description?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
coverPhoto?: string | undefined;
|
||||
coverVideoId?: number | null | undefined;
|
||||
callToAction?: string | undefined;
|
||||
}, {
|
||||
title: string;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
|
||||
description?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
coverPhoto?: string | undefined;
|
||||
coverVideoId?: number | null | undefined;
|
||||
callToAction?: string | undefined;
|
||||
allowSmtpEmail?: boolean | undefined;
|
||||
allowMailtoLink?: boolean | undefined;
|
||||
collectUserInfo?: boolean | undefined;
|
||||
@@ -92,14 +95,16 @@ export declare const updateCampaignSchema: z.ZodObject<{
|
||||
showResponseWall: z.ZodOptional<z.ZodBoolean>;
|
||||
highlightCampaign: z.ZodOptional<z.ZodBoolean>;
|
||||
coverPhoto: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
||||
coverVideoId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
|
||||
title?: string | undefined;
|
||||
description?: string | null | undefined;
|
||||
coverPhoto?: string | null | undefined;
|
||||
coverVideoId?: number | null | undefined;
|
||||
emailSubject?: string | undefined;
|
||||
emailBody?: string | undefined;
|
||||
callToAction?: string | null | undefined;
|
||||
coverPhoto?: string | null | undefined;
|
||||
allowSmtpEmail?: boolean | undefined;
|
||||
allowMailtoLink?: boolean | undefined;
|
||||
collectUserInfo?: boolean | undefined;
|
||||
@@ -111,13 +116,14 @@ export declare const updateCampaignSchema: z.ZodObject<{
|
||||
highlightCampaign?: boolean | undefined;
|
||||
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
|
||||
}, {
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
|
||||
title?: string | undefined;
|
||||
description?: string | null | undefined;
|
||||
coverPhoto?: string | null | undefined;
|
||||
coverVideoId?: number | null | undefined;
|
||||
emailSubject?: string | undefined;
|
||||
emailBody?: string | undefined;
|
||||
callToAction?: string | null | undefined;
|
||||
coverPhoto?: string | null | undefined;
|
||||
allowSmtpEmail?: boolean | undefined;
|
||||
allowMailtoLink?: boolean | undefined;
|
||||
collectUserInfo?: boolean | undefined;
|
||||
@@ -142,10 +148,10 @@ export declare const listCampaignsSchema: z.ZodObject<{
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
limit: number;
|
||||
page: number;
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
|
||||
search?: string | undefined;
|
||||
}, {
|
||||
status?: "ACTIVE" | "DRAFT" | "PAUSED" | "ARCHIVED" | undefined;
|
||||
status?: "ACTIVE" | "ARCHIVED" | "DRAFT" | "PAUSED" | undefined;
|
||||
search?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
page?: number | undefined;
|
||||
@@ -157,7 +163,99 @@ export declare const campaignIdSchema: z.ZodObject<{
|
||||
}, {
|
||||
id: string;
|
||||
}>;
|
||||
export declare const createUserCampaignSchema: z.ZodObject<{
|
||||
title: z.ZodString;
|
||||
description: z.ZodOptional<z.ZodString>;
|
||||
emailSubject: z.ZodString;
|
||||
emailBody: z.ZodString;
|
||||
callToAction: z.ZodOptional<z.ZodString>;
|
||||
targetGovernmentLevels: z.ZodArray<z.ZodNativeEnum<{
|
||||
FEDERAL: "FEDERAL";
|
||||
PROVINCIAL: "PROVINCIAL";
|
||||
MUNICIPAL: "MUNICIPAL";
|
||||
SCHOOL_BOARD: "SCHOOL_BOARD";
|
||||
}>, "many">;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
title: string;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
targetGovernmentLevels: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[];
|
||||
description?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
}, {
|
||||
title: string;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
targetGovernmentLevels: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[];
|
||||
description?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
}>;
|
||||
export declare const updateUserCampaignSchema: z.ZodObject<{
|
||||
title: z.ZodOptional<z.ZodString>;
|
||||
description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
||||
emailSubject: z.ZodOptional<z.ZodString>;
|
||||
emailBody: z.ZodOptional<z.ZodString>;
|
||||
callToAction: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
||||
targetGovernmentLevels: z.ZodOptional<z.ZodArray<z.ZodNativeEnum<{
|
||||
FEDERAL: "FEDERAL";
|
||||
PROVINCIAL: "PROVINCIAL";
|
||||
MUNICIPAL: "MUNICIPAL";
|
||||
SCHOOL_BOARD: "SCHOOL_BOARD";
|
||||
}>, "many">>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
emailSubject?: string | undefined;
|
||||
emailBody?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
|
||||
}, {
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
emailSubject?: string | undefined;
|
||||
emailBody?: string | undefined;
|
||||
callToAction?: string | undefined;
|
||||
targetGovernmentLevels?: ("FEDERAL" | "PROVINCIAL" | "MUNICIPAL" | "SCHOOL_BOARD")[] | undefined;
|
||||
}>;
|
||||
export declare const moderateCampaignSchema: z.ZodObject<{
|
||||
action: z.ZodEnum<["approve", "reject", "request_changes"]>;
|
||||
reason: z.ZodOptional<z.ZodString>;
|
||||
notes: z.ZodOptional<z.ZodString>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
action: "approve" | "reject" | "request_changes";
|
||||
reason?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
}, {
|
||||
action: "approve" | "reject" | "request_changes";
|
||||
reason?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
}>;
|
||||
export declare const listModerationQueueSchema: z.ZodObject<{
|
||||
page: z.ZodDefault<z.ZodNumber>;
|
||||
limit: z.ZodDefault<z.ZodNumber>;
|
||||
search: z.ZodOptional<z.ZodString>;
|
||||
moderationStatus: z.ZodOptional<z.ZodNativeEnum<{
|
||||
PENDING_REVIEW: "PENDING_REVIEW";
|
||||
APPROVED: "APPROVED";
|
||||
REJECTED: "REJECTED";
|
||||
CHANGES_REQUESTED: "CHANGES_REQUESTED";
|
||||
}>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
limit: number;
|
||||
page: number;
|
||||
search?: string | undefined;
|
||||
moderationStatus?: "APPROVED" | "PENDING_REVIEW" | "REJECTED" | "CHANGES_REQUESTED" | undefined;
|
||||
}, {
|
||||
search?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
page?: number | undefined;
|
||||
moderationStatus?: "APPROVED" | "PENDING_REVIEW" | "REJECTED" | "CHANGES_REQUESTED" | undefined;
|
||||
}>;
|
||||
export type CreateCampaignInput = z.infer<typeof createCampaignSchema>;
|
||||
export type UpdateCampaignInput = z.infer<typeof updateCampaignSchema>;
|
||||
export type ListCampaignsInput = z.infer<typeof listCampaignsSchema>;
|
||||
export type CreateUserCampaignInput = z.infer<typeof createUserCampaignSchema>;
|
||||
export type UpdateUserCampaignInput = z.infer<typeof updateUserCampaignSchema>;
|
||||
export type ModerateCampaignInput = z.infer<typeof moderateCampaignSchema>;
|
||||
export type ListModerationQueueInput = z.infer<typeof listModerationQueueSchema>;
|
||||
//# sourceMappingURL=campaigns.schemas.d.ts.map
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"campaigns.schemas.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;EAK9B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;EAE3B,CAAC;AAGH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;EAOnC,CAAC;AAGH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;EAAqC,CAAC;AAG3E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;EAIjC,CAAC;AAGH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;EAKpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACrE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC/E,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC/E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC3E,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
||||
@@ -1,14 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.campaignIdSchema = exports.listCampaignsSchema = exports.updateCampaignSchema = exports.createCampaignSchema = void 0;
|
||||
exports.listModerationQueueSchema = exports.moderateCampaignSchema = exports.updateUserCampaignSchema = exports.createUserCampaignSchema = exports.campaignIdSchema = exports.listCampaignsSchema = exports.updateCampaignSchema = exports.createCampaignSchema = void 0;
|
||||
const zod_1 = require("zod");
|
||||
const client_1 = require("@prisma/client");
|
||||
exports.createCampaignSchema = zod_1.z.object({
|
||||
title: zod_1.z.string().min(1, 'Title is required'),
|
||||
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(),
|
||||
title: zod_1.z.string().min(1, 'Title is required').max(200),
|
||||
description: zod_1.z.string().max(2000).optional(),
|
||||
emailSubject: zod_1.z.string().min(1, 'Email subject is required').max(200),
|
||||
emailBody: zod_1.z.string().min(1, 'Email body is required').max(10000),
|
||||
callToAction: zod_1.z.string().max(500).optional(),
|
||||
status: zod_1.z.nativeEnum(client_1.CampaignStatus).optional().default(client_1.CampaignStatus.DRAFT),
|
||||
targetGovernmentLevels: zod_1.z.array(zod_1.z.nativeEnum(client_1.GovernmentLevel)).optional().default([]),
|
||||
allowSmtpEmail: zod_1.z.boolean().optional().default(true),
|
||||
@@ -20,14 +20,15 @@ exports.createCampaignSchema = zod_1.z.object({
|
||||
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(),
|
||||
coverPhoto: zod_1.z.string().url().max(500).optional(),
|
||||
coverVideoId: zod_1.z.number().int().positive().nullable().optional(),
|
||||
});
|
||||
exports.updateCampaignSchema = zod_1.z.object({
|
||||
title: zod_1.z.string().min(1).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(),
|
||||
title: zod_1.z.string().min(1).max(200).optional(),
|
||||
description: zod_1.z.string().max(2000).nullable().optional(),
|
||||
emailSubject: zod_1.z.string().min(1).max(200).optional(),
|
||||
emailBody: zod_1.z.string().min(1).max(10000).optional(),
|
||||
callToAction: zod_1.z.string().max(500).nullable().optional(),
|
||||
status: zod_1.z.nativeEnum(client_1.CampaignStatus).optional(),
|
||||
targetGovernmentLevels: zod_1.z.array(zod_1.z.nativeEnum(client_1.GovernmentLevel)).optional(),
|
||||
allowSmtpEmail: zod_1.z.boolean().optional(),
|
||||
@@ -39,7 +40,8 @@ exports.updateCampaignSchema = zod_1.z.object({
|
||||
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(),
|
||||
coverPhoto: zod_1.z.string().url().max(500).nullable().optional(),
|
||||
coverVideoId: zod_1.z.number().int().positive().nullable().optional(),
|
||||
});
|
||||
exports.listCampaignsSchema = zod_1.z.object({
|
||||
page: zod_1.z.coerce.number().int().positive().default(1),
|
||||
@@ -50,4 +52,28 @@ exports.listCampaignsSchema = zod_1.z.object({
|
||||
exports.campaignIdSchema = zod_1.z.object({
|
||||
id: zod_1.z.string().min(1),
|
||||
});
|
||||
// User-submitted campaign (restricted fields)
|
||||
exports.createUserCampaignSchema = zod_1.z.object({
|
||||
title: zod_1.z.string().min(3, 'Title must be at least 3 characters').max(200),
|
||||
description: zod_1.z.string().max(2000).optional(),
|
||||
emailSubject: zod_1.z.string().min(3, 'Email subject is required').max(200),
|
||||
emailBody: zod_1.z.string().min(10, 'Email body must be at least 10 characters').max(5000),
|
||||
callToAction: zod_1.z.string().max(500).optional(),
|
||||
targetGovernmentLevels: zod_1.z.array(zod_1.z.nativeEnum(client_1.GovernmentLevel)).min(1, 'Select at least one government level'),
|
||||
});
|
||||
// Update own user campaign (same restricted fields)
|
||||
exports.updateUserCampaignSchema = exports.createUserCampaignSchema.partial();
|
||||
// Admin moderation action
|
||||
exports.moderateCampaignSchema = zod_1.z.object({
|
||||
action: zod_1.z.enum(['approve', 'reject', 'request_changes']),
|
||||
reason: zod_1.z.string().max(2000).optional(),
|
||||
notes: zod_1.z.string().max(2000).optional(),
|
||||
});
|
||||
// Moderation queue filters
|
||||
exports.listModerationQueueSchema = zod_1.z.object({
|
||||
page: zod_1.z.coerce.number().int().positive().default(1),
|
||||
limit: zod_1.z.coerce.number().int().positive().max(100).default(20),
|
||||
search: zod_1.z.string().optional(),
|
||||
moderationStatus: zod_1.z.nativeEnum(client_1.CampaignModerationStatus).optional(),
|
||||
});
|
||||
//# sourceMappingURL=campaigns.schemas.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import { UserRole } from '@prisma/client';
|
||||
import type { CreateCampaignInput, UpdateCampaignInput, ListCampaignsInput } from './campaigns.schemas';
|
||||
import type { CreateCampaignInput, UpdateCampaignInput, ListCampaignsInput, CreateUserCampaignInput, ModerateCampaignInput, ListModerationQueueInput } from './campaigns.schemas';
|
||||
interface AuthUser {
|
||||
id: string;
|
||||
email: string;
|
||||
@@ -19,10 +19,13 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -35,7 +38,11 @@ export declare const campaignsService: {
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}[];
|
||||
pagination: {
|
||||
page: number;
|
||||
@@ -56,10 +63,13 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -72,7 +82,11 @@ export declare const campaignsService: {
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
findBySlug(slug: string): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
@@ -86,10 +100,13 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -102,7 +119,11 @@ export declare const campaignsService: {
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
create(data: CreateCampaignInput, user: AuthUser): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
@@ -116,10 +137,13 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -132,7 +156,11 @@ export declare const campaignsService: {
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
update(id: string, data: UpdateCampaignInput): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
@@ -146,10 +174,13 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -162,7 +193,11 @@ export declare const campaignsService: {
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
findActiveCampaigns(): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
@@ -176,10 +211,11 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
coverPhoto: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -190,9 +226,9 @@ export declare const campaignsService: {
|
||||
showResponseWall: boolean;
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
}[]>;
|
||||
findBySlugPublic(slug: string): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
@@ -206,10 +242,45 @@ export declare const campaignsService: {
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
showEmailCount: boolean;
|
||||
showCallCount: boolean;
|
||||
allowEmailEditing: boolean;
|
||||
allowCustomRecipients: boolean;
|
||||
showResponseWall: boolean;
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserName: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
}>;
|
||||
delete(id: string): Promise<void>;
|
||||
createUserCampaign(data: CreateUserCampaignInput, user: AuthUser): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: {
|
||||
responses: number;
|
||||
emails: number;
|
||||
};
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
@@ -222,9 +293,175 @@ export declare const campaignsService: {
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
createdByUserId: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
findUserCampaigns(userId: string): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: {
|
||||
responses: number;
|
||||
emails: number;
|
||||
};
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
showEmailCount: boolean;
|
||||
showCallCount: boolean;
|
||||
allowEmailEditing: boolean;
|
||||
allowCustomRecipients: boolean;
|
||||
showResponseWall: boolean;
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}[]>;
|
||||
updateUserCampaign(id: string, data: Partial<CreateUserCampaignInput>, user: AuthUser): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: {
|
||||
responses: number;
|
||||
emails: number;
|
||||
};
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
showEmailCount: boolean;
|
||||
showCallCount: boolean;
|
||||
allowEmailEditing: boolean;
|
||||
allowCustomRecipients: boolean;
|
||||
showResponseWall: boolean;
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
findModerationQueue(filters: ListModerationQueueInput): Promise<{
|
||||
campaigns: {
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: {
|
||||
responses: number;
|
||||
emails: number;
|
||||
};
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
showEmailCount: boolean;
|
||||
showCallCount: boolean;
|
||||
allowEmailEditing: boolean;
|
||||
allowCustomRecipients: boolean;
|
||||
showResponseWall: boolean;
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}>;
|
||||
getModerationStats(): Promise<{
|
||||
total: number;
|
||||
pending: number;
|
||||
approved: number;
|
||||
rejected: number;
|
||||
changesRequested: number;
|
||||
}>;
|
||||
moderateCampaign(id: string, input: ModerateCampaignInput, reviewer: AuthUser): Promise<{
|
||||
status: import(".prisma/client").$Enums.CampaignStatus;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count: {
|
||||
responses: number;
|
||||
emails: number;
|
||||
};
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
coverPhoto: string | null;
|
||||
coverVideoId: number | null;
|
||||
moderationNotes: string | null;
|
||||
createdByUserId: string | null;
|
||||
emailSubject: string;
|
||||
emailBody: string;
|
||||
callToAction: string | null;
|
||||
allowSmtpEmail: boolean;
|
||||
allowMailtoLink: boolean;
|
||||
collectUserInfo: boolean;
|
||||
showEmailCount: boolean;
|
||||
showCallCount: boolean;
|
||||
allowEmailEditing: boolean;
|
||||
allowCustomRecipients: boolean;
|
||||
showResponseWall: boolean;
|
||||
highlightCampaign: boolean;
|
||||
targetGovernmentLevels: import(".prisma/client").$Enums.GovernmentLevel[];
|
||||
createdByUserEmail: string | null;
|
||||
createdByUserName: string | null;
|
||||
isUserGenerated: boolean;
|
||||
moderationStatus: import(".prisma/client").$Enums.CampaignModerationStatus | null;
|
||||
reviewedByUserId: string | null;
|
||||
reviewedAt: Date | null;
|
||||
rejectionReason: string | null;
|
||||
}>;
|
||||
delete(id: string): Promise<void>;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=campaigns.service.d.ts.map
|
||||
@@ -1 +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"}
|
||||
{"version":3,"file":"campaigns.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/campaigns/campaigns.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,QAAQ,EAA4B,MAAM,gBAAgB,CAAC;AAI5E,OAAO,KAAK,EACV,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAC5D,uBAAuB,EAA2B,qBAAqB,EAAE,wBAAwB,EAClG,MAAM,qBAAqB,CAAC;AAiH7B,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,eAAO,MAAM,gBAAgB;qBACJ,kBAAkB,SAAS,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0CvC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAaF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAaV,mBAAmB,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAgCrC,MAAM,QAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA0CrB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAiBlB,MAAM;6BAWQ,uBAAuB,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAwCtC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAQT,MAAM,QAAQ,OAAO,CAAC,uBAAuB,CAAC,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCA2CxD,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA0ChC,MAAM,SAAS,qBAAqB,YAAY,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCpF,CAAC"}
|
||||
@@ -4,6 +4,15 @@ 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 roles_1 = require("../../../utils/roles");
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
const campaignSelect = {
|
||||
id: true,
|
||||
slug: true,
|
||||
@@ -13,6 +22,7 @@ const campaignSelect = {
|
||||
emailBody: true,
|
||||
callToAction: true,
|
||||
coverPhoto: true,
|
||||
coverVideoId: true,
|
||||
status: true,
|
||||
allowSmtpEmail: true,
|
||||
allowMailtoLink: true,
|
||||
@@ -27,6 +37,46 @@ const campaignSelect = {
|
||||
createdByUserId: true,
|
||||
createdByUserEmail: true,
|
||||
createdByUserName: true,
|
||||
isUserGenerated: true,
|
||||
moderationStatus: true,
|
||||
reviewedByUserId: true,
|
||||
reviewedAt: true,
|
||||
rejectionReason: true,
|
||||
moderationNotes: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: {
|
||||
select: {
|
||||
emails: true,
|
||||
responses: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
/** Public-facing select — strips admin-only fields (emails, internal IDs, moderation notes) */
|
||||
const publicCampaignSelect = {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
description: true,
|
||||
emailSubject: true,
|
||||
emailBody: true,
|
||||
callToAction: true,
|
||||
coverPhoto: true,
|
||||
coverVideoId: true,
|
||||
status: true,
|
||||
allowSmtpEmail: true,
|
||||
allowMailtoLink: true,
|
||||
collectUserInfo: true,
|
||||
showEmailCount: true,
|
||||
showCallCount: true,
|
||||
allowEmailEditing: true,
|
||||
allowCustomRecipients: true,
|
||||
showResponseWall: true,
|
||||
highlightCampaign: true,
|
||||
targetGovernmentLevels: true,
|
||||
createdByUserName: true,
|
||||
isUserGenerated: true,
|
||||
moderationStatus: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: {
|
||||
@@ -72,8 +122,7 @@ exports.campaignsService = {
|
||||
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)) {
|
||||
if (user && !(0, roles_1.hasAnyRole)(user, roles_1.ADMIN_ROLES)) {
|
||||
where.createdByUserId = user.id;
|
||||
}
|
||||
const [campaigns, total] = await Promise.all([
|
||||
@@ -171,7 +220,7 @@ exports.campaignsService = {
|
||||
async findActiveCampaigns() {
|
||||
return database_1.prisma.campaign.findMany({
|
||||
where: { status: 'ACTIVE' },
|
||||
select: campaignSelect,
|
||||
select: publicCampaignSelect,
|
||||
orderBy: [
|
||||
{ highlightCampaign: 'desc' },
|
||||
{ createdAt: 'desc' },
|
||||
@@ -181,7 +230,7 @@ exports.campaignsService = {
|
||||
async findBySlugPublic(slug) {
|
||||
const campaign = await database_1.prisma.campaign.findUnique({
|
||||
where: { slug },
|
||||
select: campaignSelect,
|
||||
select: publicCampaignSelect,
|
||||
});
|
||||
if (!campaign) {
|
||||
throw new error_handler_1.AppError(404, 'Campaign not found', 'CAMPAIGN_NOT_FOUND');
|
||||
@@ -198,5 +247,162 @@ exports.campaignsService = {
|
||||
}
|
||||
await database_1.prisma.campaign.delete({ where: { id } });
|
||||
},
|
||||
// --- User-Generated Campaign Methods ---
|
||||
async createUserCampaign(data, user) {
|
||||
const baseSlug = generateSlug(data.title);
|
||||
const slug = await resolveSlugCollision(baseSlug);
|
||||
const dbUser = await database_1.prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { name: true },
|
||||
});
|
||||
const campaign = await database_1.prisma.campaign.create({
|
||||
data: {
|
||||
slug,
|
||||
title: escapeHtml(data.title),
|
||||
description: data.description ? escapeHtml(data.description) : null,
|
||||
emailSubject: escapeHtml(data.emailSubject),
|
||||
emailBody: escapeHtml(data.emailBody),
|
||||
callToAction: data.callToAction ? escapeHtml(data.callToAction) : null,
|
||||
targetGovernmentLevels: data.targetGovernmentLevels,
|
||||
status: 'DRAFT',
|
||||
isUserGenerated: true,
|
||||
moderationStatus: client_1.CampaignModerationStatus.PENDING_REVIEW,
|
||||
allowSmtpEmail: false,
|
||||
allowMailtoLink: true,
|
||||
collectUserInfo: true,
|
||||
showEmailCount: true,
|
||||
showCallCount: false,
|
||||
allowEmailEditing: false,
|
||||
allowCustomRecipients: false,
|
||||
showResponseWall: false,
|
||||
highlightCampaign: false,
|
||||
createdByUserId: user.id,
|
||||
createdByUserEmail: user.email,
|
||||
createdByUserName: dbUser?.name ?? null,
|
||||
},
|
||||
select: campaignSelect,
|
||||
});
|
||||
return campaign;
|
||||
},
|
||||
async findUserCampaigns(userId) {
|
||||
return database_1.prisma.campaign.findMany({
|
||||
where: { createdByUserId: userId, isUserGenerated: true },
|
||||
select: campaignSelect,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
},
|
||||
async updateUserCampaign(id, data, user) {
|
||||
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');
|
||||
}
|
||||
if (existing.createdByUserId !== user.id) {
|
||||
throw new error_handler_1.AppError(403, 'You can only edit your own campaigns', 'FORBIDDEN');
|
||||
}
|
||||
if (!existing.isUserGenerated) {
|
||||
throw new error_handler_1.AppError(403, 'Cannot edit admin-created campaigns', 'FORBIDDEN');
|
||||
}
|
||||
if (existing.moderationStatus !== client_1.CampaignModerationStatus.CHANGES_REQUESTED &&
|
||||
existing.moderationStatus !== client_1.CampaignModerationStatus.PENDING_REVIEW) {
|
||||
throw new error_handler_1.AppError(400, 'Campaign cannot be edited in its current state', 'INVALID_STATE');
|
||||
}
|
||||
const updateData = {};
|
||||
if (data.title) {
|
||||
updateData.title = escapeHtml(data.title);
|
||||
const baseSlug = generateSlug(data.title);
|
||||
updateData.slug = await resolveSlugCollision(baseSlug, id);
|
||||
}
|
||||
if (data.description !== undefined)
|
||||
updateData.description = data.description ? escapeHtml(data.description) : null;
|
||||
if (data.emailSubject)
|
||||
updateData.emailSubject = escapeHtml(data.emailSubject);
|
||||
if (data.emailBody)
|
||||
updateData.emailBody = escapeHtml(data.emailBody);
|
||||
if (data.callToAction !== undefined)
|
||||
updateData.callToAction = data.callToAction ? escapeHtml(data.callToAction) : null;
|
||||
if (data.targetGovernmentLevels)
|
||||
updateData.targetGovernmentLevels = data.targetGovernmentLevels;
|
||||
// Reset to pending review on edit
|
||||
updateData.moderationStatus = client_1.CampaignModerationStatus.PENDING_REVIEW;
|
||||
updateData.rejectionReason = null;
|
||||
return database_1.prisma.campaign.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
select: campaignSelect,
|
||||
});
|
||||
},
|
||||
// --- Moderation Methods ---
|
||||
async findModerationQueue(filters) {
|
||||
const { page, limit, search, moderationStatus } = filters;
|
||||
const skip = (page - 1) * limit;
|
||||
const where = { isUserGenerated: true };
|
||||
if (moderationStatus)
|
||||
where.moderationStatus = moderationStatus;
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ title: { contains: search, mode: 'insensitive' } },
|
||||
{ createdByUserName: { contains: search, mode: 'insensitive' } },
|
||||
{ createdByUserEmail: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
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 getModerationStats() {
|
||||
const [total, pending, approved, rejected, changesRequested] = await Promise.all([
|
||||
database_1.prisma.campaign.count({ where: { isUserGenerated: true } }),
|
||||
database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.PENDING_REVIEW } }),
|
||||
database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.APPROVED } }),
|
||||
database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.REJECTED } }),
|
||||
database_1.prisma.campaign.count({ where: { moderationStatus: client_1.CampaignModerationStatus.CHANGES_REQUESTED } }),
|
||||
]);
|
||||
return { total, pending, approved, rejected, changesRequested };
|
||||
},
|
||||
async moderateCampaign(id, input, reviewer) {
|
||||
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');
|
||||
}
|
||||
if (!existing.isUserGenerated) {
|
||||
throw new error_handler_1.AppError(400, 'Only user-generated campaigns can be moderated', 'INVALID_STATE');
|
||||
}
|
||||
const updateData = {
|
||||
reviewedByUserId: reviewer.id,
|
||||
reviewedAt: new Date(),
|
||||
moderationNotes: input.notes ?? null,
|
||||
};
|
||||
switch (input.action) {
|
||||
case 'approve':
|
||||
updateData.moderationStatus = client_1.CampaignModerationStatus.APPROVED;
|
||||
updateData.status = 'ACTIVE';
|
||||
updateData.rejectionReason = null;
|
||||
break;
|
||||
case 'reject':
|
||||
updateData.moderationStatus = client_1.CampaignModerationStatus.REJECTED;
|
||||
updateData.rejectionReason = input.reason ?? null;
|
||||
break;
|
||||
case 'request_changes':
|
||||
updateData.moderationStatus = client_1.CampaignModerationStatus.CHANGES_REQUESTED;
|
||||
updateData.rejectionReason = input.reason ?? null;
|
||||
break;
|
||||
}
|
||||
return database_1.prisma.campaign.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
select: campaignSelect,
|
||||
});
|
||||
},
|
||||
};
|
||||
//# sourceMappingURL=campaigns.service.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"version":3,"file":"email-queue.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/email-queue/email-queue.routes.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAwDxB,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,CAAC"}
|
||||
{"version":3,"file":"email-queue.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/email-queue/email-queue.routes.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAwDxB,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,CAAC"}
|
||||
@@ -2,15 +2,14 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.emailQueueRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const client_1 = require("@prisma/client");
|
||||
const auth_middleware_1 = require("../../../middleware/auth.middleware");
|
||||
const rbac_middleware_1 = require("../../../middleware/rbac.middleware");
|
||||
const email_queue_service_1 = require("../../../services/email-queue.service");
|
||||
const ADMIN_ROLES = [client_1.UserRole.SUPER_ADMIN, client_1.UserRole.INFLUENCE_ADMIN, client_1.UserRole.MAP_ADMIN];
|
||||
const roles_1 = require("../../../utils/roles");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.emailQueueRouter = router;
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
router.use((0, rbac_middleware_1.requireRole)(...ADMIN_ROLES));
|
||||
router.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
|
||||
// GET /api/email-queue/stats
|
||||
router.get('/stats', async (_req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"email-queue.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/email-queue/email-queue.routes.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAClE,2CAA0C;AAC1C,yEAAmE;AACnE,yEAAkE;AAClE,+EAA0E;AAE1E,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;AAwDL,kCAAgB;AAvDnC,MAAM,CAAC,GAAG,CAAC,8BAAY,CAAC,CAAC;AACzB,MAAM,CAAC,GAAG,CAAC,IAAA,6BAAW,EAAC,GAAG,WAAW,CAAC,CAAC,CAAC;AAExC,6BAA6B;AAC7B,MAAM,CAAC,GAAG,CACR,QAAQ,EACR,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,uCAAiB,CAAC,QAAQ,EAAE,CAAC;QACjD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,8BAA8B;AAC9B,MAAM,CAAC,IAAI,CACT,QAAQ,EACR,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,uCAAiB,CAAC,KAAK,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,+BAA+B;AAC/B,MAAM,CAAC,IAAI,CACT,SAAS,EACT,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,uCAAiB,CAAC,MAAM,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,8BAA8B;AAC9B,MAAM,CAAC,IAAI,CACT,QAAQ,EACR,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,uCAAiB,CAAC,KAAK,EAAE,CAAC;QAChD,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,OAAO,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC"}
|
||||
{"version":3,"file":"email-queue.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/email-queue/email-queue.routes.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAClE,yEAAmE;AACnE,yEAAkE;AAClE,+EAA0E;AAC1E,gDAAuD;AAEvD,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAwDL,kCAAgB;AAvDnC,MAAM,CAAC,GAAG,CAAC,8BAAY,CAAC,CAAC;AACzB,MAAM,CAAC,GAAG,CAAC,IAAA,6BAAW,EAAC,GAAG,uBAAe,CAAC,CAAC,CAAC;AAE5C,6BAA6B;AAC7B,MAAM,CAAC,GAAG,CACR,QAAQ,EACR,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,uCAAiB,CAAC,QAAQ,EAAE,CAAC;QACjD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,8BAA8B;AAC9B,MAAM,CAAC,IAAI,CACT,QAAQ,EACR,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,uCAAiB,CAAC,KAAK,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,+BAA+B;AAC/B,MAAM,CAAC,IAAI,CACT,SAAS,EACT,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,uCAAiB,CAAC,MAAM,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,8BAA8B;AAC9B,MAAM,CAAC,IAAI,CACT,QAAQ,EACR,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,uCAAiB,CAAC,KAAK,EAAE,CAAC;QAChD,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,OAAO,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC"}
|
||||
@@ -13,9 +13,9 @@ export declare const postalCodeParamSchema: z.ZodObject<{
|
||||
export declare const postalCodeQuerySchema: z.ZodObject<{
|
||||
refresh: z.ZodDefault<z.ZodOptional<z.ZodEnum<["true", "false"]>>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
refresh: "true" | "false";
|
||||
refresh: "false" | "true";
|
||||
}, {
|
||||
refresh?: "true" | "false" | undefined;
|
||||
refresh?: "false" | "true" | undefined;
|
||||
}>;
|
||||
export type PostalCodeParam = z.infer<typeof postalCodeParamSchema>;
|
||||
export type PostalCodeQuery = z.infer<typeof postalCodeQuerySchema>;
|
||||
|
||||
@@ -12,18 +12,18 @@ export declare const postalCodesService: {
|
||||
city: string | null;
|
||||
postalCode: string;
|
||||
province: string | null;
|
||||
lastUpdated: Date;
|
||||
centroidLat: Prisma.Decimal | null;
|
||||
centroidLng: Prisma.Decimal | null;
|
||||
lastUpdated: Date;
|
||||
}>;
|
||||
findByPostalCode(code: string): Promise<{
|
||||
id: string;
|
||||
city: string | null;
|
||||
postalCode: string;
|
||||
province: string | null;
|
||||
lastUpdated: Date;
|
||||
centroidLat: Prisma.Decimal | null;
|
||||
centroidLng: Prisma.Decimal | null;
|
||||
lastUpdated: Date;
|
||||
} | null>;
|
||||
findAll(filters: {
|
||||
page: number;
|
||||
@@ -35,9 +35,9 @@ export declare const postalCodesService: {
|
||||
city: string | null;
|
||||
postalCode: string;
|
||||
province: string | null;
|
||||
lastUpdated: Date;
|
||||
centroidLat: Prisma.Decimal | null;
|
||||
centroidLng: Prisma.Decimal | null;
|
||||
lastUpdated: Date;
|
||||
}[];
|
||||
pagination: {
|
||||
page: number;
|
||||
@@ -51,9 +51,9 @@ export declare const postalCodesService: {
|
||||
city: string | null;
|
||||
postalCode: string;
|
||||
province: string | null;
|
||||
lastUpdated: Date;
|
||||
centroidLat: Prisma.Decimal | null;
|
||||
centroidLng: Prisma.Decimal | null;
|
||||
lastUpdated: Date;
|
||||
}>;
|
||||
};
|
||||
export {};
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"representatives.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/representatives/representatives.routes.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAiHxB,OAAO,EAAE,MAAM,IAAI,qBAAqB,EAAE,CAAC"}
|
||||
{"version":3,"file":"representatives.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/representatives/representatives.routes.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAiHxB,OAAO,EAAE,MAAM,IAAI,qBAAqB,EAAE,CAAC"}
|
||||
@@ -2,14 +2,13 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.representativesRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const client_1 = require("@prisma/client");
|
||||
const representatives_service_1 = require("./representatives.service");
|
||||
const representatives_schemas_1 = require("./representatives.schemas");
|
||||
const postal_codes_schemas_1 = require("../postal-codes/postal-codes.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 roles_1 = require("../../../utils/roles");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.representativesRouter = router;
|
||||
// =============================================
|
||||
@@ -41,7 +40,7 @@ router.get('/test-connection', async (_req, res, next) => {
|
||||
// ADMIN ROUTES (auth + role required)
|
||||
// =============================================
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
router.use((0, rbac_middleware_1.requireRole)(...ADMIN_ROLES));
|
||||
router.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
|
||||
// GET /api/representatives/cache-stats — cache statistics
|
||||
router.get('/cache-stats', async (_req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"representatives.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/representatives/representatives.routes.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAClE,2CAA0C;AAC1C,uEAAmE;AACnE,uEAAsE;AACtE,+EAAoG;AACpG,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;AAiHL,uCAAqB;AA/GxC,gDAAgD;AAChD,mCAAmC;AACnC,gDAAgD;AAEhD,sEAAsE;AACtE,MAAM,CAAC,GAAG,CACR,wBAAwB,EACxB,IAAA,mBAAQ,EAAC,4CAAqB,EAAE,QAAQ,CAAC,EACzC,IAAA,mBAAQ,EAAC,4CAAqB,EAAE,OAAO,CAAC,EACxC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,UAAoB,CAAC;QAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,gDAAsB,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC9E,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,wEAAwE;AACxE,MAAM,CAAC,GAAG,CACR,kBAAkB,EAClB,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gDAAsB,CAAC,iBAAiB,EAAE,CAAC;QAChE,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,gDAAgD;AAChD,sCAAsC;AACtC,gDAAgD;AAEhD,MAAM,CAAC,GAAG,CAAC,8BAAY,CAAC,CAAC;AACzB,MAAM,CAAC,GAAG,CAAC,IAAA,6BAAW,EAAC,GAAG,WAAW,CAAC,CAAC,CAAC;AAExC,0DAA0D;AAC1D,MAAM,CAAC,GAAG,CACR,cAAc,EACd,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,gDAAsB,CAAC,aAAa,EAAE,CAAC;QAC3D,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,8DAA8D;AAC9D,MAAM,CAAC,GAAG,CACR,GAAG,EACH,IAAA,mBAAQ,EAAC,mDAAyB,EAAE,OAAO,CAAC,EAC5C,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gDAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,KAAY,CAAC,CAAC;QACtE,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,mDAAmD;AACnD,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,GAAG,GAAG,MAAM,gDAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,kFAAkF;AAClF,MAAM,CAAC,MAAM,CACX,wBAAwB,EACxB,IAAA,mBAAQ,EAAC,4CAAqB,EAAE,QAAQ,CAAC,EACzC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,UAAoB,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,gDAAsB,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACpE,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,6DAA6D;AAC7D,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,gDAAsB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC5C,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"}
|
||||
{"version":3,"file":"representatives.routes.js","sourceRoot":"","sources":["../../../../src/modules/influence/representatives/representatives.routes.ts"],"names":[],"mappings":"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"}
|
||||
@@ -14,13 +14,13 @@ export declare const representativesService: {
|
||||
name: string | null;
|
||||
url: string | null;
|
||||
postalCode: string;
|
||||
cachedAt: Date;
|
||||
districtName: string | null;
|
||||
electedOffice: string | null;
|
||||
partyName: string | null;
|
||||
representativeSetName: string | null;
|
||||
photoUrl: string | null;
|
||||
offices: Prisma.JsonValue | null;
|
||||
cachedAt: Date;
|
||||
}[];
|
||||
} | {
|
||||
source: "api";
|
||||
@@ -51,13 +51,13 @@ export declare const representativesService: {
|
||||
name: string | null;
|
||||
url: string | null;
|
||||
postalCode: string;
|
||||
cachedAt: Date;
|
||||
districtName: string | null;
|
||||
electedOffice: string | null;
|
||||
partyName: string | null;
|
||||
representativeSetName: string | null;
|
||||
photoUrl: string | null;
|
||||
offices: Prisma.JsonValue | null;
|
||||
cachedAt: Date;
|
||||
}[];
|
||||
pagination: {
|
||||
page: number;
|
||||
@@ -72,13 +72,13 @@ export declare const representativesService: {
|
||||
name: string | null;
|
||||
url: string | null;
|
||||
postalCode: string;
|
||||
cachedAt: Date;
|
||||
districtName: string | null;
|
||||
electedOffice: string | null;
|
||||
partyName: string | null;
|
||||
representativeSetName: string | null;
|
||||
photoUrl: string | null;
|
||||
offices: Prisma.JsonValue | null;
|
||||
cachedAt: Date;
|
||||
}>;
|
||||
clearByPostalCode(code: string): Promise<{
|
||||
deleted: number;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"responses.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/responses/responses.routes.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,oBAAoB,4CAAW,CAAC;AAiDtC,QAAA,MAAM,qBAAqB,4CAAW,CAAC;AA6EvC,QAAA,MAAM,oBAAoB,4CAAW,CAAC;AA6DtC,OAAO,EAAE,oBAAoB,IAAI,4BAA4B,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC"}
|
||||
{"version":3,"file":"responses.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/responses/responses.routes.ts"],"names":[],"mappings":"AAgBA,QAAA,MAAM,oBAAoB,4CAAW,CAAC;AAiDtC,QAAA,MAAM,qBAAqB,4CAAW,CAAC;AA6EvC,QAAA,MAAM,oBAAoB,4CAAW,CAAC;AA6DtC,OAAO,EAAE,oBAAoB,IAAI,4BAA4B,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC"}
|
||||
@@ -2,7 +2,6 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.responsesAdminRouter = exports.responsesPublicRouter = exports.responseCampaignPublicRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const client_1 = require("@prisma/client");
|
||||
const responses_service_1 = require("./responses.service");
|
||||
const responses_schemas_1 = require("./responses.schemas");
|
||||
const validate_1 = require("../../../middleware/validate");
|
||||
@@ -10,7 +9,7 @@ const auth_middleware_1 = require("../../../middleware/auth.middleware");
|
||||
const auth_middleware_2 = 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];
|
||||
const roles_1 = require("../../../utils/roles");
|
||||
// --- Campaign-scoped public routes (mount at /api/campaigns) ---
|
||||
const campaignPublicRouter = (0, express_1.Router)();
|
||||
exports.responseCampaignPublicRouter = campaignPublicRouter;
|
||||
@@ -111,7 +110,7 @@ responsesPublicRouter.get('/:id/report/:token', async (req, res, next) => {
|
||||
const responsesAdminRouter = (0, express_1.Router)();
|
||||
exports.responsesAdminRouter = responsesAdminRouter;
|
||||
responsesAdminRouter.use(auth_middleware_1.authenticate);
|
||||
responsesAdminRouter.use((0, rbac_middleware_1.requireRole)(...ADMIN_ROLES));
|
||||
responsesAdminRouter.use((0, rbac_middleware_1.requireRole)(...roles_1.INFLUENCE_ROLES));
|
||||
// GET /api/responses
|
||||
responsesAdminRouter.get('/', (0, validate_1.validate)(responses_schemas_1.listAdminResponsesSchema, 'query'), async (req, res, next) => {
|
||||
try {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -82,11 +82,11 @@ export declare const listAdminResponsesSchema: z.ZodObject<{
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
limit: number;
|
||||
page: number;
|
||||
status?: "PENDING" | "APPROVED" | "REJECTED" | undefined;
|
||||
status?: "APPROVED" | "PENDING" | "REJECTED" | undefined;
|
||||
search?: string | undefined;
|
||||
campaignId?: string | undefined;
|
||||
}, {
|
||||
status?: "PENDING" | "APPROVED" | "REJECTED" | undefined;
|
||||
status?: "APPROVED" | "PENDING" | "REJECTED" | undefined;
|
||||
search?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
page?: number | undefined;
|
||||
@@ -99,9 +99,9 @@ export declare const updateResponseStatusSchema: z.ZodObject<{
|
||||
REJECTED: "REJECTED";
|
||||
}>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
status: "PENDING" | "APPROVED" | "REJECTED";
|
||||
status: "APPROVED" | "PENDING" | "REJECTED";
|
||||
}, {
|
||||
status: "PENDING" | "APPROVED" | "REJECTED";
|
||||
status: "APPROVED" | "PENDING" | "REJECTED";
|
||||
}>;
|
||||
export type SubmitResponseInput = z.infer<typeof submitResponseSchema>;
|
||||
export type ListPublicResponsesInput = z.infer<typeof listPublicResponsesSchema>;
|
||||
|
||||
@@ -4,14 +4,14 @@ exports.updateResponseStatusSchema = exports.listAdminResponsesSchema = exports.
|
||||
const zod_1 = require("zod");
|
||||
const client_1 = require("@prisma/client");
|
||||
exports.submitResponseSchema = zod_1.z.object({
|
||||
representativeName: zod_1.z.string().min(1, 'Representative name is required'),
|
||||
representativeName: zod_1.z.string().min(1, 'Representative name is required').max(200),
|
||||
representativeLevel: zod_1.z.nativeEnum(client_1.GovernmentLevel),
|
||||
responseType: zod_1.z.nativeEnum(client_1.ResponseType),
|
||||
responseText: zod_1.z.string().min(1, 'Response text is required'),
|
||||
representativeTitle: zod_1.z.string().optional(),
|
||||
responseText: zod_1.z.string().min(1, 'Response text is required').max(5000),
|
||||
representativeTitle: zod_1.z.string().max(200).optional(),
|
||||
representativeEmail: zod_1.z.string().email().optional(),
|
||||
userComment: zod_1.z.string().optional(),
|
||||
submittedByName: zod_1.z.string().optional(),
|
||||
userComment: zod_1.z.string().max(1000).optional(),
|
||||
submittedByName: zod_1.z.string().max(200).optional(),
|
||||
submittedByEmail: zod_1.z.string().email().optional(),
|
||||
isAnonymous: zod_1.z.boolean().optional().default(false),
|
||||
sendVerification: zod_1.z.boolean().optional().default(false),
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"responses.schemas.js","sourceRoot":"","sources":["../../../../src/modules/influence/responses/responses.schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,2CAA+E;AAElE,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,iCAAiC,CAAC;IACxE,mBAAmB,EAAE,OAAC,CAAC,UAAU,CAAC,wBAAe,CAAC;IAClD,YAAY,EAAE,OAAC,CAAC,UAAU,CAAC,qBAAY,CAAC;IACxC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,2BAA2B,CAAC;IAC5D,mBAAmB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1C,mBAAmB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAClD,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACtC,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAC/C,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAClD,gBAAgB,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACxD,CAAC,CAAC;AAEU,QAAA,yBAAyB,GAAG,OAAC,CAAC,MAAM,CAAC;IAChD,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,IAAI,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC5E,KAAK,EAAE,OAAC,CAAC,UAAU,CAAC,wBAAe,CAAC,CAAC,QAAQ,EAAE;CAChD,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,uBAAc,CAAC,CAAC,QAAQ,EAAE;IAC/C,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEU,QAAA,0BAA0B,GAAG,OAAC,CAAC,MAAM,CAAC;IACjD,MAAM,EAAE,OAAC,CAAC,UAAU,CAAC,uBAAc,CAAC;CACrC,CAAC,CAAC"}
|
||||
{"version":3,"file":"responses.schemas.js","sourceRoot":"","sources":["../../../../src/modules/influence/responses/responses.schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,2CAA+E;AAElE,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,iCAAiC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACjF,mBAAmB,EAAE,OAAC,CAAC,UAAU,CAAC,wBAAe,CAAC;IAClD,YAAY,EAAE,OAAC,CAAC,UAAU,CAAC,qBAAY,CAAC;IACxC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,2BAA2B,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IACtE,mBAAmB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IACnD,mBAAmB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAClD,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IAC5C,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC/C,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAC/C,WAAW,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAClD,gBAAgB,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACxD,CAAC,CAAC;AAEU,QAAA,yBAAyB,GAAG,OAAC,CAAC,MAAM,CAAC;IAChD,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,IAAI,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC5E,KAAK,EAAE,OAAC,CAAC,UAAU,CAAC,wBAAe,CAAC,CAAC,QAAQ,EAAE;CAChD,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,uBAAc,CAAC,CAAC,QAAQ,EAAE;IAC/C,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEU,QAAA,0BAA0B,GAAG,OAAC,CAAC,MAAM,CAAC;IACjD,MAAM,EAAE,OAAC,CAAC,UAAU,CAAC,uBAAc,CAAC;CACrC,CAAC,CAAC"}
|
||||
@@ -100,8 +100,9 @@ export declare const responsesService: {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
upvoteCount: number;
|
||||
campaignSlug: string;
|
||||
submittedByUserId: string | null;
|
||||
campaignId: string;
|
||||
campaignSlug: string;
|
||||
representativeName: string;
|
||||
representativeTitle: string | null;
|
||||
representativeLevel: import(".prisma/client").$Enums.GovernmentLevel;
|
||||
@@ -119,7 +120,6 @@ export declare const responsesService: {
|
||||
verifiedAt: Date | null;
|
||||
verifiedBy: string | null;
|
||||
submittedIp: string | null;
|
||||
submittedByUserId: string | null;
|
||||
}>;
|
||||
deleteResponse(id: string): Promise<void>;
|
||||
resendVerification(id: string): Promise<{
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"responses.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/responses/responses.service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,eAAO,MAAM,gBAAgB;yBAGA,MAAM,QAAQ,mBAAmB,aAAa,MAAM;;;;;uBAmEtD,MAAM,WAAW,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;mBA0D7C,MAAM;;;;;;uBA2BF,MAAM,WAAW,MAAM,WAAW,MAAM;;;;;;;6BAgClC,MAAM,WAAW,MAAM,WAAW,MAAM;;;uBAwB9C,MAAM,SAAS,MAAM;;;;;;;;;uBAqCrB,MAAM,SAAS,MAAM;;;;;;;;;qBA6BvB,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwDvB,MAAM,QAAQ,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAUrC,MAAM;2BAOF,MAAM;;;CA6CpC,CAAC"}
|
||||
{"version":3,"file":"responses.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/influence/responses/responses.service.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,eAAO,MAAM,gBAAgB;yBAGA,MAAM,QAAQ,mBAAmB,aAAa,MAAM;;;;;uBA8FtD,MAAM,WAAW,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;mBA0D7C,MAAM;;;;;;uBA2BF,MAAM,WAAW,MAAM,WAAW,MAAM;;;;;;;6BAgClC,MAAM,WAAW,MAAM,WAAW,MAAM;;;uBAwB9C,MAAM,SAAS,MAAM;;;;;;;;;uBAqCrB,MAAM,SAAS,MAAM;;;;;;;;;qBA6BvB,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwDvB,MAAM,QAAQ,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAUrC,MAAM;2BAOF,MAAM;;;CA6CpC,CAAC"}
|
||||
@@ -6,8 +6,12 @@ const client_1 = require("@prisma/client");
|
||||
const database_1 = require("../../../config/database");
|
||||
const error_handler_1 = require("../../../middleware/error-handler");
|
||||
const email_service_1 = require("../../../services/email.service");
|
||||
const notification_queue_service_1 = require("../../../services/notification-queue.service");
|
||||
const notification_helper_1 = require("../../../services/notification.helper");
|
||||
const env_1 = require("../../../config/env");
|
||||
const logger_1 = require("../../../utils/logger");
|
||||
const metrics_1 = require("../../../utils/metrics");
|
||||
const rocketchat_webhook_service_1 = require("../../../services/rocketchat-webhook.service");
|
||||
const VERIFICATION_EXPIRY_DAYS = 30;
|
||||
exports.responsesService = {
|
||||
// --- Public ---
|
||||
@@ -64,6 +68,32 @@ exports.responsesService = {
|
||||
});
|
||||
}
|
||||
(0, metrics_1.recordResponseSubmission)();
|
||||
// Notification: admin response submitted alert
|
||||
try {
|
||||
if (await (0, notification_helper_1.isNotificationEnabled)('notifyAdminResponseSubmitted')) {
|
||||
const adminEmails = await (0, notification_helper_1.getAdminEmailsByRole)([client_1.UserRole.SUPER_ADMIN, client_1.UserRole.INFLUENCE_ADMIN]);
|
||||
if (adminEmails.length > 0) {
|
||||
const adminUrl = `${env_1.env.ADMIN_URL || 'http://localhost:3000'}/app/influence/responses`;
|
||||
await notification_queue_service_1.notificationQueueService.enqueue({
|
||||
type: 'admin-response-submitted',
|
||||
adminEmails,
|
||||
campaignTitle: campaign.title,
|
||||
representativeName: data.representativeName,
|
||||
responseType: data.responseType,
|
||||
submitterName: data.isAnonymous ? 'Anonymous' : (data.submittedByName || 'Anonymous'),
|
||||
adminUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.error('Failed to enqueue response submitted notification:', err);
|
||||
}
|
||||
// Notify Rocket.Chat
|
||||
rocketchat_webhook_service_1.rocketchatWebhookService.onCampaignResponseSubmitted({
|
||||
campaignTitle: campaign.title,
|
||||
representativeName: data.representativeName,
|
||||
}).catch(() => { });
|
||||
return {
|
||||
id: response.id,
|
||||
status: response.status,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"version":3,"file":"listmonk.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/listmonk/listmonk.routes.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAgJxB,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,CAAC"}
|
||||
{"version":3,"file":"listmonk.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/listmonk/listmonk.routes.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAwJxB,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,CAAC"}
|
||||
9
api/dist/modules/listmonk/listmonk.routes.js
vendored
9
api/dist/modules/listmonk/listmonk.routes.js
vendored
@@ -2,16 +2,17 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.listmonkRouter = void 0;
|
||||
const express_1 = require("express");
|
||||
const client_1 = require("@prisma/client");
|
||||
const auth_middleware_1 = require("../../middleware/auth.middleware");
|
||||
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
|
||||
const listmonk_client_1 = require("../../services/listmonk.client");
|
||||
const listmonk_sync_service_1 = require("../../services/listmonk-sync.service");
|
||||
const listmonk_event_sync_service_1 = require("../../services/listmonk-event-sync.service");
|
||||
const env_1 = require("../../config/env");
|
||||
const roles_1 = require("../../utils/roles");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.listmonkRouter = router;
|
||||
router.use(auth_middleware_1.authenticate);
|
||||
router.use((0, rbac_middleware_1.requireRole)(client_1.UserRole.SUPER_ADMIN));
|
||||
router.use((0, rbac_middleware_1.requireRole)(...roles_1.BROADCAST_ROLES));
|
||||
// GET /api/listmonk — sync status
|
||||
router.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
@@ -119,6 +120,10 @@ router.post('/reinitialize', async (_req, res, next) => {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/listmonk/event-sync-stats — event-driven sync stats
|
||||
router.get('/event-sync-stats', (_req, res) => {
|
||||
res.json(listmonk_event_sync_service_1.listmonkEventSyncService.getStats());
|
||||
});
|
||||
// GET /api/listmonk/proxy-url — get proxy port + token for iframe embedding
|
||||
router.get('/proxy-url', (req, res, _next) => {
|
||||
const token = req.headers.authorization?.slice(7) || '';
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"listmonk.routes.js","sourceRoot":"","sources":["../../../src/modules/listmonk/listmonk.routes.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"listmonk.routes.js","sourceRoot":"","sources":["../../../src/modules/listmonk/listmonk.routes.ts"],"names":[],"mappings":"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"}
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"canvass.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/map/canvass/canvass.routes.ts"],"names":[],"mappings":"AAyBA,QAAA,MAAM,eAAe,4CAAW,CAAC;AAgQjC,QAAA,MAAM,WAAW,4CAAW,CAAC;AAsF7B,OAAO,EAAE,eAAe,IAAI,sBAAsB,EAAE,WAAW,IAAI,kBAAkB,EAAE,CAAC"}
|
||||
{"version":3,"file":"canvass.routes.d.ts","sourceRoot":"","sources":["../../../../src/modules/map/canvass/canvass.routes.ts"],"names":[],"mappings":"AAyBA,QAAA,MAAM,eAAe,4CAAW,CAAC;AAgQjC,QAAA,MAAM,WAAW,4CAAW,CAAC;AAoG7B,OAAO,EAAE,eAAe,IAAI,sBAAsB,EAAE,WAAW,IAAI,kBAAkB,EAAE,CAAC"}
|
||||
20
api/dist/modules/map/canvass/canvass.routes.js
vendored
20
api/dist/modules/map/canvass/canvass.routes.js
vendored
@@ -12,7 +12,7 @@ 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 MAP_ADMIN_ROLES = [client_1.UserRole.SUPER_ADMIN, client_1.UserRole.MAP_ADMIN];
|
||||
const roles_1 = require("../../../utils/roles");
|
||||
// ─── Volunteer Router ────────────────────────────────────────────────
|
||||
const volunteerRouter = (0, express_1.Router)();
|
||||
exports.canvassVolunteerRouter = volunteerRouter;
|
||||
@@ -138,17 +138,17 @@ volunteerRouter.put('/locations/:id', (0, validate_1.validate)(canvass_schemas_1
|
||||
// POST /api/map/canvass/locations — create a new location (role-gated fields)
|
||||
volunteerRouter.post('/locations', (0, validate_1.validate)(canvass_schemas_1.volunteerCreateLocationSchema), async (req, res, next) => {
|
||||
try {
|
||||
const role = req.user.role;
|
||||
const data = { ...req.body };
|
||||
// Strip fields based on role
|
||||
const isAdmin = role === client_1.UserRole.SUPER_ADMIN || role === client_1.UserRole.MAP_ADMIN;
|
||||
const userRoles = req.user.roles || [req.user.role];
|
||||
const isAdmin = userRoles.some((r) => r === client_1.UserRole.SUPER_ADMIN || r === client_1.UserRole.MAP_ADMIN);
|
||||
if (!isAdmin) {
|
||||
delete data.firstName;
|
||||
delete data.lastName;
|
||||
delete data.email;
|
||||
delete data.phone;
|
||||
}
|
||||
if (role === client_1.UserRole.TEMP) {
|
||||
if (userRoles.length === 1 && userRoles[0] === client_1.UserRole.TEMP) {
|
||||
delete data.supportLevel;
|
||||
delete data.sign;
|
||||
delete data.signSize;
|
||||
@@ -210,7 +210,7 @@ volunteerRouter.post('/visits/bulk', rate_limit_1.canvassBulkVisitRateLimit, //
|
||||
const adminRouter = (0, express_1.Router)();
|
||||
exports.canvassAdminRouter = adminRouter;
|
||||
adminRouter.use(auth_middleware_1.authenticate);
|
||||
adminRouter.use((0, rbac_middleware_1.requireRole)(...MAP_ADMIN_ROLES));
|
||||
adminRouter.use((0, rbac_middleware_1.requireRole)(...roles_1.MAP_ROLES));
|
||||
// GET /api/map/canvass/stats
|
||||
adminRouter.get('/stats', async (_req, res, next) => {
|
||||
try {
|
||||
@@ -273,4 +273,14 @@ adminRouter.get('/visits', (0, validate_1.validate)(canvass_schemas_1.adminVisit
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// GET /api/map/canvass/trends
|
||||
adminRouter.get('/trends', (0, validate_1.validate)(canvass_schemas_1.outcomeTrendsQuerySchema, 'query'), async (req, res, next) => {
|
||||
try {
|
||||
const result = await canvass_service_1.canvassService.getOutcomeTrends(req.query);
|
||||
res.json(result);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=canvass.routes.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -30,20 +30,20 @@ export declare const recordVisitSchema: z.ZodObject<{
|
||||
updateLocation: boolean;
|
||||
durationSeconds?: number | undefined;
|
||||
sessionId?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
supportLevel?: "LEVEL_1" | "LEVEL_2" | "LEVEL_3" | "LEVEL_4" | undefined;
|
||||
signSize?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
}, {
|
||||
outcome: "NOT_HOME" | "REFUSED" | "MOVED" | "ALREADY_VOTED" | "SPOKE_WITH" | "LEFT_LITERATURE" | "COME_BACK_LATER";
|
||||
addressId: string;
|
||||
durationSeconds?: number | undefined;
|
||||
sessionId?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
supportLevel?: "LEVEL_1" | "LEVEL_2" | "LEVEL_3" | "LEVEL_4" | undefined;
|
||||
signSize?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
signRequested?: boolean | undefined;
|
||||
signSize?: string | undefined;
|
||||
updateLocation?: boolean | undefined;
|
||||
}>;
|
||||
export declare const bulkRecordVisitSchema: z.ZodObject<{
|
||||
@@ -56,14 +56,14 @@ export declare const bulkRecordVisitSchema: z.ZodObject<{
|
||||
outcome: "NOT_HOME" | "REFUSED" | "MOVED";
|
||||
locationId: string;
|
||||
sessionId?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
}, {
|
||||
outcome: "NOT_HOME" | "REFUSED" | "MOVED";
|
||||
locationId: string;
|
||||
sessionId?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
}>;
|
||||
export declare const startSessionSchema: z.ZodObject<{
|
||||
cutId: z.ZodString;
|
||||
@@ -156,17 +156,17 @@ export declare const adminVisitsSchema: z.ZodObject<{
|
||||
sortOrder: "asc" | "desc";
|
||||
userId?: string | undefined;
|
||||
outcome?: "NOT_HOME" | "REFUSED" | "MOVED" | "ALREADY_VOTED" | "SPOKE_WITH" | "LEFT_LITERATURE" | "COME_BACK_LATER" | undefined;
|
||||
shiftId?: string | undefined;
|
||||
cutId?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
}, {
|
||||
limit?: number | undefined;
|
||||
userId?: string | undefined;
|
||||
page?: number | undefined;
|
||||
outcome?: "NOT_HOME" | "REFUSED" | "MOVED" | "ALREADY_VOTED" | "SPOKE_WITH" | "LEFT_LITERATURE" | "COME_BACK_LATER" | undefined;
|
||||
shiftId?: string | undefined;
|
||||
cutId?: string | undefined;
|
||||
sortBy?: "outcome" | "visitedAt" | undefined;
|
||||
sortOrder?: "asc" | "desc" | undefined;
|
||||
outcome?: "NOT_HOME" | "REFUSED" | "MOVED" | "ALREADY_VOTED" | "SPOKE_WITH" | "LEFT_LITERATURE" | "COME_BACK_LATER" | undefined;
|
||||
cutId?: string | undefined;
|
||||
shiftId?: string | undefined;
|
||||
}>;
|
||||
export declare const volunteerUpdateLocationSchema: z.ZodObject<{
|
||||
supportLevel: z.ZodOptional<z.ZodNullable<z.ZodNativeEnum<{
|
||||
@@ -187,25 +187,25 @@ export declare const volunteerUpdateLocationSchema: z.ZodObject<{
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
email?: string | null | undefined;
|
||||
phone?: string | null | undefined;
|
||||
address?: string | null | undefined;
|
||||
unitNumber?: string | null | undefined;
|
||||
notes?: string | null | undefined;
|
||||
firstName?: string | null | undefined;
|
||||
lastName?: string | null | undefined;
|
||||
supportLevel?: "LEVEL_1" | "LEVEL_2" | "LEVEL_3" | "LEVEL_4" | null | undefined;
|
||||
address?: string | null | undefined;
|
||||
unitNumber?: string | null | undefined;
|
||||
sign?: boolean | undefined;
|
||||
signSize?: string | null | undefined;
|
||||
notes?: string | null | undefined;
|
||||
}, {
|
||||
email?: string | null | undefined;
|
||||
phone?: string | null | undefined;
|
||||
address?: string | null | undefined;
|
||||
unitNumber?: string | null | undefined;
|
||||
notes?: string | null | undefined;
|
||||
firstName?: string | null | undefined;
|
||||
lastName?: string | null | undefined;
|
||||
supportLevel?: "LEVEL_1" | "LEVEL_2" | "LEVEL_3" | "LEVEL_4" | null | undefined;
|
||||
address?: string | null | undefined;
|
||||
unitNumber?: string | null | undefined;
|
||||
sign?: boolean | undefined;
|
||||
signSize?: string | null | undefined;
|
||||
notes?: string | null | undefined;
|
||||
}>;
|
||||
export declare const volunteerCreateLocationSchema: z.ZodObject<{
|
||||
address: z.ZodString;
|
||||
@@ -232,25 +232,25 @@ export declare const volunteerCreateLocationSchema: z.ZodObject<{
|
||||
sign: boolean;
|
||||
email?: string | undefined;
|
||||
phone?: string | undefined;
|
||||
unitNumber?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
firstName?: string | undefined;
|
||||
lastName?: string | undefined;
|
||||
supportLevel?: "LEVEL_1" | "LEVEL_2" | "LEVEL_3" | "LEVEL_4" | undefined;
|
||||
unitNumber?: string | undefined;
|
||||
signSize?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
}, {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
address: string;
|
||||
email?: string | undefined;
|
||||
phone?: string | undefined;
|
||||
unitNumber?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
firstName?: string | undefined;
|
||||
lastName?: string | undefined;
|
||||
supportLevel?: "LEVEL_1" | "LEVEL_2" | "LEVEL_3" | "LEVEL_4" | undefined;
|
||||
unitNumber?: string | undefined;
|
||||
sign?: boolean | undefined;
|
||||
signSize?: string | undefined;
|
||||
notes?: string | undefined;
|
||||
}>;
|
||||
export type RecordVisitInput = z.infer<typeof recordVisitSchema>;
|
||||
export type BulkRecordVisitInput = z.infer<typeof bulkRecordVisitSchema>;
|
||||
@@ -259,6 +259,20 @@ export type WalkingRouteInput = z.infer<typeof walkingRouteSchema>;
|
||||
export type ListMyVisitsInput = z.infer<typeof listMyVisitsSchema>;
|
||||
export type AdminActivityInput = z.infer<typeof adminActivitySchema>;
|
||||
export type AdminVisitsInput = z.infer<typeof adminVisitsSchema>;
|
||||
export declare const outcomeTrendsQuerySchema: z.ZodObject<{
|
||||
granularity: z.ZodDefault<z.ZodEnum<["day", "week"]>>;
|
||||
dateFrom: z.ZodOptional<z.ZodString>;
|
||||
dateTo: z.ZodOptional<z.ZodString>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
granularity: "week" | "day";
|
||||
dateFrom?: string | undefined;
|
||||
dateTo?: string | undefined;
|
||||
}, {
|
||||
dateFrom?: string | undefined;
|
||||
dateTo?: string | undefined;
|
||||
granularity?: "week" | "day" | undefined;
|
||||
}>;
|
||||
export type VolunteerUpdateLocationInput = z.infer<typeof volunteerUpdateLocationSchema>;
|
||||
export type VolunteerCreateLocationInput = z.infer<typeof volunteerCreateLocationSchema>;
|
||||
export type OutcomeTrendsQueryInput = z.infer<typeof outcomeTrendsQuerySchema>;
|
||||
//# sourceMappingURL=canvass.schemas.d.ts.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"canvass.schemas.d.ts","sourceRoot":"","sources":["../../../../src/modules/map/canvass/canvass.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAW5B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;EAMhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;EAK7B,CAAC;AAEH,eAAO,MAAM,gBAAgB,gDAE3B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;EAI7B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;EAG7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;EAM9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS5B,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYxC,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAexC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACnE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACrE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AACzF,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC"}
|
||||
{"version":3,"file":"canvass.schemas.d.ts","sourceRoot":"","sources":["../../../../src/modules/map/canvass/canvass.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAW5B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;EAMhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;EAK7B,CAAC;AAEH,eAAO,MAAM,gBAAgB,gDAE3B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;EAI7B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;EAG7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;EAM9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS5B,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYxC,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAexC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACnE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACrE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACjE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;EAInC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AACzF,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AACzF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.volunteerCreateLocationSchema = exports.volunteerUpdateLocationSchema = exports.adminVisitsSchema = exports.adminActivitySchema = exports.listMyVisitsSchema = exports.walkingRouteSchema = exports.endSessionSchema = exports.startSessionSchema = exports.bulkRecordVisitSchema = exports.recordVisitSchema = void 0;
|
||||
exports.outcomeTrendsQuerySchema = exports.volunteerCreateLocationSchema = exports.volunteerUpdateLocationSchema = exports.adminVisitsSchema = exports.adminActivitySchema = exports.listMyVisitsSchema = exports.walkingRouteSchema = exports.endSessionSchema = exports.startSessionSchema = exports.bulkRecordVisitSchema = exports.recordVisitSchema = void 0;
|
||||
const zod_1 = require("zod");
|
||||
const client_1 = require("@prisma/client");
|
||||
exports.recordVisitSchema = zod_1.z.object({
|
||||
@@ -86,4 +86,9 @@ exports.volunteerCreateLocationSchema = zod_1.z.object({
|
||||
email: zod_1.z.string().email().optional().or(zod_1.z.literal('')),
|
||||
phone: zod_1.z.string().optional(),
|
||||
});
|
||||
exports.outcomeTrendsQuerySchema = zod_1.z.object({
|
||||
granularity: zod_1.z.enum(['day', 'week']).default('day'),
|
||||
dateFrom: zod_1.z.string().optional(),
|
||||
dateTo: zod_1.z.string().optional(),
|
||||
});
|
||||
//# sourceMappingURL=canvass.schemas.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import { Prisma, UserRole } from '@prisma/client';
|
||||
import type { RecordVisitInput, BulkRecordVisitInput, StartSessionInput, WalkingRouteInput, ListMyVisitsInput, AdminActivityInput, AdminVisitsInput, VolunteerUpdateLocationInput } from './canvass.schemas';
|
||||
import type { RecordVisitInput, BulkRecordVisitInput, StartSessionInput, WalkingRouteInput, ListMyVisitsInput, AdminActivityInput, AdminVisitsInput, VolunteerUpdateLocationInput, OutcomeTrendsQueryInput } from './canvass.schemas';
|
||||
export declare const canvassService: {
|
||||
getMyAssignments(userId: string): Promise<{
|
||||
shiftId: string;
|
||||
@@ -32,12 +32,12 @@ export declare const canvassService: {
|
||||
durationSeconds: number | null;
|
||||
userId: string;
|
||||
sessionId: string | null;
|
||||
notes: string | null;
|
||||
outcome: import(".prisma/client").$Enums.VisitOutcome;
|
||||
shiftId: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
signRequested: boolean;
|
||||
signSize: string | null;
|
||||
visitedAt: Date;
|
||||
addressId: string;
|
||||
})[];
|
||||
@@ -61,12 +61,12 @@ export declare const canvassService: {
|
||||
status: import(".prisma/client").$Enums.CanvassSessionStatus;
|
||||
id: string;
|
||||
userId: string;
|
||||
cutId: string;
|
||||
shiftId: string | null;
|
||||
startedAt: Date;
|
||||
endedAt: Date | null;
|
||||
startLatitude: Prisma.Decimal | null;
|
||||
startLongitude: Prisma.Decimal | null;
|
||||
cutId: string;
|
||||
}) | null>;
|
||||
startSession(userId: string, data: StartSessionInput): Promise<{
|
||||
shift: {
|
||||
@@ -81,23 +81,23 @@ export declare const canvassService: {
|
||||
status: import(".prisma/client").$Enums.CanvassSessionStatus;
|
||||
id: string;
|
||||
userId: string;
|
||||
cutId: string;
|
||||
shiftId: string | null;
|
||||
startedAt: Date;
|
||||
endedAt: Date | null;
|
||||
startLatitude: Prisma.Decimal | null;
|
||||
startLongitude: Prisma.Decimal | null;
|
||||
cutId: string;
|
||||
}>;
|
||||
endSession(sessionId: string, userId: string): Promise<{
|
||||
status: import(".prisma/client").$Enums.CanvassSessionStatus;
|
||||
id: string;
|
||||
userId: string;
|
||||
cutId: string;
|
||||
shiftId: string | null;
|
||||
startedAt: Date;
|
||||
endedAt: Date | null;
|
||||
startLatitude: Prisma.Decimal | null;
|
||||
startLongitude: Prisma.Decimal | null;
|
||||
cutId: string;
|
||||
}>;
|
||||
getCutLocationsForCanvass(cutId: string, userId: string, bounds?: {
|
||||
minLat: number;
|
||||
@@ -121,13 +121,13 @@ export declare const canvassService: {
|
||||
id: string;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
unitNumber: string | null;
|
||||
notes: string | null;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
unitNumber: string | null;
|
||||
sign: boolean;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
}[]>;
|
||||
getAllLocationsForCanvass(userId: string, bounds?: {
|
||||
minLat: number;
|
||||
@@ -151,13 +151,13 @@ export declare const canvassService: {
|
||||
id: string;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
unitNumber: string | null;
|
||||
notes: string | null;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
unitNumber: string | null;
|
||||
sign: boolean;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
}[]>;
|
||||
updateAddressAsVolunteer(addressId: string, userId: string, role: UserRole, data: VolunteerUpdateLocationInput): Promise<{
|
||||
id: string;
|
||||
@@ -165,16 +165,16 @@ export declare const canvassService: {
|
||||
phone: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
notes: string | null;
|
||||
createdByUserId: string | null;
|
||||
updatedByUserId: string | null;
|
||||
unitNumber: string | null;
|
||||
addrGuid: string | null;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
unitNumber: string | null;
|
||||
addrGuid: string | null;
|
||||
sign: boolean;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
locationId: string;
|
||||
}>;
|
||||
getWalkingRoute(cutId: string, userId: string, filters: WalkingRouteInput): Promise<import("./canvass-route.service").RouteResult>;
|
||||
@@ -191,12 +191,12 @@ export declare const canvassService: {
|
||||
durationSeconds: number | null;
|
||||
userId: string;
|
||||
sessionId: string | null;
|
||||
notes: string | null;
|
||||
outcome: import(".prisma/client").$Enums.VisitOutcome;
|
||||
shiftId: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
signRequested: boolean;
|
||||
signSize: string | null;
|
||||
visitedAt: Date;
|
||||
addressId: string;
|
||||
}>;
|
||||
@@ -207,12 +207,12 @@ export declare const canvassService: {
|
||||
durationSeconds: number | null;
|
||||
userId: string;
|
||||
sessionId: string | null;
|
||||
notes: string | null;
|
||||
outcome: import(".prisma/client").$Enums.VisitOutcome;
|
||||
shiftId: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
signRequested: boolean;
|
||||
signSize: string | null;
|
||||
visitedAt: Date;
|
||||
addressId: string;
|
||||
}[];
|
||||
@@ -252,12 +252,12 @@ export declare const canvassService: {
|
||||
durationSeconds: number | null;
|
||||
userId: string;
|
||||
sessionId: string | null;
|
||||
notes: string | null;
|
||||
outcome: import(".prisma/client").$Enums.VisitOutcome;
|
||||
shiftId: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
signRequested: boolean;
|
||||
signSize: string | null;
|
||||
visitedAt: Date;
|
||||
addressId: string;
|
||||
})[];
|
||||
@@ -301,12 +301,12 @@ export declare const canvassService: {
|
||||
durationSeconds: number | null;
|
||||
userId: string;
|
||||
sessionId: string | null;
|
||||
notes: string | null;
|
||||
outcome: import(".prisma/client").$Enums.VisitOutcome;
|
||||
shiftId: string | null;
|
||||
supportLevel: import(".prisma/client").$Enums.SupportLevel | null;
|
||||
signSize: string | null;
|
||||
notes: string | null;
|
||||
signRequested: boolean;
|
||||
signSize: string | null;
|
||||
visitedAt: Date;
|
||||
addressId: string;
|
||||
})[];
|
||||
@@ -317,6 +317,15 @@ export declare const canvassService: {
|
||||
totalPages: number;
|
||||
};
|
||||
}>;
|
||||
getOutcomeTrends(filters: OutcomeTrendsQueryInput): Promise<{
|
||||
granularity: "week" | "day";
|
||||
dateFrom: string;
|
||||
dateTo: string;
|
||||
series: {
|
||||
date: string;
|
||||
}[];
|
||||
totals: Record<string, number>;
|
||||
}>;
|
||||
recalculateCutCompletion(cutId: string): Promise<void>;
|
||||
closeAbandonedSessions(): Promise<number>;
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"canvass.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/map/canvass/canvass.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoD,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AASpG,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,4BAA4B,EAC7B,MAAM,mBAAmB,CAAC;AAwE3B,eAAO,MAAM,cAAc;6BAGM,MAAM;;;;;;;;;;;uBAgCZ,MAAM;;;;;;wBAuBL,MAAM,WAAW,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA6B7B,MAAM;;;;;;;;;;;;;;;;;;;;yBAUV,MAAM,QAAQ,iBAAiB;;;;;;;;;;;;;;;;;;;;0BAgC9B,MAAM,UAAU,MAAM;;;;;;;;;;;qCAwBzC,MAAM,UACL,MAAM,WACL;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UACnE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;sCAoGN,MAAM,WACL;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UACnE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;wCAuFH,MAAM,UACT,MAAM,QACR,QAAQ,QACR,4BAA4B;;;;;;;;;;;;;;;;;;2BA+BP,MAAM,UAAU,MAAM,WAAW,iBAAiB;wBA2BrD,MAAM,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;;;;4BA0D1B,MAAM,QAAQ,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;uBAqHvC,MAAM;;;;;;;;;8BAuDC,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BA0FlB,MAAM;;;;;;4BAIR,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCA4DR,MAAM;;CA8D7C,CAAC"}
|
||||
{"version":3,"file":"canvass.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/map/canvass/canvass.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoD,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAgBpG,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,4BAA4B,EAC5B,uBAAuB,EACxB,MAAM,mBAAmB,CAAC;AAwE3B,eAAO,MAAM,cAAc;6BAGM,MAAM;;;;;;;;;;;uBAgCZ,MAAM;;;;;;wBAuBL,MAAM,WAAW,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA6B7B,MAAM;;;;;;;;;;;;;;;;;;;;yBAUV,MAAM,QAAQ,iBAAiB;;;;;;;;;;;;;;;;;;;;0BAgC9B,MAAM,UAAU,MAAM;;;;;;;;;;;qCA4GzC,MAAM,UACL,MAAM,WACL;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UACnE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;sCAoGN,MAAM,WACL;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UACnE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;wCAuFH,MAAM,UACT,MAAM,QACR,QAAQ,QACR,4BAA4B;;;;;;;;;;;;;;;;;;2BA+BP,MAAM,UAAU,MAAM,WAAW,iBAAiB;wBA2BrD,MAAM,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;;;;4BAuH1B,MAAM,QAAQ,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;uBAqHvC,MAAM;;;;;;;;;8BAuDC,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BA0FlB,MAAM;;;;;;4BAIR,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BA0Dd,uBAAuB;;;;;;;;;oCAoDjB,MAAM;;CA8D7C,CAAC"}
|
||||
187
api/dist/modules/map/canvass/canvass.service.js
vendored
187
api/dist/modules/map/canvass/canvass.service.js
vendored
@@ -8,8 +8,15 @@ const error_handler_1 = require("../../../middleware/error-handler");
|
||||
const logger_1 = require("../../../utils/logger");
|
||||
const metrics_1 = require("../../../utils/metrics");
|
||||
const spatial_1 = require("../../../utils/spatial");
|
||||
const crm_activity_1 = require("../../../utils/crm-activity");
|
||||
const canvass_route_service_1 = require("./canvass-route.service");
|
||||
const metrics_2 = require("../../../utils/metrics");
|
||||
const notification_queue_service_1 = require("../../../services/notification-queue.service");
|
||||
const notification_helper_1 = require("../../../services/notification.helper");
|
||||
const env_1 = require("../../../config/env");
|
||||
const rocketchat_webhook_service_1 = require("../../../services/rocketchat-webhook.service");
|
||||
const listmonk_event_sync_service_1 = require("../../../services/listmonk-event-sync.service");
|
||||
const achievements_service_1 = require("../../social/achievements.service");
|
||||
const ADDRESS_SELECT = {
|
||||
id: true,
|
||||
unitNumber: true,
|
||||
@@ -201,6 +208,87 @@ exports.canvassService = {
|
||||
});
|
||||
// Recalculate cut completion percentage
|
||||
await this.recalculateCutCompletion(session.cutId);
|
||||
// Notify Rocket.Chat
|
||||
try {
|
||||
const [rcUser, rcCut, rcVisitCount] = await Promise.all([
|
||||
database_1.prisma.user.findUnique({ where: { id: userId }, select: { name: true, email: true } }),
|
||||
database_1.prisma.cut.findUnique({ where: { id: session.cutId }, select: { name: true } }),
|
||||
database_1.prisma.canvassVisit.count({ where: { sessionId } }),
|
||||
]);
|
||||
rocketchat_webhook_service_1.rocketchatWebhookService.onCanvassSessionCompleted({
|
||||
userName: rcUser?.name || rcUser?.email || 'Unknown',
|
||||
visitCount: rcVisitCount,
|
||||
cutName: rcCut?.name || undefined,
|
||||
}).catch(() => { });
|
||||
}
|
||||
catch { /* non-critical */ }
|
||||
// Notification: volunteer session summary
|
||||
try {
|
||||
if (await (0, notification_helper_1.isNotificationEnabled)('notifyVolunteerSessionSummary')) {
|
||||
const [user, cut, visitCount, outcomeGroups, trackingSession] = await Promise.all([
|
||||
database_1.prisma.user.findUnique({ where: { id: userId }, select: { email: true, name: true } }),
|
||||
database_1.prisma.cut.findUnique({ where: { id: session.cutId }, select: { name: true } }),
|
||||
database_1.prisma.canvassVisit.count({ where: { sessionId } }),
|
||||
database_1.prisma.canvassVisit.groupBy({ by: ['outcome'], where: { sessionId }, _count: true }),
|
||||
database_1.prisma.trackingSession.findFirst({
|
||||
where: { userId, canvassSessionId: sessionId },
|
||||
select: { totalDistanceM: true },
|
||||
}),
|
||||
]);
|
||||
if (user && visitCount > 0) {
|
||||
const durationMs = updated.endedAt
|
||||
? updated.endedAt.getTime() - session.startedAt.getTime()
|
||||
: 0;
|
||||
const durationMinutes = Math.round(durationMs / 60000);
|
||||
const distanceKm = trackingSession?.totalDistanceM
|
||||
? Number(trackingSession.totalDistanceM) / 1000
|
||||
: 0;
|
||||
const outcomeBreakdown = {};
|
||||
for (const row of outcomeGroups) {
|
||||
outcomeBreakdown[row.outcome] = row._count;
|
||||
}
|
||||
await notification_queue_service_1.notificationQueueService.enqueue({
|
||||
type: 'volunteer-session-summary',
|
||||
volunteerEmail: user.email,
|
||||
volunteerName: user.name || user.email,
|
||||
cutName: cut?.name || 'Unknown area',
|
||||
sessionDate: session.startedAt.toLocaleDateString('en-CA', {
|
||||
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
||||
}),
|
||||
visitCount,
|
||||
durationMinutes,
|
||||
distanceKm,
|
||||
outcomeBreakdown,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.error('Failed to enqueue session summary notification:', err);
|
||||
}
|
||||
// Listmonk event sync — add canvasser to subscribers
|
||||
try {
|
||||
const [syncUser, syncCut, syncVisitCount, syncOutcomes] = await Promise.all([
|
||||
database_1.prisma.user.findUnique({ where: { id: userId }, select: { email: true, name: true } }),
|
||||
database_1.prisma.cut.findUnique({ where: { id: session.cutId }, select: { name: true } }),
|
||||
database_1.prisma.canvassVisit.count({ where: { sessionId } }),
|
||||
database_1.prisma.canvassVisit.groupBy({ by: ['outcome'], where: { sessionId }, _count: true }),
|
||||
]);
|
||||
if (syncUser) {
|
||||
const outcomes = {};
|
||||
for (const row of syncOutcomes) {
|
||||
outcomes[row.outcome] = row._count;
|
||||
}
|
||||
listmonk_event_sync_service_1.listmonkEventSyncService.onCanvassSessionCompleted({
|
||||
email: syncUser.email,
|
||||
name: syncUser.name || syncUser.email,
|
||||
cutName: syncCut?.name || 'Unknown',
|
||||
visitCount: syncVisitCount,
|
||||
outcomes,
|
||||
}).catch(() => { });
|
||||
}
|
||||
}
|
||||
catch { /* non-critical */ }
|
||||
return updated;
|
||||
},
|
||||
async getCutLocationsForCanvass(cutId, userId, bounds, limit) {
|
||||
@@ -448,12 +536,68 @@ exports.canvassService = {
|
||||
const prefix = `[${timestamp}] ${data.notes}`;
|
||||
updateData.notes = address.notes ? `${prefix}\n${address.notes}` : prefix;
|
||||
}
|
||||
await database_1.prisma.address.update({
|
||||
const updatedAddress = await database_1.prisma.address.update({
|
||||
where: { id: data.addressId },
|
||||
data: updateData,
|
||||
include: { location: { select: { address: true } } },
|
||||
});
|
||||
// Sync support level change to Listmonk (fire-and-forget)
|
||||
if (updatedAddress.email) {
|
||||
const name = [updatedAddress.firstName, updatedAddress.lastName].filter(Boolean).join(' ');
|
||||
listmonk_event_sync_service_1.listmonkEventSyncService.onAddressUpdated({
|
||||
email: updatedAddress.email,
|
||||
name,
|
||||
supportLevel: updatedAddress.supportLevel,
|
||||
sign: updatedAddress.sign,
|
||||
address: updatedAddress.location.address,
|
||||
}).catch(() => { });
|
||||
}
|
||||
}
|
||||
(0, metrics_2.recordCanvassVisit)(data.outcome);
|
||||
// CRM activity via ContactAddress lookup (fire-and-forget)
|
||||
database_1.prisma.contactAddress.findFirst({
|
||||
where: { addressId: data.addressId },
|
||||
select: { contactId: true },
|
||||
}).then((ca) => {
|
||||
if (ca) {
|
||||
(0, crm_activity_1.recordCrmActivity)({
|
||||
contactId: ca.contactId,
|
||||
activityType: 'CANVASS_VISIT',
|
||||
title: `Canvass visit: ${data.outcome}`,
|
||||
metadata: { addressId: data.addressId, outcome: data.outcome, visitId: visit.id },
|
||||
}).catch(() => { });
|
||||
}
|
||||
}).catch(() => { });
|
||||
// Achievement check (fire-and-forget)
|
||||
achievements_service_1.achievementsService.checkAndUnlock(userId, ['canvass']).catch(() => { });
|
||||
// Notification: sign request alert for admins
|
||||
if (data.signRequested) {
|
||||
try {
|
||||
if (await (0, notification_helper_1.isNotificationEnabled)('notifyAdminSignRequested')) {
|
||||
const adminEmails = await (0, notification_helper_1.getAdminEmailsByRole)([client_1.UserRole.SUPER_ADMIN, client_1.UserRole.MAP_ADMIN]);
|
||||
if (adminEmails.length > 0) {
|
||||
const [volunteer, shift] = await Promise.all([
|
||||
database_1.prisma.user.findUnique({ where: { id: userId }, select: { name: true } }),
|
||||
data.shiftId ? database_1.prisma.shift.findUnique({ where: { id: data.shiftId }, select: { title: true } }) : null,
|
||||
]);
|
||||
const addressStr = visit.address?.location?.address || 'Unknown address';
|
||||
const adminUrl = `${env_1.env.ADMIN_URL || 'http://localhost:3000'}/app/canvass/dashboard`;
|
||||
await notification_queue_service_1.notificationQueueService.enqueue({
|
||||
type: 'admin-sign-requested',
|
||||
adminEmails,
|
||||
volunteerName: volunteer?.name || 'Unknown',
|
||||
address: addressStr,
|
||||
shiftTitle: shift?.title || 'No shift',
|
||||
signSize: data.signSize || '',
|
||||
adminUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger_1.logger.error('Failed to enqueue sign request notification:', err);
|
||||
}
|
||||
}
|
||||
return visit;
|
||||
},
|
||||
async recordBulkVisit(userId, data) {
|
||||
@@ -736,6 +880,47 @@ exports.canvassService = {
|
||||
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
|
||||
};
|
||||
},
|
||||
async getOutcomeTrends(filters) {
|
||||
const { granularity } = filters;
|
||||
const dateTo = filters.dateTo ? new Date(filters.dateTo) : new Date();
|
||||
const dateFrom = filters.dateFrom
|
||||
? new Date(filters.dateFrom)
|
||||
: new Date(dateTo.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
// Ensure dateTo covers end of day
|
||||
const dateToEnd = new Date(dateTo);
|
||||
dateToEnd.setHours(23, 59, 59, 999);
|
||||
const rows = await database_1.prisma.$queryRaw `
|
||||
SELECT DATE_TRUNC(${granularity}, "visitedAt") as period,
|
||||
outcome::text as outcome,
|
||||
COUNT(*)::int as count
|
||||
FROM canvass_visits
|
||||
WHERE "visitedAt" >= ${dateFrom} AND "visitedAt" <= ${dateToEnd}
|
||||
GROUP BY period, outcome
|
||||
ORDER BY period ASC
|
||||
`;
|
||||
// Pivot rows into series: [{ date, NOT_HOME: n, SPOKE_WITH: n, ... }]
|
||||
const pivotMap = new Map();
|
||||
const totals = {};
|
||||
for (const row of rows) {
|
||||
const dateStr = row.period.toISOString().split('T')[0];
|
||||
if (!pivotMap.has(dateStr)) {
|
||||
pivotMap.set(dateStr, {});
|
||||
}
|
||||
pivotMap.get(dateStr)[row.outcome] = row.count;
|
||||
totals[row.outcome] = (totals[row.outcome] || 0) + row.count;
|
||||
}
|
||||
const series = Array.from(pivotMap.entries()).map(([date, outcomes]) => ({
|
||||
date,
|
||||
...outcomes,
|
||||
}));
|
||||
return {
|
||||
granularity,
|
||||
dateFrom: dateFrom.toISOString().split('T')[0],
|
||||
dateTo: dateTo.toISOString().split('T')[0],
|
||||
series,
|
||||
totals,
|
||||
};
|
||||
},
|
||||
// ─── Helpers ───────────────────────────────────────────────────────
|
||||
async recalculateCutCompletion(cutId) {
|
||||
const cut = await database_1.prisma.cut.findUnique({ where: { id: cutId } });
|
||||
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user