UNPKG

@syngrisi/syngrisi

Version:
4,692 lines 135 kB
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// src/server/utils/pick.ts
var pick, pick_default;
var init_pick = __esm({
  "src/server/utils/pick.ts"() {
    "use strict";
    pick = (object, keys) => {
      return keys.reduce((obj, key) => {
        if (object && Object.prototype.hasOwnProperty.call(object, key)) {
          if (object[key] !== void 0) obj[key] = object[key];
        }
        return obj;
      }, {});
    };
    pick_default = pick;
  }
});

// src/server/utils/isJSON.ts
var isJSON, isJSON_default;
var init_isJSON = __esm({
  "src/server/utils/isJSON.ts"() {
    "use strict";
    isJSON = (text) => {
      if (!text) return false;
      const isValid = /^[\],:{}\s]*$/.test(
        text.replace(/\\["\\\/bfnrtu]/g, "@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:\s*\[)+/g, "")
      );
      return isValid;
    };
    isJSON_default = isJSON;
  }
});

// src/server/utils/catchAsync.ts
var catchAsync, catchAsync_default;
var init_catchAsync = __esm({
  "src/server/utils/catchAsync.ts"() {
    "use strict";
    catchAsync = (fn) => (req, res, next) => {
      Promise.resolve(fn(req, res, next)).catch((err) => {
        return next(err);
      });
    };
    catchAsync_default = catchAsync;
  }
});

// src/server/utils/dateToISO8601.ts
var init_dateToISO8601 = __esm({
  "src/server/utils/dateToISO8601.ts"() {
    "use strict";
  }
});

// src/server/utils/ProgressBar.ts
var init_ProgressBar = __esm({
  "src/server/utils/ProgressBar.ts"() {
    "use strict";
  }
});

// src/server/utils/ApiError.ts
var ApiError, ApiError_default;
var init_ApiError = __esm({
  "src/server/utils/ApiError.ts"() {
    "use strict";
    ApiError = class extends Error {
      statusCode;
      isOperational;
      constructor(statusCode, message, isOperational = true, stack = "") {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = isOperational;
        if (stack) {
          this.stack = stack;
        } else {
          Error.captureStackTrace(this, this.constructor);
        }
      }
    };
    ApiError_default = ApiError;
  }
});

// src/server/utils/removeEmptyProperties.ts
var removeEmptyProperties;
var init_removeEmptyProperties = __esm({
  "src/server/utils/removeEmptyProperties.ts"() {
    "use strict";
    removeEmptyProperties = (obj) => Object.fromEntries(
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      Object.entries(obj).filter(([_, v]) => v != null && v !== "")
    );
  }
});

// src/server/utils/deserializeIfJSON.ts
import mongoose from "mongoose";
var EJSON, EXTENDED_JSON_KEYS, containsExtendedJsonMarkers, deserializeIfJSON, deserializeIfJSON_default;
var init_deserializeIfJSON = __esm({
  "src/server/utils/deserializeIfJSON.ts"() {
    "use strict";
    init_utils();
    ({ EJSON } = mongoose.mongo.BSON);
    EXTENDED_JSON_KEYS = [
      "$oid",
      "$date",
      "$numberInt",
      "$numberLong",
      "$numberDouble",
      "$numberDecimal",
      "$regularExpression",
      "$binary",
      "$timestamp"
    ];
    containsExtendedJsonMarkers = (text) => EXTENDED_JSON_KEYS.some((marker) => text.includes(`"${marker}"`));
    deserializeIfJSON = (text) => {
      if (isJSON_default(text)) {
        if (containsExtendedJsonMarkers(text)) {
          return EJSON.parse(text) || void 0;
        }
        return JSON.parse(text) || void 0;
      }
      return text;
    };
    deserializeIfJSON_default = deserializeIfJSON;
  }
});

// src/server/utils/prettyCheckParams.ts
var init_prettyCheckParams = __esm({
  "src/server/utils/prettyCheckParams.ts"() {
    "use strict";
  }
});

// src/server/utils/waitUntil.ts
var init_waitUntil = __esm({
  "src/server/utils/waitUntil.ts"() {
    "use strict";
  }
});

// src/server/utils/imageUtils.ts
var init_imageUtils = __esm({
  "src/server/utils/imageUtils.ts"() {
    "use strict";
  }
});

// src/server/utils/paramsGuard.ts
var init_paramsGuard = __esm({
  "src/server/utils/paramsGuard.ts"() {
    "use strict";
  }
});

// src/server/utils/ident.ts
var init_ident = __esm({
  "src/server/utils/ident.ts"() {
    "use strict";
  }
});

// src/server/utils/buildIdentObject.ts
var init_buildIdentObject = __esm({
  "src/server/utils/buildIdentObject.ts"() {
    "use strict";
    init_ident();
  }
});

// src/server/models/plugins/paginate.plugin.ts
var paginate, paginate_plugin_default;
var init_paginate_plugin = __esm({
  "src/server/models/plugins/paginate.plugin.ts"() {
    "use strict";
    paginate = (schema) => {
      schema.statics.paginate = async function(filter, options) {
        let sort;
        if (options.sortBy) {
          const sortingCriteria = [];
          let primaryOrder = "desc";
          options.sortBy.split(",").forEach((sortOption, index) => {
            const [key, order] = sortOption.split(":");
            if (index === 0) primaryOrder = order || "asc";
            sortingCriteria.push((order === "desc" ? "-" : "") + key);
          });
          if (!sortingCriteria.some((s) => s === "_id" || s === "-_id")) {
            sortingCriteria.push((primaryOrder === "desc" ? "-" : "") + "_id");
          }
          sort = sortingCriteria.join(" ");
        } else {
          sort = { _id: -1 };
        }
        const limit = options.limit && parseInt(options.limit.toString(), 10) >= 0 ? parseInt(options.limit.toString(), 10) : 10;
        const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1;
        const skip = (page - 1) * limit;
        const countStrategy = options.countStrategy ?? (filter && Object.keys(filter).length === 0 ? "estimated" : "exact");
        const countPromise = countStrategy === "estimated" ? this.estimatedDocumentCount().exec() : this.countDocuments(filter).exec();
        let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit);
        if (options.populate) {
          options.populate.split(",").forEach((populateOption) => {
            docsPromise = docsPromise.populate(
              populateOption.split(".").reverse().reduce((a, b) => ({ path: b, populate: a }))
            );
          });
        }
        docsPromise = docsPromise.exec();
        return Promise.all([countPromise, docsPromise]).then((values) => {
          const [totalResults, results] = values;
          const totalPages = Math.ceil(totalResults / limit);
          const result = {
            results,
            page,
            limit,
            totalPages,
            totalResults,
            timestamp: Number(Date.now() + String(process.hrtime()[1]).slice(3, 6))
          };
          return Promise.resolve(result);
        });
      };
    };
    paginate_plugin_default = paginate;
  }
});

// src/server/models/plugins/toJSON.plugin.ts
var deleteAtPath, toJSON, toJSON_plugin_default;
var init_toJSON_plugin = __esm({
  "src/server/models/plugins/toJSON.plugin.ts"() {
    "use strict";
    deleteAtPath = (obj, path4, index) => {
      if (index === path4.length - 1) {
        delete obj[path4[index]];
        return;
      }
      deleteAtPath(obj[path4[index]], path4, index + 1);
    };
    toJSON = (schema) => {
      let transform;
      if (schema.options.toJSON && schema.options.toJSON.transform) {
        transform = schema.options.toJSON.transform;
      }
      schema.options.toJSON = Object.assign(schema.options.toJSON || {}, {
        transform(doc, ret, options) {
          Object.keys(schema.paths).forEach((path4) => {
            if (schema.paths[path4].options && schema.paths[path4].options.private) {
              deleteAtPath(ret, path4.split("."), 0);
            }
          });
          ret.id = ret._id.toString();
          delete ret.__v;
          delete ret.createdAt;
          delete ret.updatedAt;
          if (transform) {
            return transform(doc, ret, options);
          }
        }
      });
    };
    toJSON_plugin_default = toJSON;
  }
});

// src/server/models/plugins/paginateDistinct.plugin.ts
var init_paginateDistinct_plugin = __esm({
  "src/server/models/plugins/paginateDistinct.plugin.ts"() {
    "use strict";
    init_utils();
  }
});

// src/server/models/plugins/index.ts
var init_plugins = __esm({
  "src/server/models/plugins/index.ts"() {
    "use strict";
    init_paginate_plugin();
    init_toJSON_plugin();
    init_paginateDistinct_plugin();
  }
});

// src/server/models/Check.model.ts
import mongoose2, { Schema } from "mongoose";
var CheckSchema, Check, Check_model_default;
var init_Check_model = __esm({
  "src/server/models/Check.model.ts"() {
    "use strict";
    init_plugins();
    CheckSchema = new Schema({
      name: {
        type: String,
        required: [true, 'CheckSchema: The "name" field must be required']
      },
      test: {
        type: Schema.Types.ObjectId,
        ref: "VRSTest",
        required: [true, 'CheckSchema: The "test" field must be required']
      },
      suite: {
        type: Schema.Types.ObjectId,
        ref: "VRSSuite",
        required: [true, 'CheckSchema: The "suite" field must be required']
      },
      app: {
        type: Schema.Types.ObjectId,
        ref: "VRSApp",
        required: [true, 'CheckSchema: The "app" field must be required']
      },
      branch: {
        type: String
      },
      realBaselineId: {
        type: Schema.Types.ObjectId,
        ref: "VRSBaseline"
      },
      baselineId: {
        type: Schema.Types.ObjectId,
        ref: "VRSSnapshot"
      },
      actualSnapshotId: {
        type: Schema.Types.ObjectId,
        ref: "VRSSnapshot"
      },
      diffId: {
        type: Schema.Types.ObjectId,
        ref: "VRSSnapshot"
      },
      createdDate: {
        type: Date,
        required: true,
        default: Date.now
      },
      updatedDate: {
        type: Date
      },
      status: {
        type: [{
          type: String,
          enum: {
            values: ["new", "pending", "approved", "running", "passed", "failed", "aborted"],
            message: "status is required"
          }
        }],
        default: ["new"]
      },
      browserName: {
        type: String
      },
      browserVersion: {
        type: String
      },
      browserFullVersion: {
        type: String
      },
      viewport: {
        type: String
      },
      os: {
        type: String
      },
      domDump: {
        type: String
      },
      result: {
        type: String,
        default: "{}"
      },
      run: {
        type: Schema.Types.ObjectId
      },
      markedAs: {
        type: String,
        enum: ["bug", "accepted"]
      },
      markedDate: {
        type: Date
      },
      markedById: {
        type: Schema.Types.ObjectId,
        ref: "VRSUser"
      },
      markedByUsername: {
        type: String
      },
      markedBugComment: {
        type: String
      },
      creatorId: {
        type: Schema.Types.ObjectId,
        ref: "VRSUser"
      },
      creatorUsername: {
        type: String
      },
      failReasons: {
        type: [String]
      },
      vOffset: {
        type: String
      },
      topStablePixels: {
        type: String
      },
      toleranceThreshold: {
        type: Number,
        min: 0,
        max: 100
      },
      meta: {
        type: Object
      }
    });
    CheckSchema.plugin(toJSON_plugin_default);
    CheckSchema.plugin(paginate_plugin_default);
    Check = mongoose2.model("VRSCheck", CheckSchema);
    Check_model_default = Check;
  }
});

// src/server/models/Log.model.ts
import mongoose3, { Schema as Schema2 } from "mongoose";
var LogSchema, Log;
var init_Log_model = __esm({
  "src/server/models/Log.model.ts"() {
    "use strict";
    init_plugins();
    LogSchema = new Schema2({
      timestamp: {
        type: Date
      },
      level: {
        type: String
      },
      message: {
        type: String
      },
      meta: {
        type: Object
      },
      hostname: {
        type: Object
      }
    });
    LogSchema.plugin(toJSON_plugin_default);
    LogSchema.plugin(paginate_plugin_default);
    Log = mongoose3.model("VRSLog", LogSchema);
  }
});

// src/server/models/App.model.ts
import mongoose4, { Schema as Schema3 } from "mongoose";
var AppSchema, App;
var init_App_model = __esm({
  "src/server/models/App.model.ts"() {
    "use strict";
    init_plugins();
    AppSchema = new Schema3({
      name: {
        type: String,
        default: "Others",
        unique: true,
        required: [true, 'AppSchema: The "name" field must be required']
      },
      description: {
        type: String
      },
      version: {
        type: String
      },
      updatedDate: {
        type: Date
      },
      createdDate: {
        type: Date
      },
      meta: {
        type: Object
      }
    });
    AppSchema.plugin(paginate_plugin_default);
    AppSchema.plugin(toJSON_plugin_default);
    App = mongoose4.model("VRSApp", AppSchema);
  }
});

// src/server/models/Snapshot.model.ts
import mongoose5, { Schema as Schema4 } from "mongoose";
var SnapshotSchema, Snapshot;
var init_Snapshot_model = __esm({
  "src/server/models/Snapshot.model.ts"() {
    "use strict";
    init_plugins();
    SnapshotSchema = new Schema4({
      name: {
        type: String,
        required: [true, 'SnapshotSchema: The "name" field must be required']
      },
      path: {
        type: String
      },
      filename: {
        type: String
      },
      imghash: {
        type: String,
        required: [true, 'SnapshotSchema: The "imghash" field must be required']
      },
      createdDate: {
        type: Date,
        default: Date.now
      },
      vOffset: {
        type: Number
      },
      hOffset: {
        type: Number
      }
    });
    SnapshotSchema.plugin(toJSON_plugin_default);
    SnapshotSchema.plugin(paginate_plugin_default);
    Snapshot = mongoose5.model("VRSSnapshot", SnapshotSchema);
  }
});

// src/server/models/AppSettings.model.ts
import mongoose6, { Schema as Schema5 } from "mongoose";
var AppSettingsSchema, AppSettings, AppSettings_model_default;
var init_AppSettings_model = __esm({
  "src/server/models/AppSettings.model.ts"() {
    "use strict";
    init_plugins();
    AppSettingsSchema = new Schema5({
      name: {
        type: String,
        unique: true,
        required: [true, 'AppSettingsSchema: The "name" field must be required']
      },
      label: {
        type: String,
        required: [true, 'AppSettingsSchema: The "label" field must be required']
      },
      description: {
        type: String
      },
      type: {
        type: String,
        required: [true, 'AppSettingsSchema: The "type" field must be required']
      },
      value: {
        type: Schema5.Types.Mixed,
        required: [true, 'AppSettingsSchema: The "value" field must be required']
      },
      env_variable: {
        type: String
      },
      enabled: {
        type: Boolean
      }
    });
    AppSettingsSchema.plugin(toJSON_plugin_default);
    AppSettings = mongoose6.model("VRSAppSettings", AppSettingsSchema);
    AppSettings_model_default = AppSettings;
  }
});

// src/server/models/Suite.model.ts
import mongoose7, { Schema as Schema6 } from "mongoose";
var SuiteSchema, Suite;
var init_Suite_model = __esm({
  "src/server/models/Suite.model.ts"() {
    "use strict";
    init_plugins();
    SuiteSchema = new Schema6({
      name: {
        type: String,
        default: "Others",
        unique: true,
        required: [true, 'SuiteSchema: The "name" field must be required']
      },
      tags: {
        type: [String]
      },
      app: {
        type: Schema6.Types.ObjectId,
        ref: "VRSApp",
        required: [true, 'SuiteSchema: The "app" field must be required']
      },
      description: {
        type: String
      },
      updatedDate: {
        type: Date,
        default: Date.now
      },
      createdDate: {
        type: Date
      },
      meta: {
        type: Object
      }
    });
    SuiteSchema.plugin(paginate_plugin_default);
    SuiteSchema.plugin(toJSON_plugin_default);
    Suite = mongoose7.model("VRSSuite", SuiteSchema);
  }
});

