Initial commit: AMS Backend - Deno + Oak + MongoDB
This commit is contained in:
153
src/routes/appUpdate.ts
Normal file
153
src/routes/appUpdate.ts
Normal 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;
|
||||
Reference in New Issue
Block a user