UNPKG

@syngrisi/syngrisi

Version:
5,966 lines 185 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 ident;
var init_ident = __esm({
  "src/server/utils/ident.ts"() {
    "use strict";
    ident = ["name", "viewport", "browserName", "os", "app", "branch"];
  }
});

// src/server/utils/buildIdentObject.ts
var MissingIdentFieldError, buildIdentObject;
var init_buildIdentObject = __esm({
  "src/server/utils/buildIdentObject.ts"() {
    "use strict";
    init_ident();
    MissingIdentFieldError = class extends Error {
      constructor(field) {
        super(`Missing required ident field: ${field}`);
        this.name = "MissingIdentFieldError";
      }
    };
    buildIdentObject = (params) => {
      const result = {};
      for (const key of ident) {
        if (key in params && params[key] !== void 0) {
          result[key] = params[key];
        } else {
          throw new MissingIdentFieldError(key);
        }
      }
      return result;
    };
  }
});

// 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, path8, index) => {
      if (index === path8.length - 1) {
        delete obj[path8[index]];
        return;
      }
      deleteAtPath(obj[path8[index]], path8, 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((path8) => {
            if (schema.paths[path8].options && schema.paths[path8].options.private) {
              deleteAtPath(ret, path8.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, Snapshot_model_default;
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);
    Snapshot_model_default = Snapshot;
  }
});

// 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, Suite_model_default;
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);
    Suite_model_default = Suite;
  }
});

// src/server/models/Run.model.ts
import mongoose8, { Schema as Schema7 } from "mongoose";
var RunSchema, Run, Run_model_default;
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);
    Run_model_default = Run;
  }
});

// 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, Test_model_default;
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);
    Test_model_default = Test;
  }
});

// src/server/models/Webhook.model.ts
import mongoose12, { Schema as Schema11 } from "mongoose";
var WebhookSchema, Webhook, Webhook_model_default;
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);
    Webhook_model_default = Webhook;
  }
});

// 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, DomSnapshot_model_default;
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);
    DomSnapshot_model_default = DomSnapshot;
  }
});

// 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 calculateAcceptedStatus;
var init_calculateAcceptedStatus = __esm({
  "src/server/utils/calculateAcceptedStatus.ts"() {
    "use strict";
    init_models();
    calculateAcceptedStatus = async function calculateAcceptedStatus2(testId) {
      const checksInTest = await Check_model_default.find({ test: testId });
      const statuses = checksInTest.map((x) => x.markedAs);
      if (statuses.length < 1) {
        return "Unaccepted";
      }
      let testCalculatedStatus = "Unaccepted";
      if (statuses.some((x) => x === "accepted")) {
        testCalculatedStatus = "Partially";
      }
      if (statuses.every((x) => x === "accepted")) {
        testCalculatedStatus = "Accepted";
      }
      return testCalculatedStatus;
    };
  }
});