// src/server/models/Run.model.ts
import mongoose8, { Schema as Schema7 } from "mongoose";
var RunSchema, Run;
var init_Run_model = __esm({
  "src/server/models/Run.model.ts"() {
    "use strict";
    init_plugins();
    RunSchema = new Schema7({
      name: {
        type: String,
        required: [true, 'RunSchema: The "name" field must be required']
      },
      app: {
        type: Schema7.Types.ObjectId,
        ref: "VRSApp",
        required: [true, 'RunSchema: The "app" field must be required']
      },
      ident: {
        type: String,
        unique: true,
        required: [true, 'RunSchema: The "ident" field must be required']
      },
      description: {
        type: String
      },
      updatedDate: {
        type: Date,
        default: Date.now
      },
      createdDate: {
        type: Date
      },
      parameters: {
        type: [String]
      },
      meta: {
        type: Object
      }
    });
    RunSchema.plugin(paginate_plugin_default);
    RunSchema.plugin(toJSON_plugin_default);
    Run = mongoose8.model("VRSRun", RunSchema);
  }
});

// src/server/models/User.model.ts
import mongoose9, { Schema as Schema8 } from "mongoose";
import plm from "passport-local-mongoose";
var passportLocalMongoose, UserSchema, User, User_model_default;
var init_User_model = __esm({
  "src/server/models/User.model.ts"() {
    "use strict";
    init_plugins();
    passportLocalMongoose = plm.default || plm;
    UserSchema = new Schema8({
      username: {
        type: String,
        unique: true,
        required: [true, 'UserSchema: The "username" field must be required']
      },
      firstName: {
        type: String,
        required: [true, 'UserSchema: The "firstName" field must be required']
      },
      lastName: {
        type: String,
        required: [true, 'UserSchema: The "lastName" field must be required']
      },
      role: {
        type: String,
        enum: ["admin", "reviewer", "user"],
        required: [true, 'UserSchema: The "role" field must be required']
      },
      provider: {
        type: String,
        default: "local"
      },
      providerId: {
        type: String
      },
      password: {
        type: String
      },
      token: {
        type: String
      },
      apiKey: {
        type: String
      },
      authSource: {
        type: String,
        enum: ["local", "jwt", "ldap", "api"],
        default: "local"
      },
      createdDate: {
        type: Date
      },
      updatedDate: {
        type: Date
      },
      expiration: {
        type: Date
      },
      meta: {
        type: Object
      }
    });
    UserSchema.statics.isEmailTaken = async function(username, excludeUserId) {
      const user = await this.findOne({ username, _id: { $ne: excludeUserId } });
      return !!user;
    };
    UserSchema.plugin(toJSON_plugin_default);
    UserSchema.plugin(paginate_plugin_default);
    UserSchema.plugin(passportLocalMongoose, { hashField: "password" });
    User = mongoose9.model("VRSUser", UserSchema);
    User_model_default = User;
  }
});

// src/server/models/Baseline.model.ts
import mongoose10, { Schema as Schema9 } from "mongoose";
var BaselineSchema, Baseline, Baseline_model_default;
var init_Baseline_model = __esm({
  "src/server/models/Baseline.model.ts"() {
    "use strict";
    init_plugins();
    BaselineSchema = new Schema9({
      snapshootId: {
        type: Schema9.Types.ObjectId,
        ref: "VRSSnapshot"
      },
      name: {
        type: String,
        required: [true, 'VRSBaselineSchema: The "name" field must be required']
      },
      app: {
        type: Schema9.Types.ObjectId,
        ref: "VRSApp",
        required: [true, 'VRSBaselineSchema: The "app" field must be required']
      },
      branch: {
        type: String
      },
      browserName: {
        type: String
      },
      browserVersion: {
        type: String
      },
      browserFullVersion: {
        type: String
      },
      viewport: {
        type: String
      },
      os: {
        type: String
      },
      markedAs: {
        type: String,
        enum: ["bug", "accepted"]
      },
      lastMarkedDate: {
        type: Date
      },
      createdDate: {
        type: Date
      },
      updatedDate: {
        type: Date
      },
      markedById: {
        type: Schema9.Types.ObjectId,
        ref: "VRSUser"
      },
      markedByUsername: {
        type: String
      },
      ignoreRegions: {
        type: String
      },
      boundRegions: {
        type: String
      },
      matchType: {
        type: String,
        enum: ["antialiasing", "nothing", "colors"]
      },
      toleranceThreshold: {
        type: Number,
        default: 0,
        min: 0,
        max: 100
      },
      meta: {
        type: Object
      }
    });
    BaselineSchema.set("autoIndex", false);
    BaselineSchema.plugin(toJSON_plugin_default);
    BaselineSchema.plugin(paginate_plugin_default);
    BaselineSchema.index({
      name: 1,
      app: 1,
      branch: 1,
      browserName: 1,
      viewport: 1,
      os: 1,
      snapshootId: 1
    }, { unique: true, name: "baseline_ident_snapshot_idx" });
    Baseline = mongoose10.model("VRSBaseline", BaselineSchema);
    Baseline_model_default = Baseline;
  }
});

// src/server/models/Test.model.ts
import mongoose11, { Schema as Schema10 } from "mongoose";
var TestSchema, Test;
var init_Test_model = __esm({
  "src/server/models/Test.model.ts"() {
    "use strict";
    init_plugins();
    init_utils();
    TestSchema = new Schema10(
      {
        name: {
          type: String,
          required: "TestSchema: the test name is empty"
        },
        description: {
          type: String
        },
        status: {
          type: String
        },
        browserName: {
          type: String
        },
        browserVersion: {
          type: String
        },
        branch: {
          type: String
        },
        tags: {
          type: [String]
        },
        viewport: {
          type: String
        },
        calculatedViewport: {
          type: String
        },
        os: {
          type: String
        },
        app: {
          type: Schema10.Types.ObjectId,
          ref: "VRSApp",
          required: [true, 'TestSchema: The "app" field must be required']
        },
        blinking: {
          type: Number,
          default: 0
        },
        updatedDate: {
          type: Date
        },
        startDate: {
          type: Date
        },
        checks: [
          {
            type: mongoose11.Schema.Types.ObjectId,
            ref: "VRSCheck"
          }
        ],
        suite: {
          type: Schema10.Types.ObjectId,
          ref: "VRSSuite"
        },
        run: {
          type: Schema10.Types.ObjectId,
          ref: "VRSRun"
        },
        markedAs: {
          type: String,
          enum: ["Bug", "Accepted", "Unaccepted", "Partially"]
        },
        creatorId: {
          type: Schema10.Types.ObjectId,
          ref: "VRSUser"
        },
        creatorUsername: {
          type: String
        },
        meta: {
          type: Object
        }
      },
      { strictQuery: true }
    );
    TestSchema.plugin(toJSON_plugin_default);
    TestSchema.plugin(paginate_plugin_default);
    TestSchema.statics.paginateDistinct = async function(filter, options) {
      let sort = { _id: -1 };
      if (options.sortBy) {
        sort = {};
        options.sortBy.split(",").forEach((sortOption) => {
          const [key, order] = sortOption.split(":");
          sort[key] = order === "desc" ? -1 : 1;
        });
      }
      let limit = options.limit && parseInt(options.limit.toString(), 10) >= 0 ? parseInt(options.limit.toString(), 10) : 10;
      limit = limit === 0 ? Number.MAX_SAFE_INTEGER : limit;
      const page = options.page && parseInt(options.page.toString(), 10) > 0 ? parseInt(options.page.toString(), 10) : 1;
      const skip = (page - 1) * limit;
      const groupAggregateObj = { $group: { _id: `$${options.field}` } };
      const parsedFilter = typeof filter?.filter === "string" ? deserializeIfJSON_default(filter.filter) || {} : {};
      const documentsCount = (await this.aggregate([{ $match: parsedFilter }, groupAggregateObj]).exec()).length;
      const aggregatedDocs = (await this.aggregate([
        { $match: parsedFilter },
        groupAggregateObj,
        { $sort: sort },
        { $skip: skip },
        { $limit: limit }
      ])).filter((x) => x._id).map((x) => {
        const fieldValue = options.field ? x[options.field] : void 0;
        if (Array.isArray(fieldValue) && fieldValue.length > 0) {
          return fieldValue[0];
        }
        return { name: x._id };
      });
      const totalPages = Math.ceil(documentsCount / limit);
      return {
        results: aggregatedDocs,
        page,
        limit,
        totalPages,
        totalResults: documentsCount,
        timestamp: Date.now()
      };
    };
    Test = mongoose11.model("VRSTest", TestSchema);
  }
});

// src/server/models/Webhook.model.ts
import mongoose12, { Schema as Schema11 } from "mongoose";
var WebhookSchema, Webhook;
var init_Webhook_model = __esm({
  "src/server/models/Webhook.model.ts"() {
    "use strict";
    init_plugins();
    WebhookSchema = new Schema11({
      url: {
        type: String,
        required: [true, 'WebhookSchema: The "url" field must be required']
      },
      events: {
        type: [String],
        default: ["check.updated", "check.created"]
      },
      secret: {
        type: String
      },
      createdDate: {
        type: Date,
        default: Date.now
      },
      meta: {
        type: Object
      }
    });
    WebhookSchema.plugin(paginate_plugin_default);
    WebhookSchema.plugin(toJSON_plugin_default);
    Webhook = mongoose12.model("VRSWebhook", WebhookSchema);
  }
});

// src/server/models/ShareToken.model.ts
import mongoose13, { Schema as Schema12 } from "mongoose";
var ShareTokenSchema, ShareToken;
var init_ShareToken_model = __esm({
  "src/server/models/ShareToken.model.ts"() {
    "use strict";
    init_plugins();
    ShareTokenSchema = new Schema12({
      checkId: {
        type: Schema12.Types.ObjectId,
        ref: "VRSCheck",
        required: [true, 'ShareTokenSchema: The "checkId" field is required'],
        index: true
      },
      token: {
        type: String,
        required: [true, 'ShareTokenSchema: The "token" field is required'],
        unique: true,
        index: true
      },
      createdById: {
        type: Schema12.Types.ObjectId,
        ref: "VRSUser",
        required: [true, 'ShareTokenSchema: The "createdById" field is required']
      },
      createdByUsername: {
        type: String,
        required: [true, 'ShareTokenSchema: The "createdByUsername" field is required']
      },
      createdDate: {
        type: Date,
        required: true,
        default: Date.now
      },
      isRevoked: {
        type: Boolean,
        default: false
      },
      revokedDate: {
        type: Date
      },
      revokedById: {
        type: Schema12.Types.ObjectId,
        ref: "VRSUser"
      },
      revokedByUsername: {
        type: String
      }
    });
    ShareTokenSchema.plugin(toJSON_plugin_default);
    ShareTokenSchema.plugin(paginate_plugin_default);
    ShareToken = mongoose13.model("VRSShareToken", ShareTokenSchema);
  }
});

// src/server/models/DomSnapshot.model.ts
import mongoose14, { Schema as Schema13 } from "mongoose";
var DomSnapshotSchema, DomSnapshot;
var init_DomSnapshot_model = __esm({
  "src/server/models/DomSnapshot.model.ts"() {
    "use strict";
    init_plugins();
    DomSnapshotSchema = new Schema13({
      checkId: {
        type: Schema13.Types.ObjectId,
        ref: "VRSCheck",
        required: [true, 'DomSnapshotSchema: The "checkId" field is required'],
        index: true
      },
      baselineId: {
        type: Schema13.Types.ObjectId,
        ref: "VRSBaseline",
        index: true
      },
      type: {
        type: String,
        enum: ["actual", "baseline"],
        required: [true, 'DomSnapshotSchema: The "type" field is required']
      },
      filename: {
        type: String,
        required: [true, 'DomSnapshotSchema: The "filename" field is required']
      },
      hash: {
        type: String,
        required: [true, 'DomSnapshotSchema: The "hash" field is required'],
        index: true
      },
      compressed: {
        type: Boolean,
        default: false
      },
      originalSize: {
        type: Number,
        required: [true, 'DomSnapshotSchema: The "originalSize" field is required']
      },
      compressedSize: {
        type: Number
      },
      createdDate: {
        type: Date,
        default: Date.now
      }
    });
    DomSnapshotSchema.index({ checkId: 1, type: 1 });
    DomSnapshotSchema.index({ baselineId: 1, type: 1 });
    DomSnapshotSchema.plugin(toJSON_plugin_default);
    DomSnapshotSchema.plugin(paginate_plugin_default);
    DomSnapshot = mongoose14.model("VRSDomSnapshot", DomSnapshotSchema);
  }
});

// src/server/models/PluginSettings.model.ts
import mongoose15, { Schema as Schema14 } from "mongoose";
var PluginSettingSchemaDefinition, PluginSettingsSchema, PluginSettings;
var init_PluginSettings_model = __esm({
  "src/server/models/PluginSettings.model.ts"() {
    "use strict";
    init_plugins();
    PluginSettingSchemaDefinition = new Schema14({
      key: { type: String, required: true },
      label: { type: String, required: true },
      description: { type: String },
      type: {
        type: String,
        enum: ["string", "number", "boolean", "select", "password"],
        required: true
      },
      defaultValue: { type: Schema14.Types.Mixed },
      envVariable: { type: String },
      options: [{ value: String, label: String }],
      required: { type: Boolean, default: false }
    }, { _id: false });
    PluginSettingsSchema = new Schema14({
      pluginName: {
        type: String,
        unique: true,
        required: [true, "PluginSettings: pluginName is required"],
        index: true
      },
      displayName: {
        type: String,
        required: [true, "PluginSettings: displayName is required"]
      },
      description: {
        type: String
      },
      enabled: {
        type: Boolean,
        default: false
      },
      settings: {
        type: Schema14.Types.Mixed,
        default: {}
      },
      settingsSchema: {
        type: [PluginSettingSchemaDefinition],
        default: []
      }
    }, {
      timestamps: true
    });
    PluginSettingsSchema.plugin(toJSON_plugin_default);
    PluginSettingsSchema.statics.getEffectiveConfig = async function(pluginName, envPrefix = "SYNGRISI_PLUGIN_") {
      const doc = await this.findOne({ pluginName });
      const envPluginKey = pluginName.toUpperCase().replace(/-/g, "_");
      const envEnabledKey = `${envPrefix}${envPluginKey}_ENABLED`;
      const envEnabled = process.env[envEnabledKey]?.toLowerCase() === "true";
      const dbEnabled = doc?.enabled;
      const enabled = doc ? dbEnabled : envEnabled;
      const config2 = {};
      const schema = doc?.settingsSchema || [];
      if (schema.length === 0 && doc?.settings && Object.keys(doc.settings).length > 0) {
        for (const [key, value] of Object.entries(doc.settings)) {
          config2[key] = { value, source: "db" };
        }
        return { config: config2, enabled };
      }
      for (const field of schema) {
        const envKey = field.envVariable || `${envPrefix}${envPluginKey}_${field.key.toUpperCase()}`;
        const envValue = process.env[envKey];
        const dbValue = doc?.settings?.[field.key];
        if (dbValue !== void 0) {
          config2[field.key] = { value: dbValue, source: "db" };
        } else if (envValue !== void 0) {
          let parsedValue = envValue;
          if (field.type === "boolean") {
            parsedValue = envValue.toLowerCase() === "true";
          } else if (field.type === "number") {
            parsedValue = parseFloat(envValue);
          }
          config2[field.key] = { value: parsedValue, source: "env" };
        } else if (field.defaultValue !== void 0) {
          config2[field.key] = { value: field.defaultValue, source: "default" };
        }
      }
      return { config: config2, enabled };
    };
    PluginSettingsSchema.statics.upsertSettings = async function(pluginName, updates) {
      return this.findOneAndUpdate(
        { pluginName },
        { $set: updates },
        { upsert: true, new: true, runValidators: true }
      );
    };
    PluginSettings = mongoose15.model(
      "VRSPluginSettings",
      PluginSettingsSchema
    );
  }
});

