初回コミット: AltStore PAL向けADPホスティングSaaS
- Fastify API・Node Worker・PostgreSQL・S3互換ストレージ構成
- ADP ZIPのマルチパートアップロードとManifest検証(パストラバーサル・ZIP爆弾等を拒否)
- manifest.json/signatureは再シリアライズせず元バイト列を保持
- Cloudflare風の運用向け管理ダッシュボード(shadcn/Radix・日本語UI・4ルート)
概要/Sources/アプリ/リリース + ルートランディングページ
- 汎用OIDC SSO・Workspace単位の認可・demo mode
- 匿名配布: /sources/{slug}/source.json, /artifacts/{releaseId}/manifest.json
- Docker Compose対応
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import type { AppMetadata } from "../../../packages/core/src/index";
|
||||
import { inspectAdpArchive, storeAdpAssets } from "./manifest";
|
||||
import type { AppConfig } from "./config";
|
||||
import type { Store } from "./store";
|
||||
import type { StorageAdapter } from "./storage";
|
||||
|
||||
export async function processUpload(uploadId: string, store: Store, storage: StorageAdapter, appConfig: AppConfig) {
|
||||
const upload = await store.getUpload(uploadId);
|
||||
if (!upload) throw new Error("UPLOAD_NOT_FOUND");
|
||||
await store.updateUpload(uploadId, { status: "processing", errorCode: undefined, errorMessage: undefined });
|
||||
const workDir = await mkdtemp(join(tmpdir(), "altdock-upload-"));
|
||||
const archivePath = join(workDir, "package.zip");
|
||||
try {
|
||||
await storage.downloadToFile(upload.objectKey, archivePath);
|
||||
const inspection = await inspectAdpArchive(archivePath, appConfig.MAX_ARCHIVE_ENTRIES, appConfig.MAX_EXPANDED_BYTES);
|
||||
const source = await store.getSource(upload.sourceId);
|
||||
if (!source) throw new Error("SOURCE_NOT_FOUND");
|
||||
const existingApp = upload.appId ? await store.getApp(upload.appId) : null;
|
||||
const metadata: Partial<AppMetadata> = existingApp || {
|
||||
name: source.name,
|
||||
developerName: "AltDock Developer",
|
||||
localizedDescription: `Release ${inspection.summary.shortVersionString} (${inspection.summary.bundleVersion})`,
|
||||
subtitle: "",
|
||||
tintColor: source.tintColor,
|
||||
category: "other",
|
||||
screenshots: [],
|
||||
appPermissions: { entitlements: [], privacy: {} },
|
||||
};
|
||||
const app = await store.upsertApp(upload.sourceId, {
|
||||
bundleIdentifier: inspection.summary.bundleId,
|
||||
marketplaceID: inspection.summary.appleItemId,
|
||||
metadata,
|
||||
});
|
||||
await store.updateUpload(uploadId, { appId: app.id });
|
||||
const releaseId = randomUUID();
|
||||
const release = await store.createRelease({
|
||||
id: releaseId,
|
||||
appId: app.id,
|
||||
uploadId,
|
||||
version: inspection.summary.shortVersionString,
|
||||
buildVersion: inspection.summary.bundleVersion,
|
||||
appleItemId: inspection.summary.appleItemId,
|
||||
date: new Date().toISOString(),
|
||||
localizedDescription: metadata.localizedDescription || "",
|
||||
minOSVersion: inspection.summary.minimumSystemVersions.ios,
|
||||
sizeBytes: 0,
|
||||
manifest: inspection.summary,
|
||||
});
|
||||
const result = await storeAdpAssets(archivePath, release.id, inspection, storage);
|
||||
await store.addReleaseAssets(result.assets);
|
||||
const completed = await store.updateRelease(release.id, { sizeBytes: result.totalBytes, status: "ready" });
|
||||
await store.updateUpload(uploadId, { status: "completed", receivedSize: upload.receivedSize || upload.expectedSize });
|
||||
return completed;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "ADP_PROCESSING_FAILED";
|
||||
console.error(`[altdock] upload ${uploadId} failed: ${message}`);
|
||||
await store.updateUpload(uploadId, { status: "failed", errorCode: message.split(":")[0], errorMessage: message });
|
||||
throw error;
|
||||
} finally {
|
||||
await rm(workDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user