// 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";
function hashSync(input) {
  return createHash("sha512").update(input).digest("hex");
}
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 logOpts3, HookRegistry, hookRegistry;
var init_HookRegistry = __esm({
  "src/server/plugins/core/HookRegistry.ts"() {
    "use strict";
    init_logger();
    logOpts3 = {
      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})`, logOpts3);
      }
      /**
       * 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}'`, logOpts3);
          }
        }
      }
      /**
       * 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}'`, logOpts3);
            const result = await hook.handler(
              req,
              res,
              context
            );
            if (result !== null) {
              logger_default.debug(`Auth hook '${hook.pluginName}' returned result: ${result.authenticated}`, logOpts3);
              return result;
            }
          } catch (error) {
            logger_default.error(`Error in auth:validate hook from '${hook.pluginName}': ${error}`, logOpts3);
          }
        }
        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}'`, logOpts3);
            const result = await hook.handler(
              currentContext,
              pluginContext
            );
            if ("skip" in result && result.skip) {
              logger_default.info(`Check comparison skipped by plugin '${hook.pluginName}'`, logOpts3);
              return result;
            }
            currentContext = result;
          } catch (error) {
            logger_default.error(`Error in check:beforeCompare hook from '${hook.pluginName}': ${error}`, logOpts3);
          }
        }
        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}'`, logOpts3);
            currentResult = await hook.handler(
              context,
              currentResult,
              pluginContext
            );
          } catch (error) {
            logger_default.error(`Error in check:afterCompare hook from '${hook.pluginName}': ${error}`, logOpts3);
          }
        }
        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", logOpts3);
      }
    };
    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 logOpts4, PluginManager, pluginManager;
var init_PluginManager = __esm({
  "src/server/plugins/core/PluginManager.ts"() {
    "use strict";
    init_logger();
    init_context();
    init_HookRegistry();
    logOpts4 = {
      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", logOpts4);
        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`, logOpts4);
          return;
        }
        if (manifest.enabled === false) {
          logger_default.info(`Plugin '${pluginName}' is disabled, skipping`, logOpts4);
          return;
        }
        logger_default.info(`Loading plugin: ${pluginName} v${manifest.version}`, logOpts4);
        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`, logOpts4);
        } 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}`, logOpts4);
          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`, logOpts4);
          return;
        }
        logger_default.info(`Unloading plugin: ${pluginName}`, logOpts4);
        try {
          if (loadedPlugin.plugin.onUnload) {
            await loadedPlugin.plugin.onUnload();
          }
          this.registry.unregister(pluginName);
          this.plugins.delete(pluginName);
          logger_default.info(`Plugin '${pluginName}' unloaded successfully`, logOpts4);
        } catch (error) {
          logger_default.error(`Error unloading plugin '${pluginName}': ${error}`, logOpts4);
          throw error;
        }
      }
      /**
       * Unload all plugins
       */
      async unloadAll() {
        logger_default.info("Unloading all plugins", logOpts4);
        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}`, logOpts4);
          }
        }
        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/runs.route.ts
import express from "express";
import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";

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

// src/server/services/run.service.ts
var run_service_exports = {};
__export(run_service_exports, {
  remove: () => remove2
});
init_models();
init_logger();

// src/server/services/test.service.ts
import { Types as Types2 } from "mongoose";
init_models();
init_logger();
var remove = async (id2, user) => {
  const logOpts6 = {
    scope: "removeTest",
    itemType: "test",
    ref: id2,
    user: user?.username,
    msgType: "REMOVE"
  };
  logger_default.info(`remove test with, id: '${id2}', user: '${user.username}'`, logOpts6);
  try {
    logger_default.debug(`try to delete all checks associated to test with ID: '${id2}'`, logOpts6);
    const checks = await Check_model_default.find({ test: id2 });
    for (const check of checks) {
      await check_service_exports.remove(check._id, user);
    }
    return Test_model_default.findByIdAndDelete(id2);
  } catch (e) {
    logger_default.error(`cannot remove test with id: ${id2} error: ${e instanceof Error ? e.stack : String(e)}`, logOpts6);
    throw new Error();
  }
};

// src/server/services/run.service.ts
init_utils();
init_utils();
var remove2 = async (id2, user) => {
  const logOpts6 = {
    scope: "removeRun",
    itemType: "run",
    ref: id2,
    user: user?.username,
    msgType: "REMOVE"
  };
  logger_default.info(`remove run with, id: '${id2}', user: '${user.username}'`, logOpts6);
  const tests = await Test_model_default.find({ run: id2 }).exec();
  for (const test of tests) {
    await remove(test._id, user);
  }
  const run = await Run_model_default.findByIdAndDelete(id2).exec();
  if (!run) {
    throw new ApiError_default(httpStatus_default.NOT_FOUND, `cannot remove run with id: '${id2}', not found`);
  }
  return run;
};

// 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 logOpts6 = {
    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)}'`, logOpts6);
  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`, logOpts6);
  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 fs2 from "fs/promises";
import path2 from "path";
import { Types as Types3 } from "mongoose";

// src/server/utils/domDumpUtils.ts
import { gunzipSync, gzipSync } from "zlib";
import { createHash as createHash2 } from "crypto";
function isCompressedDomDump(data) {
  return data !== null && typeof data === "object" && "compressed" in data && data.compressed === true && "data" in data && typeof data.data === "string";
}
function decompressDomDump(data) {
  if (!data) return null;
  try {
    if (typeof data === "object" && !isCompressedDomDump(data)) {
      return data;
    }
    if (isCompressedDomDump(data)) {
      const buffer = Buffer.from(data.data, "base64");
      const decompressed = gunzipSync(buffer).toString("utf8");
      return JSON.parse(decompressed);
    }
    if (typeof data === "string") {
      const parsed = JSON.parse(data);
      if (isCompressedDomDump(parsed)) {
        return decompressDomDump(parsed);
      }
      return parsed;
    }
    return null;
  } catch (e) {
    console.error("Failed to decompress domDump:", e);
    return null;
  }
}
function calculateHash(data) {
  const input = typeof data === "string" ? Buffer.from(data, "utf8") : data;
  return createHash2("sha256").update(input).digest("hex");
}
function prepareDomDumpForStorage(data, compressionHeader) {
  if (!data) {
    return { content: null, wasCompressed: false, originalSize: 0 };
  }
  let wasCompressed = false;
  let content = null;
  let originalSize = 0;
  if (compressionHeader === "gzip") {
    wasCompressed = true;
  }
  if (typeof data === "string") {
    originalSize = Buffer.byteLength(data, "utf8");
    try {
      const parsed = JSON.parse(data);
      if (isCompressedDomDump(parsed)) {
        wasCompressed = true;
        originalSize = parsed.originalSize;
        content = decompressDomDump(parsed);
      } else {
        content = parsed;
      }
    } catch {
      return { content: null, wasCompressed: false, originalSize: 0 };
    }
  } else if (isCompressedDomDump(data)) {
    wasCompressed = true;
    originalSize = data.originalSize;
    content = decompressDomDump(data);
  } else {
    content = data;
    originalSize = Buffer.byteLength(JSON.stringify(data), "utf8");
  }
  return { content, wasCompressed, originalSize };
}
function serializeForStorage(data) {
  const jsonString = JSON.stringify(data);
  const originalSize = Buffer.byteLength(jsonString, "utf8");
  const compressed = gzipSync(Buffer.from(jsonString, "utf8"));
  return {
    buffer: compressed,
    compressed: true,
    originalSize,
    compressedSize: compressed.length
  };
}
function deserializeFromStorage(buffer, isCompressed) {
  try {
    if (isCompressed) {
      const decompressed = gunzipSync(buffer).toString("utf8");
      return JSON.parse(decompressed);
    }
    return JSON.parse(buffer.toString("utf8"));
  } catch (e) {
    console.error("Failed to deserialize domDump from storage:", e);
    return null;
  }
}

// src/server/services/dom-snapshot.service.ts
init_logger();
async function createDomSnapshot(params) {
  const { checkId, baselineId, type, content, compressionHeader } = params;
  const { content: parsedContent, wasCompressed, originalSize } = prepareDomDumpForStorage(
    content,
    compressionHeader
  );
  if (!parsedContent) {
    logger_default.debug("No DOM content to store");
    return null;
  }
  const contentString = JSON.stringify(parsedContent);
  const hash = calculateHash(contentString);
  const existingSnapshot = await DomSnapshot_model_default.findOne({ hash });
  if (existingSnapshot) {
    logger_default.debug(`Found existing DOM snapshot with hash ${hash}, creating reference`);
    const snapshot2 = await DomSnapshot_model_default.create({
      checkId: new Types3.ObjectId(checkId),
      baselineId: baselineId ? new Types3.ObjectId(baselineId) : void 0,
      type,
      filename: existingSnapshot.filename,
      hash,
      compressed: existingSnapshot.compressed,
      originalSize,
      compressedSize: existingSnapshot.compressedSize
    });
    return snapshot2;
  }
  const { buffer, compressed, compressedSize } = serializeForStorage(parsedContent);
  const filename = `${checkId}_${type}_${Date.now()}.dom.gz`;
  const filePath = path2.join(config.domSnapshotsPath, filename);
  await fs2.writeFile(filePath, buffer);
  logger_default.debug(`Saved DOM snapshot to ${filePath} (${compressedSize} bytes compressed, ${originalSize} bytes original)`);
  const snapshot = await DomSnapshot_model_default.create({
    checkId: new Types3.ObjectId(checkId),
    baselineId: baselineId ? new Types3.ObjectId(baselineId) : void 0,
    type,
    filename,
    hash,
    compressed,
    originalSize,
    compressedSize
  });
  return snapshot;
}
async function getDomContent(snapshotId) {
  const snapshot = await DomSnapshot_model_default.findById(snapshotId);
  if (!snapshot) {
    logger_default.warn(`DOM snapshot not found: ${snapshotId}`);
    return null;
  }
  return getDomContentBySnapshot(snapshot);
}
async function getDomContentBySnapshot(snapshot) {
  const filePath = path2.join(config.domSnapshotsPath, snapshot.filename);
  try {
    const buffer = await fs2.readFile(filePath);
    return deserializeFromStorage(buffer, snapshot.compressed);
  } catch (e) {
    logger_default.error(`Failed to read DOM snapshot file: ${filePath}`, e);
    return null;
  }
}
async function getDomSnapshotByCheckId(checkId, type = "actual") {
  return DomSnapshot_model_default.findOne({
    checkId: new Types3.ObjectId(checkId),
    type
  });
}
async function getDomSnapshotByBaselineId(baselineId) {
  return DomSnapshot_model_default.findOne({
    baselineId: new Types3.ObjectId(baselineId),
    type: "baseline"
  });
}
async function linkDomSnapshotToBaseline(checkId, baselineId) {
  const actualSnapshot = await getDomSnapshotByCheckId(checkId, "actual");
  if (!actualSnapshot) {
    logger_default.debug(`No DOM snapshot found for check ${checkId}`);
    return null;
  }
  const baselineSnapshot = await DomSnapshot_model_default.create({
    checkId: new Types3.ObjectId(checkId),
    baselineId: new Types3.ObjectId(baselineId),
    type: "baseline",
    filename: actualSnapshot.filename,
    hash: actualSnapshot.hash,
    compressed: actualSnapshot.compressed,
    originalSize: actualSnapshot.originalSize,
    compressedSize: actualSnapshot.compressedSize
  });
  logger_default.debug(`Linked DOM snapshot to baseline ${baselineId}`);
  return baselineSnapshot;
}
async function removeDomSnapshotsByCheckId(checkId) {
  const snapshots = await DomSnapshot_model_default.find({ checkId: new Types3.ObjectId(checkId) });
  for (const snapshot of snapshots) {
    const otherSnapshots = await DomSnapshot_model_default.countDocuments({
      filename: snapshot.filename,
      _id: { $ne: snapshot._id }
    });
    if (otherSnapshots === 0) {
      const filePath = path2.join(config.domSnapshotsPath, snapshot.filename);
      try {
        await fs2.unlink(filePath);
        logger_default.debug(`Deleted DOM snapshot file: ${filePath}`);
      } catch (e) {
        logger_default.warn(`Failed to delete DOM snapshot file: ${filePath}`, e);
      }
    }
    await snapshot.deleteOne();
  }
}
async function removeDomSnapshotsByBaselineId(baselineId) {
  const snapshots = await DomSnapshot_model_default.find({ baselineId: new Types3.ObjectId(baselineId) });
  for (const snapshot of snapshots) {
    const otherSnapshots = await DomSnapshot_model_default.countDocuments({
      filename: snapshot.filename,
      _id: { $ne: snapshot._id }
    });
    if (otherSnapshots === 0) {
      const filePath = path2.join(config.domSnapshotsPath, snapshot.filename);
      try {
        await fs2.unlink(filePath);
        logger_default.debug(`Deleted DOM snapshot file: ${filePath}`);
      } catch (e) {
        logger_default.warn(`Failed to delete DOM snapshot file: ${filePath}`, e);
      }
    }
    await snapshot.deleteOne();
  }
}
var domSnapshotService = {
  createDomSnapshot,
  getDomContent,
  getDomContentBySnapshot,
  getDomSnapshotByCheckId,
  getDomSnapshotByBaselineId,
  linkDomSnapshotToBaseline,
  removeDomSnapshotsByCheckId,
  removeDomSnapshotsByBaselineId
};

// 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();
import fs3, { promises as fsp } from "fs";
import path3 from "path";
async function createSnapshot(parameters, session) {
  const logOpts6 = {
    scope: "createSnapshot",
    itemType: "snapshot",
    msgType: "CREATE"
  };
  const { name, fileData, hashCode } = parameters;
  const opts = { name };
  if (fileData === null) {
    throw new ApiError_default(httpStatus_default.BAD_REQUEST, `cannot create the snapshot, the 'fileData' is not set, name: '${name}'`);
  }
  opts.imghash = hashCode || hashSync(fileData);
  const snapshot = new Snapshot_model_default(opts);
  const filename = `${snapshot.id}.png`;
  const imagePath = path3.join(config.defaultImagesPath, filename);
  logger_default.debug(`save screenshot for: '${name}' snapshot to: '${imagePath}'`, logOpts6);
  await fsp.writeFile(imagePath, fileData);
  snapshot.filename = filename;
  await snapshot.save({ session });
  logger_default.debug(`snapshot was saved: '${JSON.stringify(snapshot)}'`, { ...logOpts6, ...{ ref: String(snapshot._id) } });
  return snapshot;
}

// 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";
var logOpts = {
  scope: "dbitems",
  msgType: "DB"
};
async function updateItemDate(mdClass, id2, session) {
  logger_default.debug(`update date for the item: '${mdClass}' with id: '${id2}'`, logOpts);
  const itemModel = await mongoose20.model(mdClass).findById(id2).session(session || null);
  const updatedItem = await itemModel?.updateOne({ updatedDate: Date.now() }, { session });
  logger_default.debug(`'${mdClass}' date updated: '${JSON.stringify(itemModel)}'`, logOpts);
  return updatedItem;
}

// 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
var check_service_exports = {};
__export(check_service_exports, {
  accept: () => accept,
  createCheckDocument: () => createCheckDocument,
  enrichChecksWithCurrentAcceptance: () => enrichChecksWithCurrentAcceptance,
  recompare: () => recompare,
  remove: () => remove4,
  update: () => update
});
init_models();
init_utils();
import { Types as Types5 } from "mongoose";

// src/server/services/snapshot.service.ts
init_config();
init_models();
init_logger();
import fs4 from "fs";
import path4 from "path";
var logOpts2 = {
  scope: "snapshot_helper",
  msgType: "API"
};
var removeSnapshotFile = async (snapshot) => {
  let relatedSnapshots;
  if (snapshot.filename) {
    relatedSnapshots = await Snapshot_model_default.find({ filename: snapshot.filename });
    logger_default.debug(`there are '${relatedSnapshots.length}' snapshots with filename: '${snapshot.filename}'`, logOpts2);
  }
  const isLastSnapshotFile = () => {
    if (!snapshot.filename) {
      return true;
    }
    return relatedSnapshots.length === 0;
  };
  logger_default.debug({ isLastSnapshotFile: isLastSnapshotFile() });
  if (isLastSnapshotFile()) {
    const imagePath = path4.join(config.defaultImagesPath, snapshot.filename);
    logger_default.silly(`path: ${imagePath}`, logOpts2);
    if (fs4.existsSync(imagePath)) {
      logger_default.debug(`removing file: '${imagePath}'`, logOpts2, {
        msgType: "REMOVE",
        itemType: "file"
      });
      fs4.unlinkSync(imagePath);
    }
  }
};
var remove3 = async (id2) => {
  const logOpts6 = {
    scope: "removeSnapshot",
    msgType: "REMOVE",
    itemType: "snapshot",
    ref: id2
  };
  if (!id2) {
    return;
  }
  const baseline = await Baseline_model_default.findOne({ snapshootId: id2 });
  if (baseline) {
    logger_default.debug(`snapshot: '${id2}' is related to a baseline, skipping deletion`, logOpts6);
    return;
  }
  const snapshot = await Snapshot_model_default.findById(id2);
  if (snapshot) {
    await removeSnapshotFile(snapshot);
    await Snapshot_model_default.findByIdAndDelete(id2);
  } else {
  }
};

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

// src/server/services/webhook.service.ts
init_models();
init_logger();
var triggerWebhooks = async (event, payload) => {
  const webhooks = await Webhook_model_default.find({ events: event });
  for (const webhook of webhooks) {
    try {
      logger_default.info(`Triggering webhook ${webhook.url} for event ${event}`);
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 5e3);
      const response = await fetch(webhook.url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Syngrisi-Event": event,
          "X-Syngrisi-Secret": webhook.secret || ""
        },
        body: JSON.stringify({
          event,
          payload,
          timestamp: (/* @__PURE__ */ new Date()).toISOString()
        }),
        signal: controller.signal
      });
      clearTimeout(timeoutId);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
    } catch (error) {
      logger_default.error(`Failed to trigger webhook ${webhook.url}: ${error}`);
    }
  }
};
var webhookService = {
  triggerWebhooks
};

// src/server/services/comparison.service.ts
init_models();
init_config();
import path6 from "path";
import { promises as fsp2 } from "fs";

// src/server/lib/comparison/comparator.ts
init_utils();
init_logger();
import { Worker } from "worker_threads";
import fs5 from "fs";
import path5 from "path";
import { fileURLToPath } from "url";
import { createRequire } from "module";
var DEFAULT_OPTIONS = {
  output: {
    largeImageThreshold: 0,
    outputDiff: true,
    errorType: "flat",
    errorColor: { red: 255, green: 0, blue: 255 },
    transparency: 0
  },
  ignore: "nothing"
};
var __filename = fileURLToPath(import.meta.url);
var __dirname = path5.dirname(__filename);
var resolveWorkerScript = () => {
  const candidates = [
    // 1. Local development (relative to original source file)
    path5.join(__dirname, "imageDiffWorker.js"),
    // 2. Bundled mode (after tsup bundles into server.js, __dirname points to dist/server/)
    path5.join(__dirname, "lib", "comparison", "imageDiffWorker.js"),
    // 3. Legacy fallback (cwd-based)
    path5.join(process.cwd(), "dist", "server", "lib", "comparison", "imageDiffWorker.js")
  ];
  try {
    const require2 = createRequire(import.meta.url);
    const pkgPath = require2.resolve("@syngrisi/syngrisi/package.json");
    const pkgDir = path5.dirname(pkgPath);
    candidates.push(path5.join(pkgDir, "dist", "server", "lib", "comparison", "imageDiffWorker.js"));
  } catch {
  }
  for (const candidate of candidates) {
    if (fs5.existsSync(candidate)) return candidate;
  }
  return null;
};
var normalizeOptions = (options = {}) => {
  const mergedOutput = { ...DEFAULT_OPTIONS.output, ...options.output || {} };
  return {
    ...DEFAULT_OPTIONS,
    ...options,
    output: mergedOutput,
    ignoreRectangles: options.ignoredBoxes
  };
};
var runDiffInWorker = (baselineOrigin, actualOrigin, options) => new Promise((resolve, reject) => {
  const script = resolveWorkerScript();
  if (!script) throw new Error("Image diff worker script is missing");
  const worker = new Worker(script, {
    workerData: {
      baselineOrigin,
      actualOrigin,
      options: normalizeOptions(options)
    }
  });
  worker.on("message", (message) => {
    if (!message.ok) {
      reject(new Error(message.error || "Image diff worker failed"));
      return;
    }
    const diff = message.result || {};
    if (message.diffBuffer) {
      const bufferCopy = Buffer.from(message.diffBuffer);
      diff.getBuffer = () => bufferCopy;
    }
    resolve(diff);
  });
  worker.on("error", (err) => {
    reject(err);
  });
  worker.on("exit", (code) => {
    if (code !== 0) {
      reject(new Error(`Image diff worker stopped with exit code ${code}`));
    }
  });
});
async function getDiff(baselineOrigin, actualOrigin, opts = {}) {
  const logOpts6 = {
    scope: "getDiff",
    itemType: "image",
    msgType: "GET_DIFF"
  };
  try {
    const executionTimer = process.hrtime();
    logger_default.debug(`SAMPLE #1: ${process.hrtime(executionTimer).toString()}`, logOpts6);
    const directDiff = await runDiffInWorker(baselineOrigin, actualOrigin, opts);
    logger_default.debug(`SAMPLE #2: ${process.hrtime(executionTimer).toString()}`, logOpts6);
    directDiff.executionTotalTime = process.hrtime(executionTimer).toString();
    logger_default.debug(`SAMPLE #3: ${process.hrtime(executionTimer).toString()}`, logOpts6);
    logger_default.debug(`the diff is: ${JSON.stringify(directDiff, null, 4)}`, logOpts6);
    return directDiff;
  } catch (e) {
    logger_default.error(errMsg(e), logOpts6);
    throw new Error(errMsg(e));
  }
}