// src/server/models/index.ts
var init_models = __esm({
  "src/server/models/index.ts"() {
    "use strict";
    init_Check_model();
    init_Log_model();
    init_App_model();
    init_Snapshot_model();
    init_AppSettings_model();
    init_Suite_model();
    init_Run_model();
    init_User_model();
    init_Baseline_model();
    init_Test_model();
    init_Webhook_model();
    init_ShareToken_model();
    init_DomSnapshot_model();
    init_PluginSettings_model();
  }
});

// src/server/utils/calculateAcceptedStatus.ts
var init_calculateAcceptedStatus = __esm({
  "src/server/utils/calculateAcceptedStatus.ts"() {
    "use strict";
    init_models();
  }
});

// src/server/utils/subDays.ts
var init_subDays = __esm({
  "src/server/utils/subDays.ts"() {
    "use strict";
  }
});

// src/server/utils/errMsg.ts
var errMsg;
var init_errMsg = __esm({
  "src/server/utils/errMsg.ts"() {
    "use strict";
    errMsg = (e) => {
      return String(e instanceof Error ? e.stack : e);
    };
  }
});

// src/server/utils/hash.ts
import { createHash, randomUUID } from "crypto";
var init_hash = __esm({
  "src/server/utils/hash.ts"() {
    "use strict";
  }
});

// src/server/utils/stringTable.ts
var init_stringTable = __esm({
  "src/server/utils/stringTable.ts"() {
    "use strict";
  }
});

// src/server/utils/colors.ts
var RESET, colors;
var init_colors = __esm({
  "src/server/utils/colors.ts"() {
    "use strict";
    RESET = "\x1B[0m";
    colors = {
      // Standard colors
      blue: (s) => `\x1B[34m${s}${RESET}`,
      green: (s) => `\x1B[32m${s}${RESET}`,
      red: (s) => `\x1B[31m${s}${RESET}`,
      yellow: (s) => `\x1B[33m${s}${RESET}`,
      magenta: (s) => `\x1B[35m${s}${RESET}`,
      cyan: (s) => `\x1B[36m${s}${RESET}`,
      // Bright colors
      gray: (s) => `\x1B[90m${s}${RESET}`,
      whiteBright: (s) => `\x1B[97m${s}${RESET}`,
      // Reset code for manual use
      reset: RESET
    };
  }
});

// src/server/utils/httpStatus.ts
var HttpStatus, httpStatus_default;
var init_httpStatus = __esm({
  "src/server/utils/httpStatus.ts"() {
    "use strict";
    HttpStatus = {
      // 2xx Success
      OK: 200,
      CREATED: 201,
      NO_CONTENT: 204,
      // 4xx Client Errors
      BAD_REQUEST: 400,
      UNAUTHORIZED: 401,
      FORBIDDEN: 403,
      NOT_FOUND: 404,
      // 5xx Server Errors
      INTERNAL_SERVER_ERROR: 500
    };
    httpStatus_default = HttpStatus;
  }
});

// src/server/utils/cookieParser.ts
var init_cookieParser = __esm({
  "src/server/utils/cookieParser.ts"() {
    "use strict";
  }
});

// src/server/utils/index.ts
var init_utils = __esm({
  "src/server/utils/index.ts"() {
    "use strict";
    init_pick();
    init_isJSON();
    init_catchAsync();
    init_dateToISO8601();
    init_ProgressBar();
    init_ApiError();
    init_removeEmptyProperties();
    init_deserializeIfJSON();
    init_prettyCheckParams();
    init_waitUntil();
    init_imageUtils();
    init_paramsGuard();
    init_buildIdentObject();
    init_calculateAcceptedStatus();
    init_ident();
    init_subDays();
    init_errMsg();
    init_hash();
    init_stringTable();
    init_colors();
    init_httpStatus();
    init_cookieParser();
  }
});

// src/server/utils/formatISOToDateTime.ts
function formatISOToDateTime(isoDateString) {
  const date = new Date(isoDateString);
  return `${date.toISOString().slice(0, 10)} ${date.toTimeString().slice(0, 8)}`;
}
var formatISOToDateTime_default;
var init_formatISOToDateTime = __esm({
  "src/server/utils/formatISOToDateTime.ts"() {
    "use strict";
    formatISOToDateTime_default = formatISOToDateTime;
  }
});

// package.json
var version, gitHead;
var init_package = __esm({
  "package.json"() {
    version = "3.5.0";
    gitHead = "12bfda406cbe5aaccf3f17fdab02a9bd1a9d6343";
  }
});

// src/server/envConfig.ts
import { cleanEnv, host, num, port, str, bool } from "envalid";
import crypto from "crypto";
import path from "path";
import dotenv from "dotenv";
var env;
var init_envConfig = __esm({
  "src/server/envConfig.ts"() {
    "use strict";
    dotenv.config({ quiet: true });
    if (!process.env.NODE_ENV) {
      process.env.NODE_ENV = "production";
    }
    env = cleanEnv(process.env, {
      NODE_ENV: str({ choices: ["development", "production", "test"] }),
      SYNGRISI_DB_URI: str({ default: "mongodb://127.0.0.1:27017/SyngrisiDb" }),
      SYNGRISI_APP_PORT: port({ default: 3e3 }),
      SYNGRISI_IMAGES_PATH: str({ default: path.join(process.cwd(), "./.snapshots-images") }),
      SYNGRISI_DOM_SNAPSHOTS_PATH: str({ default: "" }),
      // If empty, uses SYNGRISI_IMAGES_PATH
      SYNGRISI_TMP_DIR: str({ default: path.join(process.cwd(), ".tmp") }),
      SYNGRISI_ADMIN_DATA_JOBS_PATH: str({ default: path.join(process.cwd(), ".tmp", "admin-data-jobs") }),
      SYNGRISI_ADMIN_DATA_JOBS_TTL_MS: num({ default: 24 * 60 * 60 * 1e3 }),
      SYNGRISI_ADMIN_DATA_MAX_CONCURRENT_JOBS: num({ default: 1 }),
      SYNGRISI_ADMIN_DATA_UPLOAD_MAX_SIZE_MB: num({ default: 10240 }),
      SYNGRISI_HTTP_LOG: bool({ default: false }),
      SYNGRISI_COVERAGE: bool({ default: false }),
      SYNGRISI_HOSTNAME: host({ default: "localhost" }),
      SYNGRISI_AUTH: bool({ default: true }),
      SYNGRISI_TEST_MODE: bool({ default: false }),
      SYNGRISI_DISABLE_FIRST_RUN: bool({ default: false }),
      MONGODB_ROOT_USERNAME: str({ default: "" }),
      MONGODB_ROOT_PASSWORD: str({ default: "" }),
      LOGLEVEL: str({ choices: ["error", "warn", "info", "verbose", "debug", "silly"], default: "debug" }),
      // Legacy tests expect 20 rows per page; keep default aligned for e2e
      SYNGRISI_PAGINATION_SIZE: num({ default: 20 }),
      SYNGRISI_DISABLE_DEV_CORS: bool({ default: true, devDefault: true }),
      SYNGRISI_SESSION_STORE_KEY: str({ default: crypto.randomBytes(64).toString("hex") }),
      SYNGRISI_LOG_LEVEL: str({ default: "debug" }),
      SYNGRISI_DISABLE_LOGS: bool({ default: false }),
      SYNGRISI_AUTO_REMOVE_CHECKS_POLL_INTERVAL_MS: num({ default: 10 * 60 * 1e3 }),
      // 10 minutes
      SYNGRISI_AUTO_REMOVE_CHECKS_MIN_INTERVAL_MS: num({ default: 24 * 60 * 60 * 1e3 }),
      SYNGRISI_ENABLE_SCHEDULERS_IN_TEST_MODE: bool({ default: false }),
      // RCA
      SYNGRISI_RCA: bool({ default: false }),
      // trunk features
      SYNGRISI_TRUNK_FEATURE_AI_SEVERITY: bool({ default: false }),
      SYNGRISI_AI_KEY: str({ default: "" }),
      OPENAI_API_BASE_URL: str({ default: "https://api.openai.com/v1" }),
      OPENAI_API_KEY: str({ default: "" }),
      SYNGRISI_V8_COVERAGE_ON_EXIT: bool({ default: false }),
      // Rate Limiting
      SYNGRISI_RATE_LIMIT_WINDOW_MS: num({ default: 15 * 60 * 1e3 }),
      // 15 minutes
      SYNGRISI_RATE_LIMIT_MAX: num({ default: 5e4 }),
      SYNGRISI_AUTH_RATE_LIMIT_WINDOW_MS: num({ default: 15 * 60 * 1e3 }),
      // 15 minutes
      SYNGRISI_AUTH_RATE_LIMIT_MAX: num({ default: 200 }),
      // Mongo tuneables for tests/CI flake reduction
      SYNGRISI_MONGO_SOCKET_TIMEOUT_MS: num({ default: 6e4 }),
      SYNGRISI_MONGO_MAX_POOL_SIZE: num({ default: 20 }),
      SYNGRISI_MONGO_MIN_POOL_SIZE: num({ default: 2 }),
      SYNGRISI_MONGO_MAX_IDLE_TIME_MS: num({ default: 3e4 }),
      SYNGRISI_MONGO_WAIT_QUEUE_TIMEOUT_MS: num({ default: 3e4 }),
      SYNGRISI_MONGO_SERVER_SELECTION_TIMEOUT_MS: num({ default: 1e4 }),
      SYNGRISI_MONGO_CONNECT_TIMEOUT_MS: num({ default: 3e4 }),
      // SSO Configuration
      SSO_ENABLED: bool({ default: false }),
      SSO_PROTOCOL: str({ choices: ["", "oauth2", "saml"], default: "" }),
      SSO_CLIENT_ID: str({ default: "" }),
      SSO_CLIENT_SECRET: str({ default: "" }),
      SSO_AUTHORIZATION_URL: str({ default: "" }),
      SSO_TOKEN_URL: str({ default: "" }),
      SSO_USERINFO_URL: str({ default: "" }),
      SSO_CALLBACK_URL: str({ default: "/v1/auth/sso/oauth/callback" }),
      // SAML specific
      SSO_ENTRY_POINT: str({ default: "" }),
      SSO_ISSUER: str({ default: "" }),
      SSO_CERT: str({ default: "" }),
      SSO_IDP_ISSUER: str({ default: "" }),
      SSO_IDP_METADATA_URL: str({ default: "" }),
      // URL to fetch IdP metadata XML (alternative to manual SSO_ENTRY_POINT/SSO_CERT)
      // SSO user settings
      SSO_DEFAULT_ROLE: str({ choices: ["", "user", "admin", "reviewer"], default: "reviewer" }),
      SSO_AUTO_CREATE_USERS: bool({ default: true }),
      SSO_ALLOW_ACCOUNT_LINKING: bool({ default: true }),
      // Plugin System
      SYNGRISI_PLUGINS_ENABLED: str({ default: "" }),
      // Comma-separated list of enabled plugins
      SYNGRISI_PLUGINS_DIR: str({ default: "" }),
      // Directory for external plugins
      // Okta Auth Plugin
      // Deprecated: Use SYNGRISI_PLUGIN_JWT_AUTH_* variables instead
      OKTA_JWKS_URL: str({ default: "" }),
      OKTA_ISSUER: str({ default: "" }),
      OKTA_SERVICE_USER_ROLE: str({ default: "" }),
      OKTA_AUTH_HEADER: str({ default: "" }),
      // Custom Check Validator Plugin
      CHECK_MISMATCH_THRESHOLD: str({ default: "0" }),
      // Mismatch % below which checks pass
      CHECK_VALIDATOR_SCRIPT: str({ default: "" })
      // Path to custom validation script
    });
  }
});

// src/server/data/devices.json
var devices_default;
var init_devices = __esm({
  "src/server/data/devices.json"() {
    devices_default = [
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 14 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 14 Pro",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 14 Plus",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 14",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 12 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 12 Pro",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 12 Mini",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPhone 11 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone XS",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 13 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 13 Pro",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 13 Mini",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 13",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 11 Pro",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 11",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone XS",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone 12 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone 12 Pro",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone 12 Mini",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone 12",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone 11 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPhone 11",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPhone XS",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPhone 11 Pro Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPhone 11 Pro",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPhone 11",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone XS",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone XS Max",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone XR",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone XR",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone X",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone 8",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPhone 8",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone 8",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone 8",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone 8 Plus",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone 8 Plus",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone 7",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "10",
        device: "iPhone 7",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPhone 6S",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone 6S",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone 6S Plus",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone 6",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPhone SE 2022",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPhone SE 2020",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPhone SE",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPad Air 4",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPad 9th",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPad Pro 12.9 2022",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPad Pro 12.9 2020",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPad Pro 11 2022",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPad 10th",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPad Air 5",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPad Pro 12.9 2021",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPad Pro 12.9 2020",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPad Pro 11 2021",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPad Pro 12.9 2020",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "16",
        device: "iPad 8th",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPad Pro 12.9 2018",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "15",
        device: "iPad Mini 2021",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "14",
        device: "iPad 8th",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPad Pro 12.9 2018",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPad Pro 11 2020",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPad Mini 2019",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPad Air 2019",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "13",
        device: "iPad 7th",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPad Pro 12.9 2018",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPad Pro 11 2018",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPad Mini 2019",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "12",
        device: "iPad Air 2019",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPad Pro 9.7 2016",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPad Pro 12.9 2017",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPad Mini 4",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPad 6th",
        realMobile: true
      },
      {
        os: "ios",
        os_version: "11",
        device: "iPad 5th",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Samsung Galaxy S22 Ultra",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Samsung Galaxy S22 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Samsung Galaxy S22",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Samsung Galaxy S21",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy S21 Ultra",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy S21",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy S21 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy S20",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy S20 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy S20 Ultra",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy M52",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy M32",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy A52",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy Note 20 Ultra",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy Note 20",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy A51",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy A11",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy S9 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy S10e",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy S10 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy S10",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy Note 10 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy Note 10",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy A10",
        realMobile: true
      },
      {
        os: "android",
        os_version: "8.1",
        device: "Samsung Galaxy Note 9",
        realMobile: true
      },
      {
        os: "android",
        os_version: "8.1",
        device: "Samsung Galaxy J7 Prime",
        realMobile: true
      },
      {
        os: "android",
        os_version: "8.0",
        device: "Samsung Galaxy S9 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "8.0",
        device: "Samsung Galaxy S9",
        realMobile: true
      },
      {
        os: "android",
        os_version: "7.1",
        device: "Samsung Galaxy Note 8",
        realMobile: true
      },
      {
        os: "android",
        os_version: "7.1",
        device: "Samsung Galaxy A8",
        realMobile: true
      },
      {
        os: "android",
        os_version: "7.0",
        device: "Samsung Galaxy S8 Plus",
        realMobile: true
      },
      {
        os: "android",
        os_version: "7.0",
        device: "Samsung Galaxy S8",
        realMobile: true
      },
      {
        os: "android",
        os_version: "6.0",
        device: "Samsung Galaxy S7",
        realMobile: true
      },
      {
        os: "android",
        os_version: "5.0",
        device: "Samsung Galaxy S6",
        realMobile: true
      },
      {
        os: "android",
        os_version: "13.0",
        device: "Google Pixel 7 Pro",
        realMobile: true
      },
      {
        os: "android",
        os_version: "13.0",
        device: "Google Pixel 7",
        realMobile: true
      },
      {
        os: "android",
        os_version: "13.0",
        device: "Google Pixel 6 Pro",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Google Pixel 6 Pro",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Google Pixel 6",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Google Pixel 5",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Google Pixel 5",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Google Pixel 4",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Google Pixel 4 XL",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Google Pixel 4",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Google Pixel 3",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Google Pixel 3a XL",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Google Pixel 3a",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Google Pixel 3 XL",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Google Pixel 3",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Google Pixel 2",
        realMobile: true
      },
      {
        os: "android",
        os_version: "8.0",
        device: "Google Pixel 2",
        realMobile: true
      },
      {
        os: "android",
        os_version: "7.1",
        device: "Google Pixel",
        realMobile: true
      },
      {
        os: "android",
        os_version: "6.0",
        device: "Google Nexus 6",
        realMobile: true
      },
      {
        os: "android",
        os_version: "4.4",
        device: "Google Nexus 5",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "OnePlus 9",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "OnePlus 8",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "OnePlus 7T",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "OnePlus 7",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "OnePlus 6T",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Xiaomi Redmi Note 11",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Xiaomi Redmi Note 9",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Xiaomi Redmi Note 8",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Xiaomi Redmi Note 7",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Vivo Y21",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Vivo V21",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Vivo Y50",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Oppo Reno 6",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Oppo A96",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Oppo Reno 3 Pro",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Motorola Moto G71 5G",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Motorola Moto G9 Play",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Motorola Moto G7 Play",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Huawei P30",
        realMobile: true
      },
      {
        os: "android",
        os_version: "12.0",
        device: "Samsung Galaxy Tab S8",
        realMobile: true
      },
      {
        os: "android",
        os_version: "11.0",
        device: "Samsung Galaxy Tab S7",
        realMobile: true
      },
      {
        os: "android",
        os_version: "10.0",
        device: "Samsung Galaxy Tab S7",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy Tab S6",
        realMobile: true
      },
      {
        os: "android",
        os_version: "9.0",
        device: "Samsung Galaxy Tab S5e",
        realMobile: true
      },
      {
        os: "android",
        os_version: "8.1",
        device: "Samsung Galaxy Tab S4",
        realMobile: true
      }
    ];
  }
});

