import { Router } from "@oak/oak"; import { getMongoClient } from "../db/mongo.ts"; import { authMiddleware } from "../middleware/auth.ts"; import { ObjectId } from "mongodb"; const router = new Router({ prefix: "/api/app" }); interface AppRelease { _id?: ObjectId; version: string; versionCode: number; downloadUrl: string; releaseNotes: string; size?: number; createdAt: Date; updatedAt: Date; } async function getReleasesCollection() { const client = await getMongoClient(); const db = client.db("ams"); return db.collection("app_releases"); } // Öffentlich: Version prüfen (App ruft das ohne Auth auf) router.get("/update/check", async (ctx) => { const currentVersion = ctx.request.url.searchParams.get("currentVersion") || "0"; const currentCode = parseInt(ctx.request.url.searchParams.get("currentCode") || "0"); const col = await getReleasesCollection(); const latest = await col.findOne({}, { sort: { versionCode: -1 } }); if (!latest) { ctx.response.body = { updateAvailable: false }; return; } const updateAvailable = latest.versionCode > currentCode; ctx.response.body = { updateAvailable, currentVersion, latestVersion: latest.version, latestVersionCode: latest.versionCode, downloadUrl: latest.downloadUrl, releaseNotes: latest.releaseNotes, size: latest.size, }; }); // Öffentlich: Download-Redirect zur APK router.get("/update/download", async (ctx) => { const col = await getReleasesCollection(); const latest = await col.findOne({}, { sort: { versionCode: -1 } }); if (!latest) { ctx.response.status = 404; ctx.response.body = { error: "Kein Release verfügbar" }; return; } ctx.response.redirect(latest.downloadUrl); }); // Admin: Neues Release anlegen router.post("/update/release", authMiddleware, async (ctx) => { const body = await ctx.request.body.json(); const { version, versionCode, downloadUrl, releaseNotes, size } = body; if (!version || !versionCode || !downloadUrl) { ctx.response.status = 400; ctx.response.body = { error: "version, versionCode und downloadUrl sind Pflichtfelder" }; return; } const col = await getReleasesCollection(); const now = new Date(); const result = await col.insertOne({ version, versionCode, downloadUrl, releaseNotes: releaseNotes || "", size: size || 0, createdAt: now, updatedAt: now, }); ctx.response.status = 201; ctx.response.body = { id: result.insertedId, version, versionCode }; }); // Admin: Alle Releases auflisten router.get("/update/releases", authMiddleware, async (ctx) => { const col = await getReleasesCollection(); const releases = await col.find({}).sort({ versionCode: -1 }).toArray(); ctx.response.body = { releases }; }); // Admin: Release löschen router.delete("/update/release/:id", authMiddleware, async (ctx) => { const id = ctx.params.id; const col = await getReleasesCollection(); await col.deleteOne({ _id: new ObjectId(id) }); ctx.response.body = { deleted: true }; }); // Service-API: Release per API-Key anlegen (für CI-Pipeline) router.post("/update/release/ci", async (ctx) => { const apiKey = ctx.request.headers.get("X-API-Key") || ctx.request.url.searchParams.get("apiKey"); const expectedKey = Deno.env.get("AMS_SERVICE_API_KEY"); if (!apiKey || apiKey !== expectedKey) { ctx.response.status = 401; ctx.response.body = { error: "Ungültiger API-Key" }; return; } const body = await ctx.request.body.json(); const { version, versionCode, downloadUrl, releaseNotes, size } = body; if (!version || !versionCode || !downloadUrl) { ctx.response.status = 400; ctx.response.body = { error: "version, versionCode und downloadUrl sind Pflichtfelder" }; return; } const col = await getReleasesCollection(); const now = new Date(); // Upsert by versionCode await col.updateOne( { versionCode }, { $set: { version, versionCode, downloadUrl, releaseNotes: releaseNotes || "", size: size || 0, updatedAt: now, }, $setOnInsert: { createdAt: now }, }, { upsert: true } ); ctx.response.status = 201; ctx.response.body = { success: true, version, versionCode }; }); export const appUpdateRouter = router;