// 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/plugins/index.ts
async function executeBeforeCompareHook(checkContext) {
  if (!hookRegistry.hasHooks("check:beforeCompare")) {
    return checkContext;
  }
  const pluginContext = buildPluginContext();
  return hookRegistry.executeCheckBeforeCompare(checkContext, pluginContext);
}
async function executeAfterCompareHook(checkContext, compareResult) {
  if (!hookRegistry.hasHooks("check:afterCompare")) {
    return compareResult;
  }
  const pluginContext = buildPluginContext();
  return hookRegistry.executeCheckAfterCompare(checkContext, compareResult, pluginContext);
}

// src/server/services/comparison.service.ts
var isWithinToleranceThreshold = (rawMismatch, threshold) => {
  if (!Number.isFinite(rawMismatch) || !Number.isFinite(threshold)) return false;
  const normalizedThreshold = Math.max(0, Math.min(100, threshold));
  return rawMismatch > 0 && rawMismatch <= normalizedThreshold;
};
var compareSnapshots = async (baselineSnapshot, actual, opts = {}) => {
  const logOpts6 = {
    scope: "compareSnapshots",
    ref: baselineSnapshot.id,
    itemType: "snapshot",
    msgType: "COMPARE"
  };
  try {
    logger_default.debug(`compare baseline and actual snapshots with ids: [${baselineSnapshot.id}, ${actual.id}]`, logOpts6);
    logger_default.debug(`current baseline snapshot: ${JSON.stringify(baselineSnapshot)}`, logOpts6);
    let diff;
    if (baselineSnapshot.imghash === actual.imghash) {
      logger_default.debug(`baseline and actual snapshot have the identical image hashes: '${baselineSnapshot.imghash}'`, logOpts6);
      diff = {
        isSameDimensions: true,
        dimensionDifference: { width: 0, height: 0 },
        rawMisMatchPercentage: 0,
        misMatchPercentage: "0.00",
        analysisTime: 0,
        executionTotalTime: "0",
        getBuffer: null
      };
    } else {
      if (!baselineSnapshot.filename || !actual.filename) {
        throw new Error("Snapshot filename is missing");
      }
      const baselinePath = path6.join(config.defaultImagesPath, baselineSnapshot.filename);
      const actualPath = path6.join(config.defaultImagesPath, actual.filename);
      const baselineData = await fsp2.readFile(baselinePath);
      const actualData = await fsp2.readFile(actualPath);
      logger_default.debug(`baseline path: ${baselinePath}`, logOpts6);
      logger_default.debug(`actual path: ${actualPath}`, logOpts6);
      diff = await getDiff(baselineData, actualData, opts);
    }
    logger_default.silly(`the diff is: '${JSON.stringify(diff, null, 2)}'`);
    if (diff.rawMisMatchPercentage.toString() !== "0") {
      logger_default.debug(`images are different, ids: [${baselineSnapshot.id}, ${actual.id}], rawMisMatchPercentage: '${diff.rawMisMatchPercentage}'`);
    }
    if (diff.stabMethod && diff.vOffset) {
      if (diff.stabMethod === "downup") {
        actual.vOffset = -diff.vOffset;
        await actual.save();
      }
      if (diff.stabMethod === "updown") {
        baselineSnapshot.vOffset = -diff.vOffset;
        await baselineSnapshot.save();
      }
    }
    return diff;
  } catch (e) {
    const errMsg2 = `cannot compare snapshots: ${e}
 ${e instanceof Error ? e.stack : e}`;
    logger_default.error(errMsg2, logOpts6);
    throw new Error(String(e));
  }
};
var ignoreDifferentResolutions = ({ height, width }) => {
  if (width === 0 && height === -1) return true;
  if (width === 0 && height === 1) return true;
  return false;
};
var compareCheck = async (expectedSnapshot, actualSnapshot, newCheckParams, skipSaveOnCompareError, currentUser, session) => {
  const logOpts6 = {
    scope: "createCheck.compare",
    user: currentUser.username,
    itemType: "check",
    msgType: "COMPARE"
  };
  const executionTimer = process.hrtime();
  const compareResult = {};
  compareResult.failReasons = [...newCheckParams.failReasons];
  let checkCompareResult;
  let diffSnapshot = null;
  const areSnapshotsDifferent = (result) => result.rawMisMatchPercentage.toString() !== "0";
  const areSnapshotsWrongDimensions = (result) => !result.isSameDimensions && !ignoreDifferentResolutions(result.dimensionDifference);
  if (newCheckParams.status !== "new" && !compareResult.failReasons.includes("not_accepted")) {
    try {
      logger_default.debug(`'the check with name: '${newCheckParams.name}' isn't new, make comparing'`, logOpts6);
      const baseline = await Baseline_model_default.findOne({ snapshootId: expectedSnapshot._id }).exec();
      const compareOptions = { vShifting: newCheckParams.vShifting };
      if (baseline) {
        if (baseline.ignoreRegions) {
          logger_default.debug(`ignore regions: '${baseline.ignoreRegions}', type: '${typeof baseline.ignoreRegions}'`);
          compareOptions.ignoredBoxes = JSON.parse(baseline.ignoreRegions);
        }
        compareOptions.ignore = baseline.matchType || "nothing";
      }
      const apiThreshold = typeof newCheckParams.toleranceThreshold === "number" ? newCheckParams.toleranceThreshold : null;
      const baselineThreshold = Number(baseline?.toleranceThreshold || 0);
      const toleranceThreshold = Math.max(0, Math.min(100, apiThreshold ?? baselineThreshold));
      const toleranceSource = apiThreshold !== null ? "api" : "baseline";
      const checkContext = {
        expectedSnapshot,
        actualSnapshot,
        checkParams: newCheckParams,
        baseline: baseline || void 0,
        compareOptions
      };
      const beforeHookResult = await executeBeforeCompareHook(checkContext);
      if ("skip" in beforeHookResult && beforeHookResult.skip) {
        logger_default.info(`Comparison skipped by plugin, using override result`, logOpts6);
        const overrideResult = beforeHookResult.result;
        return {
          failReasons: overrideResult.failReasons || [],
          status: overrideResult.status,
          result: overrideResult.result || JSON.stringify({ pluginOverride: true })
        };
      }
      checkCompareResult = await compareSnapshots(expectedSnapshot, actualSnapshot, compareOptions);
      logger_default.silly(`ignoreDifferentResolutions: '${ignoreDifferentResolutions(checkCompareResult.dimensionDifference)}'`);
      logger_default.silly(`dimensionDifference: '${JSON.stringify(checkCompareResult.dimensionDifference)}`);
      if (areSnapshotsDifferent(checkCompareResult) && ignoreDifferentResolutions(checkCompareResult.dimensionDifference)) {
        const baselineDims = checkCompareResult.baselineDimensions;
        const actualDims = checkCompareResult.actualDimensions;
        if (baselineDims && actualDims) {
          const heightDiff = actualDims.height - baselineDims.height;
          let ignoredBox;
          if (heightDiff === 1) {
            ignoredBox = { left: 0, top: actualDims.height - 1, right: actualDims.width, bottom: actualDims.height };
          } else if (heightDiff === -1) {
            ignoredBox = { left: 0, top: baselineDims.height - 1, right: baselineDims.width, bottom: baselineDims.height };
          }
          if (ignoredBox) {
            logger_default.debug(`Retrying comparison with ignored box for 1px diff: ${JSON.stringify(ignoredBox)}`, logOpts6);
            const retryOptions = { ...compareOptions };
            retryOptions.ignoredBoxes = retryOptions.ignoredBoxes ? [...retryOptions.ignoredBoxes, ignoredBox] : [ignoredBox];
            const retryResult = await compareSnapshots(expectedSnapshot, actualSnapshot, retryOptions);
            if (!areSnapshotsDifferent(retryResult)) {
              logger_default.debug(`Retry passed with ignored box`, logOpts6);
              checkCompareResult = retryResult;
            }
          }
        }
      }
      const rawMismatch = Number(checkCompareResult.rawMisMatchPercentage || 0);
      const mismatchWithinTolerance = isWithinToleranceThreshold(rawMismatch, toleranceThreshold);
      if (mismatchWithinTolerance) {
        logger_default.debug(`mismatch '${rawMismatch}' is within tolerance '${toleranceThreshold}', mark check as passed`, logOpts6);
      }
      if (areSnapshotsDifferent(checkCompareResult) && !mismatchWithinTolerance || areSnapshotsWrongDimensions(checkCompareResult)) {
        let logMsg;
        if (areSnapshotsWrongDimensions(checkCompareResult)) {
          logMsg = "snapshots have different dimensions";
          compareResult.failReasons.push("wrong_dimensions");
        }
        if (areSnapshotsDifferent(checkCompareResult) && !mismatchWithinTolerance) {
          logMsg = "snapshots have differences";
          compareResult.failReasons.push("different_images");
        }
        if (logMsg) logger_default.debug(logMsg, logOpts6);
        logger_default.debug(`saving diff snapshot for check with name: '${newCheckParams.name}'`, logOpts6);
        if (!skipSaveOnCompareError) {
          diffSnapshot = await createSnapshot({
            name: newCheckParams.name,
            fileData: checkCompareResult.getBuffer()
          }, session);
          compareResult.diffId = diffSnapshot.id;
          compareResult.diffSnapshot = diffSnapshot;
        }
        compareResult.status = "failed";
      } else {
        compareResult.status = "passed";
      }
      checkCompareResult.appliedToleranceThreshold = toleranceThreshold;
      checkCompareResult.passedByTolerance = mismatchWithinTolerance;
      checkCompareResult.toleranceSource = toleranceSource;
      checkCompareResult.totalCheckHandleTime = process.hrtime(executionTimer).toString();
      compareResult.result = JSON.stringify(checkCompareResult, null, "	");
    } catch (e) {
      compareResult.status = "failed";
      compareResult.result = JSON.stringify({ server_error: `error during comparing - ${errMsg(e)}` });
      compareResult.failReasons.push("internal_server_error");
      throw new ApiError_default(httpStatus_default.INTERNAL_SERVER_ERROR, `error during comparing: ${errMsg(e)}`);
    }
  }
  if (compareResult.failReasons.length > 0) {
    compareResult.status = "failed";
  }
  const checkContextForAfterHook = {
    expectedSnapshot,
    actualSnapshot,
    checkParams: newCheckParams
  };
  const finalResult = await executeAfterCompareHook(
    checkContextForAfterHook,
    compareResult
  );
  return finalResult;
};