// src/server/config.ts
import fs from "fs";
import dotenv2 from "dotenv";
import crypto2 from "crypto";
import { execSync } from "child_process";
var getCommitHash, customDevicesPath, logsFolder, CURRENT_VERSION, major, minor, minSupportedMinor, MIN_SUPPORTED_SDK_VERSION, config;
var init_config = __esm({
  "src/server/config.ts"() {
    "use strict";
    init_package();
    init_envConfig();
    init_devices();
    getCommitHash = () => {
      if (gitHead) {
        return gitHead.substring(0, 7);
      }
      try {
        return execSync("git rev-parse --short HEAD", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
      } catch {
        return "";
      }
    };
    customDevicesPath = "./server/data/custom_devices.json";
    logsFolder = "./logs";
    dotenv2.config();
    CURRENT_VERSION = version;
    [major, minor] = CURRENT_VERSION.split(".").map(Number);
    minSupportedMinor = Math.max(0, minor - 2);
    MIN_SUPPORTED_SDK_VERSION = `${major}.${minSupportedMinor}.0`;
    config = {
      version,
      commitHash: getCommitHash(),
      minSupportedSdkVersion: MIN_SUPPORTED_SDK_VERSION,
      apiVersion: "1",
      // this isn't used
      getDevices: async () => {
        if (fs.existsSync(customDevicesPath)) {
          return [...devices_default, ...(await import(customDevicesPath)).default];
        }
        return devices_default;
      },
      defaultImagesPath: env.SYNGRISI_IMAGES_PATH,
      domSnapshotsPath: env.SYNGRISI_DOM_SNAPSHOTS_PATH || env.SYNGRISI_IMAGES_PATH,
      connectionString: env.SYNGRISI_DB_URI || "mongodb://127.0.0.1:27017/SyngrisiDb",
      host: env.SYNGRISI_HOSTNAME,
      port: env.SYNGRISI_APP_PORT || 3e3,
      backupsFolder: "./backups",
      enableHttpLogger: env.SYNGRISI_HTTP_LOG,
      httpLoggerFilePath: `${logsFolder}/http.log`,
      storeSessionKey: env.SYNGRISI_SESSION_STORE_KEY || crypto2.randomBytes(64).toString("hex"),
      codeCoverage: env.SYNGRISI_COVERAGE,
      disableCors: env.SYNGRISI_DISABLE_DEV_CORS,
      fileUploadMaxSize: 50 * 1024 * 1024,
      adminDataJobsPath: env.SYNGRISI_ADMIN_DATA_JOBS_PATH,
      adminDataJobsTtlMs: env.SYNGRISI_ADMIN_DATA_JOBS_TTL_MS,
      adminDataMaxConcurrentJobs: env.SYNGRISI_ADMIN_DATA_MAX_CONCURRENT_JOBS,
      adminDataUploadMaxSize: env.SYNGRISI_ADMIN_DATA_UPLOAD_MAX_SIZE_MB * 1024 * 1024,
      testMode: env.SYNGRISI_TEST_MODE,
      jsonLimit: "50mb",
      tmpDir: env.SYNGRISI_TMP_DIR,
      helmet: {
        crossOriginEmbedderPolicy: false,
        crossOriginResourcePolicy: false,
        crossOriginOpenerPolicy: false,
        contentSecurityPolicy: {
          useDefaults: false,
          directives: {
            defaultSrc: ["'self'", "*", "'unsafe-inline'", "'unsafe-eval'", "data:", "blob:"],
            frameAncestors: ["'self'", "*"],
            frameSrc: ["'self'", "*"],
            scriptSrc: ["'self'", "*", "'unsafe-inline'", "'unsafe-eval'"],
            styleSrc: ["'self'", "*", "'unsafe-inline'"],
            imgSrc: ["'self'", "*", "data:", "blob:"],
            fontSrc: ["'self'", "*", "data:"],
            connectSrc: ["'self'", "*"],
            baseUri: ["'self'"],
            formAction: ["'self'"],
            objectSrc: ["'none'"],
            scriptSrcAttr: ["'none'"]
          }
        },
        hsts: false
      },
      rateLimit: {
        windowMs: env.SYNGRISI_RATE_LIMIT_WINDOW_MS,
        max: env.SYNGRISI_RATE_LIMIT_MAX,
        standardHeaders: true,
        legacyHeaders: false
      },
      authRateLimit: {
        windowMs: env.SYNGRISI_AUTH_RATE_LIMIT_WINDOW_MS,
        max: env.SYNGRISI_AUTH_RATE_LIMIT_MAX,
        standardHeaders: true,
        legacyHeaders: false
      }
    };
    if (!fs.existsSync(config.defaultImagesPath)) {
      fs.mkdirSync(config.defaultImagesPath, { recursive: true });
    }
    if (config.domSnapshotsPath !== config.defaultImagesPath && !fs.existsSync(config.domSnapshotsPath)) {
      fs.mkdirSync(config.domSnapshotsPath, { recursive: true });
    }
    if (!fs.existsSync(config.adminDataJobsPath)) {
      fs.mkdirSync(config.adminDataJobsPath, { recursive: true });
    }
    if (!fs.existsSync(logsFolder)) {
      fs.mkdirSync(logsFolder, { recursive: true });
    }
  }
});

// src/server/lib/logger.ts
import winston from "winston";
import "winston-mongodb";
function createWinstonLogger(opts) {
  if (env.SYNGRISI_DISABLE_LOGS) {
    return winston.createLogger({ transports: [], silent: true });
  }
  const transports = [
    new winston.transports.Console({
      level: logLevel || "silly",
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.timestamp(),
        winston.format.ms(),
        winston.format.metadata(),
        winston.format.printf((info) => {
          const user = info.metadata.user ? colors.blue(` <${info.metadata.user}>`) : "";
          const ref = info.metadata.ref ? colors.gray(` ${info.metadata.ref}`) : "";
          const msgType = info.metadata.msgType ? ` ${info.metadata.msgType}` : "";
          const itemType = info.metadata.itemType ? colors.magenta(` ${info.metadata.itemType}`) : "";
          const scope = info.metadata.scope ? colors.magenta(` [${info.metadata.scope}]`) : "";
          const msg = typeof info.message === "object" ? `
${JSON.stringify(info.message, null, 2)}` : info.message;
          return `${info.level} ${scope}${formatISOToDateTime_default(info.metadata.timestamp)} ${info.metadata.ms}${user}${ref}${msgType}${itemType} '${msg}'`;
        }),
        winston.format.padLevels()
      )
    })
  ];
  if (!env.SYNGRISI_TEST_MODE) {
    transports.push(
      new winston.transports.MongoDB({
        level: logLevel || "debug",
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.json(),
          winston.format.metadata()
        ),
        options: {},
        db: opts.dbConnectionString,
        collection: "vrslogs"
      })
    );
  }
  return winston.createLogger({ transports });
}
var logLevel, Logger, logger_default;
var init_logger = __esm({
  "src/server/lib/logger.ts"() {
    "use strict";
    init_colors();
    init_formatISOToDateTime();
    init_config();
    init_utils();
    init_envConfig();
    logLevel = env.SYNGRISI_LOG_LEVEL;
    Logger = class _Logger {
      winstonLogger;
      constructor(opts = { dbConnectionString: config.connectionString }) {
        this.winstonLogger = createWinstonLogger(opts);
      }
      static mergeMeta(objects) {
        return objects.reduce((acc, obj) => {
          return { ...acc, ...obj };
        }, {});
      }
      static sanitizeForLog(obj) {
        try {
          return JSON.parse(JSON.stringify(obj));
        } catch (e) {
          return String(obj);
        }
      }
      log(severity, msg, meta) {
        const mergedMeta = _Logger.sanitizeForLog(_Logger.mergeMeta(meta));
        const formattedMsg = typeof msg === "object" ? JSON.stringify(msg, null, 2) : msg;
        this.winstonLogger.log(severity, formattedMsg, mergedMeta);
      }
      error(msg, ...meta) {
        let message = String(msg);
        let code = 0;
        if (msg instanceof Object) {
          try {
            message = JSON.stringify(_Logger.sanitizeForLog(msg));
          } catch (e) {
            message = String(msg);
          }
        }
        if (msg instanceof Error) {
          message = msg.stack || msg.message;
        }
        if (msg instanceof ApiError_default) {
          code = msg.statusCode;
        }
        this.log("error", `${code !== 0 ? "[" + code + "]" : ""}${message}
 stacktrace: ${new Error().stack}`, meta);
      }
      warn(msg, ...meta) {
        this.log("warn", `${msg}
 stacktrace: ${new Error().stack}`, meta);
      }
      info(msg, ...meta) {
        this.log("info", msg, meta);
      }
      verbose(msg, ...meta) {
        this.log("verbose", msg, meta);
      }
      debug(msg, ...meta) {
        this.log("debug", msg, meta);
      }
      silly(msg, ...meta) {
        this.log("silly", msg, meta);
      }
    };
    logger_default = new Logger();
  }
});

// src/server/plugins/core/HookRegistry.ts
import { match } from "path-to-regexp";
var logOpts, HookRegistry, hookRegistry;
var init_HookRegistry = __esm({
  "src/server/plugins/core/HookRegistry.ts"() {
    "use strict";
    init_logger();
    logOpts = {
      scope: "HookRegistry",
      msgType: "PLUGIN"
    };
    HookRegistry = class {
      hooks = /* @__PURE__ */ new Map();
      /**
       * Register a hook from a plugin
       */
      register(pluginName, hookName, handler, priority = 100, routes = ["*"]) {
        const existing = this.hooks.get(hookName) || [];
        const registeredHook = {
          pluginName,
          hookName,
          handler,
          priority,
          routes
        };
        existing.push(registeredHook);
        existing.sort((a, b) => a.priority - b.priority);
        this.hooks.set(hookName, existing);
        logger_default.info(`Registered hook '${hookName}' from plugin '${pluginName}' (priority: ${priority})`, logOpts);
      }
      /**
       * Unregister all hooks from a plugin
       */
      unregister(pluginName) {
        for (const [hookName, hooks] of this.hooks.entries()) {
          const filtered = hooks.filter((h) => h.pluginName !== pluginName);
          if (filtered.length !== hooks.length) {
            this.hooks.set(hookName, filtered);
            logger_default.info(`Unregistered hooks from plugin '${pluginName}' for '${hookName}'`, logOpts);
          }
        }
      }
      /**
       * Get all hooks for a given hook name, optionally filtered by route
       */
      getHooks(hookName, route) {
        const hooks = this.hooks.get(hookName) || [];
        if (!route) {
          return hooks;
        }
        return hooks.filter((hook) => this.matchesRoute(hook.routes, route));
      }
      /**
       * Check if a route matches any of the patterns
       */
      matchesRoute(patterns, route) {
        for (const pattern of patterns) {
          if (pattern === "*") {
            return true;
          }
          const regexPattern = pattern.replace(/\*/g, "(.*)");
          try {
            const matcher = match(regexPattern, { decode: decodeURIComponent });
            if (matcher(route)) {
              return true;
            }
          } catch {
            if (route.startsWith(pattern.replace("*", ""))) {
              return true;
            }
          }
        }
        return false;
      }
      /**
       * Execute auth:validate hooks (first-match mode)
       */
      async executeAuthValidate(req, res, context) {
        const hooks = this.getHooks("auth:validate", req.path);
        for (const hook of hooks) {
          try {
            logger_default.debug(`Executing auth:validate hook from '${hook.pluginName}'`, logOpts);
            const result = await hook.handler(
              req,
              res,
              context
            );
            if (result !== null) {
              logger_default.debug(`Auth hook '${hook.pluginName}' returned result: ${result.authenticated}`, logOpts);
              return result;
            }
          } catch (error) {
            logger_default.error(`Error in auth:validate hook from '${hook.pluginName}': ${error}`, logOpts);
          }
        }
        return null;
      }
      /**
       * Execute check:beforeCompare hooks (waterfall mode)
       */
      async executeCheckBeforeCompare(context, pluginContext) {
        const hooks = this.getHooks("check:beforeCompare");
        let currentContext = context;
        for (const hook of hooks) {
          try {
            logger_default.debug(`Executing check:beforeCompare hook from '${hook.pluginName}'`, logOpts);
            const result = await hook.handler(
              currentContext,
              pluginContext
            );
            if ("skip" in result && result.skip) {
              logger_default.info(`Check comparison skipped by plugin '${hook.pluginName}'`, logOpts);
              return result;
            }
            currentContext = result;
          } catch (error) {
            logger_default.error(`Error in check:beforeCompare hook from '${hook.pluginName}': ${error}`, logOpts);
          }
        }
        return currentContext;
      }
      /**
       * Execute check:afterCompare hooks (waterfall mode)
       */
      async executeCheckAfterCompare(context, compareResult, pluginContext) {
        const hooks = this.getHooks("check:afterCompare");
        let currentResult = compareResult;
        for (const hook of hooks) {
          try {
            logger_default.debug(`Executing check:afterCompare hook from '${hook.pluginName}'`, logOpts);
            currentResult = await hook.handler(
              context,
              currentResult,
              pluginContext
            );
          } catch (error) {
            logger_default.error(`Error in check:afterCompare hook from '${hook.pluginName}': ${error}`, logOpts);
          }
        }
        return currentResult;
      }
      /**
       * Check if any hooks are registered for a given hook name
       */
      hasHooks(hookName) {
        const hooks = this.hooks.get(hookName);
        return hooks !== void 0 && hooks.length > 0;
      }
      /**
       * Get count of registered hooks
       */
      getHookCount(hookName) {
        if (hookName) {
          return (this.hooks.get(hookName) || []).length;
        }
        let total = 0;
        for (const hooks of this.hooks.values()) {
          total += hooks.length;
        }
        return total;
      }
      /**
       * Clear all hooks
       */
      clear() {
        this.hooks.clear();
        logger_default.info("Cleared all hooks", logOpts);
      }
    };
    hookRegistry = new HookRegistry();
  }
});

