"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const endpoint_params_json_1 = __importDefault(require("../schemas/queues/endpoint-params.json"));
const scan_querystring_json_1 = __importDefault(require("../schemas/queues/scan-querystring.json"));
const endpoint_jobid_params_json_1 = __importDefault(require("../schemas/queues/endpoint-jobid-params.json"));
const body_json_1 = __importDefault(require("../schemas/queues/POST/body.json"));
const update_cron_json_1 = __importDefault(require("../schemas/queues/update-cron.json"));
const is_valid_cron_1 = require("../../../shared/is-valid-cron");
const repeat_1 = require("../../../shared/repeat");
const Url = __importStar(require("url"));
const jobs = (fastify, opts, done) => {
    const jobsRepo = fastify.jobs;
    const queueRepo = jobsRepo.queueRepo;
    fastify.addHook("preValidation", fastify.tokenAuthPreValidation);
    function hasValidCronExpression(body) {
        var _a;
        if ((_a = body.repeat) === null || _a === void 0 ? void 0 : _a.cron) {
            return (0, is_valid_cron_1.isValidCronExpression)(body.repeat.cron);
        }
        return true;
    }
    function hasValidCronTimezone(body) {
        var _a;
        if ((_a = body.repeat) === null || _a === void 0 ? void 0 : _a.cronTimezone) {
            return (0, repeat_1.isValidTimezone)(body.repeat.cronTimezone);
        }
        return true;
    }
    function isAbsoluteURL(string) {
        const url = Url.parse(string);
        return Boolean(url.protocol && url.hostname);
    }
    const baseSchema = {
        tags: ["Queueing"],
        security: fastify.adminBasedAuthEnabled
            ? [
                {
                    Admin: [],
                    Impersonation: [],
                },
            ]
            : undefined,
    };
    const INVALID_CRON_EXPRESSION_ERROR = {
        statusCode: 400,
        error: "Bad Request",
        message: "body.repeat.cron uses unsupported syntax. See https://github.com/harrisiirak/cron-parser for reference.",
    };
    const INVALID_ENDPOINT_ERROR = {
        statusCode: 400,
        error: "Bad Request",
        message: "endpoint needs to be absolute URL.",
    };
    const INVALID_TIMEZONE_ERROR = {
        statusCode: 400,
        error: "Bad Request",
        message: "body.repeat.cronTimezone is invalid, please provide a valid IANA timezone.",
    };
    function captureJobEnqueued(tokenId, job) {
        var _a;
        (_a = fastify.postHog) === null || _a === void 0 ? void 0 : _a.capture({
            distinctId: tokenId,
            event: "jobs enqueued",
            properties: {
                endpoint: job.endpoint,
                exclusive: job.exclusive,
                repeat: job.repeat,
                retry: job.retry,
            },
        });
    }
    fastify.post("/:endpoint", {
        schema: {
            ...baseSchema,
            body: body_json_1.default,
            params: endpoint_params_json_1.default,
            summary: "Enqueue a job",
        },
    }, async (request, reply) => {
        var _a, _b, _c;
        (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("enqueue");
        const { tokenId, body } = request;
        const { endpoint } = request.params;
        if (!isAbsoluteURL(endpoint)) {
            return reply.status(400).send(INVALID_ENDPOINT_ERROR);
        }
        if (!hasValidCronExpression(body)) {
            return reply.status(400).send(INVALID_CRON_EXPRESSION_ERROR);
        }
        if (!hasValidCronTimezone(body)) {
            return reply.status(400).send(INVALID_TIMEZONE_ERROR);
        }
        if (request.body.exclusive) {
            (_b = fastify.telemetrist) === null || _b === void 0 ? void 0 : _b.dispatch("exclusive");
        }
        const job = await jobsRepo.enqueue(tokenId, endpoint, body);
        captureJobEnqueued(tokenId, job);
        (_c = fastify.logger) === null || _c === void 0 ? void 0 : _c.jobCreated({ ...job, tokenId });
        reply.status(201).send(job);
    });
    fastify.delete("/:endpoint", {
        schema: {
            ...baseSchema,
            params: endpoint_params_json_1.default,
            summary: "Empty a Queue",
        },
    }, async (request, reply) => {
        var _a;
        (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("empty_queue");
        const { tokenId } = request;
        const { endpoint } = request.params;
        await jobsRepo.emptyQueue(tokenId, endpoint);
        reply.status(204).send();
    });
    fastify.post("/:endpoint/batch", {
        schema: {
            ...baseSchema,
            body: {
                type: "array",
                items: body_json_1.default,
            },
            params: endpoint_params_json_1.default,
            summary: "Enqueue multiple jobs",
        },
    }, async (request, reply) => {
        var _a;
        (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("enqueue_batch");
        const { tokenId, body } = request;
        const { endpoint } = request.params;
        if (body.length > 1000) {
            return reply
                .status(400)
                .send("That's a whole lot of jobs! If this wasn't a mistake, please get in touch to lift the 5k limit.");
        }
        if (!isAbsoluteURL(endpoint)) {
            return reply.status(400).send(INVALID_ENDPOINT_ERROR);
        }
        if (!body.every(hasValidCronExpression)) {
            return reply.status(400).send(INVALID_CRON_EXPRESSION_ERROR);
        }
        if (!body.every(hasValidCronTimezone)) {
            return reply.status(400).send(INVALID_TIMEZONE_ERROR);
        }
        const jobs = await Promise.all(body.map((b) => jobsRepo.enqueue(tokenId, endpoint, b)));
        jobs.forEach((job) => { var _a; return (_a = fastify.logger) === null || _a === void 0 ? void 0 : _a.jobCreated({ ...job, tokenId }); });
        jobs.forEach((job) => captureJobEnqueued(tokenId, job));
        reply.status(201).send(jobs);
    });
    fastify.get("/", {
        schema: {
            ...baseSchema,
            summary: "List existing queues",
            description: "Lists all queues that were used at some point, including currently empty ones.",
        },
        async handler(request, reply) {
            var _a, _b;
            (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("get_queues");
            const queues = await queueRepo.get(request.tokenId);
            (_b = fastify.postHog) === null || _b === void 0 ? void 0 : _b.capture({
                distinctId: request.tokenId,
                event: "queues listed",
            });
            reply.status(200).send(queues);
        },
    });
    fastify.get("/stats", {
        schema: {
            ...baseSchema,
            summary: "Statistics about existing queues",
        },
        async handler(request, reply) {
            var _a;
            const stats = await jobsRepo.queueStatsByTokenId(request.tokenId);
            (_a = fastify.postHog) === null || _a === void 0 ? void 0 : _a.capture({
                distinctId: request.tokenId,
                event: "queues stats",
            });
            reply.status(200).send(stats);
        },
    });
    fastify.get("/:endpoint", {
        schema: {
            ...baseSchema,
            params: endpoint_params_json_1.default,
            querystring: scan_querystring_json_1.default,
            summary: "Iterate pending jobs",
        },
        async handler(request, reply) {
            var _a, _b, _c;
            (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("scan_endpoint");
            const { cursor, jobs } = await jobsRepo.find(request.tokenId, request.params.endpoint, {
                cursor: (_b = request.query.cursor) !== null && _b !== void 0 ? _b : 0,
            });
            (_c = fastify.postHog) === null || _c === void 0 ? void 0 : _c.capture({
                distinctId: request.tokenId,
                event: "jobs scanned",
                properties: {
                    endpoint: request.params.endpoint,
                },
            });
            reply.status(200).send({
                cursor: cursor === 0 ? null : cursor,
                jobs,
            });
        },
    });
    fastify.get("/:endpoint/:id", {
        schema: {
            ...baseSchema,
            params: endpoint_jobid_params_json_1.default,
            summary: "Fetch specific job",
        },
        async handler(request, reply) {
            var _a, _b;
            (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("get_job");
            const { endpoint, id } = request.params;
            (_b = fastify.postHog) === null || _b === void 0 ? void 0 : _b.capture({
                distinctId: request.tokenId,
                event: "job fetched",
                properties: {
                    endpoint,
                    id,
                },
            });
            const job = await jobsRepo.findById(request.tokenId, endpoint, id);
            if (job) {
                reply.status(200).send(job);
            }
            else {
                reply.status(404).send("Not Found");
            }
        },
    });
    fastify.post("/:endpoint/:id", {
        schema: {
            ...baseSchema,
            params: endpoint_jobid_params_json_1.default,
            summary: "Invoke job",
            description: "Moves a job to the beginning of the queue.",
        },
        async handler(request, reply) {
            var _a, _b;
            (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("invoke");
            const { endpoint, id } = request.params;
            const result = await jobsRepo.invoke(request.tokenId, endpoint, id);
            (_b = fastify.postHog) === null || _b === void 0 ? void 0 : _b.capture({
                distinctId: request.tokenId,
                event: "job invoked",
                properties: {
                    endpoint,
                    id,
                    result,
                },
            });
            switch (result) {
                case "invoked":
                    return reply.status(204).send();
                case "not_found":
                    return reply.status(404).send();
            }
        },
    });
    fastify.delete("/:endpoint/:id", {
        schema: {
            ...baseSchema,
            params: endpoint_jobid_params_json_1.default,
            summary: "Delete job",
        },
        async handler(request, reply) {
            var _a, _b, _c;
            (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("delete");
            const { endpoint, id } = request.params;
            const result = await jobsRepo.delete(request.tokenId, endpoint, id);
            if (result === "deleted") {
                (_b = fastify.logger) === null || _b === void 0 ? void 0 : _b.jobDeleted({ endpoint, id, tokenId: request.tokenId });
            }
            (_c = fastify.postHog) === null || _c === void 0 ? void 0 : _c.capture({
                distinctId: request.tokenId,
                event: "job deleted",
                properties: {
                    endpoint,
                    id,
                    result,
                },
            });
            switch (result) {
                case "deleted":
                    return reply.status(204).send();
                case "not_found":
                    return reply.status(404).send();
            }
        },
    });
    fastify.put("/update-cron", {
        schema: {
            ...baseSchema,
            body: update_cron_json_1.default,
            summary: "Update cron jobs",
        },
    }, async (request, reply) => {
        var _a, _b, _c;
        (_a = fastify.telemetrist) === null || _a === void 0 ? void 0 : _a.dispatch("update-cron");
        const { tokenId, body } = request;
        const cronsAreValid = body.crons.every((cron) => (0, is_valid_cron_1.isValidCronExpression)(cron.schedule));
        if (!cronsAreValid) {
            return reply.status(400).send("invalid cron expression");
        }
        const timezonesAreValid = body.crons.every((cron) => !cron.timezone || (0, repeat_1.isValidTimezone)(cron.timezone));
        if (!timezonesAreValid) {
            return reply.status(400).send("invalid timezone");
        }
        const response = await jobsRepo.updateCron(tokenId, body);
        (_b = fastify.logger) === null || _b === void 0 ? void 0 : _b.cronUpdated(body, response.deleted);
        (_c = fastify.postHog) === null || _c === void 0 ? void 0 : _c.capture({
            distinctId: request.tokenId,
            event: "cron updated",
            properties: {
                number: body.crons.length,
            },
        });
        reply.status(200).send(response);
    });
    done();
};
exports.default = jobs;
//# sourceMappingURL=queues.js.map