// src/server/services/check.service.ts
async function calculateTestStatus(testId) {
  const checksInTest = await Check_model_default.find({ test: testId });
  const statuses = checksInTest.map((x) => x.status[0]);
  let testCalculatedStatus = "Failed";
  if (statuses.every((x) => x === "new" || x === "passed")) {
    testCalculatedStatus = "Passed";
  }
  if (statuses.every((x) => x === "new")) {
    testCalculatedStatus = "New";
  }
  return testCalculatedStatus;
}
var validateBaselineParam = (params) => {
  const mandatoryParams = ["markedAs", "markedById", "markedByUsername", "markedDate"];
  for (const param of mandatoryParams) {
    if (!params[param]) {
      const errMsg2 = `invalid baseline parameters, '${param}' is empty, params: ${JSON.stringify(params)}`;
      logger_default.error(errMsg2);
      throw new Error(errMsg2);
    }
  }
};
async function createNewBaseline(params) {
  const logOpts6 = {
    scope: "createNewBaseline",
    msgType: "CREATE"
  };
  validateBaselineParam(params);
  const identFields = buildIdentObject(params);
  const lastBaseline = await Baseline_model_default.findOne(identFields).sort({ createdDate: -1 }).exec();
  const filter = { ...identFields, snapshootId: params.actualSnapshotId };
  const baselineParams = { ...identFields };
  if (lastBaseline?.ignoreRegions) {
    baselineParams.ignoreRegions = lastBaseline.ignoreRegions;
  }
  if (typeof lastBaseline?.toleranceThreshold === "number") {
    baselineParams.toleranceThreshold = lastBaseline.toleranceThreshold;
  }
  const update2 = {
    $setOnInsert: {
      ...baselineParams,
      snapshootId: params.actualSnapshotId,
      createdDate: /* @__PURE__ */ new Date()
    },
    $set: {
      markedAs: params.markedAs,
      markedById: params.markedById,
      markedByUsername: params.markedByUsername,
      lastMarkedDate: params.markedDate
    }
  };
  try {
    const baseline = await Baseline_model_default.findOneAndUpdate(
      filter,
      update2,
      { new: true, upsert: true }
    ).exec();
    logger_default.debug(`baseline upserted for snapshot id: ${params.actualSnapshotId}`, logOpts6);
    logger_default.silly({ baseline });
    return baseline;
  } catch (err) {
    if (err?.code === 11e3) {
      logger_default.warn(`baseline duplicate key detected for filter ${JSON.stringify(filter)}, retrying fetch`, logOpts6);
      const existing = await Baseline_model_default.findOne(filter).exec();
      if (existing) {
        existing.markedAs = params.markedAs;
        existing.markedById = params.markedById;
        existing.markedByUsername = params.markedByUsername;
        existing.lastMarkedDate = params.markedDate;
        existing.createdDate = /* @__PURE__ */ new Date();
        existing.snapshootId = params.actualSnapshotId;
        return existing.save();
      }
    }
    logger_default.error(`cannot upsert baseline: ${err instanceof Error ? err.message : String(err)}`, logOpts6);
    throw err;
  }
}
var extractSnapshotId = (snapshot) => {
  if (!snapshot) return void 0;
  if (typeof snapshot === "string") return snapshot;
  if (typeof snapshot === "object") {
    const snapshotObj = snapshot;
    if (snapshotObj._id) return String(snapshotObj._id);
    if (snapshotObj.id) return String(snapshotObj.id);
    if (typeof snapshotObj.toString === "function") return snapshotObj.toString();
  }
  return void 0;
};
var unwrapIdentValue = (value, visited = /* @__PURE__ */ new WeakSet()) => {
  if (!value) return void 0;
  if (typeof value !== "object") return value;
  if (value instanceof Types5.ObjectId || value?._bsontype === "ObjectID") {
    return value;
  }
  const obj = value;
  if (visited.has(obj)) return void 0;
  visited.add(obj);
  if (obj._id && obj._id !== value) {
    return unwrapIdentValue(obj._id, visited);
  }
  if (obj.id && obj.id !== value) {
    return unwrapIdentValue(obj.id, visited);
  }
  return value;
};
var extractIdentValueAsString = (value) => {
  const unwrapped = unwrapIdentValue(value);
  if (!unwrapped) return "";
  if (typeof unwrapped === "string") return unwrapped;
  if (unwrapped instanceof Types5.ObjectId || unwrapped?._bsontype === "ObjectID") {
    return unwrapped.toString();
  }
  return String(unwrapped);
};
var normalizeIdentValueForQuery = (field, value) => {
  if (value === void 0 || value === null) return void 0;
  const unwrapped = unwrapIdentValue(value);
  if (field === "app") {
    if (unwrapped instanceof Types5.ObjectId || unwrapped?._bsontype === "ObjectID") {
      return unwrapped;
    }
    const strValue = extractIdentValueAsString(unwrapped);
    if (!strValue) return void 0;
    return Types5.ObjectId.isValid(strValue) ? new Types5.ObjectId(strValue) : strValue;
  }
  return extractIdentValueAsString(unwrapped);
};
var enrichChecksWithCurrentAcceptance = async (checks) => {
  if (!checks || checks.length === 0) return [];
  const plainChecks = checks.map((check) => check && typeof check.toJSON === "function" ? check.toJSON() : { ...check });
  const identFields = ["name", "viewport", "browserName", "os", "app", "branch"];
  const baselineQueries = [];
  const checksByIdentKey = /* @__PURE__ */ new Map();
  plainChecks.forEach((check) => {
    const identKey = identFields.map((field) => extractIdentValueAsString(check?.[field])).join("|");
    if (!checksByIdentKey.has(identKey)) {
      checksByIdentKey.set(identKey, []);
      const query = {};
      identFields.forEach((field) => {
        const normalized = normalizeIdentValueForQuery(field, check?.[field]);
        if (normalized !== void 0) query[field] = normalized;
      });
      const hasAllFields = identFields.every((field) => query[field] !== void 0);
      if (hasAllFields) {
        baselineQueries.push(query);
      } else {
        logger_default.warn(`Check ${check._id} missing required ident fields. Has: ${Object.keys(query).join(", ")}`, {
          scope: "enrichChecksWithCurrentAcceptance"
        });
      }
    }
    checksByIdentKey.get(identKey)?.push(check);
  });
  const baselinesMap = /* @__PURE__ */ new Map();
  if (baselineQueries.length > 0) {
    try {
      const baselines = await Baseline_model_default.aggregate([
        {
          $match: { $or: baselineQueries }
        },
        {
          $sort: { createdDate: -1 }
        },
        {
          $group: {
            _id: {
              name: "$name",
              viewport: "$viewport",
              browserName: "$browserName",
              os: "$os",
              app: "$app",
              branch: "$branch"
            },
            doc: { $first: "$$ROOT" }
          }
        },
        {
          $replaceRoot: { newRoot: "$doc" }
        }
      ]).exec();
      baselines.forEach((baseline) => {
        const baselineObj = baseline;
        const identKey = identFields.map((field) => extractIdentValueAsString(baselineObj?.[field])).join("|");
        baselinesMap.set(identKey, baselineObj);
        logger_default.debug(`[enrichChecks] Found baseline for identKey=${identKey}, snapshootId=${baselineObj.snapshootId}`, {
          scope: "enrichChecksWithCurrentAcceptance"
        });
      });
    } catch (err) {
      logger_default.error(`[enrichChecks] Error fetching baselines: ${err}`, { scope: "enrichChecksWithCurrentAcceptance" });
      throw err;
    }
  }
  return plainChecks.map((check) => {
    const identKey = identFields.map((field) => extractIdentValueAsString(check?.[field])).join("|");
    const baseline = baselinesMap.get(identKey);
    const actualSnapshotId = extractSnapshotId(check?.actualSnapshotId);
    const baselineSnapshotId = baseline ? extractSnapshotId(baseline.snapshootId) : void 0;
    const checkBaselineSnapshotId = extractSnapshotId(check?.baselineId);
    const matchesOwnBaseline = Boolean(
      actualSnapshotId && checkBaselineSnapshotId && actualSnapshotId === checkBaselineSnapshotId
    );
    const matchesLatestBaseline = Boolean(
      actualSnapshotId && baselineSnapshotId && actualSnapshotId === baselineSnapshotId
    );
    const isCurrentlyAccepted = Boolean(
      check?.markedAs === "accepted" && (matchesOwnBaseline || matchesLatestBaseline)
    );
    const hasKnownBaseline = Boolean(checkBaselineSnapshotId || baselineSnapshotId);
    const wasAcceptedEarlier = Boolean(
      check?.markedAs === "accepted" && hasKnownBaseline && !isCurrentlyAccepted
    );
    if (check?.markedAs === "accepted") {
      logger_default.debug(`[enrichChecks] Check ${check._id}: actualSnapshot=${actualSnapshotId}, baselineSnapshot=${baselineSnapshotId}, checkBaselineSnapshot=${checkBaselineSnapshotId}, isCurrentlyAccepted=${isCurrentlyAccepted}, wasAcceptedEarlier=${wasAcceptedEarlier}, hasBaseline=${Boolean(baseline)}`, {
        scope: "enrichChecksWithCurrentAcceptance"
      });
    }
    return {
      ...check,
      isCurrentlyAccepted,
      wasAcceptedEarlier
    };
  });
};
var accept = async (id2, baselineId, user) => {
  const logOpts6 = {
    msgType: "ACCEPT",
    itemType: "check",
    ref: id2,
    user: user?.username,
    scope: "accept"
  };
  logger_default.debug(`accept check: ${id2}`, logOpts6);
  const check = await Check_model_default.findById(id2).exec();
  if (!check) throw new Error(`cannot find check with id: ${id2}`);
  const test = await Test_model_default.findById(check.test).exec();
  if (!test) throw new Error(`cannot find test with id: ${check.test}`);
  check.markedById = user._id;
  check.markedByUsername = user.username;
  check.markedDate = /* @__PURE__ */ new Date();
  check.markedAs = "accepted";
  check.status = check.status[0] === "new" ? ["new"] : ["passed"];
  check.updatedDate = /* @__PURE__ */ new Date();
  if (baselineId) {
    check.baselineId = new Types5.ObjectId(baselineId);
  }
  logger_default.debug(`update check with options: '${JSON.stringify(check.toObject())}'`, logOpts6);
  const baseline = await createNewBaseline(check.toObject());
  try {
    await domSnapshotService.linkDomSnapshotToBaseline(id2, baseline._id.toString());
    logger_default.debug(`DOM snapshot linked to baseline: '${baseline._id}'`, logOpts6);
  } catch (domErr) {
    logger_default.warn(`Failed to link DOM snapshot to baseline: ${domErr}`, logOpts6);
  }
  await check.save();
  const testCalculatedStatus = await calculateTestStatus(String(check.test));
  const testCalculatedAcceptedStatus = await calculateAcceptedStatus(check.test);
  test.status = testCalculatedStatus;
  test.markedAs = testCalculatedAcceptedStatus;
  test.updatedDate = /* @__PURE__ */ new Date();
  await Suite_model_default.findByIdAndUpdate(check.suite, { updatedDate: Date.now() });
  logger_default.debug(`update test with status: '${testCalculatedStatus}', marked: '${testCalculatedAcceptedStatus}'`, logOpts6, {
    msgType: "UPDATE",
    itemType: "test",
    ref: test._id
  });
  await test.save();
  await check.save();
  logger_default.debug(`check with id: '${id2}' was updated`, logOpts6);
  const [enrichedCheck] = await enrichChecksWithCurrentAcceptance([check]);
  webhookService.triggerWebhooks("check.updated", enrichedCheck).catch((e) => logger_default.error(`Webhook error: ${e}`));
  return enrichedCheck;
};
async function removeCheck(id2, user) {
  const logMeta = {
    scope: "removeCheck",
    itemType: "check",
    ref: id2,
    msgType: "REMOVE",
    user: user?.username
  };
  try {
    const check = await Check_model_default.findByIdAndDelete(id2).exec();
    if (!check) throw new Error(`cannot find check with id: ${id2}`);
    logger_default.debug(`check with id: '${id2}' was removed, update test: ${check.test}`, logMeta);
    const test = await Test_model_default.findById(check.test).exec();
    if (!test) throw new Error(`cannot find test with id: ${check.test}`);
    const testCalculatedStatus = await calculateTestStatus(String(check.test));
    const testCalculatedAcceptedStatus = await calculateAcceptedStatus(check.test);
    test.status = testCalculatedStatus;
    test.markedAs = testCalculatedAcceptedStatus;
    test.updatedDate = /* @__PURE__ */ new Date();
    await updateItemDate("VRSSuite", check.suite);
    await test.save();
    if (check.baselineId && String(check.baselineId) !== "undefined") {
      logger_default.debug(`try to remove the snapshot, baseline: ${check.baselineId}`, logMeta);
      await remove3(check.baselineId.toString());
    }
    if (check.actualSnapshotId && String(check.baselineId) !== "undefined") {
      logger_default.debug(`try to remove the snapshot, actual: ${check.actualSnapshotId}`, logMeta);
      await remove3(check.actualSnapshotId.toString());
    }
    if (check.diffId && String(check.baselineId) !== "undefined") {
      logger_default.debug(`try to remove snapshot, diff: ${check.diffId}`, logMeta);
      await remove3(check.diffId.toString());
    }
    try {
      await domSnapshotService.removeDomSnapshotsByCheckId(id2);
      logger_default.debug(`DOM snapshots removed for check: ${id2}`, logMeta);
    } catch (domErr) {
      logger_default.warn(`Failed to remove DOM snapshots for check ${id2}: ${domErr}`, logMeta);
    }
    return check;
  } catch (e) {
    const errMsg2 = `cannot remove a check with id: '${id2}', error: '${e instanceof Error ? e.stack : String(e)}'`;
    logger_default.error(errMsg2, logMeta);
    throw new Error(errMsg2);
  }
}
var remove4 = async (id2, user) => {
  const logOpts6 = {
    scope: "removeCheck",
    itemType: "check",
    ref: id2,
    user: user?.username,
    msgType: "REMOVE"
  };
  logger_default.info(`remove check with, id: '${id2}', user: '${user.username}'`, logOpts6);
  return removeCheck(id2, user);
};
var update = async (id2, opts, user) => {
  const logMeta = {
    msgType: "UPDATE",
    itemType: "check",
    ref: id2,
    user,
    scope: "updateCheck"
  };
  logger_default.debug(`update check with id '${id2}' with params '${JSON.stringify(opts, null, 2)}'`, logMeta);
  const check = await Check_model_default.findOneAndUpdate({ _id: id2 }, opts, { new: true }).exec();
  if (!check) throw new Error(`cannot find check with id: ${id2}`);
  const test = await Test_model_default.findOne({ _id: check.test }).exec();
  if (!test) throw new Error(`cannot find test with id: ${check.test}`);
  test.status = await calculateTestStatus(String(check.test));
  await updateItemDate("VRSCheck", check);
  await updateItemDate("VRSTest", test);
  await test.save();
  await check.save();
  webhookService.triggerWebhooks("check.updated", check).catch((e) => logger_default.error(`Webhook error: ${e}`));
  return check;
};
var recompare = async (id2, user) => {
  const logOpts6 = {
    scope: "recompareCheck",
    itemType: "check",
    ref: id2,
    user: user?.username,
    msgType: "COMPARE"
  };
  const check = await Check_model_default.findById(id2).exec();
  if (!check) throw new Error(`cannot find check with id: ${id2}`);
  if (!check.baselineId) {
    throw new Error(`cannot recompare check '${id2}': baselineId is empty`);
  }
  if (!check.actualSnapshotId) {
    throw new Error(`cannot recompare check '${id2}': actualSnapshotId is empty`);
  }
  const baselineSnapshot = await Snapshot_model_default.findById(check.baselineId).exec();
  if (!baselineSnapshot) {
    throw new Error(`cannot recompare check '${id2}': baseline snapshot '${check.baselineId}' not found`);
  }
  const actualSnapshot = await Snapshot_model_default.findById(check.actualSnapshotId).exec();
  if (!actualSnapshot) {
    throw new Error(`cannot recompare check '${id2}': actual snapshot '${check.actualSnapshotId}' not found`);
  }
  const oldDiffId = check.diffId ? check.diffId.toString() : null;
  const checkParamsForCompare = {
    test: check.test.toString(),
    name: check.name,
    status: "pending",
    viewport: check.viewport || "",
    browserName: check.browserName || "",
    browserVersion: check.browserVersion || "",
    browserFullVersion: check.browserFullVersion || "",
    os: check.os || "",
    updatedDate: Date.now(),
    suite: check.suite.toString(),
    app: check.app.toString(),
    branch: check.branch || "",
    run: check.run ? check.run.toString() : "",
    creatorId: check.creatorId ? check.creatorId.toString() : user._id.toString(),
    creatorUsername: check.creatorUsername || user.username,
    failReasons: [],
    actualSnapshotId: check.actualSnapshotId.toString(),
    hashCode: "",
    toleranceThreshold: check.toleranceThreshold
  };
  const compareResult = await compareCheck(
    baselineSnapshot,
    actualSnapshot,
    checkParamsForCompare,
    false,
    user
  );
  check.status = [compareResult.status];
  check.result = compareResult.result;
  check.failReasons = compareResult.failReasons;
  check.updatedDate = /* @__PURE__ */ new Date();
  if (compareResult.diffId) {
    check.diffId = new Types5.ObjectId(compareResult.diffId);
  } else {
    check.diffId = void 0;
  }
  await check.save();
  const test = await Test_model_default.findById(check.test).exec();
  if (test) {
    const testCalculatedStatus = await calculateTestStatus(String(check.test));
    const testCalculatedAcceptedStatus = await calculateAcceptedStatus(check.test);
    test.status = testCalculatedStatus;
    test.markedAs = testCalculatedAcceptedStatus;
    test.updatedDate = /* @__PURE__ */ new Date();
    await test.save();
    await Suite_model_default.findByIdAndUpdate(check.suite, { updatedDate: Date.now() });
  }
  const newDiffId = check.diffId ? check.diffId.toString() : null;
  if (oldDiffId && oldDiffId !== newDiffId) {
    remove3(oldDiffId).catch((e) => {
      logger_default.warn(`failed to remove old diff snapshot '${oldDiffId}': ${String(e)}`, logOpts6);
    });
  }
  const [enrichedCheck] = await enrichChecksWithCurrentAcceptance([check]);
  webhookService.triggerWebhooks("check.updated", enrichedCheck).catch((e) => logger_default.error(`Webhook error: ${e}`));
  return enrichedCheck;
};
var createCheckDocument = async (checkParams, session) => {
  const [check] = await Check_model_default.create([checkParams], { session });
  webhookService.triggerWebhooks("check.created", check).catch((e) => logger_default.error(`Webhook error: ${e}`));
  return check;
};