// src/server/plugins/sdk/context.ts
function buildPluginContext(pluginConfig = {}) {
  const appConfig = {
    connectionString: config.connectionString,
    defaultImagesPath: config.defaultImagesPath
  };
  return {
    config: appConfig,
    logger: logger_default,
    models: {
      User: User_model_default,
      Check: Check_model_default,
      Baseline: Baseline_model_default
    },
    pluginConfig
  };
}
var init_context = __esm({
  "src/server/plugins/sdk/context.ts"() {
    "use strict";
    init_models();
    init_config();
    init_logger();
  }
});

// src/server/plugins/core/PluginManager.ts
var logOpts2, PluginManager, pluginManager;
var init_PluginManager = __esm({
  "src/server/plugins/core/PluginManager.ts"() {
    "use strict";
    init_logger();
    init_context();
    init_HookRegistry();
    logOpts2 = {
      scope: "PluginManager",
      msgType: "PLUGIN"
    };
    PluginManager = class {
      plugins = /* @__PURE__ */ new Map();
      context = null;
      registry;
      pluginConfigs = /* @__PURE__ */ new Map();
      constructor(registry2 = hookRegistry) {
        this.registry = registry2;
      }
      /**
       * Initialize the plugin manager with application context
       */
      async initialize(pluginConfigs = {}) {
        logger_default.info("Initializing Plugin Manager", logOpts2);
        for (const [name, config2] of Object.entries(pluginConfigs)) {
          this.pluginConfigs.set(name, config2);
        }
        this.context = null;
      }
      /**
       * Register and load a plugin
       */
      async loadPlugin(pluginExport, config2) {
        let plugin;
        if (typeof pluginExport === "function") {
          const factory = pluginExport;
          plugin = factory(config2 || {});
        } else {
          plugin = pluginExport;
        }
        const { manifest } = plugin;
        const pluginName = manifest.name;
        if (this.plugins.has(pluginName)) {
          logger_default.warn(`Plugin '${pluginName}' is already loaded, skipping`, logOpts2);
          return;
        }
        if (manifest.enabled === false) {
          logger_default.info(`Plugin '${pluginName}' is disabled, skipping`, logOpts2);
          return;
        }
        logger_default.info(`Loading plugin: ${pluginName} v${manifest.version}`, logOpts2);
        const loadedPlugin = {
          plugin,
          loaded: false
        };
        try {
          const pluginConfig = config2 || this.pluginConfigs.get(pluginName) || {};
          const context = buildPluginContext(pluginConfig);
          if (plugin.onLoad) {
            await plugin.onLoad(context);
          }
          this.registerPluginHooks(plugin);
          loadedPlugin.loaded = true;
          this.plugins.set(pluginName, loadedPlugin);
          logger_default.info(`Plugin '${pluginName}' loaded successfully`, logOpts2);
        } catch (error) {
          loadedPlugin.error = error instanceof Error ? error : new Error(String(error));
          this.plugins.set(pluginName, loadedPlugin);
          logger_default.error(`Failed to load plugin '${pluginName}': ${error}`, logOpts2);
          throw error;
        }
      }
      /**
       * Register all hooks from a plugin
       */
      registerPluginHooks(plugin) {
        const { manifest, hooks } = plugin;
        if (!hooks) {
          return;
        }
        const priority = manifest.priority ?? 100;
        const routes = manifest.routes ?? ["*"];
        for (const [hookName, handler] of Object.entries(hooks)) {
          if (handler) {
            this.registry.register(
              manifest.name,
              hookName,
              handler,
              priority,
              routes
            );
          }
        }
      }
      /**
       * Unload a plugin
       */
      async unloadPlugin(pluginName) {
        const loadedPlugin = this.plugins.get(pluginName);
        if (!loadedPlugin) {
          logger_default.warn(`Plugin '${pluginName}' is not loaded`, logOpts2);
          return;
        }
        logger_default.info(`Unloading plugin: ${pluginName}`, logOpts2);
        try {
          if (loadedPlugin.plugin.onUnload) {
            await loadedPlugin.plugin.onUnload();
          }
          this.registry.unregister(pluginName);
          this.plugins.delete(pluginName);
          logger_default.info(`Plugin '${pluginName}' unloaded successfully`, logOpts2);
        } catch (error) {
          logger_default.error(`Error unloading plugin '${pluginName}': ${error}`, logOpts2);
          throw error;
        }
      }
      /**
       * Unload all plugins
       */
      async unloadAll() {
        logger_default.info("Unloading all plugins", logOpts2);
        const pluginNames = Array.from(this.plugins.keys());
        for (const pluginName of pluginNames) {
          try {
            await this.unloadPlugin(pluginName);
          } catch (error) {
            logger_default.error(`Error unloading plugin '${pluginName}': ${error}`, logOpts2);
          }
        }
        this.registry.clear();
      }
      /**
       * Get a loaded plugin by name
       */
      getPlugin(pluginName) {
        return this.plugins.get(pluginName);
      }
      /**
       * Get all loaded plugins
       */
      getLoadedPlugins() {
        return new Map(this.plugins);
      }
      /**
       * Get plugin names
       */
      getPluginNames() {
        return Array.from(this.plugins.keys());
      }
      /**
       * Check if a plugin is loaded
       */
      isPluginLoaded(pluginName) {
        const plugin = this.plugins.get(pluginName);
        return plugin?.loaded ?? false;
      }
      /**
       * Get plugin count
       */
      getPluginCount() {
        return this.plugins.size;
      }
      /**
       * Get the hook registry
       */
      getHookRegistry() {
        return this.registry;
      }
    };
    pluginManager = new PluginManager();
  }
});

// src/server/plugins/core/PluginLoader.ts
var init_PluginLoader = __esm({
  "src/server/plugins/core/PluginLoader.ts"() {
    "use strict";
    init_logger();
    init_PluginManager();
    init_models();
  }
});

// src/server/routes/v1/snapshots.route.ts
import express from "express";
import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";

// src/server/controllers/snapshots.controller.ts
init_utils();

// src/server/services/run.service.ts
init_models();
init_logger();

// src/server/services/test.service.ts
import { Types as Types2 } from "mongoose";
init_models();
init_logger();

// src/server/services/run.service.ts
init_utils();
init_utils();

// src/server/services/suite.service.ts
init_models();
init_logger();
init_utils();
init_utils();

// src/server/services/logs.service.ts
init_models();
init_logger();
init_envConfig();

// src/server/services/generic.service.ts
var generic_service_exports = {};
__export(generic_service_exports, {
  get: () => get,
  put: () => put
});
init_utils();
init_logger();
import mongoose16 from "mongoose";
var get = async (modelName, filter, options) => {
  const itemModel = mongoose16.model(modelName);
  return itemModel.paginate(filter, options);
};
var put = async (modelName, id2, options, user) => {
  const itemModel = mongoose16.model(modelName);
  const logOpts4 = {
    scope: "generic.service.put",
    ref: id2,
    itemType: modelName,
    msgType: "UPDATE",
    user: user?.username
  };
  const opts = removeEmptyProperties(options);
  logger_default.debug(`start update '${modelName}' with id: '${id2}', body: '${JSON.stringify(opts)}'`, logOpts4);
  const item = await itemModel.findByIdAndUpdate(id2, options).exec();
  if (!item) throw new Error(`cannot find the item: ${modelName}, id: ${id2}, options: ${JSON.stringify(options)}`);
  await item.save();
  logger_default.debug(`baseline with id: '${id2}' and opts: '${JSON.stringify(opts)}' was updated`, logOpts4);
  return item;
};

// src/server/services/app.service.ts
init_models();

// src/server/services/tasks.service.ts
init_envConfig();
init_config();
init_logger();

// src/tasks/lib/output-writer.ts
init_logger();

// src/tasks/core/handle-database-consistency.task.ts
init_config();
init_stringTable();
import mongoose17 from "mongoose";

// src/tasks/lib/index.ts
init_Check_model();
init_Log_model();
init_App_model();
init_Snapshot_model();
init_Suite_model();
init_Run_model();
init_User_model();
init_Baseline_model();
init_Test_model();

// src/tasks/core/handle-old-checks.task.ts
init_config();
init_utils();
init_stringTable();
import mongoose18 from "mongoose";

// src/tasks/core/handle-orphan-files.task.ts
init_config();
init_stringTable();

// src/tasks/core/handle-orphan-baselines.task.ts
init_models();

// src/tasks/core/remove-old-logs.task.ts
init_utils();

// src/server/services/tasks.service.ts
init_models();

// src/server/services/client.service.ts
init_utils();
init_logger();

// src/server/services/dom-snapshot.service.ts
init_models();
init_config();
import { Types as Types3 } from "mongoose";

// src/server/utils/domDumpUtils.ts
import { gunzipSync, gzipSync } from "zlib";
import { createHash as createHash2 } from "crypto";

// src/server/services/dom-snapshot.service.ts
init_logger();

// src/server/services/client.service.ts
init_utils();

// src/server/services/snapshot-file.service.ts
init_utils();
init_models();
init_utils();
init_config();
init_logger();

// src/server/services/test-run.service.ts
init_utils();
init_models();

// src/server/lib/dbItems/updateItem.ts
init_logger();
import mongoose19 from "mongoose";

// src/server/lib/dbItems/updateItemDate.ts
init_logger();
import mongoose20 from "mongoose";

// src/server/lib/dbItems/createItemIfNotExist.ts
init_logger();
import mongoose21 from "mongoose";

// src/server/lib/dbItems/createItemProm.ts
init_logger();
import mongoose22 from "mongoose";

// src/server/lib/dbItems/createAppIfNotExist.ts
init_models();
init_logger();

// src/server/lib/dbItems/createSuiteIfNotExist.ts
init_models();
init_logger();

// src/server/lib/dbItems/createRunIfNotExist.ts
init_models();
init_utils();
init_logger();

// src/server/services/test-run.service.ts
init_logger();

// src/server/services/baseline.service.ts
init_models();
init_utils();
init_logger();
init_utils();
import { Types as Types4 } from "mongoose";

// src/server/services/check.service.ts
init_models();
init_utils();
import { Types as Types5 } from "mongoose";

// src/server/services/snapshot.service.ts
init_config();
init_models();
init_logger();

// src/server/services/check.service.ts
init_logger();

// src/server/services/webhook.service.ts
init_models();
init_logger();

// src/server/services/comparison.service.ts
init_models();
init_config();

// src/server/lib/comparison/comparator.ts
init_utils();
init_logger();
import { Worker } from "worker_threads";
import fs2 from "fs";
import path2 from "path";
import { fileURLToPath } from "url";
import { createRequire } from "module";
var __filename = fileURLToPath(import.meta.url);
var __dirname = path2.dirname(__filename);

// src/server/services/comparison.service.ts
init_logger();
init_utils();
init_utils();

// src/server/plugins/index.ts
init_envConfig();
init_logger();

// src/server/plugins/core/index.ts
init_HookRegistry();
init_PluginManager();
init_PluginLoader();

// src/server/plugins/index.ts
init_models();
init_context();

// src/server/plugins/sdk/hooks.ts
var HOOK_EXECUTION_MODE = {
  /** First successful result wins (auth hooks) */
  FIRST_MATCH: "first-match",
  /** Result passes through all handlers (waterfall) */
  WATERFALL: "waterfall",
  /** All handlers run, results collected */
  PARALLEL: "parallel"
};
var HOOK_MODES = {
  "auth:validate": HOOK_EXECUTION_MODE.FIRST_MATCH,
  "check:beforeCompare": HOOK_EXECUTION_MODE.WATERFALL,
  "check:afterCompare": HOOK_EXECUTION_MODE.WATERFALL,
  "request:before": HOOK_EXECUTION_MODE.WATERFALL,
  "request:after": HOOK_EXECUTION_MODE.WATERFALL
};

// src/server/plugins/sdk/index.ts
init_context();

// src/server/services/client.service.ts
import mongoose23 from "mongoose";
init_config();

// src/server/services/user.service.ts
init_utils();
init_models();
init_utils();
init_logger();

