chore: add .gitignore, remove dist from tracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 00:20:48 +02:00
parent 6b17cb857f
commit ade4287a5c
52 changed files with 8 additions and 4128 deletions
-121
View File
@@ -1,121 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var index_exports = {};
__export(index_exports, {
server: () => server
});
module.exports = __toCommonJS(index_exports);
var import_env = require("./lib/env");
var import_rjweb_server = require("rjweb-server");
var import_runtime_node = require("@rjweb/runtime-node");
var import_node_fs = require("node:fs");
var import_path = require("path");
var import_db = require("./lib/db");
var import_logger = require("./lib/logger");
var import_cors = require("./lib/middlewares/cors");
var import_main = require("./lib/middlewares/main");
var import_auth = require("./lib/middlewares/auth");
var import_response = require("./lib/response");
var import_errors = require("./lib/errors");
var import_jobWorker = require("./workers/jobWorker");
var import_cronWorker = require("./workers/cronWorker");
var import_adminSettings = require("./lib/adminSettings");
var import_orphanCleanup = require("./services/orphanCleanup");
var import_auth2 = require("./routes/auth");
var import_webhook = require("./routes/webhook");
var import_user = require("./routes/api/user");
var import_repos = require("./routes/api/repos");
var import_previews = require("./routes/api/previews");
var import_admin = require("./routes/api/admin");
const uiBuildPath = (0, import_path.join)(__dirname, "../../frontend/dist");
const uiIndexPath = (0, import_path.join)(uiBuildPath, "index.html");
const hasUiBuild = (0, import_node_fs.existsSync)(uiBuildPath);
const server = new import_rjweb_server.Server(
import_runtime_node.Runtime,
{
port: import_env.env.PORT,
bind: "0.0.0.0",
version: false,
performance: { lastModified: false, eTag: false },
logging: { warn: true, debug: false, error: true }
},
[
import_cors.corsMiddleware.use({}),
import_main.mainMiddleware.use({}),
import_auth.authResolutionMiddleware.use({}),
import_auth.authEnforcementMiddleware.use({})
]
);
server.path(
"/api/auth",
(path) => path.http("POST", "/login", (http) => http.onRequest(import_auth2.loginHandler)).http("POST", "/logout", (http) => http.onRequest(import_auth2.logoutHandler)).http("GET", "/me", (http) => http.onRequest(import_auth2.meHandler)).http("GET", "/setup-status", (http) => http.onRequest(import_auth2.setupStatusHandler)).http("POST", "/first-user", (http) => http.onRequest(import_auth2.firstUserHandler))
);
server.path(
"/webhook",
(path) => path.http("POST", "/:userId", (http) => http.onRequest(import_webhook.webhookHandler))
);
server.path(
"/api/user",
(path) => path.http("GET", "/settings", (http) => http.onRequest(import_user.getUserSettings)).http("PATCH", "/username", (http) => http.onRequest(import_user.updateUsername)).http("PATCH", "/password", (http) => http.onRequest(import_user.updatePassword)).http("PUT", "/gitea", (http) => http.onRequest(import_user.updateGitea)).http("PUT", "/aws", (http) => http.onRequest(import_user.updateAws)).http("GET", "/webhook-secret", (http) => http.onRequest(import_user.getWebhookSecret)).http("POST", "/webhook-secret/regenerate", (http) => http.onRequest(import_user.regenerateWebhookSecret))
);
server.path(
"/api/repos",
(path) => path.http("GET", "/", (http) => http.onRequest(import_repos.listRepos)).http("POST", "/config", (http) => http.onRequest(import_repos.saveRepoConfig)).http("POST", "/toggle", (http) => http.onRequest(import_repos.toggleRepoEnabled)).http("GET", "/:owner/:repo/config", (http) => http.onRequest(import_repos.getRepoConfig))
);
server.path(
"/api/previews",
(path) => path.http("GET", "/", (http) => http.onRequest(import_previews.listPreviews)).http("GET", "/:id", (http) => http.onRequest(import_previews.getPreview)).http("POST", "/:id/stop", (http) => http.onRequest(import_previews.stopPreviewRoute)).ws(
"/:id/logs",
(ws) => ws.onOpen(import_previews.previewLogsWs).onMessage(async () => {
}).onClose(async () => {
})
)
);
server.path(
"/api/admin",
(path) => path.http("GET", "/users", (http) => http.onRequest(import_admin.listUsers)).http("POST", "/users", (http) => http.onRequest(import_admin.createUser)).http("PATCH", "/users/:id", (http) => http.onRequest(import_admin.updateUser)).http("DELETE", "/users/:id", (http) => http.onRequest(import_admin.deleteUser)).http("GET", "/settings", (http) => http.onRequest(import_admin.getSettings)).http("PUT", "/settings", (http) => http.onRequest(import_admin.updateSettings)).http("GET", "/previews", (http) => http.onRequest(import_admin.adminListPreviews)).http("POST", "/previews/:id/stop", (http) => http.onRequest(import_admin.adminStopPreview))
);
if (hasUiBuild) {
server.path("/", (path) => path.static(uiBuildPath));
}
server.notFound(async (ctr) => {
const STATIC_EXT = /\.(js|mjs|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|map|json|txt|xml|webp|avif)(\?.*)?$/i;
if (!ctr.url.path.startsWith("/api") && !STATIC_EXT.test(ctr.url.path) && (0, import_node_fs.existsSync)(uiIndexPath)) {
return ctr.status(200).printFile(uiIndexPath, { addTypes: true });
}
return (0, import_response.makeResponse)({ ctr, content: { code: import_errors.ERROR_MESSAGES.NOT_FOUND.code, message: import_errors.ERROR_MESSAGES.NOT_FOUND.message } });
});
server.error("httpRequest", async (ctr, error) => {
import_logger.logger.error(error, "Unhandled HTTP request error");
return (0, import_response.makeResponse)({ ctr, content: { code: import_errors.ERROR_MESSAGES.INTERNAL_SERVER_ERROR.code } });
});
server.start().then(async (port) => {
await import_db.prisma.$connect();
import_logger.logger.info({ port }, "PP backend running");
await (0, import_adminSettings.getAdminSettings)();
(0, import_jobWorker.startJobWorker)();
(0, import_cronWorker.startCronWorkers)();
(0, import_orphanCleanup.runOrphanCleanup)().catch((e) => import_logger.logger.warn(e, "Orphan cleanup error"));
import_logger.logger.info("All workers started");
}).catch((err) => import_logger.logger.error(err, "Server failed to start"));
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
server
});
//# sourceMappingURL=index.js.map
-7
View File
File diff suppressed because one or more lines are too long
-36
View File
@@ -1,36 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var adminSettings_exports = {};
__export(adminSettings_exports, {
getAdminSettings: () => getAdminSettings
});
module.exports = __toCommonJS(adminSettings_exports);
var import_db = require("./db");
async function getAdminSettings() {
let settings = await import_db.prisma.adminSettings.findUnique({ where: { id: 1 } });
if (!settings) {
settings = await import_db.prisma.adminSettings.create({ data: { id: 1 } });
}
return settings;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getAdminSettings
});
//# sourceMappingURL=adminSettings.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/adminSettings.ts"],
"sourcesContent": ["import { prisma } from \"./db\";\n\nexport async function getAdminSettings() {\n let settings = await prisma.adminSettings.findUnique({ where: { id: 1 } });\n if (!settings) {\n settings = await prisma.adminSettings.create({ data: { id: 1 } });\n }\n return settings;\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAuB;AAEvB,eAAsB,mBAAmB;AACvC,MAAI,WAAW,MAAM,iBAAO,cAAc,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AACzE,MAAI,CAAC,UAAU;AACb,eAAW,MAAM,iBAAO,cAAc,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;AAAA,EAClE;AACA,SAAO;AACT;",
"names": []
}
-33
View File
@@ -1,33 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var db_exports = {};
__export(db_exports, {
prisma: () => prisma
});
module.exports = __toCommonJS(db_exports);
var import_client = require("@prisma/client");
const prisma = new import_client.PrismaClient({
log: ["error", "warn"],
errorFormat: "pretty"
});
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
prisma
});
//# sourceMappingURL=db.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/db.ts"],
"sourcesContent": ["import { PrismaClient } from \"@prisma/client\";\n\nexport const prisma = new PrismaClient({\n log: [\"error\", \"warn\"],\n errorFormat: \"pretty\",\n});\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAEtB,MAAM,SAAS,IAAI,2BAAa;AAAA,EACrC,KAAK,CAAC,SAAS,MAAM;AAAA,EACrB,aAAa;AACf,CAAC;",
"names": []
}
-50
View File
@@ -1,50 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var encryption_exports = {};
__export(encryption_exports, {
decrypt: () => decrypt,
encrypt: () => encrypt
});
module.exports = __toCommonJS(encryption_exports);
var import_crypto = require("crypto");
var import_env = require("./env");
const ALGORITHM = "aes-256-gcm";
const KEY = Buffer.from(import_env.env.ENCRYPTION_KEY, "hex");
function encrypt(plaintext) {
const iv = (0, import_crypto.randomBytes)(12);
const cipher = (0, import_crypto.createCipheriv)(ALGORITHM, KEY, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
return Buffer.concat([iv, authTag, encrypted]).toString("base64");
}
function decrypt(ciphertext) {
const buf = Buffer.from(ciphertext, "base64");
const iv = buf.slice(0, 12);
const authTag = buf.slice(12, 28);
const encrypted = buf.slice(28);
const decipher = (0, import_crypto.createDecipheriv)(ALGORITHM, KEY, iv);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
decrypt,
encrypt
});
//# sourceMappingURL=encryption.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/encryption.ts"],
"sourcesContent": ["import { createCipheriv, createDecipheriv, randomBytes } from \"crypto\";\nimport { env } from \"./env\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY = Buffer.from(env.ENCRYPTION_KEY, \"hex\");\n\nexport function encrypt(plaintext: string): string {\n const iv = randomBytes(12);\n const cipher = createCipheriv(ALGORITHM, KEY, iv);\n const encrypted = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n return Buffer.concat([iv, authTag, encrypted]).toString(\"base64\");\n}\n\nexport function decrypt(ciphertext: string): string {\n const buf = Buffer.from(ciphertext, \"base64\");\n const iv = buf.slice(0, 12);\n const authTag = buf.slice(12, 28);\n const encrypted = buf.slice(28);\n const decipher = createDecipheriv(ALGORITHM, KEY, iv);\n decipher.setAuthTag(authTag);\n return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString(\"utf8\");\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA8D;AAC9D,iBAAoB;AAEpB,MAAM,YAAY;AAClB,MAAM,MAAM,OAAO,KAAK,eAAI,gBAAgB,KAAK;AAE1C,SAAS,QAAQ,WAA2B;AACjD,QAAM,SAAK,2BAAY,EAAE;AACzB,QAAM,aAAS,8BAAe,WAAW,KAAK,EAAE;AAChD,QAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AAClF,QAAM,UAAU,OAAO,WAAW;AAClC,SAAO,OAAO,OAAO,CAAC,IAAI,SAAS,SAAS,CAAC,EAAE,SAAS,QAAQ;AAClE;AAEO,SAAS,QAAQ,YAA4B;AAClD,QAAM,MAAM,OAAO,KAAK,YAAY,QAAQ;AAC5C,QAAM,KAAK,IAAI,MAAM,GAAG,EAAE;AAC1B,QAAM,UAAU,IAAI,MAAM,IAAI,EAAE;AAChC,QAAM,YAAY,IAAI,MAAM,EAAE;AAC9B,QAAM,eAAW,gCAAiB,WAAW,KAAK,EAAE;AACpD,WAAS,WAAW,OAAO;AAC3B,SAAO,OAAO,OAAO,CAAC,SAAS,OAAO,SAAS,GAAG,SAAS,MAAM,CAAC,CAAC,EAAE,SAAS,MAAM;AACtF;",
"names": []
}
-58
View File
@@ -1,58 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var env_exports = {};
__export(env_exports, {
env: () => env
});
module.exports = __toCommonJS(env_exports);
var import_dotenv = __toESM(require("dotenv"));
var import_path = require("path");
var import_zod = require("zod");
import_dotenv.default.config({ path: (0, import_path.join)(__dirname, "../../.env") });
const schema = import_zod.z.object({
NODE_ENV: import_zod.z.enum(["development", "production", "test"]).default("development"),
DATABASE_URL: import_zod.z.string().min(1),
PORT: import_zod.z.coerce.number().int().positive().default(5e3),
SESSION_SECRET: import_zod.z.string().min(16),
PP_BASE_URL: import_zod.z.string().min(1),
ENCRYPTION_KEY: import_zod.z.string().length(64, "ENCRYPTION_KEY must be 64 hex chars (32 bytes AES-256)"),
LOG_LEVEL: import_zod.z.string().default("info")
});
const result = schema.safeParse(process.env);
if (!result.success) {
const formatted = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
throw new Error(`Invalid environment variables:
${formatted}`);
}
const env = result.data;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
env
});
//# sourceMappingURL=env.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/env.ts"],
"sourcesContent": ["import dotenv from \"dotenv\";\nimport { join } from \"path\";\nimport { z } from \"zod\";\n\ndotenv.config({ path: join(__dirname, \"../../.env\") });\n\nconst schema = z.object({\n NODE_ENV: z.enum([\"development\", \"production\", \"test\"]).default(\"development\"),\n DATABASE_URL: z.string().min(1),\n PORT: z.coerce.number().int().positive().default(5000),\n SESSION_SECRET: z.string().min(16),\n PP_BASE_URL: z.string().min(1),\n ENCRYPTION_KEY: z.string().length(64, \"ENCRYPTION_KEY must be 64 hex chars (32 bytes AES-256)\"),\n LOG_LEVEL: z.string().default(\"info\"),\n});\n\nconst result = schema.safeParse(process.env);\n\nif (!result.success) {\n const formatted = result.error.issues\n .map((i) => ` ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`Invalid environment variables:\\n${formatted}`);\n}\n\nexport const env = result.data;\n"],
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AACnB,kBAAqB;AACrB,iBAAkB;AAElB,cAAAA,QAAO,OAAO,EAAE,UAAM,kBAAK,WAAW,YAAY,EAAE,CAAC;AAErD,MAAM,SAAS,aAAE,OAAO;AAAA,EACtB,UAAU,aAAE,KAAK,CAAC,eAAe,cAAc,MAAM,CAAC,EAAE,QAAQ,aAAa;AAAA,EAC7E,cAAc,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC9B,MAAM,aAAE,OAAO,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,GAAI;AAAA,EACrD,gBAAgB,aAAE,OAAO,EAAE,IAAI,EAAE;AAAA,EACjC,aAAa,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,gBAAgB,aAAE,OAAO,EAAE,OAAO,IAAI,wDAAwD;AAAA,EAC9F,WAAW,aAAE,OAAO,EAAE,QAAQ,MAAM;AACtC,CAAC;AAED,MAAM,SAAS,OAAO,UAAU,QAAQ,GAAG;AAE3C,IAAI,CAAC,OAAO,SAAS;AACnB,QAAM,YAAY,OAAO,MAAM,OAC5B,IAAI,CAAC,MAAM,KAAK,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAChD,KAAK,IAAI;AACZ,QAAM,IAAI,MAAM;AAAA,EAAmC,SAAS,EAAE;AAChE;AAEO,MAAM,MAAM,OAAO;",
"names": ["dotenv"]
}
-37
View File
@@ -1,37 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var errors_exports = {};
__export(errors_exports, {
ERROR_MESSAGES: () => ERROR_MESSAGES
});
module.exports = __toCommonJS(errors_exports);
const ERROR_MESSAGES = {
UNAUTHORIZED: { code: 401, message: "You are not authorized to access this resource." },
FORBIDDEN: { code: 403, message: "You do not have permission to access this resource." },
NOT_FOUND: { code: 404, message: "The requested resource was not found." },
INTERNAL_SERVER_ERROR: { code: 500, message: "An unexpected server error has occurred." },
BAD_REQUEST: { code: 400, message: "The request was invalid or malformed." },
CONFLICT: { code: 409, message: "The request conflicts with the current state of the resource." },
TOO_MANY_REQUESTS: { code: 429, message: "Too many requests. Please try again later." }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ERROR_MESSAGES
});
//# sourceMappingURL=errors.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/errors.ts"],
"sourcesContent": ["export const ERROR_MESSAGES = {\n UNAUTHORIZED: { code: 401, message: \"You are not authorized to access this resource.\" },\n FORBIDDEN: { code: 403, message: \"You do not have permission to access this resource.\" },\n NOT_FOUND: { code: 404, message: \"The requested resource was not found.\" },\n INTERNAL_SERVER_ERROR: { code: 500, message: \"An unexpected server error has occurred.\" },\n BAD_REQUEST: { code: 400, message: \"The request was invalid or malformed.\" },\n CONFLICT: { code: 409, message: \"The request conflicts with the current state of the resource.\" },\n TOO_MANY_REQUESTS: { code: 429, message: \"Too many requests. Please try again later.\" },\n} as const;\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,MAAM,iBAAiB;AAAA,EAC5B,cAAc,EAAE,MAAM,KAAK,SAAS,kDAAkD;AAAA,EACtF,WAAW,EAAE,MAAM,KAAK,SAAS,sDAAsD;AAAA,EACvF,WAAW,EAAE,MAAM,KAAK,SAAS,wCAAwC;AAAA,EACzE,uBAAuB,EAAE,MAAM,KAAK,SAAS,2CAA2C;AAAA,EACxF,aAAa,EAAE,MAAM,KAAK,SAAS,wCAAwC;AAAA,EAC3E,UAAU,EAAE,MAAM,KAAK,SAAS,gEAAgE;AAAA,EAChG,mBAAmB,EAAE,MAAM,KAAK,SAAS,6CAA6C;AACxF;",
"names": []
}
-49
View File
@@ -1,49 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var logger_exports = {};
__export(logger_exports, {
createLogger: () => createLogger,
logger: () => logger
});
module.exports = __toCommonJS(logger_exports);
var import_env = require("./env");
var import_pino = __toESM(require("pino"));
const logger = (0, import_pino.default)({
level: import_env.env.LOG_LEVEL,
transport: import_env.env.NODE_ENV !== "production" ? { target: "pino-pretty", options: { colorize: true } } : void 0
});
function createLogger(component) {
return logger.child({ component });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createLogger,
logger
});
//# sourceMappingURL=logger.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/logger.ts"],
"sourcesContent": ["import { env } from \"./env\";\nimport pino from \"pino\";\n\nexport const logger = pino({\n level: env.LOG_LEVEL,\n transport:\n env.NODE_ENV !== \"production\"\n ? { target: \"pino-pretty\", options: { colorize: true } }\n : undefined,\n});\n\nexport function createLogger(component: string) {\n return logger.child({ component });\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAoB;AACpB,kBAAiB;AAEV,MAAM,aAAS,YAAAA,SAAK;AAAA,EACzB,OAAO,eAAI;AAAA,EACX,WACE,eAAI,aAAa,eACb,EAAE,QAAQ,eAAe,SAAS,EAAE,UAAU,KAAK,EAAE,IACrD;AACR,CAAC;AAEM,SAAS,aAAa,WAAmB;AAC9C,SAAO,OAAO,MAAM,EAAE,UAAU,CAAC;AACnC;",
"names": ["pino"]
}
-88
View File
@@ -1,88 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var auth_exports = {};
__export(auth_exports, {
COOKIE_NAME_EXPORT: () => COOKIE_NAME_EXPORT,
authEnforcementMiddleware: () => authEnforcementMiddleware,
authResolutionMiddleware: () => authResolutionMiddleware
});
module.exports = __toCommonJS(auth_exports);
var import_rjweb_server = require("rjweb-server");
var import_db = require("../db");
var import_logger = require("../logger");
var import_errors = require("../errors");
const log = (0, import_logger.createLogger)("AUTH");
const COOKIE_NAME = "pp_session";
const authResolutionMiddleware = new import_rjweb_server.Middleware(
"Auth Resolution Middleware",
"1.0.0"
).load(() => {
log.info("Auth resolution middleware loaded");
}).httpRequest(async (_config, _server, context, ctr) => {
const cookieToken = ctr.cookies.get(COOKIE_NAME);
const tokenProvided = Boolean(cookieToken);
const data = context.data(authResolutionMiddleware);
if (!cookieToken) {
data.auth = { success: false, message: "No session", tokenProvided: false };
return;
}
const session = await import_db.prisma.session.findFirst({
where: { hash: cookieToken },
include: { user: true }
});
if (!session) {
data.auth = { success: false, message: "Invalid session", tokenProvided };
return;
}
data.auth = { success: true, user: session.user, sessionId: session.id };
}).httpRequestContext(
(_config, Original) => class extends Original {
getAuth() {
const data = this.context.data(authResolutionMiddleware);
if (!data.auth) {
return { success: false, message: "Auth not resolved", tokenProvided: false };
}
return data.auth;
}
}
).export();
const authEnforcementMiddleware = new import_rjweb_server.Middleware(
"Auth Enforcement Middleware",
"1.0.0"
).httpRequest(async (_config, _server, context, ctr, end) => {
const data = context.data(authResolutionMiddleware);
const auth = data.auth;
if (!auth || auth.success || !auth.tokenProvided) {
return;
}
return end(
ctr.status(import_errors.ERROR_MESSAGES.UNAUTHORIZED.code).print({
status: "FAILED",
message: auth.message
})
);
}).export();
const COOKIE_NAME_EXPORT = COOKIE_NAME;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
COOKIE_NAME_EXPORT,
authEnforcementMiddleware,
authResolutionMiddleware
});
//# sourceMappingURL=auth.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../../src/lib/middlewares/auth.ts"],
"sourcesContent": ["import { Middleware } from \"rjweb-server\";\nimport { type User } from \"@prisma/client\";\nimport { prisma } from \"../db\";\nimport { createLogger } from \"../logger\";\nimport { ERROR_MESSAGES } from \"../errors\";\n\nconst log = createLogger(\"AUTH\");\n\nconst COOKIE_NAME = \"pp_session\";\n\nexport type AuthState =\n | { success: true; user: User; sessionId: number }\n | { success: false; message: string; tokenProvided: boolean };\n\ntype AuthContext = {\n auth?: AuthState;\n};\n\nexport const authResolutionMiddleware = new Middleware<{}, AuthContext>(\n \"Auth Resolution Middleware\",\n \"1.0.0\",\n)\n .load(() => {\n log.info(\"Auth resolution middleware loaded\");\n })\n .httpRequest(async (_config, _server, context, ctr) => {\n const cookieToken = ctr.cookies.get(COOKIE_NAME);\n const tokenProvided = Boolean(cookieToken);\n const data = context.data(authResolutionMiddleware);\n\n if (!cookieToken) {\n data.auth = { success: false, message: \"No session\", tokenProvided: false };\n return;\n }\n\n const session = await prisma.session.findFirst({\n where: { hash: cookieToken },\n include: { user: true },\n });\n\n if (!session) {\n data.auth = { success: false, message: \"Invalid session\", tokenProvided };\n return;\n }\n\n data.auth = { success: true, user: session.user, sessionId: session.id };\n })\n .httpRequestContext(\n (_config, Original) =>\n class extends Original {\n getAuth(): AuthState {\n const data = this.context.data(authResolutionMiddleware);\n if (!data.auth) {\n return { success: false, message: \"Auth not resolved\", tokenProvided: false };\n }\n return data.auth;\n }\n },\n )\n .export();\n\nexport const authEnforcementMiddleware = new Middleware<{}, {}>(\n \"Auth Enforcement Middleware\",\n \"1.0.0\",\n)\n .httpRequest(async (_config, _server, context, ctr, end) => {\n const data = context.data(authResolutionMiddleware) as AuthContext;\n const auth = data.auth;\n\n if (!auth || auth.success || !auth.tokenProvided) {\n return;\n }\n\n return end(\n ctr.status(ERROR_MESSAGES.UNAUTHORIZED.code).print({\n status: \"FAILED\",\n message: auth.message,\n }),\n );\n })\n .export();\n\nexport const COOKIE_NAME_EXPORT = COOKIE_NAME;\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA2B;AAE3B,gBAAuB;AACvB,oBAA6B;AAC7B,oBAA+B;AAE/B,MAAM,UAAM,4BAAa,MAAM;AAE/B,MAAM,cAAc;AAUb,MAAM,2BAA2B,IAAI;AAAA,EAC1C;AAAA,EACA;AACF,EACG,KAAK,MAAM;AACV,MAAI,KAAK,mCAAmC;AAC9C,CAAC,EACA,YAAY,OAAO,SAAS,SAAS,SAAS,QAAQ;AACrD,QAAM,cAAc,IAAI,QAAQ,IAAI,WAAW;AAC/C,QAAM,gBAAgB,QAAQ,WAAW;AACzC,QAAM,OAAO,QAAQ,KAAK,wBAAwB;AAElD,MAAI,CAAC,aAAa;AAChB,SAAK,OAAO,EAAE,SAAS,OAAO,SAAS,cAAc,eAAe,MAAM;AAC1E;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,iBAAO,QAAQ,UAAU;AAAA,IAC7C,OAAO,EAAE,MAAM,YAAY;AAAA,IAC3B,SAAS,EAAE,MAAM,KAAK;AAAA,EACxB,CAAC;AAED,MAAI,CAAC,SAAS;AACZ,SAAK,OAAO,EAAE,SAAS,OAAO,SAAS,mBAAmB,cAAc;AACxE;AAAA,EACF;AAEA,OAAK,OAAO,EAAE,SAAS,MAAM,MAAM,QAAQ,MAAM,WAAW,QAAQ,GAAG;AACzE,CAAC,EACA;AAAA,EACC,CAAC,SAAS,aACR,cAAc,SAAS;AAAA,IACrB,UAAqB;AACnB,YAAM,OAAO,KAAK,QAAQ,KAAK,wBAAwB;AACvD,UAAI,CAAC,KAAK,MAAM;AACd,eAAO,EAAE,SAAS,OAAO,SAAS,qBAAqB,eAAe,MAAM;AAAA,MAC9E;AACA,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACJ,EACC,OAAO;AAEH,MAAM,4BAA4B,IAAI;AAAA,EAC3C;AAAA,EACA;AACF,EACG,YAAY,OAAO,SAAS,SAAS,SAAS,KAAK,QAAQ;AAC1D,QAAM,OAAO,QAAQ,KAAK,wBAAwB;AAClD,QAAM,OAAO,KAAK;AAElB,MAAI,CAAC,QAAQ,KAAK,WAAW,CAAC,KAAK,eAAe;AAChD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,OAAO,6BAAe,aAAa,IAAI,EAAE,MAAM;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS,KAAK;AAAA,IAChB,CAAC;AAAA,EACH;AACF,CAAC,EACA,OAAO;AAEH,MAAM,qBAAqB;",
"names": []
}
-40
View File
@@ -1,40 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var cors_exports = {};
__export(cors_exports, {
corsMiddleware: () => corsMiddleware
});
module.exports = __toCommonJS(cors_exports);
var import_rjweb_server = require("rjweb-server");
var import_env = require("../env");
const ALLOWED_ORIGINS = /* @__PURE__ */ new Set([import_env.env.PP_BASE_URL]);
const corsMiddleware = new import_rjweb_server.Middleware("CORS Middleware", "1.0.0").httpRequest(async (_config, _server, _context, ctr) => {
const origin = ctr.headers.get("origin") || "";
if (ALLOWED_ORIGINS.has(origin) || import_env.env.NODE_ENV === "development") {
ctr.headers.set("Access-Control-Allow-Origin", origin || "*");
ctr.headers.set("Access-Control-Allow-Credentials", "true");
ctr.headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
ctr.headers.set("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With");
}
}).export();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
corsMiddleware
});
//# sourceMappingURL=cors.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../../src/lib/middlewares/cors.ts"],
"sourcesContent": ["import { Middleware } from \"rjweb-server\";\nimport { env } from \"../env\";\n\nconst ALLOWED_ORIGINS = new Set([env.PP_BASE_URL]);\n\nexport const corsMiddleware = new Middleware<{}, {}>(\"CORS Middleware\", \"1.0.0\")\n .httpRequest(async (_config, _server, _context, ctr) => {\n const origin = ctr.headers.get(\"origin\") || \"\";\n if (ALLOWED_ORIGINS.has(origin) || env.NODE_ENV === \"development\") {\n ctr.headers.set(\"Access-Control-Allow-Origin\", origin || \"*\");\n ctr.headers.set(\"Access-Control-Allow-Credentials\", \"true\");\n ctr.headers.set(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,PATCH,DELETE,OPTIONS\");\n ctr.headers.set(\"Access-Control-Allow-Headers\", \"Content-Type,Authorization,X-Requested-With\");\n }\n })\n .export();\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA2B;AAC3B,iBAAoB;AAEpB,MAAM,kBAAkB,oBAAI,IAAI,CAAC,eAAI,WAAW,CAAC;AAE1C,MAAM,iBAAiB,IAAI,+BAAmB,mBAAmB,OAAO,EAC5E,YAAY,OAAO,SAAS,SAAS,UAAU,QAAQ;AACtD,QAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAC5C,MAAI,gBAAgB,IAAI,MAAM,KAAK,eAAI,aAAa,eAAe;AACjE,QAAI,QAAQ,IAAI,+BAA+B,UAAU,GAAG;AAC5D,QAAI,QAAQ,IAAI,oCAAoC,MAAM;AAC1D,QAAI,QAAQ,IAAI,gCAAgC,mCAAmC;AACnF,QAAI,QAAQ,IAAI,gCAAgC,6CAA6C;AAAA,EAC/F;AACF,CAAC,EACA,OAAO;",
"names": []
}
-38
View File
@@ -1,38 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var main_exports = {};
__export(main_exports, {
mainMiddleware: () => mainMiddleware
});
module.exports = __toCommonJS(main_exports);
var import_rjweb_server = require("rjweb-server");
var import_logger = require("../logger");
const log = (0, import_logger.createLogger)("HTTP");
const mainMiddleware = new import_rjweb_server.Middleware("Main Middleware", "1.0.0").load(() => {
log.info("Main middleware loaded");
}).httpRequest(async (_config, _server, _context, ctr) => {
if (ctr.url.method === "OPTIONS") {
ctr.status(204).print("");
}
}).export();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
mainMiddleware
});
//# sourceMappingURL=main.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../../src/lib/middlewares/main.ts"],
"sourcesContent": ["import { Middleware } from \"rjweb-server\";\nimport { createLogger } from \"../logger\";\n\nconst log = createLogger(\"HTTP\");\n\nexport const mainMiddleware = new Middleware<{}, {}>(\"Main Middleware\", \"1.0.0\")\n .load(() => {\n log.info(\"Main middleware loaded\");\n })\n .httpRequest(async (_config, _server, _context, ctr) => {\n if (ctr.url.method === \"OPTIONS\") {\n ctr.status(204).print(\"\");\n }\n })\n .export();\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA2B;AAC3B,oBAA6B;AAE7B,MAAM,UAAM,4BAAa,MAAM;AAExB,MAAM,iBAAiB,IAAI,+BAAmB,mBAAmB,OAAO,EAC5E,KAAK,MAAM;AACV,MAAI,KAAK,wBAAwB;AACnC,CAAC,EACA,YAAY,OAAO,SAAS,SAAS,UAAU,QAAQ;AACtD,MAAI,IAAI,IAAI,WAAW,WAAW;AAChC,QAAI,OAAO,GAAG,EAAE,MAAM,EAAE;AAAA,EAC1B;AACF,CAAC,EACA,OAAO;",
"names": []
}
-64
View File
@@ -1,64 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var response_exports = {};
__export(response_exports, {
endResponse: () => endResponse,
makeResponse: () => makeResponse
});
module.exports = __toCommonJS(response_exports);
var import_errors = require("./errors");
function resolve(content) {
const code = "code" in content ? content.code : content.status;
const message = code >= 500 ? import_errors.ERROR_MESSAGES.INTERNAL_SERVER_ERROR.message : content.message;
return { code, message };
}
function buildBody(code, message, data) {
if (code >= 400) {
return { status: "FAILED", message };
}
return {
status: "OK",
...message !== void 0 ? { message } : {},
...data !== void 0 ? { data } : {}
};
}
async function makeResponse({
ctr,
content
}) {
const { code, message } = resolve(content);
const data = "data" in content ? content.data : void 0;
return ctr.status(code).print(buildBody(code, message, data));
}
async function endResponse({
ctr,
end,
content
}) {
const { code, message } = resolve(content);
const data = "data" in content ? content.data : void 0;
ctr.status(code).print(buildBody(code, message, data));
end();
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
endResponse,
makeResponse
});
//# sourceMappingURL=response.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/lib/response.ts"],
"sourcesContent": ["import { ERROR_MESSAGES } from \"./errors\";\n\ntype ResponseContent =\n | { code: number; message?: string; data?: unknown }\n | { status: number; message?: string; data?: unknown };\n\nfunction resolve(content: ResponseContent) {\n const code = \"code\" in content ? content.code : content.status;\n const message = code >= 500 ? ERROR_MESSAGES.INTERNAL_SERVER_ERROR.message : content.message;\n return { code, message };\n}\n\nfunction buildBody(code: number, message: string | undefined, data: unknown) {\n if (code >= 400) {\n return { status: \"FAILED\", message };\n }\n return {\n status: \"OK\",\n ...(message !== undefined ? { message } : {}),\n ...(data !== undefined ? { data } : {}),\n };\n}\n\nexport async function makeResponse({\n ctr,\n content,\n}: {\n ctr: any;\n content: ResponseContent;\n}) {\n const { code, message } = resolve(content);\n const data = \"data\" in content ? content.data : undefined;\n return ctr.status(code).print(buildBody(code, message, data));\n}\n\nexport async function endResponse({\n ctr,\n end,\n content,\n}: {\n ctr: any;\n end: () => void;\n content: ResponseContent;\n}) {\n const { code, message } = resolve(content);\n const data = \"data\" in content ? content.data : undefined;\n ctr.status(code).print(buildBody(code, message, data));\n end();\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+B;AAM/B,SAAS,QAAQ,SAA0B;AACzC,QAAM,OAAO,UAAU,UAAU,QAAQ,OAAO,QAAQ;AACxD,QAAM,UAAU,QAAQ,MAAM,6BAAe,sBAAsB,UAAU,QAAQ;AACrF,SAAO,EAAE,MAAM,QAAQ;AACzB;AAEA,SAAS,UAAU,MAAc,SAA6B,MAAe;AAC3E,MAAI,QAAQ,KAAK;AACf,WAAO,EAAE,QAAQ,UAAU,QAAQ;AAAA,EACrC;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,EACvC;AACF;AAEA,eAAsB,aAAa;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,QAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ,OAAO;AACzC,QAAM,OAAO,UAAU,UAAU,QAAQ,OAAO;AAChD,SAAO,IAAI,OAAO,IAAI,EAAE,MAAM,UAAU,MAAM,SAAS,IAAI,CAAC;AAC9D;AAEA,eAAsB,YAAY;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ,OAAO;AACzC,QAAM,OAAO,UAAU,UAAU,QAAQ,OAAO;AAChD,MAAI,OAAO,IAAI,EAAE,MAAM,UAAU,MAAM,SAAS,IAAI,CAAC;AACrD,MAAI;AACN;",
"names": []
}
-192
View File
@@ -1,192 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var admin_exports = {};
__export(admin_exports, {
adminListPreviews: () => adminListPreviews,
adminStopPreview: () => adminStopPreview,
createUser: () => createUser,
deleteUser: () => deleteUser,
getSettings: () => getSettings,
listUsers: () => listUsers,
updateSettings: () => updateSettings,
updateUser: () => updateUser
});
module.exports = __toCommonJS(admin_exports);
var import_bcryptjs = __toESM(require("bcryptjs"));
var import_db = require("../../lib/db");
var import_response = require("../../lib/response");
var import_errors = require("../../lib/errors");
var import_adminSettings = require("../../lib/adminSettings");
function requireAdmin(ctr) {
const auth = ctr.getAuth?.();
if (!auth?.success) return null;
if (!auth.user.isAdmin) return null;
return auth.user;
}
async function listUsers(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const users = await import_db.prisma.user.findMany({
select: { id: true, username: true, isAdmin: true, isFounder: true, createdAt: true, giteaInstanceUrl: true },
orderBy: { createdAt: "asc" }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: users } });
}
async function createUser(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const body = await ctr.body();
const { username, password } = body || {};
if (!username || !password) return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Username and password required" } });
const existing = await import_db.prisma.user.findUnique({ where: { username } });
if (existing) return (0, import_response.makeResponse)({ ctr, content: { code: 409, message: "Username already taken" } });
const userCount = await import_db.prisma.user.count();
const isFirst = userCount === 0;
const hash = await import_bcryptjs.default.hash(password, 12);
const user = await import_db.prisma.user.create({
data: { username, passwordHash: hash, isAdmin: isFirst, isFounder: isFirst },
select: { id: true, username: true, isAdmin: true, isFounder: true }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 201, data: user } });
}
async function updateUser(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const id = parseInt(ctr.params.get("id") || "0", 10);
const target = await import_db.prisma.user.findUnique({ where: { id } });
if (!target) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: import_errors.ERROR_MESSAGES.NOT_FOUND.message } });
const body = await ctr.body();
const { username, password, isAdmin } = body || {};
const data = {};
if (username) {
const existing = await import_db.prisma.user.findFirst({ where: { username, id: { not: id } } });
if (existing) return (0, import_response.makeResponse)({ ctr, content: { code: 409, message: "Username taken" } });
data.username = username;
}
if (password) data.passwordHash = await import_bcryptjs.default.hash(password, 12);
if (isAdmin !== void 0 && !target.isFounder) data.isAdmin = Boolean(isAdmin);
await import_db.prisma.user.update({ where: { id }, data });
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "User updated" } });
}
async function deleteUser(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const id = parseInt(ctr.params.get("id") || "0", 10);
const target = await import_db.prisma.user.findUnique({ where: { id } });
if (!target) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: import_errors.ERROR_MESSAGES.NOT_FOUND.message } });
if (target.isFounder) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: "Cannot delete founder" } });
if (id === admin.id) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: "Cannot delete yourself" } });
await import_db.prisma.user.delete({ where: { id } });
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "User deleted" } });
}
async function getSettings(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const settings = await (0, import_adminSettings.getAdminSettings)();
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: settings } });
}
async function updateSettings(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const body = await ctr.body();
const {
defaultInstanceType,
maxConcurrentInstancesPerUser,
logSizeLimitBytes,
previewRetentionDays,
webhookRateLimitPerMinute,
contactEmail
} = body || {};
const data = {};
if (defaultInstanceType) data.defaultInstanceType = defaultInstanceType;
if (maxConcurrentInstancesPerUser) data.maxConcurrentInstancesPerUser = Number(maxConcurrentInstancesPerUser);
if (logSizeLimitBytes) data.logSizeLimitBytes = Number(logSizeLimitBytes);
if (previewRetentionDays) data.previewRetentionDays = Number(previewRetentionDays);
if (webhookRateLimitPerMinute) data.webhookRateLimitPerMinute = Number(webhookRateLimitPerMinute);
if (contactEmail !== void 0) data.contactEmail = contactEmail;
await import_db.prisma.adminSettings.upsert({
where: { id: 1 },
update: data,
create: { id: 1, ...data }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Settings updated" } });
}
async function adminListPreviews(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const previews = await import_db.prisma.preview.findMany({
include: {
repoConfig: { include: { user: { select: { id: true, username: true } } } }
},
orderBy: { updatedAt: "desc" },
take: 200
});
return (0, import_response.makeResponse)({
ctr,
content: {
code: 200,
data: previews.map((p) => ({
id: p.id,
prNumber: p.prNumber,
prTitle: p.prTitle,
status: p.status,
instanceIp: p.instanceIp,
port: p.port,
createdAt: p.createdAt,
updatedAt: p.updatedAt,
repoOwner: p.repoConfig.repoOwner,
repoName: p.repoConfig.repoName,
user: p.repoConfig.user
}))
}
});
}
async function adminStopPreview(ctr) {
const admin = requireAdmin(ctr);
if (!admin) return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: import_errors.ERROR_MESSAGES.FORBIDDEN.message } });
const id = parseInt(ctr.params.get("id") || "0", 10);
const preview = await import_db.prisma.preview.findUnique({ where: { id } });
if (!preview) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: import_errors.ERROR_MESSAGES.NOT_FOUND.message } });
await import_db.prisma.job.create({
data: { previewId: id, type: "STOP", status: "PENDING", payload: { reason: "Admin stop" } }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Stop job enqueued" } });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
adminListPreviews,
adminStopPreview,
createUser,
deleteUser,
getSettings,
listUsers,
updateSettings,
updateUser
});
//# sourceMappingURL=admin.js.map
File diff suppressed because one or more lines are too long
-153
View File
@@ -1,153 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var previews_exports = {};
__export(previews_exports, {
getPreview: () => getPreview,
listPreviews: () => listPreviews,
previewLogsWs: () => previewLogsWs,
stopPreviewRoute: () => stopPreviewRoute
});
module.exports = __toCommonJS(previews_exports);
var import_db = require("../../lib/db");
var import_response = require("../../lib/response");
var import_errors = require("../../lib/errors");
var import_deploy = require("../../services/deploy");
function requireAuth(ctr) {
const auth = ctr.getAuth?.();
if (!auth?.success) return null;
return auth.user;
}
async function listPreviews(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const previews = await import_db.prisma.preview.findMany({
where: { repoConfig: { userId: user.id } },
include: { repoConfig: { select: { repoOwner: true, repoName: true } } },
orderBy: { updatedAt: "desc" }
});
return (0, import_response.makeResponse)({
ctr,
content: {
code: 200,
data: previews.map((p) => ({
id: p.id,
prNumber: p.prNumber,
prTitle: p.prTitle,
commitSha: p.commitSha,
status: p.status,
instanceIp: p.instanceIp,
port: p.port,
createdAt: p.createdAt,
updatedAt: p.updatedAt,
lastActivityAt: p.lastActivityAt,
repoOwner: p.repoConfig.repoOwner,
repoName: p.repoConfig.repoName
}))
}
});
}
async function getPreview(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const id = parseInt(ctr.params.get("id") || "0", 10);
const preview = await import_db.prisma.preview.findFirst({
where: { id, repoConfig: { userId: user.id } },
include: {
repoConfig: { select: { repoOwner: true, repoName: true } },
jobs: { orderBy: { createdAt: "desc" }, take: 10 }
}
});
if (!preview) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: import_errors.ERROR_MESSAGES.NOT_FOUND.message } });
return (0, import_response.makeResponse)({
ctr,
content: {
code: 200,
data: {
id: preview.id,
prNumber: preview.prNumber,
prTitle: preview.prTitle,
commitSha: preview.commitSha,
status: preview.status,
instanceIp: preview.instanceIp,
port: preview.port,
logs: preview.logs,
createdAt: preview.createdAt,
updatedAt: preview.updatedAt,
stoppedAt: preview.stoppedAt,
lastActivityAt: preview.lastActivityAt,
repoOwner: preview.repoConfig.repoOwner,
repoName: preview.repoConfig.repoName,
jobs: preview.jobs.map((j) => ({
id: j.id,
type: j.type,
status: j.status,
createdAt: j.createdAt,
startedAt: j.startedAt,
finishedAt: j.finishedAt,
error: j.error
}))
}
}
});
}
async function stopPreviewRoute(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const id = parseInt(ctr.params.get("id") || "0", 10);
const preview = await import_db.prisma.preview.findFirst({
where: { id, repoConfig: { userId: user.id } }
});
if (!preview) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: import_errors.ERROR_MESSAGES.NOT_FOUND.message } });
if (preview.status === "STOPPED") return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Already stopped" } });
await import_db.prisma.job.create({
data: { previewId: id, type: "STOP", status: "PENDING", payload: { reason: "Manual stop via UI" } }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Stop job enqueued" } });
}
async function previewLogsWs(ctr) {
const auth = ctr.getAuth?.();
if (!auth?.success) {
ctr.close(1008, "Unauthorized");
return;
}
const id = parseInt(ctr.params.get("id") || "0", 10);
const preview = await import_db.prisma.preview.findFirst({
where: { id, repoConfig: { userId: auth.user.id } }
});
if (!preview) {
ctr.close(1008, "Not found");
return;
}
await ctr.print(JSON.stringify({ type: "init", logs: preview.logs }));
const unsub = (0, import_deploy.subscribeToLogs)(id, (text) => {
try {
ctr.print(JSON.stringify({ type: "append", text }));
} catch {
}
});
ctr.$abort(unsub);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getPreview,
listPreviews,
previewLogsWs,
stopPreviewRoute
});
//# sourceMappingURL=previews.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../../src/routes/api/previews.ts"],
"sourcesContent": ["import { prisma } from \"../../lib/db\";\nimport { makeResponse } from \"../../lib/response\";\nimport { ERROR_MESSAGES } from \"../../lib/errors\";\nimport { subscribeToLogs } from \"../../services/deploy\";\n\nfunction requireAuth(ctr: any) {\n const auth = ctr.getAuth?.();\n if (!auth?.success) return null;\n return auth.user;\n}\n\nexport async function listPreviews(ctr: any) {\n const user = requireAuth(ctr);\n if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });\n\n const previews = await prisma.preview.findMany({\n where: { repoConfig: { userId: user.id } },\n include: { repoConfig: { select: { repoOwner: true, repoName: true } } },\n orderBy: { updatedAt: \"desc\" },\n });\n\n return makeResponse({\n ctr, content: {\n code: 200, data: previews.map(p => ({\n id: p.id,\n prNumber: p.prNumber,\n prTitle: p.prTitle,\n commitSha: p.commitSha,\n status: p.status,\n instanceIp: p.instanceIp,\n port: p.port,\n createdAt: p.createdAt,\n updatedAt: p.updatedAt,\n lastActivityAt: p.lastActivityAt,\n repoOwner: p.repoConfig.repoOwner,\n repoName: p.repoConfig.repoName,\n }))\n }\n });\n}\n\nexport async function getPreview(ctr: any) {\n const user = requireAuth(ctr);\n if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });\n\n const id = parseInt(ctr.params.get(\"id\") || \"0\", 10);\n const preview = await prisma.preview.findFirst({\n where: { id, repoConfig: { userId: user.id } },\n include: {\n repoConfig: { select: { repoOwner: true, repoName: true } },\n jobs: { orderBy: { createdAt: \"desc\" }, take: 10 },\n },\n });\n\n if (!preview) return makeResponse({ ctr, content: { code: 404, message: ERROR_MESSAGES.NOT_FOUND.message } });\n\n return makeResponse({\n ctr, content: {\n code: 200, data: {\n id: preview.id,\n prNumber: preview.prNumber,\n prTitle: preview.prTitle,\n commitSha: preview.commitSha,\n status: preview.status,\n instanceIp: preview.instanceIp,\n port: preview.port,\n logs: preview.logs,\n createdAt: preview.createdAt,\n updatedAt: preview.updatedAt,\n stoppedAt: preview.stoppedAt,\n lastActivityAt: preview.lastActivityAt,\n repoOwner: preview.repoConfig.repoOwner,\n repoName: preview.repoConfig.repoName,\n jobs: preview.jobs.map(j => ({\n id: j.id,\n type: j.type,\n status: j.status,\n createdAt: j.createdAt,\n startedAt: j.startedAt,\n finishedAt: j.finishedAt,\n error: j.error,\n })),\n }\n }\n });\n}\n\nexport async function stopPreviewRoute(ctr: any) {\n const user = requireAuth(ctr);\n if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });\n\n const id = parseInt(ctr.params.get(\"id\") || \"0\", 10);\n const preview = await prisma.preview.findFirst({\n where: { id, repoConfig: { userId: user.id } },\n });\n\n if (!preview) return makeResponse({ ctr, content: { code: 404, message: ERROR_MESSAGES.NOT_FOUND.message } });\n if (preview.status === \"STOPPED\") return makeResponse({ ctr, content: { code: 400, message: \"Already stopped\" } });\n\n await prisma.job.create({\n data: { previewId: id, type: \"STOP\", status: \"PENDING\", payload: { reason: \"Manual stop via UI\" } },\n });\n\n return makeResponse({ ctr, content: { code: 200, message: \"Stop job enqueued\" } });\n}\n\nexport async function previewLogsWs(ctr: any) {\n const auth = ctr.getAuth?.();\n if (!auth?.success) {\n ctr.close(1008, \"Unauthorized\");\n return;\n }\n\n const id = parseInt(ctr.params.get(\"id\") || \"0\", 10);\n const preview = await prisma.preview.findFirst({\n where: { id, repoConfig: { userId: auth.user.id } },\n });\n\n if (!preview) {\n ctr.close(1008, \"Not found\");\n return;\n }\n\n await ctr.print(JSON.stringify({ type: \"init\", logs: preview.logs }));\n\n const unsub = subscribeToLogs(id, (text) => {\n try {\n ctr.print(JSON.stringify({ type: \"append\", text }));\n } catch {}\n });\n\n ctr.$abort(unsub);\n}\n"],
"mappings": "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",
"names": []
}
-180
View File
@@ -1,180 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var repos_exports = {};
__export(repos_exports, {
getRepoConfig: () => getRepoConfig,
listRepos: () => listRepos,
saveRepoConfig: () => saveRepoConfig,
toggleRepoEnabled: () => toggleRepoEnabled
});
module.exports = __toCommonJS(repos_exports);
var import_db = require("../../lib/db");
var import_response = require("../../lib/response");
var import_errors = require("../../lib/errors");
var import_gitea = require("../../services/gitea");
var import_adminSettings = require("../../lib/adminSettings");
var import_env = require("../../lib/env");
function requireAuth(ctr) {
const auth = ctr.getAuth?.();
if (!auth?.success) return null;
return auth.user;
}
async function listRepos(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const fullUser = await import_db.prisma.user.findUnique({ where: { id: user.id } });
if (!fullUser?.giteaInstanceUrl || !fullUser?.giteaPAT) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Gitea credentials not configured" } });
}
const giteaRepos = await (0, import_gitea.fetchUserRepos)(fullUser);
const configs = await import_db.prisma.repoConfig.findMany({ where: { userId: user.id } });
const configMap = new Map(configs.map((c) => [`${c.repoOwner}/${c.repoName}`, c]));
const allOwners = [...new Set(giteaRepos.map((r) => r.full_name?.split("/")[0]).filter(Boolean))];
const allConfigs = await import_db.prisma.repoConfig.findMany({
where: { repoOwner: { in: allOwners } },
select: { repoOwner: true, repoName: true, userId: true }
});
const claimedByOthers = new Set(
allConfigs.filter((c) => c.userId !== user.id).map((c) => `${c.repoOwner}/${c.repoName}`)
);
const result = giteaRepos.map((r) => {
const [owner, name] = (r.full_name || "").split("/");
const key = `${owner}/${name}`;
const config = configMap.get(key);
const { giteaWebhookId, ...safeConfig } = config || {};
return {
owner,
name,
fullName: r.full_name,
htmlUrl: r.html_url,
isEnabled: config?.isEnabled ?? false,
claimedByOther: claimedByOthers.has(key),
config: config ? safeConfig : null
};
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: result } });
}
async function getRepoConfig(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const owner = ctr.params.get("owner");
const repo = ctr.params.get("repo");
const config = await import_db.prisma.repoConfig.findFirst({
where: { repoOwner: owner, repoName: repo, userId: user.id }
});
if (!config) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: "Repo config not found" } });
const { giteaWebhookId, ...safeConfig } = config;
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: safeConfig } });
}
async function saveRepoConfig(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const body = await ctr.body();
const { owner, repo, ...configData } = body || {};
if (!owner || !repo) return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "owner and repo required" } });
const existing = await import_db.prisma.repoConfig.findFirst({
where: { repoOwner: owner, repoName: repo }
});
if (existing && existing.userId !== user.id) {
return (0, import_response.makeResponse)({ ctr, content: { code: 409, message: "This repo is already configured by another user." } });
}
const settings = await (0, import_adminSettings.getAdminSettings)();
const sanitized = {
repoOwner: owner,
repoName: repo,
userId: user.id,
instanceType: configData.instanceType ?? settings.defaultInstanceType,
inactivityHours: Math.min(72, Math.max(0.5, Number(configData.inactivityHours ?? 12))),
port: Number(configData.port ?? 3e3),
envVars: configData.envVars ?? {},
useDockerCompose: Boolean(configData.useDockerCompose ?? false),
composeFilePath: configData.composeFilePath ?? null,
aptPackages: Array.isArray(configData.aptPackages) ? configData.aptPackages : [],
setupCommands: Array.isArray(configData.setupCommands) ? configData.setupCommands : [],
buildCommands: Array.isArray(configData.buildCommands) ? configData.buildCommands : [],
postBuildCommands: Array.isArray(configData.postBuildCommands) ? configData.postBuildCommands : [],
runCommand: configData.runCommand ?? null,
denyList: Array.isArray(configData.denyList) ? configData.denyList : []
};
let config;
if (existing) {
config = await import_db.prisma.repoConfig.update({ where: { id: existing.id }, data: sanitized });
} else {
config = await import_db.prisma.repoConfig.create({ data: sanitized });
}
const { giteaWebhookId, ...safeConfig } = config;
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: safeConfig } });
}
async function toggleRepoEnabled(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const body = await ctr.body();
const { owner, repo, enabled } = body || {};
if (!owner || !repo || enabled === void 0) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "owner, repo, enabled required" } });
}
const fullUser = await import_db.prisma.user.findUnique({ where: { id: user.id } });
if (!fullUser?.giteaInstanceUrl || !fullUser?.giteaPAT) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Gitea credentials not configured" } });
}
let config = await import_db.prisma.repoConfig.findFirst({ where: { repoOwner: owner, repoName: repo } });
if (config && config.userId !== user.id) {
return (0, import_response.makeResponse)({ ctr, content: { code: 409, message: "This repo is already configured by another user." } });
}
const webhookToken = await import_db.prisma.webhookToken.findUnique({ where: { userId: user.id } });
if (!webhookToken) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "No webhook token configured. Go to Settings to generate one." } });
}
if (enabled) {
if (!config) {
config = await import_db.prisma.repoConfig.create({
data: { repoOwner: owner, repoName: repo, userId: user.id, isEnabled: false }
});
}
const webhookUrl = `${import_env.env.PP_BASE_URL}/webhook/${user.id}`;
const hookId = await (0, import_gitea.registerWebhook)(fullUser, owner, repo, webhookUrl, webhookToken.token);
await import_db.prisma.repoConfig.update({
where: { id: config.id },
data: { isEnabled: true, giteaWebhookId: String(hookId) }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Repo enabled and webhook registered" } });
} else {
if (!config) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: "Repo config not found" } });
if (config.giteaWebhookId) {
try {
await (0, import_gitea.deleteWebhook)(fullUser, owner, repo, config.giteaWebhookId);
} catch (e) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: `Failed to delete Gitea webhook: ${e.message}` } });
}
}
await import_db.prisma.repoConfig.update({
where: { id: config.id },
data: { isEnabled: false, giteaWebhookId: null }
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Repo disabled and webhook removed" } });
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getRepoConfig,
listRepos,
saveRepoConfig,
toggleRepoEnabled
});
//# sourceMappingURL=repos.js.map
File diff suppressed because one or more lines are too long
-204
View File
@@ -1,204 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var user_exports = {};
__export(user_exports, {
getUserSettings: () => getUserSettings,
getWebhookSecret: () => getWebhookSecret,
regenerateWebhookSecret: () => regenerateWebhookSecret,
updateAws: () => updateAws,
updateGitea: () => updateGitea,
updatePassword: () => updatePassword,
updateUsername: () => updateUsername
});
module.exports = __toCommonJS(user_exports);
var import_bcryptjs = __toESM(require("bcryptjs"));
var import_crypto = require("crypto");
var import_db = require("../../lib/db");
var import_response = require("../../lib/response");
var import_errors = require("../../lib/errors");
var import_encryption = require("../../lib/encryption");
var import_gitea = require("../../services/gitea");
var import_ec2 = require("../../services/ec2");
var import_env = require("../../lib/env");
function requireAuth(ctr) {
const auth = ctr.getAuth?.();
if (!auth?.success) return null;
return auth.user;
}
async function getUserSettings(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: import_errors.ERROR_MESSAGES.UNAUTHORIZED.code, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const webhookToken = await import_db.prisma.webhookToken.findUnique({ where: { userId: user.id } });
const fullUser = await import_db.prisma.user.findUnique({ where: { id: user.id } });
return (0, import_response.makeResponse)({
ctr,
content: {
code: 200,
data: {
id: user.id,
username: user.username,
giteaUsername: fullUser?.giteaUsername,
giteaInstanceUrl: fullUser?.giteaInstanceUrl,
giteaPatSet: !!fullUser?.giteaPAT,
awsAccessKeyId: fullUser?.awsAccessKeyId ? "****" : null,
awsRegion: fullUser?.awsRegion,
webhookUrl: `${import_env.env.PP_BASE_URL}/webhook/${user.id}`,
webhookSecret: webhookToken?.token ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : null,
webhookTokenExists: !!webhookToken
}
}
});
}
async function updateUsername(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const body = await ctr.body();
const { username } = body || {};
if (!username || typeof username !== "string") return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Username required" } });
const existing = await import_db.prisma.user.findFirst({ where: { username, id: { not: user.id } } });
if (existing) return (0, import_response.makeResponse)({ ctr, content: { code: 409, message: "Username already taken" } });
await import_db.prisma.user.update({ where: { id: user.id }, data: { username } });
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Username updated" } });
}
async function updatePassword(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const body = await ctr.body();
const { currentPassword, newPassword } = body || {};
if (!currentPassword || !newPassword) return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Current and new passwords required" } });
const fullUser = await import_db.prisma.user.findUnique({ where: { id: user.id } });
if (!fullUser) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: "User not found" } });
const valid = await import_bcryptjs.default.compare(currentPassword, fullUser.passwordHash);
if (!valid) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: "Current password incorrect" } });
const hash = await import_bcryptjs.default.hash(newPassword, 12);
await import_db.prisma.user.update({ where: { id: user.id }, data: { passwordHash: hash } });
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Password updated" } });
}
async function updateGitea(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const body = await ctr.body();
const { giteaInstanceUrl, giteaUsername, giteaPAT } = body || {};
if (!giteaInstanceUrl || !giteaUsername) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Gitea URL and username required" } });
}
const cleanUrl = giteaInstanceUrl.replace(/\/+$/, "");
const validation = await (0, import_gitea.validateGiteaUrl)(cleanUrl);
if (!validation.success) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: `Gitea validation failed: ${validation.error}` } });
}
const data = { giteaInstanceUrl: cleanUrl, giteaUsername };
if (giteaPAT) data.giteaPAT = (0, import_encryption.encrypt)(giteaPAT);
await import_db.prisma.user.update({ where: { id: user.id }, data });
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: `Connected to Gitea ${validation.version}`, data: { version: validation.version } } });
}
async function updateAws(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const body = await ctr.body();
const { awsAccessKeyId, awsSecretAccessKey, awsRegion } = body || {};
if (!awsAccessKeyId || !awsSecretAccessKey || !awsRegion) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "AWS credentials and region required" } });
}
const tempUser = {
...user,
awsAccessKeyId: (0, import_encryption.encrypt)(awsAccessKeyId),
awsSecretAccessKey: (0, import_encryption.encrypt)(awsSecretAccessKey),
awsRegion
};
const validation = await (0, import_ec2.validateAwsCredentials)(tempUser);
if (!validation.success) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: `AWS validation failed: ${validation.error}` } });
}
await import_db.prisma.user.update({
where: { id: user.id },
data: {
awsAccessKeyId: (0, import_encryption.encrypt)(awsAccessKeyId),
awsSecretAccessKey: (0, import_encryption.encrypt)(awsSecretAccessKey),
awsRegion
}
});
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: `Connected as ${validation.arn}`, data: { arn: validation.arn } } });
}
async function getWebhookSecret(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
let token = await import_db.prisma.webhookToken.findUnique({ where: { userId: user.id } });
if (!token) {
const secret = (0, import_crypto.randomBytes)(32).toString("hex");
token = await import_db.prisma.webhookToken.create({ data: { userId: user.id, token: secret } });
}
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: { token: token.token } } });
}
async function regenerateWebhookSecret(ctr) {
const user = requireAuth(ctr);
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
const newSecret = (0, import_crypto.randomBytes)(32).toString("hex");
await import_db.prisma.webhookToken.upsert({
where: { userId: user.id },
update: { token: newSecret },
create: { userId: user.id, token: newSecret }
});
const fullUser = await import_db.prisma.user.findUnique({ where: { id: user.id } });
if (!fullUser?.giteaInstanceUrl) {
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Secret regenerated (no Gitea hooks to update)", data: { token: newSecret } } });
}
const configs = await import_db.prisma.repoConfig.findMany({
where: { userId: user.id, giteaWebhookId: { not: null } }
});
const { updateWebhookSecret } = await import("../../services/gitea");
const webhookUrl = `${import_env.env.PP_BASE_URL}/webhook/${user.id}`;
const errors = [];
for (const config of configs) {
try {
await updateWebhookSecret(fullUser, config.repoOwner, config.repoName, config.giteaWebhookId, webhookUrl, newSecret);
} catch (e) {
errors.push(`${config.repoOwner}/${config.repoName}: ${e.message}`);
}
}
return (0, import_response.makeResponse)({
ctr,
content: {
code: 200,
message: errors.length ? `Secret regenerated with ${errors.length} hook update errors` : "Secret regenerated and all hooks updated",
data: { token: newSecret, errors }
}
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getUserSettings,
getWebhookSecret,
regenerateWebhookSecret,
updateAws,
updateGitea,
updatePassword,
updateUsername
});
//# sourceMappingURL=user.js.map
File diff suppressed because one or more lines are too long
-161
View File
@@ -1,161 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var auth_exports = {};
__export(auth_exports, {
firstUserHandler: () => firstUserHandler,
loginHandler: () => loginHandler,
logoutHandler: () => logoutHandler,
meHandler: () => meHandler,
setupStatusHandler: () => setupStatusHandler
});
module.exports = __toCommonJS(auth_exports);
var import_crypto = require("crypto");
var import_bcryptjs = __toESM(require("bcryptjs"));
var import_rjweb_server = require("rjweb-server");
var import_db = require("../lib/db");
var import_response = require("../lib/response");
var import_errors = require("../lib/errors");
var import_logger = require("../lib/logger");
const log = (0, import_logger.createLogger)("AUTH_ROUTE");
const COOKIE_NAME = "pp_session";
const COOKIE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
async function loginHandler(ctr) {
let body;
try {
body = await ctr.body();
} catch {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Invalid body" } });
}
const { username, password } = body || {};
if (!username || !password) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Username and password required" } });
}
const user = await import_db.prisma.user.findUnique({ where: { username } });
if (!user) {
return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: "Invalid credentials" } });
}
const valid = await import_bcryptjs.default.compare(password, user.passwordHash);
if (!valid) {
return (0, import_response.makeResponse)({ ctr, content: { code: 401, message: "Invalid credentials" } });
}
const sessionHash = (0, import_crypto.randomBytes)(32).toString("hex");
await import_db.prisma.session.create({ data: { hash: sessionHash, userId: user.id } });
ctr.cookies.set(
COOKIE_NAME,
new import_rjweb_server.Cookie(sessionHash, {
httpOnly: true,
expires: new Date(Date.now() + COOKIE_MAX_AGE_MS),
path: "/",
sameSite: "Lax"
})
);
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: { id: user.id, username: user.username, isAdmin: user.isAdmin } } });
}
async function logoutHandler(ctr) {
const auth = ctr.getAuth?.();
if (auth?.success) {
await import_db.prisma.session.delete({ where: { id: auth.sessionId } }).catch(() => {
});
}
ctr.cookies.set(COOKIE_NAME, new import_rjweb_server.Cookie("", { expires: /* @__PURE__ */ new Date(0), path: "/" }));
return (0, import_response.makeResponse)({ ctr, content: { code: 200, message: "Logged out" } });
}
async function meHandler(ctr) {
const auth = ctr.getAuth?.();
if (!auth?.success) {
return (0, import_response.makeResponse)({ ctr, content: { code: import_errors.ERROR_MESSAGES.UNAUTHORIZED.code, message: import_errors.ERROR_MESSAGES.UNAUTHORIZED.message } });
}
const user = await import_db.prisma.user.findUnique({ where: { id: auth.user.id } });
if (!user) return (0, import_response.makeResponse)({ ctr, content: { code: 404, message: "User not found" } });
return (0, import_response.makeResponse)({
ctr,
content: {
code: 200,
data: {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
isFounder: user.isFounder,
giteaUsername: user.giteaUsername,
giteaInstanceUrl: user.giteaInstanceUrl,
giteaPatSet: !!user.giteaPAT,
awsAccessKeyId: user.awsAccessKeyId ? "****" : null,
awsRegion: user.awsRegion,
awsConfigured: !!(user.awsAccessKeyId && user.awsSecretAccessKey && user.awsRegion),
setupComplete: !!(user.giteaInstanceUrl && user.giteaPAT && user.awsAccessKeyId && user.awsSecretAccessKey && user.awsRegion)
}
}
});
}
async function setupStatusHandler(ctr) {
const count = await import_db.prisma.user.count();
return (0, import_response.makeResponse)({ ctr, content: { code: 200, data: { needsSetup: count === 0 } } });
}
async function firstUserHandler(ctr) {
const count = await import_db.prisma.user.count();
if (count > 0) {
return (0, import_response.makeResponse)({ ctr, content: { code: 403, message: "Setup already completed. Contact an administrator to create your account." } });
}
let body;
try {
body = await ctr.body();
} catch {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Invalid body" } });
}
const { username, password } = body || {};
if (!username || !password || password.length < 8) {
return (0, import_response.makeResponse)({ ctr, content: { code: 400, message: "Username and password (min 8 chars) required" } });
}
const hash = await import_bcryptjs.default.hash(password, 12);
const user = await import_db.prisma.user.create({
data: { username, passwordHash: hash, isAdmin: true, isFounder: true }
});
const sessionHash = (0, import_crypto.randomBytes)(32).toString("hex");
await import_db.prisma.session.create({ data: { hash: sessionHash, userId: user.id } });
ctr.cookies.set(
COOKIE_NAME,
new import_rjweb_server.Cookie(sessionHash, {
httpOnly: true,
expires: new Date(Date.now() + COOKIE_MAX_AGE_MS),
path: "/",
sameSite: "Lax"
})
);
log.info({ username }, "First user (founder) created");
return (0, import_response.makeResponse)({ ctr, content: { code: 201, data: { id: user.id, username: user.username, isAdmin: true, isFounder: true } } });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
firstUserHandler,
loginHandler,
logoutHandler,
meHandler,
setupStatusHandler
});
//# sourceMappingURL=auth.js.map
File diff suppressed because one or more lines are too long
-279
View File
@@ -1,279 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var webhook_exports = {};
__export(webhook_exports, {
webhookHandler: () => webhookHandler
});
module.exports = __toCommonJS(webhook_exports);
var import_crypto = require("crypto");
var import_db = require("../lib/db");
var import_logger = require("../lib/logger");
var import_adminSettings = require("../lib/adminSettings");
var import_gitea = require("../services/gitea");
var import_env = require("../lib/env");
const log = (0, import_logger.createLogger)("WEBHOOK");
const rateLimitMap = /* @__PURE__ */ new Map();
function checkRateLimit(userId, limitPerMin) {
const now = Date.now();
const entry = rateLimitMap.get(userId);
if (!entry || now > entry.resetAt) {
rateLimitMap.set(userId, { count: 1, resetAt: now + 6e4 });
return true;
}
if (entry.count >= limitPerMin) return false;
entry.count++;
return true;
}
async function webhookHandler(ctr) {
const userId = parseInt(ctr.params.get("userId") || "0", 10);
if (!userId) return ctr.status(400).print({ status: "FAILED", message: "Invalid user ID" });
const settings = await (0, import_adminSettings.getAdminSettings)();
if (!checkRateLimit(userId, settings.webhookRateLimitPerMinute)) {
return ctr.status(429).print({ status: "FAILED", message: "Rate limit exceeded" });
}
const user = await import_db.prisma.user.findUnique({ where: { id: userId } });
if (!user) return ctr.status(404).print({ status: "FAILED", message: "User not found" });
const webhookToken = await import_db.prisma.webhookToken.findUnique({ where: { userId } });
if (!webhookToken) return ctr.status(401).print({ status: "FAILED", message: "No webhook token configured" });
const signature = ctr.headers.get("x-gitea-signature-256") || ctr.headers.get("x-hub-signature-256") || "";
const rawBody = await ctr.$body().text();
const expected = "sha256=" + (0, import_crypto.createHmac)("sha256", webhookToken.token).update(rawBody).digest("hex");
if (!timingSafeEqual(signature, expected)) {
log.warn({ userId, signature }, "Invalid webhook signature");
return ctr.status(401).print({ status: "FAILED", message: "Invalid signature" });
}
let payload;
try {
payload = JSON.parse(rawBody);
} catch {
return ctr.status(400).print({ status: "FAILED", message: "Invalid JSON payload" });
}
const event = ctr.headers.get("x-gitea-event") || "";
ctr.status(200).print({ status: "OK" });
setImmediate(() => handleWebhookAsync(user, event, payload).catch((e) => log.error({ e }, "Webhook processing error")));
}
async function handleWebhookAsync(user, event, payload) {
if (event === "pull_request") {
await handlePullRequestEvent(user, payload);
} else if (event === "issue_comment") {
await handleIssueCommentEvent(user, payload);
}
}
async function handlePullRequestEvent(user, payload) {
const action = payload.action;
const pr = payload.pull_request;
const repo = payload.repository;
if (!pr || !repo) return;
const owner = repo.owner?.login || repo.full_name?.split("/")[0];
const repoName = repo.name;
const prNumber = pr.number;
const prTitle = pr.title || `PR #${prNumber}`;
const commitSha = pr.head?.sha || "";
const cloneUrl = pr.head?.repo?.clone_url || repo.clone_url;
const repoConfig = await import_db.prisma.repoConfig.findFirst({
where: { repoOwner: owner, repoName, userId: user.id, isEnabled: true }
});
if (!repoConfig) {
const existing = await import_db.prisma.noConfigComment.findUnique({
where: { userId_repoOwner_repoName_prNumber: { userId: user.id, repoOwner: owner, repoName, prNumber } }
});
if (!existing) {
const body = `No previews configured for \`${owner}/${repoName}\`. Configure this repo in [PR Previews](${import_env.env.PP_BASE_URL}).`;
try {
await (0, import_gitea.postComment)(user, owner, repoName, prNumber, body);
await import_db.prisma.noConfigComment.create({ data: { userId: user.id, repoOwner: owner, repoName, prNumber } });
} catch {
}
}
return;
}
if (repoConfig.denyList.includes(pr.user?.login || "")) return;
if (action === "opened" || action === "reopened") {
let preview = await import_db.prisma.preview.findFirst({
where: { repoConfigId: repoConfig.id, prNumber },
orderBy: { createdAt: "desc" }
});
if (preview && preview.status === "IGNORED") return;
if (!preview || preview.status === "STOPPED") {
preview = await import_db.prisma.preview.create({
data: {
repoConfigId: repoConfig.id,
prNumber,
prTitle,
commitSha,
status: "PROVISIONING",
port: repoConfig.port
}
});
await import_db.prisma.job.create({
data: {
previewId: preview.id,
type: "DEPLOY",
status: "PENDING",
payload: { commitSha, prNumber, prTitle, cloneUrl, isFirstDeploy: true }
}
});
}
} else if (action === "synchronize") {
const preview = await import_db.prisma.preview.findFirst({
where: { repoConfigId: repoConfig.id, prNumber },
orderBy: { createdAt: "desc" }
});
if (!preview || preview.status === "IGNORED") return;
await import_db.prisma.preview.update({ where: { id: preview.id }, data: { lastActivityAt: /* @__PURE__ */ new Date() } });
await import_db.prisma.job.create({
data: {
previewId: preview.id,
type: "DEPLOY",
status: "PENDING",
payload: { commitSha, prNumber, prTitle, cloneUrl, isFirstDeploy: preview.status === "STOPPED" }
}
});
} else if (action === "closed") {
const preview = await import_db.prisma.preview.findFirst({
where: { repoConfigId: repoConfig.id, prNumber },
orderBy: { createdAt: "desc" }
});
if (preview && preview.status !== "STOPPED" && preview.status !== "IGNORED") {
await import_db.prisma.job.create({
data: {
previewId: preview.id,
type: "STOP",
status: "PENDING",
payload: { reason: "PR closed" }
}
});
}
}
}
async function handleIssueCommentEvent(user, payload) {
const action = payload.action;
if (action !== "created") return;
const comment = payload.comment;
const issue = payload.issue;
const repo = payload.repository;
if (!comment || !issue || !repo || !issue.pull_request) return;
const body = comment.body || "";
if (!body.trimStart().startsWith("/pp ")) return;
const owner = repo.owner?.login || repo.full_name?.split("/")[0];
const repoName = repo.name;
const prNumber = issue.number;
if (user.giteaUsername && comment.user?.login === user.giteaUsername) return;
const repoConfig = await import_db.prisma.repoConfig.findFirst({
where: { repoOwner: owner, repoName, userId: user.id, isEnabled: true }
});
if (!repoConfig) return;
const commenter = comment.user?.login || "";
const prAuthor = issue.user?.login || "";
const isAllowed = commenter === prAuthor || await isRepoAdmin(user, owner, repoName, commenter);
if (!isAllowed) return;
await import_db.prisma.preview.updateMany({
where: {
repoConfigId: repoConfig.id,
prNumber,
status: { in: ["RUNNING", "BUILDING", "FAILED", "PROVISIONING"] }
},
data: { lastActivityAt: /* @__PURE__ */ new Date() }
});
const commandLine = body.trimStart().split("\n")[0].trim();
const command = commandLine.replace("/pp ", "").trim();
const preview = await import_db.prisma.preview.findFirst({
where: { repoConfigId: repoConfig.id, prNumber },
orderBy: { createdAt: "desc" }
});
const cloneUrl = repo.clone_url;
if (command === "rebuild") {
if (!preview || preview.status !== "RUNNING" && preview.status !== "FAILED") return;
await import_db.prisma.job.create({
data: {
previewId: preview.id,
type: "DEPLOY",
status: "PENDING",
payload: { commitSha: preview.commitSha, prNumber, prTitle: preview.prTitle, cloneUrl, isFirstDeploy: false }
}
});
} else if (command === "stop") {
if (!preview || preview.status === "STOPPED") return;
await import_db.prisma.job.create({
data: { previewId: preview.id, type: "STOP", status: "PENDING", payload: { reason: "Manual stop" } }
});
} else if (command === "start") {
if (!preview) {
const newPreview = await import_db.prisma.preview.create({
data: { repoConfigId: repoConfig.id, prNumber, prTitle: issue.title, commitSha: "", status: "PROVISIONING", port: repoConfig.port }
});
await import_db.prisma.job.create({
data: { previewId: newPreview.id, type: "DEPLOY", status: "PENDING", payload: { commitSha: "", prNumber, prTitle: issue.title, cloneUrl, isFirstDeploy: true } }
});
} else if (preview.status === "STOPPED" || preview.status === "IGNORED") {
await import_db.prisma.preview.update({ where: { id: preview.id }, data: { status: "PROVISIONING" } });
await import_db.prisma.job.create({
data: { previewId: preview.id, type: "DEPLOY", status: "PENDING", payload: { commitSha: preview.commitSha, prNumber, prTitle: preview.prTitle, cloneUrl, isFirstDeploy: true } }
});
}
} else if (command === "logs") {
if (!preview) return;
const lastLines = (preview.logs || "").split("\n").slice(-50).join("\n");
const logBody = `**PP Logs** (last 50 lines)
\`\`\`
${lastLines}
\`\`\``;
await (0, import_gitea.postComment)(user, owner, repoName, prNumber, logBody);
} else if (command === "ignore") {
if (!preview) {
await import_db.prisma.preview.create({
data: { repoConfigId: repoConfig.id, prNumber, prTitle: issue.title, commitSha: "", status: "IGNORED", port: repoConfig.port }
});
} else {
await import_db.prisma.preview.update({ where: { id: preview.id }, data: { status: "IGNORED" } });
}
}
}
async function isRepoAdmin(user, owner, repo, username) {
try {
const { getRepoCollaboratorPermission } = await import("../services/gitea");
const permission = await getRepoCollaboratorPermission(user, owner, repo, username);
return permission === "owner" || permission === "admin";
} catch {
return false;
}
}
function timingSafeEqual(a, b) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) {
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return diff === 0;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
webhookHandler
});
//# sourceMappingURL=webhook.js.map
File diff suppressed because one or more lines are too long
-440
View File
@@ -1,440 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var deploy_exports = {};
__export(deploy_exports, {
abortJobForPreview: () => abortJobForPreview,
appendLog: () => appendLog,
runDeploy: () => runDeploy,
signalAbort: () => signalAbort,
stopPreview: () => stopPreview,
subscribeToLogs: () => subscribeToLogs
});
module.exports = __toCommonJS(deploy_exports);
var import_db = require("../lib/db");
var import_logger = require("../lib/logger");
var import_encryption = require("../lib/encryption");
var import_adminSettings = require("../lib/adminSettings");
var import_env = require("../lib/env");
var import_ec2 = require("./ec2");
var import_ssh = require("./ssh");
var import_gitea = require("./gitea");
const log = (0, import_logger.createLogger)("DEPLOY");
const activeSshSessions = /* @__PURE__ */ new Map();
function abortJobForPreview(previewId) {
const session = activeSshSessions.get(previewId);
if (session) {
session.abort();
activeSshSessions.delete(previewId);
}
}
async function appendLog(previewId, text) {
const settings = await (0, import_adminSettings.getAdminSettings)();
const preview = await import_db.prisma.preview.findUnique({ where: { id: previewId } });
if (!preview) return;
let logs = (preview.logs || "") + text;
if (Buffer.byteLength(logs, "utf8") > settings.logSizeLimitBytes) {
const marker = "--- logs truncated ---\n";
while (Buffer.byteLength(logs, "utf8") > settings.logSizeLimitBytes) {
const nl = logs.indexOf("\n");
if (nl === -1) break;
logs = logs.slice(nl + 1);
}
logs = marker + logs;
}
await import_db.prisma.preview.update({ where: { id: previewId }, data: { logs } });
broadcastLogUpdate(previewId, text);
}
const logSubscribers = /* @__PURE__ */ new Map();
function subscribeToLogs(previewId, cb) {
if (!logSubscribers.has(previewId)) logSubscribers.set(previewId, /* @__PURE__ */ new Set());
logSubscribers.get(previewId).add(cb);
return () => logSubscribers.get(previewId)?.delete(cb);
}
function broadcastLogUpdate(previewId, text) {
logSubscribers.get(previewId)?.forEach((cb) => cb(text));
}
async function updateStatus(previewId, status, extra = {}) {
await import_db.prisma.preview.update({ where: { id: previewId }, data: { status, ...extra } });
}
async function updateGiteaComment(user, preview, repoConfig, statusLine, lastLogLines) {
const body = (0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber: preview.prNumber,
status: statusLine,
commitSha: preview.commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL,
lastLogLines,
instanceIp: preview.instanceIp ?? void 0,
port: preview.port
});
if (preview.giteaCommentId) {
try {
await (0, import_gitea.updateComment)(user, repoConfig.repoOwner, repoConfig.repoName, preview.giteaCommentId, body);
} catch (e) {
log.warn({ e }, "Failed to update Gitea comment");
}
}
}
async function runDeploy(jobId) {
const job = await import_db.prisma.job.findUnique({ where: { id: jobId }, include: { preview: { include: { repoConfig: { include: { user: true } } } } } });
if (!job || !job.preview) {
log.error({ jobId }, "Job or preview not found");
return;
}
await import_db.prisma.job.update({ where: { id: jobId }, data: { status: "RUNNING", startedAt: /* @__PURE__ */ new Date() } });
const preview = job.preview;
const repoConfig = preview.repoConfig;
const user = preview.repoConfig.user;
const payload = job.payload;
const { commitSha, prNumber, prTitle, cloneUrl, isFirstDeploy } = payload;
try {
if (isFirstDeploy) {
await firstDeploy(jobId, preview, repoConfig, user, commitSha, prNumber, prTitle, cloneUrl);
} else {
await redeploy(jobId, preview, repoConfig, user, commitSha, prNumber, prTitle);
}
await import_db.prisma.job.update({ where: { id: jobId }, data: { status: "DONE", finishedAt: /* @__PURE__ */ new Date() } });
} catch (e) {
if (e.message === "ABORTED") {
log.info({ jobId, previewId: preview.id }, "Job aborted");
await import_db.prisma.job.update({ where: { id: jobId }, data: { status: "FAILED", error: "Aborted by newer deploy", finishedAt: /* @__PURE__ */ new Date() } });
return;
}
log.error({ e, jobId, previewId: preview.id }, "Deploy failed");
await import_db.prisma.job.update({ where: { id: jobId }, data: { status: "FAILED", error: e.message, finishedAt: /* @__PURE__ */ new Date() } });
const freshPreview = await import_db.prisma.preview.findUnique({ where: { id: preview.id } });
if (!freshPreview) return;
const lastLines = (freshPreview.logs || "").split("\n").slice(-10).join("\n");
await updateStatus(preview.id, "FAILED");
const failBody = (0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber: freshPreview.prNumber,
status: `\u{1F534} Failed \u2014 last log lines:
\`\`\`
${lastLines}
\`\`\``,
commitSha: freshPreview.commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
});
if (freshPreview.giteaCommentId) {
try {
await (0, import_gitea.updateComment)(user, repoConfig.repoOwner, repoConfig.repoName, freshPreview.giteaCommentId, failBody);
} catch {
}
}
}
}
async function firstDeploy(jobId, preview, repoConfig, user, commitSha, prNumber, prTitle, cloneUrl) {
const settings = await (0, import_adminSettings.getAdminSettings)();
const ec2 = (0, import_ec2.makeEc2Client)(user);
const previewId = preview.id;
const activeCount = await import_db.prisma.preview.count({
where: {
repoConfig: { userId: user.id },
status: { in: ["PROVISIONING", "BUILDING", "RUNNING"] },
id: { not: previewId }
}
});
if (activeCount >= settings.maxConcurrentInstancesPerUser) {
const body = (0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber,
status: `\u{1F534} Cannot provision preview \u2014 concurrent instance limit (${settings.maxConcurrentInstancesPerUser}) reached.`,
commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
});
const commentId2 = await (0, import_gitea.postComment)(user, repoConfig.repoOwner, repoConfig.repoName, prNumber, body);
await import_db.prisma.preview.update({ where: { id: previewId }, data: { giteaCommentId: commentId2 } });
throw new Error("Concurrent instance limit reached");
}
await updateStatus(previewId, "PROVISIONING", { commitSha, prNumber, prTitle });
const commentBody = (0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber,
status: "\u{1F7E1} Provisioning EC2 instance...",
commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
});
let commentId = await (0, import_gitea.postComment)(user, repoConfig.repoOwner, repoConfig.repoName, prNumber, commentBody);
await import_db.prisma.preview.update({ where: { id: previewId }, data: { giteaCommentId: commentId } });
checkAbort(previewId);
const keyName = `pp-preview-${previewId}`;
const { privateKey } = await (0, import_ec2.generateAndImportKeyPair)(ec2, keyName);
const encPrivateKey = (0, import_encryption.encrypt)(privateKey);
await import_db.prisma.preview.update({ where: { id: previewId }, data: { sshPrivateKey: encPrivateKey, sshKeyName: keyName } });
checkAbort(previewId);
const sgName = `pp-preview-${previewId}`;
const securityGroupId = await (0, import_ec2.createPreviewSecurityGroup)(ec2, sgName, repoConfig.port);
checkAbort(previewId);
const instanceId = await (0, import_ec2.launchInstance)({
ec2,
region: user.awsRegion,
instanceType: repoConfig.instanceType,
keyName,
securityGroupId,
tags: {
"pp:managed": "true",
"pp:userId": String(user.id),
"pp:repo": `${repoConfig.repoOwner}/${repoConfig.repoName}`,
"pp:prNumber": String(prNumber),
"pp:previewId": String(previewId)
}
});
await import_db.prisma.preview.update({ where: { id: previewId }, data: { instanceId } });
await appendLog(previewId, `[PP] EC2 instance ${instanceId} launched. Waiting for it to be running...
`);
checkAbort(previewId);
const instanceIp = await (0, import_ec2.waitForInstanceRunning)(ec2, instanceId);
await import_db.prisma.preview.update({ where: { id: previewId }, data: { instanceIp, port: repoConfig.port } });
await (0, import_gitea.updateComment)(
user,
repoConfig.repoOwner,
repoConfig.repoName,
commentId,
(0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber,
status: `\u{1F7E1} Building... (EC2 ready at ${instanceIp})`,
commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
})
);
await appendLog(previewId, `[PP] Instance running at ${instanceIp}. Waiting for SSH...
`);
checkAbort(previewId);
const sshSession = await (0, import_ssh.connectSsh)(instanceIp, privateKey, 3e5);
activeSshSessions.set(previewId, sshSession);
try {
if (repoConfig.aptPackages.length > 0) {
await runSshStep(previewId, sshSession, `sudo apt-get install -y ${repoConfig.aptPackages.join(" ")}`);
}
const giteaPat = user.giteaPAT ? (0, import_encryption.decrypt)(user.giteaPAT) : "";
const authCloneUrl = cloneUrl.replace("https://", `https://${user.giteaUsername}:${giteaPat}@`);
await runSshStep(previewId, sshSession, `git clone ${authCloneUrl} /opt/app`);
await runSshStep(previewId, sshSession, `cd /opt/app && git fetch origin pull/${prNumber}/head:pp-pr && git checkout pp-pr`);
await setupAndBuild(previewId, sshSession, repoConfig, preview, commitSha, true);
await updateStatus(previewId, "RUNNING", { commitSha, instanceIp, port: repoConfig.port, lastActivityAt: /* @__PURE__ */ new Date() });
const freshPreview = await import_db.prisma.preview.findUnique({ where: { id: previewId } });
await (0, import_gitea.updateComment)(
user,
repoConfig.repoOwner,
repoConfig.repoName,
commentId,
(0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber,
status: `\u{1F7E2} Live at http://${instanceIp}:${repoConfig.port}`,
commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
})
);
} finally {
sshSession.close();
activeSshSessions.delete(previewId);
}
}
async function redeploy(jobId, preview, repoConfig, user, commitSha, prNumber, prTitle) {
const previewId = preview.id;
const instanceIp = preview.instanceIp;
const privateKey = (0, import_encryption.decrypt)(preview.sshPrivateKey);
const sshSession = await (0, import_ssh.connectSsh)(instanceIp, privateKey, 3e4);
activeSshSessions.set(previewId, sshSession);
await appendLog(previewId, `
--- Redeploy: ${commitSha} ---
`);
const freshPreview = await import_db.prisma.preview.findUnique({ where: { id: previewId } });
const commentBody = (0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber,
status: `\u{1F7E1} Building... (EC2 at ${instanceIp})`,
commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
});
const newCommentId = await (0, import_gitea.postComment)(user, repoConfig.repoOwner, repoConfig.repoName, prNumber, commentBody);
await import_db.prisma.preview.update({ where: { id: previewId }, data: { giteaCommentId: newCommentId, commitSha } });
try {
if (repoConfig.useDockerCompose) {
const composePath = repoConfig.composeFilePath || "docker-compose.yml";
await runSshStep(previewId, sshSession, `cd /opt/app && docker compose -f ${composePath} down 2>&1 || true`);
} else if (freshPreview?.pid) {
await runSshStep(previewId, sshSession, `kill ${freshPreview.pid} 2>/dev/null || true; sleep 5; kill -9 ${freshPreview.pid} 2>/dev/null || true`);
}
await runSshStep(previewId, sshSession, `cd /opt/app && git fetch origin pull/${prNumber}/head:pp-pr && git checkout pp-pr && git reset --hard FETCH_HEAD`);
await updateStatus(previewId, "BUILDING");
await setupAndBuild(previewId, sshSession, repoConfig, preview, commitSha, false);
await updateStatus(previewId, "RUNNING", { commitSha, lastActivityAt: /* @__PURE__ */ new Date() });
await (0, import_gitea.updateComment)(
user,
repoConfig.repoOwner,
repoConfig.repoName,
newCommentId,
(0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber,
status: `\u{1F7E2} Live at http://${instanceIp}:${repoConfig.port}`,
commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
})
);
} finally {
sshSession.close();
activeSshSessions.delete(previewId);
}
}
async function setupAndBuild(previewId, sshSession, repoConfig, preview, commitSha, isFirstProvision) {
await detectAndUseNode(previewId, sshSession);
const envVars = repoConfig.envVars;
const envContent = Object.entries(envVars).map(([k, v]) => `${k}=${v}`).join("\n");
await runSshStep(previewId, sshSession, `cat > /opt/app/.env << 'PPEOF'
${envContent}
PPEOF`);
if (isFirstProvision) {
for (const cmd of repoConfig.setupCommands) {
await runSshStep(previewId, sshSession, `cd /opt/app && ${cmd}`);
}
}
await updateStatus(previewId, "BUILDING");
if (repoConfig.useDockerCompose) {
const composePath = repoConfig.composeFilePath || "docker-compose.yml";
await runSshStep(previewId, sshSession, `cd /opt/app && docker compose -f ${composePath} up -d --build --force-recreate 2>&1`);
} else {
for (const cmd of repoConfig.buildCommands) {
await runSshStep(previewId, sshSession, `cd /opt/app && ${cmd}`);
}
for (const cmd of repoConfig.postBuildCommands) {
await runSshStep(previewId, sshSession, `cd /opt/app && ${cmd}`);
}
if (repoConfig.runCommand) {
const res = await runSshStep(
previewId,
sshSession,
`cd /opt/app && nohup ${repoConfig.runCommand} > /opt/app/pp.log 2>&1 & echo $!`
);
const pid = parseInt(res.stdout.trim(), 10);
if (!isNaN(pid)) {
await import_db.prisma.preview.update({ where: { id: previewId }, data: { pid } });
}
}
}
}
async function detectAndUseNode(previewId, sshSession) {
const nvmSource = `export NVM_DIR="/root/.nvm" && source "$NVM_DIR/nvm.sh"`;
const res = await runSshStep(previewId, sshSession, `${nvmSource} && [ -f /opt/app/.nvmrc ] && nvm install && nvm use || nvm use default 2>&1`, false);
}
async function runSshStep(previewId, sshSession, command, throwOnFail = true) {
checkAbortSession(sshSession);
await appendLog(previewId, `$ ${command}
`);
const res = await sshSession.exec(command);
if (res.stdout) await appendLog(previewId, res.stdout);
if (res.stderr) await appendLog(previewId, res.stderr);
if (throwOnFail && res.code !== 0) {
throw new Error(`Command failed with exit code ${res.code}: ${command}`);
}
return res;
}
function checkAbortSession(session) {
if (session.aborted) throw new Error("ABORTED");
}
const abortSignals = /* @__PURE__ */ new Set();
function signalAbort(previewId) {
abortSignals.add(previewId);
abortJobForPreview(previewId);
}
function checkAbort(previewId) {
if (abortSignals.has(previewId)) {
abortSignals.delete(previewId);
throw new Error("ABORTED");
}
}
async function stopPreview(previewId, reason = "STOPPED") {
const preview = await import_db.prisma.preview.findUnique({
where: { id: previewId },
include: { repoConfig: { include: { user: true } } }
});
if (!preview) return;
const user = preview.repoConfig.user;
const repoConfig = preview.repoConfig;
if (preview.instanceId) {
const ec2 = (0, import_ec2.makeEc2Client)(user);
try {
await (0, import_ec2.deleteKeyPairAws)(ec2, `pp-preview-${previewId}`);
} catch {
}
try {
await (0, import_ec2.deleteSecurityGroupAws)(ec2, `pp-preview-${previewId}`);
} catch {
}
try {
await (0, import_ec2.terminateInstance)(ec2, preview.instanceId);
} catch {
}
}
await import_db.prisma.preview.update({
where: { id: previewId },
data: {
status: reason,
stoppedAt: /* @__PURE__ */ new Date(),
sshPrivateKey: null,
sshKeyName: null,
instanceId: null
}
});
const stoppedBody = (0, import_gitea.buildPrCommentBody)({
owner: repoConfig.repoOwner,
repo: repoConfig.repoName,
prNumber: preview.prNumber,
status: "\u26AB Stopped (inactivity timeout / PR closed / manual stop)",
commitSha: preview.commitSha,
updatedAt: /* @__PURE__ */ new Date(),
ppBaseUrl: import_env.env.PP_BASE_URL
});
if (preview.giteaCommentId) {
try {
await (0, import_gitea.updateComment)(user, repoConfig.repoOwner, repoConfig.repoName, preview.giteaCommentId, stoppedBody);
} catch {
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
abortJobForPreview,
appendLog,
runDeploy,
signalAbort,
stopPreview,
subscribeToLogs
});
//# sourceMappingURL=deploy.js.map
File diff suppressed because one or more lines are too long
-244
View File
@@ -1,244 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var ec2_exports = {};
__export(ec2_exports, {
createPreviewSecurityGroup: () => createPreviewSecurityGroup,
deleteKeyPairAws: () => deleteKeyPairAws,
deleteSecurityGroupAws: () => deleteSecurityGroupAws,
describeAllManagedInstances: () => describeAllManagedInstances,
generateAndImportKeyPair: () => generateAndImportKeyPair,
generateSshKeyPair: () => generateSshKeyPair,
launchInstance: () => launchInstance,
makeEc2Client: () => makeEc2Client,
makeStsClient: () => makeStsClient,
terminateInstance: () => terminateInstance,
validateAwsCredentials: () => validateAwsCredentials,
waitForInstanceRunning: () => waitForInstanceRunning
});
module.exports = __toCommonJS(ec2_exports);
var import_client_ec2 = require("@aws-sdk/client-ec2");
var import_client_sts = require("@aws-sdk/client-sts");
var import_crypto = require("crypto");
var import_logger = require("../lib/logger");
var import_encryption = require("../lib/encryption");
const log = (0, import_logger.createLogger)("EC2");
const UBUNTU_22_04_AMI = {
"us-east-1": "ami-0e86e20dae9224db8",
"us-east-2": "ami-0a0d9cf81c479446a",
"us-west-1": "ami-05c969369880fa2c2",
"us-west-2": "ami-03f8acd418785369b",
"eu-west-1": "ami-0694d931cee176e7d",
"eu-west-2": "ami-0f3d9639a5674d559",
"eu-west-3": "ami-022e307f4b9e39f45",
"eu-central-1": "ami-0faab6bdbac9486fb",
"ap-southeast-1": "ami-0823c236601fef765",
"ap-southeast-2": "ami-07620139298af599e",
"ap-northeast-1": "ami-0b7546e839d7ace12",
"ap-northeast-2": "ami-042e76978adeb8c48",
"ap-south-1": "ami-076e3a557efe1aa9c",
"sa-east-1": "ami-0eed58016fbe42de3",
"ca-central-1": "ami-024f768de9e73d4f4",
"eu-north-1": "ami-00381a880aa48c6c6",
"me-south-1": "ami-09574f34b8dcd2eac",
"af-south-1": "ami-08fdcf06b39fe83ec"
};
function makeEc2Client(user) {
const accessKeyId = user.awsAccessKeyId ? (0, import_encryption.decrypt)(user.awsAccessKeyId) : "";
const secretAccessKey = user.awsSecretAccessKey ? (0, import_encryption.decrypt)(user.awsSecretAccessKey) : "";
return new import_client_ec2.EC2Client({
region: user.awsRegion,
credentials: { accessKeyId, secretAccessKey }
});
}
function makeStsClient(user) {
const accessKeyId = user.awsAccessKeyId ? (0, import_encryption.decrypt)(user.awsAccessKeyId) : "";
const secretAccessKey = user.awsSecretAccessKey ? (0, import_encryption.decrypt)(user.awsSecretAccessKey) : "";
return new import_client_sts.STSClient({
region: user.awsRegion,
credentials: { accessKeyId, secretAccessKey }
});
}
async function validateAwsCredentials(user) {
try {
const sts = makeStsClient(user);
const res = await sts.send(new import_client_sts.GetCallerIdentityCommand({}));
return { success: true, arn: res.Arn };
} catch (e) {
return { success: false, error: e.message };
}
}
function generateSshKeyPair() {
const { privateKey: privKeyPem, publicKey: pubKeyPem } = (0, import_crypto.generateKeyPairSync)("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs1", format: "pem" }
});
const pubKeyObj = (0, import_crypto.createPublicKey)(pubKeyPem);
const pubKeyDer = pubKeyObj.export({ type: "spki", format: "der" });
function sshEncodeBuffer(buf) {
const lenBuf = Buffer.allocUnsafe(4);
lenBuf.writeUInt32BE(buf.length, 0);
return Buffer.concat([lenBuf, buf]);
}
const keyTypeStr = Buffer.from("ssh-rsa");
const keyTypeEncoded = sshEncodeBuffer(keyTypeStr);
const base64Encoded = pubKeyDer.toString("base64");
const openSshKey = `ssh-rsa ${Buffer.concat([keyTypeEncoded]).toString("base64")} pp-generated`;
return { privateKey: privKeyPem, publicKeyOpenssh: `ssh-rsa ${base64Encoded} pp-generated` };
}
async function generateAndImportKeyPair(ec2, keyName) {
const { privateKey, publicKeyOpenssh } = generateSshKeyPair();
await ec2.send(new import_client_ec2.ImportKeyPairCommand({
KeyName: keyName,
PublicKeyMaterial: Buffer.from(publicKeyOpenssh)
}));
return { privateKey };
}
async function createPreviewSecurityGroup(ec2, groupName, port) {
const describe = await ec2.send(new import_client_ec2.DescribeSecurityGroupsCommand({
Filters: [{ Name: "group-name", Values: [groupName] }]
}));
if (describe.SecurityGroups && describe.SecurityGroups.length > 0) {
return describe.SecurityGroups[0].GroupId;
}
const res = await ec2.send(new import_client_ec2.CreateSecurityGroupCommand({
GroupName: groupName,
Description: `PP Preview security group: ${groupName}`
}));
const groupId = res.GroupId;
const ingress = [
{
IpProtocol: "tcp",
FromPort: 22,
ToPort: 22,
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
}
];
if (port !== 22) {
ingress.push({
IpProtocol: "tcp",
FromPort: port,
ToPort: port,
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
});
}
await ec2.send(new import_client_ec2.AuthorizeSecurityGroupIngressCommand({
GroupId: groupId,
IpPermissions: ingress
}));
return groupId;
}
const BOOTSTRAP_SCRIPT = `#!/bin/bash
set -e
apt-get update -y
apt-get install -y curl git unzip build-essential
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
apt-get install -y docker-compose-plugin
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
export NVM_DIR="/root/.nvm"
source "$NVM_DIR/nvm.sh"
nvm install --lts
nvm alias default lts/*
`;
async function launchInstance(opts) {
const ami = UBUNTU_22_04_AMI[opts.region] ?? UBUNTU_22_04_AMI["us-east-1"];
const tagSpecs = Object.entries(opts.tags).map(([k, v]) => ({ Key: k, Value: v }));
tagSpecs.push({ Key: "Name", Value: `pp-preview-${opts.tags["pp:previewId"]}` });
const res = await opts.ec2.send(new import_client_ec2.RunInstancesCommand({
ImageId: ami,
InstanceType: opts.instanceType,
MinCount: 1,
MaxCount: 1,
KeyName: opts.keyName,
SecurityGroupIds: [opts.securityGroupId],
UserData: Buffer.from(BOOTSTRAP_SCRIPT).toString("base64"),
TagSpecifications: [
{ ResourceType: "instance", Tags: tagSpecs }
]
}));
return res.Instances[0].InstanceId;
}
async function waitForInstanceRunning(ec2, instanceId, maxWaitMs = 3e5) {
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
const res = await ec2.send(new import_client_ec2.DescribeInstancesCommand({
InstanceIds: [instanceId]
}));
const inst = res.Reservations?.[0]?.Instances?.[0];
if (inst?.State?.Name === "running" && inst.PublicIpAddress) {
return inst.PublicIpAddress;
}
await sleep(5e3);
}
throw new Error(`Instance ${instanceId} did not reach running state within timeout`);
}
async function terminateInstance(ec2, instanceId) {
await ec2.send(new import_client_ec2.TerminateInstancesCommand({ InstanceIds: [instanceId] }));
}
async function deleteKeyPairAws(ec2, keyName) {
try {
await ec2.send(new import_client_ec2.DeleteKeyPairCommand({ KeyName: keyName }));
} catch (e) {
log.warn({ e, keyName }, "Failed to delete key pair");
}
}
async function deleteSecurityGroupAws(ec2, groupName) {
try {
const describe = await ec2.send(new import_client_ec2.DescribeSecurityGroupsCommand({
Filters: [{ Name: "group-name", Values: [groupName] }]
}));
const groupId = describe.SecurityGroups?.[0]?.GroupId;
if (groupId) {
await sleep(5e3);
await ec2.send(new import_client_ec2.DeleteSecurityGroupCommand({ GroupId: groupId }));
}
} catch (e) {
log.warn({ e, groupName }, "Failed to delete security group");
}
}
async function describeAllManagedInstances(ec2) {
const res = await ec2.send(new import_client_ec2.DescribeInstancesCommand({
Filters: [
{ Name: "tag:pp:managed", Values: ["true"] },
{ Name: "instance-state-name", Values: ["running", "pending", "stopping"] }
]
}));
return (res.Reservations ?? []).flatMap((r) => r.Instances ?? []);
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createPreviewSecurityGroup,
deleteKeyPairAws,
deleteSecurityGroupAws,
describeAllManagedInstances,
generateAndImportKeyPair,
generateSshKeyPair,
launchInstance,
makeEc2Client,
makeStsClient,
terminateInstance,
validateAwsCredentials,
waitForInstanceRunning
});
//# sourceMappingURL=ec2.js.map
File diff suppressed because one or more lines are too long
-170
View File
@@ -1,170 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var gitea_exports = {};
__export(gitea_exports, {
buildPrCommentBody: () => buildPrCommentBody,
checkUserPermission: () => checkUserPermission,
deleteWebhook: () => deleteWebhook,
fetchUserRepos: () => fetchUserRepos,
getRepoCollaboratorPermission: () => getRepoCollaboratorPermission,
giteaApi: () => giteaApi,
postComment: () => postComment,
registerWebhook: () => registerWebhook,
updateComment: () => updateComment,
updateWebhookSecret: () => updateWebhookSecret,
validateGiteaUrl: () => validateGiteaUrl
});
module.exports = __toCommonJS(gitea_exports);
var import_axios = __toESM(require("axios"));
var import_encryption = require("../lib/encryption");
function giteaApi(user) {
const pat = user.giteaPAT ? (0, import_encryption.decrypt)(user.giteaPAT) : "";
return import_axios.default.create({
baseURL: `${user.giteaInstanceUrl}/api/v1`,
headers: {
Authorization: `token ${pat}`,
"Content-Type": "application/json"
},
timeout: 15e3
});
}
async function validateGiteaUrl(url) {
try {
const res = await import_axios.default.get(`${url}/api/v1/version`, { timeout: 1e4 });
return { success: true, version: res.data.version };
} catch (e) {
return { success: false, error: e.message };
}
}
async function fetchUserRepos(user) {
const api = giteaApi(user);
const repos = [];
let page = 1;
while (true) {
const res = await api.get(`/repos/search?limit=50&page=${page}`);
const data = res.data?.data ?? [];
if (data.length === 0) break;
repos.push(...data);
if (data.length < 50) break;
page++;
}
return repos;
}
async function registerWebhook(user, owner, repo, webhookUrl, secret) {
const api = giteaApi(user);
const res = await api.post(`/repos/${owner}/${repo}/hooks`, {
type: "gitea",
config: {
url: webhookUrl,
secret,
content_type: "json"
},
events: ["pull_request", "issue_comment"],
active: true
});
return res.data.id;
}
async function deleteWebhook(user, owner, repo, hookId) {
const api = giteaApi(user);
await api.delete(`/repos/${owner}/${repo}/hooks/${hookId}`);
}
async function updateWebhookSecret(user, owner, repo, hookId, webhookUrl, newSecret) {
const api = giteaApi(user);
await api.patch(`/repos/${owner}/${repo}/hooks/${hookId}`, {
config: {
url: webhookUrl,
secret: newSecret,
content_type: "json"
},
events: ["pull_request", "issue_comment"],
active: true
});
}
async function postComment(user, owner, repo, issueNumber, body) {
const api = giteaApi(user);
const res = await api.post(`/repos/${owner}/${repo}/issues/${issueNumber}/comments`, { body });
return res.data.id;
}
async function updateComment(user, owner, repo, commentId, body) {
const api = giteaApi(user);
await api.patch(`/repos/${owner}/${repo}/issues/comments/${commentId}`, { body });
}
async function checkUserPermission(user, owner, repo, username) {
try {
const api = giteaApi(user);
const res = await api.get(`/repos/${owner}/${repo}/collaborators/${username}`);
return res.status === 204;
} catch {
return false;
}
}
async function getRepoCollaboratorPermission(user, owner, repo, username) {
try {
const api = giteaApi(user);
const res = await api.get(`/repos/${owner}/${repo}/collaborators/${username}/permission`);
return res.data?.permission ?? null;
} catch {
return null;
}
}
function buildPrCommentBody(opts) {
const { owner, repo, prNumber, status, commitSha, updatedAt, ppBaseUrl, lastLogLines, instanceIp, port } = opts;
const ts = updatedAt.toISOString().replace("T", " ").slice(0, 19) + " UTC";
let statusLine = status;
if (lastLogLines) {
statusLine += `
\`\`\`
${lastLogLines}
\`\`\``;
}
return `## \u{1F680} PR Preview \u2014 \`${owner}/${repo}\` #${prNumber}
**Status:** ${statusLine}
**Commit:** \`${commitSha.slice(0, 8)}\`
**Updated:** ${ts}
---
_Powered by [PR Previews](${ppBaseUrl})_`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
buildPrCommentBody,
checkUserPermission,
deleteWebhook,
fetchUserRepos,
getRepoCollaboratorPermission,
giteaApi,
postComment,
registerWebhook,
updateComment,
updateWebhookSecret,
validateGiteaUrl
});
//# sourceMappingURL=gitea.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/services/gitea.ts"],
"sourcesContent": ["import axios from \"axios\";\nimport { decrypt } from \"../lib/encryption\";\nimport type { User } from \"@prisma/client\";\n\nexport function giteaApi(user: User) {\n const pat = user.giteaPAT ? decrypt(user.giteaPAT) : \"\";\n return axios.create({\n baseURL: `${user.giteaInstanceUrl}/api/v1`,\n headers: {\n Authorization: `token ${pat}`,\n \"Content-Type\": \"application/json\",\n },\n timeout: 15000,\n });\n}\n\nexport async function validateGiteaUrl(url: string): Promise<{ success: boolean; version?: string; error?: string }> {\n try {\n const res = await axios.get(`${url}/api/v1/version`, { timeout: 10000 });\n return { success: true, version: res.data.version };\n } catch (e: any) {\n return { success: false, error: e.message };\n }\n}\n\nexport async function fetchUserRepos(user: User): Promise<any[]> {\n const api = giteaApi(user);\n const repos: any[] = [];\n let page = 1;\n while (true) {\n const res = await api.get(`/repos/search?limit=50&page=${page}`);\n const data = res.data?.data ?? [];\n if (data.length === 0) break;\n repos.push(...data);\n if (data.length < 50) break;\n page++;\n }\n return repos;\n}\n\nexport async function registerWebhook(user: User, owner: string, repo: string, webhookUrl: string, secret: string): Promise<number> {\n const api = giteaApi(user);\n const res = await api.post(`/repos/${owner}/${repo}/hooks`, {\n type: \"gitea\",\n config: {\n url: webhookUrl,\n secret,\n content_type: \"json\",\n },\n events: [\"pull_request\", \"issue_comment\"],\n active: true,\n });\n return res.data.id;\n}\n\nexport async function deleteWebhook(user: User, owner: string, repo: string, hookId: string): Promise<void> {\n const api = giteaApi(user);\n await api.delete(`/repos/${owner}/${repo}/hooks/${hookId}`);\n}\n\nexport async function updateWebhookSecret(user: User, owner: string, repo: string, hookId: string, webhookUrl: string, newSecret: string): Promise<void> {\n const api = giteaApi(user);\n await api.patch(`/repos/${owner}/${repo}/hooks/${hookId}`, {\n config: {\n url: webhookUrl,\n secret: newSecret,\n content_type: \"json\",\n },\n events: [\"pull_request\", \"issue_comment\"],\n active: true,\n });\n}\n\nexport async function postComment(user: User, owner: string, repo: string, issueNumber: number, body: string): Promise<number> {\n const api = giteaApi(user);\n const res = await api.post(`/repos/${owner}/${repo}/issues/${issueNumber}/comments`, { body });\n return res.data.id;\n}\n\nexport async function updateComment(user: User, owner: string, repo: string, commentId: number, body: string): Promise<void> {\n const api = giteaApi(user);\n await api.patch(`/repos/${owner}/${repo}/issues/comments/${commentId}`, { body });\n}\n\nexport async function checkUserPermission(user: User, owner: string, repo: string, username: string): Promise<boolean> {\n try {\n const api = giteaApi(user);\n const res = await api.get(`/repos/${owner}/${repo}/collaborators/${username}`);\n return res.status === 204;\n } catch {\n return false;\n }\n}\n\nexport async function getRepoCollaboratorPermission(user: User, owner: string, repo: string, username: string): Promise<string | null> {\n try {\n const api = giteaApi(user);\n const res = await api.get(`/repos/${owner}/${repo}/collaborators/${username}/permission`);\n return res.data?.permission ?? null;\n } catch {\n return null;\n }\n}\n\nexport function buildPrCommentBody(opts: {\n owner: string;\n repo: string;\n prNumber: number;\n status: string;\n commitSha: string;\n updatedAt: Date;\n ppBaseUrl: string;\n lastLogLines?: string;\n instanceIp?: string;\n port?: number;\n}): string {\n const { owner, repo, prNumber, status, commitSha, updatedAt, ppBaseUrl, lastLogLines, instanceIp, port } = opts;\n const ts = updatedAt.toISOString().replace(\"T\", \" \").slice(0, 19) + \" UTC\";\n\n let statusLine = status;\n if (lastLogLines) {\n statusLine += `\\n\\n\\`\\`\\`\\n${lastLogLines}\\n\\`\\`\\``;\n }\n\n return `## \uD83D\uDE80 PR Preview \u2014 \\`${owner}/${repo}\\` #${prNumber}\n\n**Status:** ${statusLine}\n**Commit:** \\`${commitSha.slice(0, 8)}\\`\n**Updated:** ${ts}\n\n---\n_Powered by [PR Previews](${ppBaseUrl})_`;\n}\n"],
"mappings": "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",
"names": ["axios"]
}
-74
View File
@@ -1,74 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var orphanCleanup_exports = {};
__export(orphanCleanup_exports, {
runOrphanCleanup: () => runOrphanCleanup
});
module.exports = __toCommonJS(orphanCleanup_exports);
var import_db = require("../lib/db");
var import_logger = require("../lib/logger");
var import_ec2 = require("./ec2");
const log = (0, import_logger.createLogger)("ORPHAN_CLEANUP");
async function runOrphanCleanup() {
log.info("Running orphan EC2 instance cleanup");
const users = await import_db.prisma.user.findMany({
where: {
awsAccessKeyId: { not: null },
awsSecretAccessKey: { not: null },
awsRegion: { not: null }
}
});
const activePreviews = await import_db.prisma.preview.findMany({
where: { status: { notIn: ["STOPPED", "IGNORED"] }, instanceId: { not: null } },
select: { id: true, instanceId: true }
});
const activeInstanceIds = new Set(activePreviews.map((p) => p.instanceId));
for (const user of users) {
try {
const ec2 = (0, import_ec2.makeEc2Client)(user);
const instances = await (0, import_ec2.describeAllManagedInstances)(ec2);
for (const inst of instances) {
const instanceId = inst.InstanceId;
if (!instanceId) continue;
const tags = Object.fromEntries((inst.Tags || []).map((t) => [t.Key, t.Value]));
const previewId = parseInt(tags["pp:previewId"] || "0", 10);
if (!activeInstanceIds.has(instanceId)) {
log.info({ instanceId, previewId }, "Terminating orphan instance");
try {
const keyName = `pp-preview-${previewId}`;
const sgName = `pp-preview-${previewId}`;
await (0, import_ec2.deleteKeyPairAws)(ec2, keyName);
await (0, import_ec2.terminateInstance)(ec2, instanceId);
setTimeout(() => (0, import_ec2.deleteSecurityGroupAws)(ec2, sgName), 3e4);
} catch (e) {
log.warn({ e, instanceId }, "Failed to terminate orphan");
}
}
}
} catch (e) {
log.warn({ e, userId: user.id }, "Failed to check orphans for user");
}
}
log.info("Orphan cleanup complete");
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
runOrphanCleanup
});
//# sourceMappingURL=orphanCleanup.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/services/orphanCleanup.ts"],
"sourcesContent": ["import { prisma } from \"../lib/db\";\nimport { createLogger } from \"../lib/logger\";\nimport { makeEc2Client, describeAllManagedInstances, terminateInstance, deleteKeyPairAws, deleteSecurityGroupAws } from \"./ec2\";\n\nconst log = createLogger(\"ORPHAN_CLEANUP\");\n\nexport async function runOrphanCleanup() {\n log.info(\"Running orphan EC2 instance cleanup\");\n\n const users = await prisma.user.findMany({\n where: {\n awsAccessKeyId: { not: null },\n awsSecretAccessKey: { not: null },\n awsRegion: { not: null },\n },\n });\n\n const activePreviews = await prisma.preview.findMany({\n where: { status: { notIn: [\"STOPPED\", \"IGNORED\"] }, instanceId: { not: null } },\n select: { id: true, instanceId: true },\n });\n const activeInstanceIds = new Set(activePreviews.map(p => p.instanceId!));\n\n for (const user of users) {\n try {\n const ec2 = makeEc2Client(user as any);\n const instances = await describeAllManagedInstances(ec2);\n\n for (const inst of instances) {\n const instanceId = inst.InstanceId;\n if (!instanceId) continue;\n\n const tags = Object.fromEntries((inst.Tags || []).map((t: any) => [t.Key, t.Value]));\n const previewId = parseInt(tags[\"pp:previewId\"] || \"0\", 10);\n\n if (!activeInstanceIds.has(instanceId)) {\n log.info({ instanceId, previewId }, \"Terminating orphan instance\");\n try {\n const keyName = `pp-preview-${previewId}`;\n const sgName = `pp-preview-${previewId}`;\n await deleteKeyPairAws(ec2, keyName);\n await terminateInstance(ec2, instanceId);\n setTimeout(() => deleteSecurityGroupAws(ec2, sgName), 30_000);\n } catch (e) {\n log.warn({ e, instanceId }, \"Failed to terminate orphan\");\n }\n }\n }\n } catch (e) {\n log.warn({ e, userId: user.id }, \"Failed to check orphans for user\");\n }\n }\n\n log.info(\"Orphan cleanup complete\");\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAuB;AACvB,oBAA6B;AAC7B,iBAAwH;AAExH,MAAM,UAAM,4BAAa,gBAAgB;AAEzC,eAAsB,mBAAmB;AACvC,MAAI,KAAK,qCAAqC;AAE9C,QAAM,QAAQ,MAAM,iBAAO,KAAK,SAAS;AAAA,IACvC,OAAO;AAAA,MACL,gBAAgB,EAAE,KAAK,KAAK;AAAA,MAC5B,oBAAoB,EAAE,KAAK,KAAK;AAAA,MAChC,WAAW,EAAE,KAAK,KAAK;AAAA,IACzB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,MAAM,iBAAO,QAAQ,SAAS;AAAA,IACnD,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,WAAW,SAAS,EAAE,GAAG,YAAY,EAAE,KAAK,KAAK,EAAE;AAAA,IAC9E,QAAQ,EAAE,IAAI,MAAM,YAAY,KAAK;AAAA,EACvC,CAAC;AACD,QAAM,oBAAoB,IAAI,IAAI,eAAe,IAAI,OAAK,EAAE,UAAW,CAAC;AAExE,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,UAAM,0BAAc,IAAW;AACrC,YAAM,YAAY,UAAM,wCAA4B,GAAG;AAEvD,iBAAW,QAAQ,WAAW;AAC5B,cAAM,aAAa,KAAK;AACxB,YAAI,CAAC,WAAY;AAEjB,cAAM,OAAO,OAAO,aAAa,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAW,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACnF,cAAM,YAAY,SAAS,KAAK,cAAc,KAAK,KAAK,EAAE;AAE1D,YAAI,CAAC,kBAAkB,IAAI,UAAU,GAAG;AACtC,cAAI,KAAK,EAAE,YAAY,UAAU,GAAG,6BAA6B;AACjE,cAAI;AACF,kBAAM,UAAU,cAAc,SAAS;AACvC,kBAAM,SAAS,cAAc,SAAS;AACtC,sBAAM,6BAAiB,KAAK,OAAO;AACnC,sBAAM,8BAAkB,KAAK,UAAU;AACvC,uBAAW,UAAM,mCAAuB,KAAK,MAAM,GAAG,GAAM;AAAA,UAC9D,SAAS,GAAG;AACV,gBAAI,KAAK,EAAE,GAAG,WAAW,GAAG,4BAA4B;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,UAAI,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,GAAG,kCAAkC;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,KAAK,yBAAyB;AACpC;",
"names": []
}
-115
View File
@@ -1,115 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var ssh_exports = {};
__export(ssh_exports, {
connectSsh: () => connectSsh
});
module.exports = __toCommonJS(ssh_exports);
var import_ssh2 = require("ssh2");
var import_logger = require("../lib/logger");
const log = (0, import_logger.createLogger)("SSH");
async function connectSsh(host, privateKey, maxWaitMs = 3e5) {
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
try {
const conn = await tryConnect(host, privateKey, 1e4);
let aborted = false;
return {
get aborted() {
return aborted;
},
abort() {
aborted = true;
try {
conn.end();
} catch {
}
},
async exec(command) {
if (aborted) throw new Error("SSH session aborted");
return execOnConn(conn, command);
},
close() {
try {
conn.end();
} catch {
}
}
};
} catch (e) {
if (Date.now() - start > maxWaitMs) throw e;
log.debug({ host, error: e.message }, "SSH connect retry");
await sleep(5e3);
}
}
throw new Error(`Could not SSH into ${host} within timeout`);
}
function tryConnect(host, privateKey, timeoutMs) {
return new Promise((resolve, reject) => {
const conn = new import_ssh2.Client();
const timer = setTimeout(() => {
conn.end();
reject(new Error(`SSH connection to ${host} timed out`));
}, timeoutMs);
conn.on("ready", () => {
clearTimeout(timer);
resolve(conn);
});
conn.on("error", (e) => {
clearTimeout(timer);
reject(e);
});
conn.connect({
host,
port: 22,
username: "ubuntu",
privateKey,
readyTimeout: timeoutMs,
algorithms: {
serverHostKey: ["ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"]
}
});
});
}
function execOnConn(conn, command) {
return new Promise((resolve, reject) => {
conn.exec(command, (err, stream) => {
if (err) return reject(err);
let stdout = "";
let stderr = "";
stream.on("data", (d) => {
stdout += d.toString();
});
stream.stderr.on("data", (d) => {
stderr += d.toString();
});
stream.on("close", (code) => {
resolve({ stdout, stderr, code: code ?? 0 });
});
});
});
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
connectSsh
});
//# sourceMappingURL=ssh.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/services/ssh.ts"],
"sourcesContent": ["import { Client } from \"ssh2\";\nimport { createLogger } from \"../lib/logger\";\n\nconst log = createLogger(\"SSH\");\n\nexport interface SshSession {\n exec(command: string): Promise<{ stdout: string; stderr: string; code: number }>;\n close(): void;\n aborted: boolean;\n abort(): void;\n}\n\nexport async function connectSsh(host: string, privateKey: string, maxWaitMs = 300_000): Promise<SshSession> {\n const start = Date.now();\n\n while (Date.now() - start < maxWaitMs) {\n try {\n const conn = await tryConnect(host, privateKey, 10000);\n let aborted = false;\n\n return {\n get aborted() { return aborted; },\n abort() {\n aborted = true;\n try { conn.end(); } catch {}\n },\n async exec(command: string) {\n if (aborted) throw new Error(\"SSH session aborted\");\n return execOnConn(conn, command);\n },\n close() {\n try { conn.end(); } catch {}\n },\n };\n } catch (e: any) {\n if (Date.now() - start > maxWaitMs) throw e;\n log.debug({ host, error: e.message }, \"SSH connect retry\");\n await sleep(5000);\n }\n }\n throw new Error(`Could not SSH into ${host} within timeout`);\n}\n\nfunction tryConnect(host: string, privateKey: string, timeoutMs: number): Promise<Client> {\n return new Promise((resolve, reject) => {\n const conn = new Client();\n const timer = setTimeout(() => {\n conn.end();\n reject(new Error(`SSH connection to ${host} timed out`));\n }, timeoutMs);\n\n conn.on(\"ready\", () => {\n clearTimeout(timer);\n resolve(conn);\n });\n conn.on(\"error\", (e) => {\n clearTimeout(timer);\n reject(e);\n });\n conn.connect({\n host,\n port: 22,\n username: \"ubuntu\",\n privateKey,\n readyTimeout: timeoutMs,\n algorithms: {\n serverHostKey: [\"ssh-rsa\", \"ecdsa-sha2-nistp256\", \"ecdsa-sha2-nistp384\", \"ecdsa-sha2-nistp521\"],\n },\n });\n });\n}\n\nfunction execOnConn(conn: Client, command: string): Promise<{ stdout: string; stderr: string; code: number }> {\n return new Promise((resolve, reject) => {\n conn.exec(command, (err, stream) => {\n if (err) return reject(err);\n let stdout = \"\";\n let stderr = \"\";\n stream.on(\"data\", (d: Buffer) => { stdout += d.toString(); });\n stream.stderr.on(\"data\", (d: Buffer) => { stderr += d.toString(); });\n stream.on(\"close\", (code: number) => {\n resolve({ stdout, stderr, code: code ?? 0 });\n });\n });\n });\n}\n\nfunction sleep(ms: number) {\n return new Promise(r => setTimeout(r, ms));\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAuB;AACvB,oBAA6B;AAE7B,MAAM,UAAM,4BAAa,KAAK;AAS9B,eAAsB,WAAW,MAAc,YAAoB,YAAY,KAA8B;AAC3G,QAAM,QAAQ,KAAK,IAAI;AAEvB,SAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,QAAI;AACF,YAAM,OAAO,MAAM,WAAW,MAAM,YAAY,GAAK;AACrD,UAAI,UAAU;AAEd,aAAO;AAAA,QACL,IAAI,UAAU;AAAE,iBAAO;AAAA,QAAS;AAAA,QAChC,QAAQ;AACN,oBAAU;AACV,cAAI;AAAE,iBAAK,IAAI;AAAA,UAAG,QAAQ;AAAA,UAAC;AAAA,QAC7B;AAAA,QACA,MAAM,KAAK,SAAiB;AAC1B,cAAI,QAAS,OAAM,IAAI,MAAM,qBAAqB;AAClD,iBAAO,WAAW,MAAM,OAAO;AAAA,QACjC;AAAA,QACA,QAAQ;AACN,cAAI;AAAE,iBAAK,IAAI;AAAA,UAAG,QAAQ;AAAA,UAAC;AAAA,QAC7B;AAAA,MACF;AAAA,IACF,SAAS,GAAQ;AACf,UAAI,KAAK,IAAI,IAAI,QAAQ,UAAW,OAAM;AAC1C,UAAI,MAAM,EAAE,MAAM,OAAO,EAAE,QAAQ,GAAG,mBAAmB;AACzD,YAAM,MAAM,GAAI;AAAA,IAClB;AAAA,EACF;AACA,QAAM,IAAI,MAAM,sBAAsB,IAAI,iBAAiB;AAC7D;AAEA,SAAS,WAAW,MAAc,YAAoB,WAAoC;AACxF,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,OAAO,IAAI,mBAAO;AACxB,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,IAAI;AACT,aAAO,IAAI,MAAM,qBAAqB,IAAI,YAAY,CAAC;AAAA,IACzD,GAAG,SAAS;AAEZ,SAAK,GAAG,SAAS,MAAM;AACrB,mBAAa,KAAK;AAClB,cAAQ,IAAI;AAAA,IACd,CAAC;AACD,SAAK,GAAG,SAAS,CAAC,MAAM;AACtB,mBAAa,KAAK;AAClB,aAAO,CAAC;AAAA,IACV,CAAC;AACD,SAAK,QAAQ;AAAA,MACX;AAAA,MACA,MAAM;AAAA,MACN,UAAU;AAAA,MACV;AAAA,MACA,cAAc;AAAA,MACd,YAAY;AAAA,QACV,eAAe,CAAC,WAAW,uBAAuB,uBAAuB,qBAAqB;AAAA,MAChG;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,WAAW,MAAc,SAA4E;AAC5G,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,SAAK,KAAK,SAAS,CAAC,KAAK,WAAW;AAClC,UAAI,IAAK,QAAO,OAAO,GAAG;AAC1B,UAAI,SAAS;AACb,UAAI,SAAS;AACb,aAAO,GAAG,QAAQ,CAAC,MAAc;AAAE,kBAAU,EAAE,SAAS;AAAA,MAAG,CAAC;AAC5D,aAAO,OAAO,GAAG,QAAQ,CAAC,MAAc;AAAE,kBAAU,EAAE,SAAS;AAAA,MAAG,CAAC;AACnE,aAAO,GAAG,SAAS,CAAC,SAAiB;AACnC,gBAAQ,EAAE,QAAQ,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAAA,MAC7C,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,MAAM,IAAY;AACzB,SAAO,IAAI,QAAQ,OAAK,WAAW,GAAG,EAAE,CAAC;AAC3C;",
"names": []
}
-100
View File
@@ -1,100 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var cronWorker_exports = {};
__export(cronWorker_exports, {
startCronWorkers: () => startCronWorkers
});
module.exports = __toCommonJS(cronWorker_exports);
var import_node_cron = __toESM(require("node-cron"));
var import_db = require("../lib/db");
var import_logger = require("../lib/logger");
var import_adminSettings = require("../lib/adminSettings");
const log = (0, import_logger.createLogger)("CRON");
function startCronWorkers() {
import_node_cron.default.schedule("*/30 * * * *", async () => {
try {
await checkInactivity();
} catch (e) {
log.error({ e }, "Inactivity check error");
}
});
import_node_cron.default.schedule("0 3 * * *", async () => {
try {
await dailyCleanup();
} catch (e) {
log.error({ e }, "Daily cleanup error");
}
});
log.info("Cron workers started");
}
async function checkInactivity() {
const settings = await (0, import_adminSettings.getAdminSettings)();
const now = /* @__PURE__ */ new Date();
const running = await import_db.prisma.preview.findMany({
where: { status: "RUNNING" }
});
for (const preview of running) {
const inactivityMs = settings.maxConcurrentInstancesPerUser;
const repoConfig = await import_db.prisma.repoConfig.findUnique({ where: { id: preview.repoConfigId } });
if (!repoConfig) continue;
const deadline = new Date(preview.lastActivityAt.getTime() + repoConfig.inactivityHours * 3600 * 1e3);
if (now >= deadline) {
log.info({ previewId: preview.id }, "Preview inactive, enqueuing INACTIVITY_STOP");
await import_db.prisma.job.create({
data: {
previewId: preview.id,
type: "INACTIVITY_STOP",
status: "PENDING",
payload: {}
}
});
}
}
}
async function dailyCleanup() {
const settings = await (0, import_adminSettings.getAdminSettings)();
const cutoff = new Date(Date.now() - settings.previewRetentionDays * 86400 * 1e3);
const old = await import_db.prisma.preview.findMany({
where: {
status: { in: ["STOPPED", "FAILED"] },
stoppedAt: { lt: cutoff }
},
select: { id: true }
});
for (const { id } of old) {
await import_db.prisma.job.deleteMany({ where: { previewId: id } });
await import_db.prisma.preview.delete({ where: { id } });
}
if (old.length > 0) log.info({ count: old.length }, "Cleaned up old previews");
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
startCronWorkers
});
//# sourceMappingURL=cronWorker.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/workers/cronWorker.ts"],
"sourcesContent": ["import cron from \"node-cron\";\nimport { prisma } from \"../lib/db\";\nimport { createLogger } from \"../lib/logger\";\nimport { getAdminSettings } from \"../lib/adminSettings\";\n\nconst log = createLogger(\"CRON\");\n\nexport function startCronWorkers() {\n // Inactivity check every 30 minutes\n cron.schedule(\"*/30 * * * *\", async () => {\n try {\n await checkInactivity();\n } catch (e) {\n log.error({ e }, \"Inactivity check error\");\n }\n });\n\n // Daily cleanup\n cron.schedule(\"0 3 * * *\", async () => {\n try {\n await dailyCleanup();\n } catch (e) {\n log.error({ e }, \"Daily cleanup error\");\n }\n });\n\n log.info(\"Cron workers started\");\n}\n\nasync function checkInactivity() {\n const settings = await getAdminSettings();\n const now = new Date();\n\n const running = await prisma.preview.findMany({\n where: { status: \"RUNNING\" },\n });\n\n for (const preview of running) {\n const inactivityMs = settings.maxConcurrentInstancesPerUser; // will use actual inactivityHours from repoConfig\n const repoConfig = await prisma.repoConfig.findUnique({ where: { id: preview.repoConfigId } });\n if (!repoConfig) continue;\n\n const deadline = new Date(preview.lastActivityAt.getTime() + repoConfig.inactivityHours * 3600 * 1000);\n if (now >= deadline) {\n log.info({ previewId: preview.id }, \"Preview inactive, enqueuing INACTIVITY_STOP\");\n await prisma.job.create({\n data: {\n previewId: preview.id,\n type: \"INACTIVITY_STOP\",\n status: \"PENDING\",\n payload: {},\n },\n });\n }\n }\n}\n\nasync function dailyCleanup() {\n const settings = await getAdminSettings();\n const cutoff = new Date(Date.now() - settings.previewRetentionDays * 86400 * 1000);\n\n const old = await prisma.preview.findMany({\n where: {\n status: { in: [\"STOPPED\", \"FAILED\"] },\n stoppedAt: { lt: cutoff },\n },\n select: { id: true },\n });\n\n for (const { id } of old) {\n await prisma.job.deleteMany({ where: { previewId: id } });\n await prisma.preview.delete({ where: { id } });\n }\n\n if (old.length > 0) log.info({ count: old.length }, \"Cleaned up old previews\");\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAiB;AACjB,gBAAuB;AACvB,oBAA6B;AAC7B,2BAAiC;AAEjC,MAAM,UAAM,4BAAa,MAAM;AAExB,SAAS,mBAAmB;AAEjC,mBAAAA,QAAK,SAAS,gBAAgB,YAAY;AACxC,QAAI;AACF,YAAM,gBAAgB;AAAA,IACxB,SAAS,GAAG;AACV,UAAI,MAAM,EAAE,EAAE,GAAG,wBAAwB;AAAA,IAC3C;AAAA,EACF,CAAC;AAGD,mBAAAA,QAAK,SAAS,aAAa,YAAY;AACrC,QAAI;AACF,YAAM,aAAa;AAAA,IACrB,SAAS,GAAG;AACV,UAAI,MAAM,EAAE,EAAE,GAAG,qBAAqB;AAAA,IACxC;AAAA,EACF,CAAC;AAED,MAAI,KAAK,sBAAsB;AACjC;AAEA,eAAe,kBAAkB;AAC/B,QAAM,WAAW,UAAM,uCAAiB;AACxC,QAAM,MAAM,oBAAI,KAAK;AAErB,QAAM,UAAU,MAAM,iBAAO,QAAQ,SAAS;AAAA,IAC5C,OAAO,EAAE,QAAQ,UAAU;AAAA,EAC7B,CAAC;AAED,aAAW,WAAW,SAAS;AAC7B,UAAM,eAAe,SAAS;AAC9B,UAAM,aAAa,MAAM,iBAAO,WAAW,WAAW,EAAE,OAAO,EAAE,IAAI,QAAQ,aAAa,EAAE,CAAC;AAC7F,QAAI,CAAC,WAAY;AAEjB,UAAM,WAAW,IAAI,KAAK,QAAQ,eAAe,QAAQ,IAAI,WAAW,kBAAkB,OAAO,GAAI;AACrG,QAAI,OAAO,UAAU;AACnB,UAAI,KAAK,EAAE,WAAW,QAAQ,GAAG,GAAG,6CAA6C;AACjF,YAAM,iBAAO,IAAI,OAAO;AAAA,QACtB,MAAM;AAAA,UACJ,WAAW,QAAQ;AAAA,UACnB,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,SAAS,CAAC;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,eAAe;AAC5B,QAAM,WAAW,UAAM,uCAAiB;AACxC,QAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,uBAAuB,QAAQ,GAAI;AAEjF,QAAM,MAAM,MAAM,iBAAO,QAAQ,SAAS;AAAA,IACxC,OAAO;AAAA,MACL,QAAQ,EAAE,IAAI,CAAC,WAAW,QAAQ,EAAE;AAAA,MACpC,WAAW,EAAE,IAAI,OAAO;AAAA,IAC1B;AAAA,IACA,QAAQ,EAAE,IAAI,KAAK;AAAA,EACrB,CAAC;AAED,aAAW,EAAE,GAAG,KAAK,KAAK;AACxB,UAAM,iBAAO,IAAI,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,EAAE,CAAC;AACxD,UAAM,iBAAO,QAAQ,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAAA,EAC/C;AAEA,MAAI,IAAI,SAAS,EAAG,KAAI,KAAK,EAAE,OAAO,IAAI,OAAO,GAAG,yBAAyB;AAC/E;",
"names": ["cron"]
}
-114
View File
@@ -1,114 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var jobWorker_exports = {};
__export(jobWorker_exports, {
startJobWorker: () => startJobWorker,
stopJobWorker: () => stopJobWorker
});
module.exports = __toCommonJS(jobWorker_exports);
var import_db = require("../lib/db");
var import_logger = require("../lib/logger");
var import_deploy = require("../services/deploy");
const log = (0, import_logger.createLogger)("JOB_WORKER");
let running = false;
async function startJobWorker() {
if (running) return;
running = true;
log.info("Job worker started");
await resetStuckJobs();
pollLoop();
}
async function resetStuckJobs() {
const count = await import_db.prisma.job.updateMany({
where: { status: "RUNNING" },
data: { status: "PENDING", startedAt: null }
});
if (count.count > 0) log.info({ count: count.count }, "Reset stuck running jobs to PENDING");
}
async function pollLoop() {
while (running) {
try {
await processPendingJobs();
} catch (e) {
log.error({ e }, "Job worker poll error");
}
await sleep(1e3);
}
}
const activePreviewJobs = /* @__PURE__ */ new Map();
async function processPendingJobs() {
const pending = await import_db.prisma.job.findMany({
where: { status: "PENDING" },
orderBy: { createdAt: "asc" },
take: 20
});
for (const job of pending) {
const previewId = job.previewId;
if (!previewId) continue;
if (activePreviewJobs.has(previewId)) {
const existingJobId = activePreviewJobs.get(previewId);
if (job.type === "DEPLOY") {
log.info({ previewId, newJobId: job.id, abortingJobId: existingJobId }, "New deploy cancels existing");
(0, import_deploy.signalAbort)(previewId);
await sleep(500);
} else {
continue;
}
}
activePreviewJobs.set(previewId, job.id);
processJob(job).finally(() => {
if (activePreviewJobs.get(previewId) === job.id) {
activePreviewJobs.delete(previewId);
}
});
}
}
async function processJob(job) {
log.info({ jobId: job.id, type: job.type, previewId: job.previewId }, "Processing job");
try {
if (job.type === "DEPLOY") {
await (0, import_deploy.runDeploy)(job.id);
} else if (job.type === "STOP" || job.type === "INACTIVITY_STOP") {
if (job.previewId) {
await import_db.prisma.job.update({ where: { id: job.id }, data: { status: "RUNNING", startedAt: /* @__PURE__ */ new Date() } });
await (0, import_deploy.stopPreview)(job.previewId);
await import_db.prisma.job.update({ where: { id: job.id }, data: { status: "DONE", finishedAt: /* @__PURE__ */ new Date() } });
}
}
} catch (e) {
log.error({ e, jobId: job.id }, "Job processing error");
await import_db.prisma.job.update({
where: { id: job.id },
data: { status: "FAILED", error: e.message, finishedAt: /* @__PURE__ */ new Date() }
}).catch(() => {
});
}
}
function stopJobWorker() {
running = false;
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
startJobWorker,
stopJobWorker
});
//# sourceMappingURL=jobWorker.js.map
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../src/workers/jobWorker.ts"],
"sourcesContent": ["import { prisma } from \"../lib/db\";\nimport { createLogger } from \"../lib/logger\";\nimport { runDeploy, stopPreview, signalAbort } from \"../services/deploy\";\n\nconst log = createLogger(\"JOB_WORKER\");\nlet running = false;\n\nexport async function startJobWorker() {\n if (running) return;\n running = true;\n log.info(\"Job worker started\");\n\n await resetStuckJobs();\n pollLoop();\n}\n\nasync function resetStuckJobs() {\n const count = await prisma.job.updateMany({\n where: { status: \"RUNNING\" },\n data: { status: \"PENDING\", startedAt: null },\n });\n if (count.count > 0) log.info({ count: count.count }, \"Reset stuck running jobs to PENDING\");\n}\n\nasync function pollLoop() {\n while (running) {\n try {\n await processPendingJobs();\n } catch (e) {\n log.error({ e }, \"Job worker poll error\");\n }\n await sleep(1000);\n }\n}\n\nconst activePreviewJobs = new Map<number, number>();\n\nasync function processPendingJobs() {\n const pending = await prisma.job.findMany({\n where: { status: \"PENDING\" },\n orderBy: { createdAt: \"asc\" },\n take: 20,\n });\n\n for (const job of pending) {\n const previewId = job.previewId;\n if (!previewId) continue;\n\n if (activePreviewJobs.has(previewId)) {\n const existingJobId = activePreviewJobs.get(previewId)!;\n\n if (job.type === \"DEPLOY\") {\n log.info({ previewId, newJobId: job.id, abortingJobId: existingJobId }, \"New deploy cancels existing\");\n signalAbort(previewId);\n await sleep(500);\n } else {\n continue;\n }\n }\n\n activePreviewJobs.set(previewId, job.id);\n\n processJob(job).finally(() => {\n if (activePreviewJobs.get(previewId) === job.id) {\n activePreviewJobs.delete(previewId);\n }\n });\n }\n}\n\nasync function processJob(job: { id: number; type: string; previewId: number | null; payload: any }) {\n log.info({ jobId: job.id, type: job.type, previewId: job.previewId }, \"Processing job\");\n\n try {\n if (job.type === \"DEPLOY\") {\n await runDeploy(job.id);\n } else if (job.type === \"STOP\" || job.type === \"INACTIVITY_STOP\") {\n if (job.previewId) {\n await prisma.job.update({ where: { id: job.id }, data: { status: \"RUNNING\", startedAt: new Date() } });\n await stopPreview(job.previewId);\n await prisma.job.update({ where: { id: job.id }, data: { status: \"DONE\", finishedAt: new Date() } });\n }\n }\n } catch (e: any) {\n log.error({ e, jobId: job.id }, \"Job processing error\");\n await prisma.job.update({\n where: { id: job.id },\n data: { status: \"FAILED\", error: e.message, finishedAt: new Date() },\n }).catch(() => {});\n }\n}\n\nexport function stopJobWorker() {\n running = false;\n}\n\nfunction sleep(ms: number) {\n return new Promise(r => setTimeout(r, ms));\n}\n"],
"mappings": "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",
"names": []
}