// 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 fs6 from "fs";
import { promises as fsp3 } from "fs";
import path7 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) => path7.join(config.adminDataJobsPath, jobId);
var getJobMetaPath = (jobId) => path7.join(getJobDir(jobId), META_FILENAME);
var getJobLogPath = (jobId) => path7.join(getJobDir(jobId), LOG_FILENAME);
async function ensureDir(dirPath) {
  await fsp3.mkdir(dirPath, { recursive: true });
}
async function removeDirSafe(dirPath) {
  if (!dirPath) return;
  await fsp3.rm(dirPath, { recursive: true, force: true });
}
async function fileExists(filePath) {
  try {
    await fsp3.access(filePath);
    return true;
  } catch {
    return false;
  }
}
async function writeJob(job) {
  await ensureDir(job.workDir);
  await fsp3.writeFile(getJobMetaPath(job.id), JSON.stringify(job, null, 2));
}
async function appendLog(jobId, message) {
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
`;
  await fsp3.appendFile(getJobLogPath(jobId), line);
}
async function readJob(jobId) {
  try {
    const raw = await fsp3.readFile(getJobMetaPath(jobId), "utf8");
    return JSON.parse(raw);
  } catch {
    return null;
  }
}
async function listJobsInternal() {
  await ensureDir(config.adminDataJobsPath);
  const entries = await fsp3.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 fsp3.opendir(currentDir);
    for await (const entry of dir) {
      const entryPath = path7.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(path7.dirname(targetPath));
  await pipelineAsync(fs6.createReadStream(sourcePath), fs6.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 fsp3.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();
    });
    fs6.createReadStream(filePath).on("error", reject).pipe(entry).on("error", reject);
  });
}
async function createTarGzArchive(outputPath, items) {
  await ensureDir(path7.dirname(outputPath));
  const pack = tar.pack();
  const gzip = createGzip();
  const output = fs6.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 = path7.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(path7.dirname(outputPath)).then(() => pipelineAsync(stream, fs6.createWriteStream(outputPath))).then(() => finishEntry()).catch((error) => finishEntry(error));
    });
    extract.on("finish", () => resolve());
    extract.on("error", reject);
    fs6.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);
    fs6.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(path7.dirname(outputPath));
  const gzip = createGzip();
  const output = fs6.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 fsp3.opendir(currentDir);
    for await (const entry of dir) {
      const fullPath = path7.join(currentDir, entry.name);
      const relativePath = path7.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 = path7.join(job.workDir, archiveName);
  const exportDir = path7.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 = path7.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 = path7.join(exportDir, "manifest.json");
  await fsp3.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: path7.join(exportDir, collection.dumpFile),
      name: `collections/${collection.dumpFile}`
    }))
  ];
  await createTarGzArchive(archivePath, tarItems);
  const stat = await fsp3.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 = fs6.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 = path7.join(job.workDir, "extracted-db");
  await updateProgress(job.id, { stage: "extracting", percent: 10 }, "Extracting database archive");
  await extractTarGzArchive(uploadPath, extractDir);
  const manifestPath = path7.join(extractDir, "manifest.json");
  if (!await fileExists(manifestPath)) {
    throw new Error("manifest.json is missing from database archive");
  }
  const manifest = JSON.parse(await fsp3.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 = path7.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 = path7.join(job.workDir, archiveName);
  const totalFiles = await countFilesRecursive(config.defaultImagesPath);
  let processedFiles = 0;
  await ensureDir(path7.dirname(archivePath));
  const pack = tar.pack();
  const gzip = createGzip();
  const output = fs6.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 fsp3.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 = path7.join(config.defaultImagesPath, relativePath);
      void (async () => {
        assertNotCancelled(job.id);
        const exists = await fileExists(targetPath);
        if (exists && skipExisting) {
          skippedFiles += 1;
          stream.resume();
        } else {
          await ensureDir(path7.dirname(targetPath));
          if (exists) {
            await fsp3.rm(targetPath, { force: true });
          }
          await pipelineAsync(stream, fs6.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);
    fs6.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(path7.join(job.workDir, "staging"));
  await removeDirSafe(path7.join(job.workDir, "extracted"));
  await removeDirSafe(path7.join(job.workDir, "extracted-db"));
  await removeDirSafe(path7.join(job.workDir, DB_EXPORT_DIRNAME));
  if (job.status === "completed" && job.downloadAvailable) {
    if (job.uploadPath) {
      await fsp3.rm(job.uploadPath, { force: true });
    }
    return;
  }
  if (job.uploadPath) {
    await fsp3.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 = path7.join(jobDir, fileName);
  await ensureDir(jobDir);
  if (file.tempFilePath) {
    await fsp3.rename(file.tempFilePath, targetPath).catch(async () => {
      await streamToFile(file.tempFilePath, targetPath);
      await fsp3.rm(file.tempFilePath, { force: true });
    });
  } else {
    await fsp3.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, path7.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, path7.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 fsp3.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 || path7.basename(job.archivePath),
    stream: fs6.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/runs.controller.ts
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("VRSRun", filter, options);
  res.send(result);
});
var remove5 = catchAsync_default(async (req, res) => {
  const { id: id2 } = req.params;
  if (!req.user) throw new Error("req.user is empty");
  const result = await run_service_exports.remove(id2, req?.user);
  res.send(result);
});

// 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 logOpts5 = {
  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, path8) {
  let currentValue = request;
  path8.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)}`, logOpts5);
      }
      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, logOpts5);
      res.status(statusCode).send(new ServiceResponse(1 /* Failed */, errorMessage, null, statusCode));
    } else {
      logger_default.error(`Unexpected error: ${errMsg(err)}`, logOpts5);
      next(err);
    }
  }
};

