Initial commit: AMS Backend - Deno + Oak + MongoDB

This commit is contained in:
FluxKit
2026-02-19 14:02:53 +00:00
commit 656a37efda
36 changed files with 7648 additions and 0 deletions

153
src/routes/appUpdate.ts Normal file
View File

@@ -0,0 +1,153 @@
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<AppRelease>("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;