// src/server/services/admin-data-job.service.ts
init_config();
import fs3 from "fs";
import { promises as fsp } from "fs";
import path3 from "path";
import { randomUUID as randomUUID2 } from "crypto";
import { createGzip, createGunzip } from "zlib";
import { promisify } from "util";
import { pipeline } from "stream";
import tar from "tar-stream";
import mongoose24 from "mongoose";
var pipelineAsync = promisify(pipeline);
var { BSON } = mongoose24.mongo;
var activeTasks = /* @__PURE__ */ new Map();
var META_FILENAME = "job.json";
var LOG_FILENAME = "job.log";
var DB_EXPORT_DIRNAME = "db-export";
var isActiveStatus = (status) => status === "pending" || status === "running";
var getJobDir = (jobId) => path3.join(config.adminDataJobsPath, jobId);
var getJobMetaPath = (jobId) => path3.join(getJobDir(jobId), META_FILENAME);
var getJobLogPath = (jobId) => path3.join(getJobDir(jobId), LOG_FILENAME);
async function ensureDir(dirPath) {
  await fsp.mkdir(dirPath, { recursive: true });
}
async function removeDirSafe(dirPath) {
  if (!dirPath) return;
  await fsp.rm(dirPath, { recursive: true, force: true });
}
async function fileExists(filePath) {
  try {
    await fsp.access(filePath);
    return true;
  } catch {
    return false;
  }
}
async function writeJob(job) {
  await ensureDir(job.workDir);
  await fsp.writeFile(getJobMetaPath(job.id), JSON.stringify(job, null, 2));
}
async function appendLog(jobId, message) {
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
`;
  await fsp.appendFile(getJobLogPath(jobId), line);
}
async function readJob(jobId) {
  try {
    const raw = await fsp.readFile(getJobMetaPath(jobId), "utf8");
    return JSON.parse(raw);
  } catch {
    return null;
  }
}
async function listJobsInternal() {
  await ensureDir(config.adminDataJobsPath);
  const entries = await fsp.readdir(config.adminDataJobsPath, { withFileTypes: true });
  const jobs = await Promise.all(entries.filter((entry) => entry.isDirectory()).map((entry) => readJob(entry.name)));
  return jobs.filter((job) => Boolean(job)).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}
async function updateJob(jobId, patch) {
  const current = await readJob(jobId);
  if (!current) {
    throw new Error(`Job not found: ${jobId}`);
  }
  const nextJob = { ...current, ...patch };
  await writeJob(nextJob);
  return nextJob;
}
async function updateProgress(jobId, progress, message, statsPatch) {
  const current = await readJob(jobId);
  if (!current) return;
  const nextProgress = { ...current.progress, ...progress };
  if (typeof nextProgress.current === "number" && typeof nextProgress.total === "number" && nextProgress.total > 0) {
    nextProgress.percent = Math.min(100, Math.round(nextProgress.current / nextProgress.total * 100));
  }
  await writeJob({
    ...current,
    progress: nextProgress,
    message: message ?? current.message,
    stats: { ...current.stats, ...statsPatch }
  });
}
async function finalizeJob(jobId, status, patch = {}) {
  const current = await readJob(jobId);
  if (!current) return null;
  const job = {
    ...current,
    ...patch,
    status,
    finishedAt: (/* @__PURE__ */ new Date()).toISOString()
  };
  await writeJob(job);
  return job;
}
function normalizeArchiveName(type) {
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
  switch (type) {
    case "db_backup":
      return `syngrisi-db-backup-${stamp}.tar.gz`;
    case "screenshots_backup":
      return `syngrisi-screenshots-backup-${stamp}.tar.gz`;
    default:
      return `${type}-${stamp}.tar.gz`;
  }
}
async function getRunningJobs() {
  const jobs = await listJobsInternal();
  return jobs.filter((job) => isActiveStatus(job.status));
}
async function hasActiveDatabaseRestoreJob() {
  const jobs = await getRunningJobs();
  return jobs.some((job) => job.type === "db_restore");
}
async function assertCanStartJob() {
  const runningJobs = await getRunningJobs();
  if (runningJobs.length >= config.adminDataMaxConcurrentJobs) {
    throw new Error(`Another data job is already active: ${runningJobs[0].id}`);
  }
}
async function countFilesRecursive(rootDir) {
  let count = 0;
  const stack = [rootDir];
  while (stack.length > 0) {
    const currentDir = stack.pop();
    if (!currentDir) continue;
    const dir = await fsp.opendir(currentDir);
    for await (const entry of dir) {
      const entryPath = path3.join(currentDir, entry.name);
      if (entry.isDirectory()) {
        stack.push(entryPath);
      } else if (entry.isFile()) {
        count += 1;
      }
    }
  }
  return count;
}
async function streamToFile(sourcePath, targetPath) {
  await ensureDir(path3.dirname(targetPath));
  await pipelineAsync(fs3.createReadStream(sourcePath), fs3.createWriteStream(targetPath));
}
function getActiveState(jobId) {
  return activeTasks.get(jobId);
}
function assertNotCancelled(jobId) {
  if (getActiveState(jobId)?.cancelRequested) {
    throw new Error("Job cancelled");
  }
}
async function createJob(type, params) {
  await assertCanStartJob();
  const id2 = randomUUID2();
  const workDir = getJobDir(id2);
  await ensureDir(workDir);
  const job = {
    id: id2,
    type,
    status: "pending",
    params,
    progress: { stage: "queued", percent: 0 },
    message: "Queued",
    stats: {},
    downloadAvailable: false,
    workDir,
    logFilePath: getJobLogPath(id2),
    createdAt: (/* @__PURE__ */ new Date()).toISOString()
  };
  await writeJob(job);
  await appendLog(id2, `Job created: ${type}`);
  return job;
}
async function addFileToTar(pack, filePath, entryName) {
  const stat = await fsp.stat(filePath);
  await new Promise((resolve, reject) => {
    const entry = pack.entry({ name: entryName, size: stat.size, mode: stat.mode }, (error) => {
      if (error) {
        reject(error);
        return;
      }
      resolve();
    });
    fs3.createReadStream(filePath).on("error", reject).pipe(entry).on("error", reject);
  });
}
async function createTarGzArchive(outputPath, items) {
  await ensureDir(path3.dirname(outputPath));
  const pack = tar.pack();
  const gzip = createGzip();
  const output = fs3.createWriteStream(outputPath);
  const pipelinePromise = pipelineAsync(pack, gzip, output);
  for (const item of items) {
    await addFileToTar(pack, item.path, item.name);
  }
  pack.finalize();
  await pipelinePromise;
}
async function extractTarGzArchive(archivePath, destinationDir) {
  await ensureDir(destinationDir);
  const extract = tar.extract();
  await new Promise((resolve, reject) => {
    extract.on("entry", (header, stream, next) => {
      const outputPath = path3.join(destinationDir, header.name);
      const finishEntry = (error) => {
        if (error) {
          reject(error);
          return;
        }
        next();
      };
      if (header.type === "directory") {
        void ensureDir(outputPath).then(() => {
          stream.resume();
          finishEntry();
        }).catch((error) => finishEntry(error));
        return;
      }
      void ensureDir(path3.dirname(outputPath)).then(() => pipelineAsync(stream, fs3.createWriteStream(outputPath))).then(() => finishEntry()).catch((error) => finishEntry(error));
    });
    extract.on("finish", () => resolve());
    extract.on("error", reject);
    fs3.createReadStream(archivePath).on("error", reject).pipe(createGunzip()).on("error", reject).pipe(extract).on("error", reject);
  });
}
async function countFilesInTarGzArchive(archivePath) {
  const extract = tar.extract();
  let totalFiles = 0;
  await new Promise((resolve, reject) => {
    extract.on("entry", (_header, stream, next) => {
      if (_header.type === "file") {
        totalFiles += 1;
      }
      stream.resume();
      next();
    });
    extract.on("finish", () => resolve());
    extract.on("error", reject);
    fs3.createReadStream(archivePath).on("error", reject).pipe(createGunzip()).on("error", reject).pipe(extract).on("error", reject);
  });
  return totalFiles;
}
async function writeCollectionDump(jobId, collectionName, outputPath) {
  const db = mongoose24.connection.db;
  if (!db) {
    throw new Error("MongoDB connection is not available");
  }
  let documentCount = 0;
  await ensureDir(path3.dirname(outputPath));
  const gzip = createGzip();
  const output = fs3.createWriteStream(outputPath);
  gzip.pipe(output);
  const cursor = db.collection(collectionName).find({}, { timeout: false });
  for await (const doc of cursor) {
    assertNotCancelled(jobId);
    gzip.write(BSON.serialize(doc));
    documentCount += 1;
    if (documentCount % 1e3 === 0) {
      await appendLog(jobId, `Exported ${documentCount} documents from ${collectionName}`);
    }
  }
  gzip.end();
  await new Promise((resolve, reject) => {
    output.on("finish", () => resolve());
    output.on("error", reject);
    gzip.on("error", reject);
  });
  return documentCount;
}
async function walkFiles(rootDir, onFile) {
  const stack = [rootDir];
  while (stack.length > 0) {
    const currentDir = stack.pop();
    if (!currentDir) continue;
    const dir = await fsp.opendir(currentDir);
    for await (const entry of dir) {
      const fullPath = path3.join(currentDir, entry.name);
      const relativePath = path3.relative(rootDir, fullPath);
      if (entry.isDirectory()) {
        stack.push(fullPath);
      } else if (entry.isFile()) {
        await onFile(fullPath, relativePath);
      }
    }
  }
}
async function runDbBackup(job) {
  const db = mongoose24.connection.db;
  if (!db) {
    throw new Error("MongoDB connection is not available");
  }
  const archiveName = normalizeArchiveName(job.type);
  const archivePath = path3.join(job.workDir, archiveName);
  const exportDir = path3.join(job.workDir, DB_EXPORT_DIRNAME);
  await ensureDir(exportDir);
  await updateProgress(job.id, { stage: "indexing", percent: 5 }, "Inspecting database");
  const collectionInfos = await db.listCollections({}, { nameOnly: true }).toArray();
  const collections = collectionInfos.map((item) => item.name).filter((name) => !name.startsWith("system."));
  const manifest = {
    format: "syngrisi-db-backup-v1",
    exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
    databaseName: db.databaseName,
    collections: []
  };
  let processedCollections = 0;
  for (const collectionName of collections) {
    assertNotCancelled(job.id);
    const dumpFileName = `${collectionName}.bson.gz`;
    const dumpPath = path3.join(exportDir, dumpFileName);
    await updateProgress(job.id, { stage: "dumping", current: processedCollections, total: collections.length }, `Exporting ${collectionName}`);
    const documentCount = await writeCollectionDump(job.id, collectionName, dumpPath);
    const indexes = await db.collection(collectionName).indexes();
    manifest.collections.push({
      name: collectionName,
      dumpFile: dumpFileName,
      documentCount,
      indexes: indexes.map((index) => JSON.parse(JSON.stringify(index)))
    });
    processedCollections += 1;
    await updateProgress(job.id, { stage: "dumping", current: processedCollections, total: collections.length }, `Exported ${collectionName}`);
  }
  const manifestPath = path3.join(exportDir, "manifest.json");
  await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
  await updateProgress(job.id, { stage: "archiving", percent: 80 }, "Creating archive");
  const tarItems = [
    { path: manifestPath, name: "manifest.json" },
    ...manifest.collections.map((collection) => ({
      path: path3.join(exportDir, collection.dumpFile),
      name: `collections/${collection.dumpFile}`
    }))
  ];
  await createTarGzArchive(archivePath, tarItems);
  const stat = await fsp.stat(archivePath);
  await finalizeJob(job.id, "completed", {
    message: "Database backup completed",
    archivePath,
    archiveName,
    downloadAvailable: true,
    stats: {
      archiveSizeBytes: stat.size,
      processedFiles: manifest.collections.reduce((acc, item) => acc + item.documentCount, 0),
      totalFiles: manifest.collections.length
    },
    progress: { stage: "completed", percent: 100, current: manifest.collections.length, total: manifest.collections.length }
  });
}
async function recreateIndexes(collectionName, indexes) {
  const db = mongoose24.connection.db;
  if (!db) {
    throw new Error("MongoDB connection is not available");
  }
  const filtered = indexes.filter((index) => index.name !== "_id_");
  if (filtered.length === 0) {
    return;
  }
  const definitions = filtered.map((index) => {
    const { key, ...options } = index;
    return { key, ...options };
  });
  await db.collection(collectionName).createIndexes(definitions);
}
async function importCollectionDump(jobId, collectionName, dumpPath) {
  const db = mongoose24.connection.db;
  if (!db) {
    throw new Error("MongoDB connection is not available");
  }
  const collection = db.collection(collectionName);
  const batch = [];
  let inserted = 0;
  const flush = async () => {
    if (batch.length === 0) return;
    await collection.insertMany(batch, { ordered: false });
    inserted += batch.length;
    batch.length = 0;
  };
  const input = fs3.createReadStream(dumpPath).pipe(createGunzip());
  let pending = Buffer.alloc(0);
  for await (const chunk of input) {
    assertNotCancelled(jobId);
    pending = Buffer.concat([pending, chunk]);
    while (pending.length >= 4) {
      const documentSize = pending.readInt32LE(0);
      if (documentSize <= 0) {
        throw new Error(`Invalid BSON document size ${documentSize} in ${collectionName}`);
      }
      if (pending.length < documentSize) {
        break;
      }
      const documentBuffer = pending.subarray(0, documentSize);
      pending = pending.subarray(documentSize);
      batch.push(BSON.deserialize(documentBuffer));
      if (batch.length >= 1e3) {
        await flush();
        await appendLog(jobId, `Imported ${inserted} documents into ${collectionName}`);
      }
    }
  }
  if (pending.length > 0) {
    throw new Error(`Unexpected trailing BSON bytes in ${collectionName}`);
  }
  await flush();
  return inserted;
}
async function runDbRestore(job) {
  const db = mongoose24.connection.db;
  if (!db) {
    throw new Error("MongoDB connection is not available");
  }
  const uploadPath = String(job.uploadPath || "");
  if (!uploadPath || !await fileExists(uploadPath)) {
    throw new Error("Uploaded archive is missing");
  }
  const extractDir = path3.join(job.workDir, "extracted-db");
  await updateProgress(job.id, { stage: "extracting", percent: 10 }, "Extracting database archive");
  await extractTarGzArchive(uploadPath, extractDir);
  const manifestPath = path3.join(extractDir, "manifest.json");
  if (!await fileExists(manifestPath)) {
    throw new Error("manifest.json is missing from database archive");
  }
  const manifest = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
  if (manifest.format !== "syngrisi-db-backup-v1") {
    throw new Error("Unsupported database backup format");
  }
  await updateProgress(job.id, { stage: "dropping_db", percent: 30 }, "Dropping current database");
  await db.dropDatabase();
  let importedCollections = 0;
  for (const collectionInfo of manifest.collections) {
    assertNotCancelled(job.id);
    const dumpPath = path3.join(extractDir, "collections", collectionInfo.dumpFile);
    await updateProgress(
      job.id,
      { stage: "restoring", current: importedCollections, total: manifest.collections.length },
      `Restoring ${collectionInfo.name}`
    );
    await importCollectionDump(job.id, collectionInfo.name, dumpPath);
    await recreateIndexes(collectionInfo.name, collectionInfo.indexes);
    importedCollections += 1;
    await updateProgress(
      job.id,
      { stage: "restoring", current: importedCollections, total: manifest.collections.length },
      `Restored ${collectionInfo.name}`
    );
  }
  await finalizeJob(job.id, "completed", {
    message: "Database restore completed",
    progress: { stage: "completed", percent: 100, current: importedCollections, total: manifest.collections.length },
    stats: {
      processedFiles: manifest.collections.reduce((acc, item) => acc + item.documentCount, 0),
      totalFiles: manifest.collections.length
    }
  });
}
async function runScreenshotsBackup(job) {
  const archiveName = normalizeArchiveName(job.type);
  const archivePath = path3.join(job.workDir, archiveName);
  const totalFiles = await countFilesRecursive(config.defaultImagesPath);
  let processedFiles = 0;
  await ensureDir(path3.dirname(archivePath));
  const pack = tar.pack();
  const gzip = createGzip();
  const output = fs3.createWriteStream(archivePath);
  const archivePipeline = pipelineAsync(pack, gzip, output);
  await updateProgress(job.id, { stage: "archiving", current: 0, total: totalFiles }, "Archiving screenshots", { totalFiles });
  await walkFiles(config.defaultImagesPath, async (fullPath, relativePath) => {
    assertNotCancelled(job.id);
    await addFileToTar(pack, fullPath, relativePath);
    processedFiles += 1;
    if (processedFiles % 200 === 0 || processedFiles === totalFiles) {
      await updateProgress(job.id, { stage: "archiving", current: processedFiles, total: totalFiles }, "Archiving screenshots", { processedFiles, totalFiles });
    }
  });
  pack.finalize();
  await archivePipeline;
  const stat = await fsp.stat(archivePath);
  await finalizeJob(job.id, "completed", {
    message: "Screenshots backup completed",
    archivePath,
    archiveName,
    downloadAvailable: true,
    stats: { archiveSizeBytes: stat.size, processedFiles, totalFiles },
    progress: { stage: "completed", percent: 100, current: processedFiles, total: totalFiles }
  });
}
async function runScreenshotsRestore(job) {
  const uploadPath = String(job.uploadPath || "");
  const skipExisting = Boolean(job.params.skipExisting);
  if (!uploadPath || !await fileExists(uploadPath)) {
    throw new Error("Uploaded archive is missing");
  }
  const totalFiles = await countFilesInTarGzArchive(uploadPath);
  let processedFiles = 0;
  let importedFiles = 0;
  let skippedFiles = 0;
  let errorFiles = 0;
  await updateProgress(job.id, { stage: "importing", current: 0, total: totalFiles }, "Importing screenshots", { totalFiles });
  const extract = tar.extract();
  await new Promise((resolve, reject) => {
    extract.on("entry", (header, stream, next) => {
      const finish = (error) => {
        if (error) {
          reject(error);
          return;
        }
        next();
      };
      if (header.type !== "file") {
        stream.resume();
        finish();
        return;
      }
      const relativePath = header.name;
      const targetPath = path3.join(config.defaultImagesPath, relativePath);
      void (async () => {
        assertNotCancelled(job.id);
        const exists = await fileExists(targetPath);
        if (exists && skipExisting) {
          skippedFiles += 1;
          stream.resume();
        } else {
          await ensureDir(path3.dirname(targetPath));
          if (exists) {
            await fsp.rm(targetPath, { force: true });
          }
          await pipelineAsync(stream, fs3.createWriteStream(targetPath));
          importedFiles += 1;
        }
        processedFiles += 1;
        if (processedFiles % 200 === 0 || processedFiles === totalFiles) {
          await updateProgress(
            job.id,
            { stage: "importing", current: processedFiles, total: totalFiles },
            "Importing screenshots",
            { processedFiles, importedFiles, skippedFiles, errorFiles, totalFiles }
          );
        }
      })().then(() => finish()).catch(async (error) => {
        errorFiles += 1;
        await appendLog(job.id, `Failed to import ${relativePath}: ${error.message}`);
        stream.resume();
        processedFiles += 1;
        finish();
      });
    });
    extract.on("finish", () => resolve());
    extract.on("error", reject);
    fs3.createReadStream(uploadPath).on("error", reject).pipe(createGunzip()).on("error", reject).pipe(extract).on("error", reject);
  });
  await finalizeJob(job.id, "completed", {
    message: "Screenshots restore completed",
    progress: { stage: "completed", percent: 100, current: processedFiles, total: totalFiles },
    stats: { totalFiles, processedFiles, importedFiles, skippedFiles, errorFiles }
  });
}
async function cleanupExpiredJobs() {
  const jobs = await listJobsInternal();
  const now = Date.now();
  await Promise.all(jobs.map(async (job) => {
    if (isActiveStatus(job.status)) return;
    const finishedAt = job.finishedAt ? new Date(job.finishedAt).getTime() : new Date(job.createdAt).getTime();
    if (now - finishedAt > config.adminDataJobsTtlMs) {
      await removeDirSafe(job.workDir);
    }
  }));
}
async function cleanupJobWorkdirs(jobId) {
  const job = await readJob(jobId);
  if (!job) return;
  await removeDirSafe(path3.join(job.workDir, "staging"));
  await removeDirSafe(path3.join(job.workDir, "extracted"));
  await removeDirSafe(path3.join(job.workDir, "extracted-db"));
  await removeDirSafe(path3.join(job.workDir, DB_EXPORT_DIRNAME));
  if (job.status === "completed" && job.downloadAvailable) {
    if (job.uploadPath) {
      await fsp.rm(job.uploadPath, { force: true });
    }
    return;
  }
  if (job.uploadPath) {
    await fsp.rm(job.uploadPath, { force: true });
  }
}
async function markStaleJobsFailed() {
  const jobs = await listJobsInternal();
  await Promise.all(jobs.map(async (job) => {
    if (isActiveStatus(job.status)) {
      await finalizeJob(job.id, "failed", {
        message: "Marked as failed after server restart",
        error: "Server restarted while job was running",
        downloadAvailable: false
      });
    }
  }));
}
async function initialize() {
  await ensureDir(config.adminDataJobsPath);
  await markStaleJobsFailed();
  await cleanupExpiredJobs();
}
async function startJob(job) {
  setImmediate(() => {
    void runJob(job.id);
  });
}
async function runJob(jobId) {
  const currentJob = await readJob(jobId);
  if (!currentJob || currentJob.status === "cancelled") {
    return;
  }
  activeTasks.set(jobId, { cancelRequested: false });
  const job = await updateJob(jobId, {
    status: "running",
    startedAt: (/* @__PURE__ */ new Date()).toISOString(),
    progress: { stage: "preparing", percent: 0 },
    message: "Preparing"
  });
  try {
    switch (job.type) {
      case "db_backup":
        await runDbBackup(job);
        break;
      case "db_restore":
        await runDbRestore(job);
        break;
      case "screenshots_backup":
        await runScreenshotsBackup(job);
        break;
      case "screenshots_restore":
        await runScreenshotsRestore(job);
        break;
      default:
        throw new Error(`Unsupported job type: ${job.type}`);
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    const cancelled = message === "Job cancelled";
    await appendLog(jobId, `Job ${cancelled ? "cancelled" : "failed"}: ${message}`);
    await finalizeJob(jobId, cancelled ? "cancelled" : "failed", {
      error: cancelled ? void 0 : message,
      message: cancelled ? "Cancelled" : message,
      downloadAvailable: false
    });
  } finally {
    activeTasks.delete(jobId);
    await cleanupJobWorkdirs(jobId);
  }
}
async function persistUploadToJobDir(file, jobDir, fileName) {
  const targetPath = path3.join(jobDir, fileName);
  await ensureDir(jobDir);
  if (file.tempFilePath) {
    await fsp.rename(file.tempFilePath, targetPath).catch(async () => {
      await streamToFile(file.tempFilePath, targetPath);
      await fsp.rm(file.tempFilePath, { force: true });
    });
  } else {
    await fsp.writeFile(targetPath, file.data);
  }
  return targetPath;
}
async function createDbBackupJob() {
  const job = await createJob("db_backup", {});
  await startJob(job);
  return readJob(job.id);
}
async function createScreenshotsBackupJob() {
  const job = await createJob("screenshots_backup", {});
  await startJob(job);
  return readJob(job.id);
}
async function createDbRestoreJob(file) {
  const job = await createJob("db_restore", {});
  const uploadPath = await persistUploadToJobDir(file, path3.join(job.workDir, "staging"), file.name || "db-restore.tar.gz");
  await updateJob(job.id, {
    uploadPath,
    message: "Upload stored, waiting for restore"
  });
  const updated = await readJob(job.id);
  if (!updated) throw new Error("Failed to read created job");
  await startJob(updated);
  return readJob(updated.id);
}
async function createScreenshotsRestoreJob(file, skipExisting) {
  const job = await createJob("screenshots_restore", { skipExisting });
  const uploadPath = await persistUploadToJobDir(file, path3.join(job.workDir, "staging"), file.name || "screenshots-restore.tar.gz");
  await updateJob(job.id, {
    uploadPath,
    message: "Upload stored, waiting for restore"
  });
  const updated = await readJob(job.id);
  if (!updated) throw new Error("Failed to read created job");
  await startJob(updated);
  return readJob(updated.id);
}
async function getJob(jobId) {
  return readJob(jobId);
}
async function getJobLog(jobId) {
  try {
    return await fsp.readFile(getJobLogPath(jobId), "utf8");
  } catch {
    return "";
  }
}
async function cancelJob(jobId) {
  const job = await readJob(jobId);
  if (!job) {
    throw new Error(`Job not found: ${jobId}`);
  }
  const active = activeTasks.get(jobId);
  if (!active) {
    return finalizeJob(jobId, job.status === "pending" ? "cancelled" : job.status, {
      message: job.status === "pending" ? "Cancelled" : job.message
    });
  }
  active.cancelRequested = true;
  await appendLog(jobId, "Cancellation requested");
  return updateJob(jobId, { message: "Cancellation requested" });
}
async function createDownloadStream(jobId) {
  const job = await readJob(jobId);
  if (!job || job.status !== "completed" || !job.downloadAvailable || !job.archivePath) {
    throw new Error("Download is not available for this job");
  }
  if (!await fileExists(job.archivePath)) {
    throw new Error("Archive file is missing");
  }
  return {
    fileName: job.archiveName || path3.basename(job.archivePath),
    stream: fs3.createReadStream(job.archivePath)
  };
}
async function deleteJob(jobId) {
  const job = await readJob(jobId);
  if (!job) {
    throw new Error(`Job not found: ${jobId}`);
  }
  if (isActiveStatus(job.status)) {
    throw new Error("Cannot delete an active job. Cancel it first.");
  }
  await removeDirSafe(job.workDir);
  return { deleted: true, id: jobId };
}
var adminDataJobService = {
  initialize,
  listJobs: listJobsInternal,
  getJob,
  getJobLog,
  createDbBackupJob,
  createDbRestoreJob,
  createScreenshotsBackupJob,
  createScreenshotsRestoreJob,
  cancelJob,
  deleteJob,
  createDownloadStream,
  hasActiveDatabaseRestoreJob
};

// src/server/controllers/snapshots.controller.ts
init_utils();
var get2 = catchAsync_default(async (req, res) => {
  const filter = typeof req.query.filter === "string" ? deserializeIfJSON_default(req.query.filter) || {} : {};
  const options = pick_default(req.query, ["sortBy", "limit", "page"]);
  const result = await generic_service_exports.get("VRSSnapshot", filter, options);
  res.send(result);
});

// src/server/middlewares/ensureLogin/ensureLoggedIn.ts
init_models();
init_logger();

// src/server/lib/AppSettings/AppSettings.ts
init_models();

// src/seeds/initialAppSettings.json
var initialAppSettings_default = [
  {
    name: "first_run",
    label: "First Run",
    description: "Indicates if the application is running the first time",
    type: "Boolean",
    value: "true",
    enabled: true
  },
  {
    name: "authentication",
    label: "Authentication",
    description: "Enable application authentication",
    type: "Boolean",
    value: "false",
    enabled: true
  },
  {
    name: "auto_remove_old_checks",
    label: "Auto remove old checks",
    description: "Automatically delete checks older than the configured number of days once per day",
    type: "AutoOldChecks",
    value: {
      days: 365,
      lastRunAt: null
    },
    enabled: false
  },
  {
    name: "auto_remove_old_logs",
    label: "Auto remove old logs",
    description: "Automatically delete logs older than the configured number of days once per day",
    type: "AutoOldLogs",
    value: {
      days: 120,
      lastRunAt: null
    },
    enabled: true
  },
  {
    name: "share_enabled",
    label: "Enable Sharing",
    description: "Allow users to share checks via public links",
    type: "Boolean",
    value: "true",
    enabled: true
  },
  {
    name: "sso_enabled",
    label: "Enable SSO",
    description: "Enable Single Sign-On (SSO) authentication",
    type: "Boolean",
    value: "false",
    enabled: true
  },
  {
    name: "sso_protocol",
    label: "SSO Protocol",
    description: "Protocol used for SSO (oauth2 or saml)",
    type: "String",
    value: "oauth2",
    enabled: true
  },
  {
    name: "sso_entry_point",
    label: "SSO Entry Point",
    description: "URL of the Identity Provider (IdP) entry point",
    type: "String",
    value: "",
    enabled: true
  },
  {
    name: "sso_issuer",
    label: "SSO Issuer",
    description: "Entity ID of the Identity Provider",
    type: "String",
    value: "",
    enabled: true
  },
  {
    name: "sso_client_id",
    label: "SSO Client ID",
    description: "Client ID for OAuth2",
    type: "String",
    value: "",
    enabled: true
  }
];

// src/server/lib/AppSettings/AppSettings.ts
var AppSettings2 = class {
  model;
  cache;
  lastFetch = 0;
  TTL = 30 * 1e3;
  // 30 seconds
  constructor() {
    this.model = AppSettings_model_default;
    this.cache = null;
  }
  async init() {
    await this.refreshCache();
    return this;
  }
  async refreshCache() {
    this.cache = await this.model.find().lean().exec();
    this.lastFetch = Date.now();
  }
  async ensureInitialized() {
    if (!this.cache) {
      await this.refreshCache();
    }
    if (Date.now() - this.lastFetch > this.TTL) {
      await this.refreshCache();
    }
  }
  async count() {
    await this.ensureInitialized();
    return this.model.countDocuments().exec();
  }
  async loadInitialFromFile() {
    await this.ensureInitialized();
    for (const setting of initialAppSettings_default) {
      await this.model.updateOne(
        { name: setting.name },
        { $setOnInsert: setting },
        { upsert: true }
      );
    }
    await this.refreshCache();
  }
  async get(name) {
    await this.ensureInitialized();
    return this.cache.find((x) => x.name === name) || this.model.findOne({ name }).exec();
  }
  async set(name, value) {
    await this.ensureInitialized();
    const item = await this.model.findOneAndUpdate(
      { name },
      { value },
      { new: true }
    ).lean().exec();
    if (!item) {
      throw new Error(`Setting '${name}' not found`);
    }
    const cachedItem = this.cache.find((x) => x.name === name);
    if (cachedItem) {
      cachedItem["value"] = item.value;
    }
  }
  async enable(name) {
    await this.ensureInitialized();
    const item = await this.model.findOneAndUpdate(
      { name },
      { enabled: true },
      { new: true }
    ).lean().exec();
    if (!item) {
      throw new Error(`Setting '${name}' not found`);
    }
    const cachedItem = this.cache.find((x) => x.name === name);
    if (cachedItem) {
      cachedItem["enabled"] = true;
    }
  }
  async disable(name) {
    await this.ensureInitialized();
    const item = await this.model.findOneAndUpdate(
      { name },
      { enabled: false },
      { new: true }
    ).lean().exec();
    if (!item) {
      throw new Error(`Setting '${name}' not found`);
    }
    const cachedItem = this.cache.find((x) => x.name === name);
    if (cachedItem) {
      cachedItem["enabled"] = false;
    }
  }
  async isAuthEnabled() {
    await this.ensureInitialized();
    const envOverride = process.env.SYNGRISI_AUTH_OVERRIDE;
    if (typeof envOverride !== "undefined") {
      if (envOverride === "true") {
        return true;
      }
    }
    const envAuth = process.env.SYNGRISI_AUTH;
    if (envAuth === "true") {
      return true;
    }
    if (envAuth === "false") {
      return false;
    }
    return (await this.get("authentication"))?.value === "true";
  }
  async isFirstRun() {
    await this.ensureInitialized();
    return (await this.get("first_run"))?.value === "true";
  }
};
var appSettings = new AppSettings2();

// src/server/middlewares/ensureLogin/ensureLoggedIn.ts
init_envConfig();
init_hash();

// src/server/lib/startup/createBasicUsers.ts
init_models();

// src/seeds/guest.json
var guest_default = {
  username: "Guest",
  role: "user",
  firstName: "Syngrisi",
  lastName: "Guest",
  salt: "861d0193d9a4cd27597d672dd73100b1218cc7629c59bf18f9faef4480d1f58a",
  password: "cdd8541c318008fe0eee35d311b428c666d7ddf78722735e40b21b0b9e734eabe1b60f6db6deb8f3f101ed559a7365a497bfa73156ba3fadcbb14c5cae441edfe301568a47320abcda308920fecc482b1082468fd2197893561f5d174ecafdfb930bdc8e070fbed07353231d255616d3072e899a4ae1e6ff71f171edc34ba871d091b657a3207b571d2db0f3fce1fb2bc48942657d35aac7fa1fc2ebcbacdc00e46b184e1e5e4988171bcd843f7bb4adb2920df06c4f6b830154234350f9d65cc6e10d445a242c2149a3ce24458bd741035629bb73bdf29e0db4dcd3e37d85332d58d58674ca56b632294135a3354705b3281ea7564b962d544cfcee7069f3065c394098e11999385b077755f9c7e3d42f55b23ecb3366187d3a8ce2475615fbd06d707020274d7f50fe6c824e589955a667210fa031828f832e3cc050d1c0c5360647bfd88d411d3f76e8c6d9c610e2bc5a22da498888d2c94ad25b6448ca2eb9606210b2018136116fda6fdd87b24bcdfe86daefcdaa97d1f9e3a102188a4314a5a4018599cc70dab82157f5d88f7ada008a1f8e0d6e8abf51ed87ebdab982c01645f4f907414d1bb13b1fac410648cc2e94554ecbe0f472aee26623888878139bd44e109d5ce673ecfbe71269d22bea55a03ad4409fc8578de6001f5601fab4cf800be1d420f76586ed9c31214f30c632de99260256c9d2ce45476c135d32",
  apiKey: "4ba2659e94f48ce771a78ee479dc3ab12c2cc6d2484c967b4c564f0a1718a8a254f6aaec3169eb7b29e7d2542d73d378339e311cf912e46fe67eac4eef44636c"
};

// src/server/lib/startup/createBasicUsers.ts
init_logger();
var buildUserInsertPayload = (userData) => ({
  username: userData.username,
  role: userData.role,
  firstName: userData.firstName,
  lastName: userData.lastName,
  provider: "local",
  authSource: "local",
  salt: userData.salt,
  password: userData.password,
  apiKey: userData.apiKey || userData.apikey
});
async function ensureGuestUserExists() {
  const guestInsert = buildUserInsertPayload(guest_default);
  return User_model_default.updateOne(
    { username: "Guest" },
    { $setOnInsert: guestInsert },
    { upsert: true }
  );
}

// src/server/services/share.service.ts
init_hash();
init_models();
init_logger();
init_utils();

// src/server/middlewares/ensureLogin/ensureLoggedIn.ts
var transientGuestUser = {
  username: "Guest",
  role: "user",
  firstName: "Syngrisi",
  lastName: "Guest"
};
var handleBasicAuth = async (req, retryCount = 0) => {
  const logOpts4 = {
    scope: "handleBasicAuth",
    msgType: "AUTH_API"
  };
  const MAX_RETRIES = 10;
  const RETRY_DELAY_MS = 500;
  const AppSettings3 = appSettings;
  const authEnabled = await AppSettings3.isAuthEnabled();
  logger_default.debug(`handleBasicAuth: checking auth`, {
    ...logOpts4,
    authEnabled,
    isAuthenticated: req.isAuthenticated(),
    hasUser: !!req.user,
    username: req.user?.username
  });
  if (req.isAuthenticated()) {
    logger_default.debug(`handleBasicAuth: user already authenticated, returning success`, logOpts4);
    return { type: "success", status: 200 };
  }
  if (!authEnabled) {
    const guest = await User_model_default.findOne({ username: "Guest" });
    if (!guest) {
      if (await adminDataJobService.hasActiveDatabaseRestoreJob()) {
        logger_default.warn("Guest user is temporarily unavailable during active database restore, using transient guest user", logOpts4);
        return {
          type: "success",
          status: 200,
          value: "",
          user: transientGuestUser
        };
      }
      await ensureGuestUserExists().catch(() => void 0);
      if (retryCount < MAX_RETRIES) {
        logger_default.warn(`Guest user not found in handleBasicAuth (attempt ${retryCount + 1}/${MAX_RETRIES}), retrying in ${RETRY_DELAY_MS}ms...`, logOpts4);
        await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
        return handleBasicAuth(req, retryCount + 1);
      }
      logger_default.error(`cannot find Guest user after ${MAX_RETRIES} retries`, logOpts4);
      return {
        type: "redirect",
        status: 301,
        value: `/auth?=Error: cannot find Guest user after ${MAX_RETRIES} retries`,
        user: null
      };
    }
    logger_default.debug(`Auth disabled - setting Guest user directly (bypassing session login)`, logOpts4);
    return {
      type: "success",
      status: 200,
      value: "",
      user: guest
    };
  }
  const result = {
    type: "error",
    status: 400,
    value: "",
    user: null
  };
  if (authEnabled && await AppSettings3.isFirstRun() && !env.SYNGRISI_DISABLE_FIRST_RUN) {
    logger_default.info("first run, set admin password", logOpts4);
    result.type = "redirect";
    result.status = 301;
    result.value = "/auth/change?first_run=true";
    return result;
  }
  if (authEnabled) {
    logger_default.info(`user is not authenticated, will redirected - ${req.originalUrl}`, logOpts4);
    result.type = "redirect";
    result.status = 301;
    if (req?.originalUrl !== "/") {
      result.value = `/auth?origin=${encodeURIComponent(req.originalUrl)}`;
      return result;
    }
    result.value = "/auth";
    return result;
  }
};
function ensureLoggedIn(options) {
  return async (req, res, next) => {
    const result = await handleBasicAuth(req);
    req.user = result.user || req.user;
    if (result.type === "success") {
      return next();
    }
    res.status(result.status).redirect(result.value);
  };
}

// src/server/utils/validateRequest.ts
init_httpStatus();
import { ZodError } from "zod";

// src/server/utils/ServiceResponse.ts
var ServiceResponse = class {
  success;
  message;
  responseObject;
  statusCode;
  constructor(status, message, responseObject, statusCode) {
    this.success = status === 0 /* Success */;
    this.message = message;
    this.responseObject = responseObject;
    this.statusCode = statusCode;
  }
};

// src/server/utils/validateRequest.ts
init_logger();
init_errMsg();
var logOpts3 = {
  scope: "validateRequests",
  itemType: "type",
  msgType: "VALIDATION"
};
var sensitiveKeys = /* @__PURE__ */ new Set([
  "password",
  "currentpassword",
  "newpassword",
  "apikey",
  "sso_client_secret",
  "sso_cert",
  "clientsecret",
  "secret",
  "token"
]);
var sanitizeValueForLogging = (value) => {
  if (Array.isArray(value)) {
    return value.map((item) => sanitizeValueForLogging(item));
  }
  if (value && typeof value === "object") {
    return Object.entries(value).reduce((acc, [key, val]) => {
      if (sensitiveKeys.has(key.toLowerCase())) {
        acc[key] = "[REDACTED]";
      } else {
        acc[key] = sanitizeValueForLogging(val);
      }
      return acc;
    }, Array.isArray(value) ? [] : {});
  }
  return value;
};
function getReceivedValueFromRequest(request, path4) {
  let currentValue = request;
  path4.forEach((segment) => {
    currentValue = currentValue[segment];
  });
  return currentValue;
}
var validateRequest = (schema, endpoint = "") => (req, res, next) => {
  try {
    const parsed = schema.parse({
      body: req.body,
      query: req.query,
      params: req.params
    });
    if (parsed.body) req.body = parsed.body;
    next();
  } catch (err) {
    if (err instanceof ZodError) {
      const zodErrors = Array.isArray(err.errors) ? err.errors : [];
      if (zodErrors.length === 0) {
        logger_default.error(`ZodError with empty errors array! Raw error: ${JSON.stringify(err)}`, logOpts3);
      }
      const sanitizedBody = sanitizeValueForLogging(req.body);
      const sanitizedQuery = sanitizeValueForLogging(req.query);
      const sanitizedParams = sanitizeValueForLogging(req.params);
      const errors = zodErrors.map((e) => {
        const receivedValue = getReceivedValueFromRequest(
          { body: sanitizedBody, query: sanitizedQuery, params: sanitizedParams },
          e.path
        );
        return `
Error path: '${e.path.join(".")}': 
Error ${e.message}, but received ${JSON.stringify(receivedValue)}`;
      }).join(", ");
      const errorMessage = ` ${endpoint ? '\nValidation error in the endpoint: "' + endpoint + '"' : ""}${errors}, 
HTTP PROPERTIES:
	body: ${JSON.stringify(sanitizedBody, null, "	")}, 
	query: ${JSON.stringify(sanitizedQuery, null, "	")}, 
	params: ${JSON.stringify(sanitizedParams, null, "	")}`;
      const statusCode = HttpStatus.BAD_REQUEST;
      logger_default.error(errorMessage, logOpts3);
      res.status(statusCode).send(new ServiceResponse(1 /* Failed */, errorMessage, null, statusCode));
    } else {
      logger_default.error(`Unexpected error: ${errMsg(err)}`, logOpts3);
      next(err);
    }
  }
};

// src/server/schemas/Snapshots.schema.ts
import { z as z3 } from "zod";

// src/server/schemas/utils/commonValidations.ts
import { z as z2 } from "zod";
import { extendZodWithOpenApi as extendZodWithOpenApi2 } from "@asteasolutions/zod-to-openapi";

// src/server/schemas/common/Version.schema.ts
import { z } from "zod";
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
extendZodWithOpenApi(z);
var VersionBaseSchema = z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be in the format "x.y.z"');
var VersionSchema = VersionBaseSchema.transform((value) => {
  const parts = value.split(".");
  return {
    major: parseInt(parts[0]),
    minor: parseInt(parts[1]),
    patch: parseInt(parts[2])
  };
});

// src/server/schemas/utils/commonValidations.ts
extendZodWithOpenApi2(z2);
var mongooseIdRegex = /^[0-9a-fA-F]{24}$/;
var id = z2.string().regex(mongooseIdRegex, {
  message: "Invalid Mongoose ObjectId format: /^[0-9a-fA-F]{24}$/"
}).openapi({
  description: "baseline ID",
  example: "6bbF35cAB3C59dA969edAe79"
});
var commonValidations = {
  id,
  version: VersionBaseSchema.openapi({ example: "1.1.2" }),
  positiveNumberString: z2.string().refine((value) => {
    const num2 = Number(value);
    return Number.isInteger(num2) && num2 >= 0;
  }, {
    message: "String must be a positive number or 0"
  }),
  password: z2.string().min(6).regex(/(?=.*[0-9])/, "Password must include a number").regex(/(?=.*[a-z])/, "Password must include a lowercase letter").regex(/(?=.*[A-Z])/, "Password must include an uppercase letter").refine((value) => {
    return /(?=.*[!@#$%^&*(),.?":{}|<>-])/.test(value);
  }, {
    message: "Password must include a special symbol"
  }).openapi({ example: "Aa1!IJASSNOJ" }),
  username: z2.string().min(1).openapi({ example: "john.doe@example.com" }),
  // TODO: workaround TBD
  date: z2.string().refine((val) => {
    const date = new Date(val);
    return !isNaN(date.getTime());
  }, {
    message: "Invalid date format"
  }),
  paramsId: { params: z2.object({ id }) },
  paramsTestId: { params: z2.object({ testid: id }) },
  success: z2.object({
    message: z2.literal("success")
  })
};

// src/server/schemas/Snapshots.schema.ts
var SnapshotSchema2 = z3.object({
  _id: commonValidations.id,
  name: z3.string().min(1).openapi({
    description: "Name of the snapshot",
    example: "Login page"
  }),
  filename: z3.string().min(1).openapi({
    description: "Filename of the snapshot",
    example: "6651dd4f7c9186e315910b24.png"
  }),
  imghash: z3.string().min(1).openapi({
    description: "Image hash of the snapshot",
    example: "96e8359554f12142bc19e44288295aa67a59cd128e242b6756651bf8e3d9f34caa7f587367ca8e5cdcfbaaf180adfd8825250fc7485784c41de11a9c08c1f9ab"
  }),
  createdDate: z3.string().datetime().openapi({
    description: "Creation date of the snapshot",
    example: "2024-05-25T13:15:30.946Z"
  }),
  id: commonValidations.id.openapi({
    description: "Snapshot identifier",
    example: "6651e47285f83573a821d20e"
  }),
  stabMethod: z3.string().optional(),
  vOffset: z3.number().optional()
});
var SnapshotsResponseSchema = z3.array(
  SnapshotSchema2
);

// src/server/api-docs/openAPIResponseBuilders.ts
init_utils();

// src/server/api-docs/serviceResponse.ts
import { z as z4 } from "zod";
var ServiceResponsePaginationSchema = (dataSchema) => z4.object({
  results: z4.array(dataSchema.optional()),
  page: z4.number().openapi({ example: 1 }),
  limit: z4.number().openapi({ example: 10 }),
  totalPages: z4.number().openapi({ example: 2 }),
  totalResults: z4.number().openapi({ example: 12 }),
  timestamp: z4.number().openapi({ example: 1718035239731968 })
});

// src/server/api-docs/openAPIResponseBuilders.ts
function createPaginatedApiResponse(schema, description, statusCode = httpStatus_default.OK) {
  return {
    [statusCode]: {
      description,
      content: {
        "application/json": {
          schema: ServiceResponsePaginationSchema(schema)
        }
      }
    }
  };
}

// src/server/schemas/utils/createRequestQuerySchema.ts
import { z as z5 } from "zod";
var createRequestQuerySchema = (schema) => z5.object({ query: schema });

// src/server/schemas/common/RequestPagination.schema.ts
import { extendZodWithOpenApi as extendZodWithOpenApi4 } from "@asteasolutions/zod-to-openapi";
import { z as z7 } from "zod";

// src/server/schemas/common/requestQueryFilterSchema.schema.ts
import { extendZodWithOpenApi as extendZodWithOpenApi3 } from "@asteasolutions/zod-to-openapi";
import { z as z6 } from "zod";
extendZodWithOpenApi3(z6);
var requestQueryFilterSchema = z6.string().optional().refine((data) => {
  if (!data) return false;
  try {
    const parsed = JSON.parse(data);
    const valueSchema = z6.lazy(() => z6.union([
      z6.string(),
      z6.number(),
      z6.boolean(),
      z6.array(z6.any()),
      z6.record(z6.string(), z6.any())
    ]));
    const schema = z6.record(z6.string(), valueSchema);
    schema.parse(parsed);
    return true;
  } catch (e) {
    return false;
  }
}, {
  message: "Invalid JSON string or does not match the required schema"
}).openapi({ example: '{"key1": "value1", "key2": 123, "key3": true, "$and":[{"name":"CheckName"}]}' });

// src/server/schemas/common/RequestPagination.schema.ts
extendZodWithOpenApi4(z7);
var RequestPaginationSchema = z7.object({
  filter: requestQueryFilterSchema.optional(),
  limit: commonValidations.positiveNumberString.optional().openapi({ example: "10" }),
  page: commonValidations.positiveNumberString.optional().openapi({ example: "1" }),
  sortBy: z7.string().optional().openapi({ example: "name:desc" }),
  populate: z7.string().optional().openapi({ example: "test" }),
  includeUsage: z7.string().optional().openapi({ example: "true" }),
  baselineSnapshotId: z7.string().optional().openapi({ example: "656dd9e1a9f9dcd4a0c1beef" })
});

// src/server/routes/v1/snapshots.route.ts
var registry = new OpenAPIRegistry();
var router = express.Router();
registry.registerPath({
  method: "get",
  path: "/v1/snapshots",
  summary: "List of snapshots with pagination, and optional filtering and sorting.",
  tags: ["Snapshots"],
  request: { query: RequestPaginationSchema },
  responses: createPaginatedApiResponse(SnapshotsResponseSchema, "Success")
});
router.get(
  "/",
  ensureLoggedIn(),
  validateRequest(createRequestQuerySchema(RequestPaginationSchema), "get, /v1/snapshots"),
  get2
);
var snapshots_route_default = router;
export {
  snapshots_route_default as default,
  registry
};
//# sourceMappingURL=snapshots.route.js.map