// src/server/schemas/Runs.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/Runs.schema.ts
var RunResponseSchema = z3.object({
  _id: commonValidations.id,
  name: z3.string().min(1).openapi({
    description: "Name of the run",
    example: "DEBUG (VIKTAR)"
  }),
  app: commonValidations.id.openapi({
    description: "App identifier",
    example: "6651dd45b9c3e1e0b8c1ce26"
  }),
  ident: z3.string().uuid().openapi({
    description: "Identifier for the run",
    example: "7a930247-e422-4833-8ab6-b136c23d07e9"
  }),
  createdDate: z3.string().datetime().openapi({
    description: "Creation date of the run",
    example: "2024-05-25T13:15:26.592Z"
  }),
  parameters: z3.array(z3.unknown()).openapi({
    description: "Parameters of the run",
    example: []
  }),
  updatedDate: z3.string().datetime().openapi({
    description: "Last update date of the run",
    example: "2024-05-25T13:15:30.969Z"
  }),
  id: commonValidations.id.openapi({
    description: "Run identifier",
    example: "6651e46e85f83573a821d1f4"
  })
});
var RunGetSchema = z3.object({
  _id: commonValidations.id,
  name: z3.string().min(1).openapi({
    description: "Name of the run",
    example: "Sample Run"
  })
  // additional fields here...
});

// 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 createApiResponse(schema, description, statusCode = httpStatus_default.OK) {
  return {
    [statusCode]: {
      description,
      content: {
        "application/json": {
          schema
        }
      }
    }
  };
}
function createPaginatedApiResponse(schema, description, statusCode = httpStatus_default.OK) {
  return {
    [statusCode]: {
      description,
      content: {
        "application/json": {
          schema: ServiceResponsePaginationSchema(schema)
        }
      }
    }
  };
}

// 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 logOpts6 = {
    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`, {
    ...logOpts6,
    authEnabled,
    isAuthenticated: req.isAuthenticated(),
    hasUser: !!req.user,
    username: req.user?.username
  });
  if (req.isAuthenticated()) {
    logger_default.debug(`handleBasicAuth: user already authenticated, returning success`, logOpts6);
    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", logOpts6);
        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...`, logOpts6);
        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`, logOpts6);
      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)`, logOpts6);
    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", logOpts6);
    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}`, logOpts6);
    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/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/schemas/utils/createRequestParamsSchema.ts
import { z as z8 } from "zod";
var createRequestParamsSchema = (schema) => z8.object({ params: schema });
var getByIdParamsSchema = (id2 = "id") => createRequestParamsSchema(
  z8.object({ [id2]: commonValidations.id })
);

// src/server/schemas/common/ApiError.schema.ts
import { extendZodWithOpenApi as extendZodWithOpenApi5 } from "@asteasolutions/zod-to-openapi";
import { z as z9 } from "zod";
extendZodWithOpenApi5(z9);
var ApiErrorSchema = z9.object({
  name: z9.string().openapi({
    description: "Name of the error type",
    example: "Error"
  }),
  message: z9.string().openapi({
    description: "Detailed message describing the error",
    example: "cannot remove run with id: '6651e46e85f83573a821d1f4', not found"
  }),
  status: z9.number().openapi({
    description: "HTTP status code that corresponds to the error",
    example: 404
  }),
  stacktrace: z9.string().openapi({
    description: "Stack trace of the error for debugging purposes",
    example: "Error: cannot remove run with id: '6651e46e85f83573a821d1f4', not found\\n    at Object.remove2 (/Users/exadel/Projects/SYNGRISI/packages/syngrisi/src/server/services/run.service.ts:27:15)\\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\\n    at /Users/exadel/Projects/SYNGRISI/packages/syngrisi/src/server/controllers/runs.controller.ts:25:20"
  })
});

// src/server/routes/v1/runs.route.ts
init_utils();
var registry = new OpenAPIRegistry();
var router = express.Router();
registry.registerPath({
  method: "get",
  path: "/v1/runs",
  summary: "List of runs with pagination, and optional filtering and sorting.",
  tags: ["Runs"],
  request: { query: RequestPaginationSchema },
  responses: createPaginatedApiResponse(RunResponseSchema, "Success")
});
router.get(
  "/",
  ensureLoggedIn(),
  validateRequest(createRequestQuerySchema(RequestPaginationSchema), "get, /v1/runs"),
  get2
);
registry.registerPath({
  method: "delete",
  path: "/v1/runs/{id}",
  summary: "Remove a run by ID",
  description: "Remove a run by ID",
  tags: ["Runs"],
  request: commonValidations.paramsId,
  responses: {
    ...createApiResponse(RunResponseSchema, "Success"),
    ...createApiResponse(ApiErrorSchema, "ApiError", httpStatus_default.NOT_FOUND)
  }
});
router.delete(
  "/:id",
  ensureLoggedIn(),
  validateRequest(getByIdParamsSchema(), "delete, /v1/runs/{id}"),
  remove5
);
var runs_route_default = router;
export {
  runs_route_default as default,
  registry
};
//# sourceMappingURL=runs.route.js.map