初回コミット: 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,2 @@
|
||||
export * from "./source";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,102 @@
|
||||
import type {
|
||||
AppRecord,
|
||||
ReleaseRecord,
|
||||
SourceRecord,
|
||||
} from "./types";
|
||||
|
||||
export interface AltStoreSourceDocument {
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
iconURL?: string;
|
||||
headerURL?: string;
|
||||
website?: string;
|
||||
tintColor?: string;
|
||||
featuredApps: string[];
|
||||
apps: Array<{
|
||||
name: string;
|
||||
bundleIdentifier: string;
|
||||
marketplaceID: string;
|
||||
developerName: string;
|
||||
subtitle?: string;
|
||||
localizedDescription: string;
|
||||
iconURL?: string;
|
||||
tintColor?: string;
|
||||
category: string;
|
||||
screenshots: Array<{ imageURL: string; width?: number; height?: number }>;
|
||||
versions: Array<{
|
||||
version: string;
|
||||
buildVersion: string;
|
||||
date: string;
|
||||
localizedDescription: string;
|
||||
downloadURL: string;
|
||||
size: number;
|
||||
minOSVersion?: string;
|
||||
}>;
|
||||
appPermissions: {
|
||||
entitlements: string[];
|
||||
privacy: Record<string, string>;
|
||||
};
|
||||
}>;
|
||||
news: [];
|
||||
}
|
||||
|
||||
export function buildSourceDocument(
|
||||
source: SourceRecord,
|
||||
apps: AppRecord[],
|
||||
releases: ReleaseRecord[],
|
||||
publicBaseUrl: string,
|
||||
): AltStoreSourceDocument {
|
||||
const sourceApps = apps
|
||||
.map((app) => {
|
||||
const appReleases = releases
|
||||
.filter(
|
||||
(release) =>
|
||||
release.appId === app.id && release.status === "published",
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const dateOrder = b.date.localeCompare(a.date);
|
||||
if (dateOrder !== 0) return dateOrder;
|
||||
return b.buildVersion.localeCompare(a.buildVersion, undefined, {
|
||||
numeric: true,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
name: app.name,
|
||||
bundleIdentifier: app.bundleIdentifier,
|
||||
marketplaceID: app.marketplaceID,
|
||||
developerName: app.developerName,
|
||||
subtitle: app.subtitle || undefined,
|
||||
localizedDescription: app.localizedDescription,
|
||||
iconURL: app.iconURL,
|
||||
tintColor: app.tintColor,
|
||||
category: app.category,
|
||||
screenshots: app.screenshots,
|
||||
appPermissions: app.appPermissions,
|
||||
versions: appReleases.map((release) => ({
|
||||
version: release.version,
|
||||
buildVersion: release.buildVersion,
|
||||
date: release.date,
|
||||
localizedDescription: release.localizedDescription,
|
||||
downloadURL: `${publicBaseUrl.replace(/\/$/, "")}/artifacts/${release.id}/manifest.json`,
|
||||
size: release.sizeBytes,
|
||||
minOSVersion: release.minOSVersion,
|
||||
})),
|
||||
};
|
||||
})
|
||||
.filter((app) => app.versions.length > 0);
|
||||
|
||||
return {
|
||||
name: source.name,
|
||||
subtitle: source.subtitle || undefined,
|
||||
description: source.description || undefined,
|
||||
iconURL: source.iconURL || sourceApps[0]?.iconURL,
|
||||
headerURL: source.headerURL,
|
||||
website: source.website,
|
||||
tintColor: source.tintColor,
|
||||
featuredApps: sourceApps.slice(0, 5).map((app) => app.bundleIdentifier),
|
||||
apps: sourceApps,
|
||||
news: [],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
export type Visibility = "draft" | "unlisted" | "public";
|
||||
|
||||
export type ReleaseStatus =
|
||||
| "uploaded"
|
||||
| "processing"
|
||||
| "ready"
|
||||
| "rejected"
|
||||
| "published"
|
||||
| "archived";
|
||||
|
||||
export interface WorkspaceRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
ownerSubject: string;
|
||||
ownerEmail: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface SourceRecord {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
iconURL?: string;
|
||||
headerURL?: string;
|
||||
website?: string;
|
||||
tintColor: string;
|
||||
visibility: Visibility;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface AppMetadata {
|
||||
name: string;
|
||||
developerName: string;
|
||||
subtitle: string;
|
||||
localizedDescription: string;
|
||||
iconURL?: string;
|
||||
tintColor: string;
|
||||
category: string;
|
||||
screenshots: Array<{ imageURL: string; width?: number; height?: number }>;
|
||||
appPermissions: {
|
||||
entitlements: string[];
|
||||
privacy: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppRecord extends AppMetadata {
|
||||
id: string;
|
||||
sourceId: string;
|
||||
bundleIdentifier: string;
|
||||
marketplaceID: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ManifestSummary {
|
||||
distributionPackageRevision?: number;
|
||||
appleItemId: string;
|
||||
bundleId: string;
|
||||
shortVersionString: string;
|
||||
bundleVersion: string;
|
||||
appleVersionId?: string;
|
||||
platforms: string[];
|
||||
minimumSystemVersions: Record<string, string>;
|
||||
variantPaths: string[];
|
||||
deltaPaths: string[];
|
||||
}
|
||||
|
||||
export interface ReleaseRecord {
|
||||
id: string;
|
||||
appId: string;
|
||||
uploadId: string;
|
||||
version: string;
|
||||
buildVersion: string;
|
||||
appleItemId: string;
|
||||
date: string;
|
||||
localizedDescription: string;
|
||||
minOSVersion?: string;
|
||||
sizeBytes: number;
|
||||
status: ReleaseStatus;
|
||||
errorCode?: string;
|
||||
errorMessage?: string;
|
||||
manifest: ManifestSummary;
|
||||
createdAt: string;
|
||||
publishedAt?: string;
|
||||
}
|
||||
|
||||
export interface UploadRecord {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
sourceId: string;
|
||||
appId?: string;
|
||||
objectKey: string;
|
||||
multipartUploadId?: string;
|
||||
originalFilename: string;
|
||||
expectedSize: number;
|
||||
receivedSize: number;
|
||||
status: "created" | "uploaded" | "queued" | "processing" | "completed" | "failed";
|
||||
errorCode?: string;
|
||||
errorMessage?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface ReleaseAsset {
|
||||
releaseId: string;
|
||||
path: string;
|
||||
objectKey: string;
|
||||
sizeBytes: number;
|
||||
sha256: string;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
export interface Identity {
|
||||
subject: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
}
|
||||
Reference in New Issue
Block a user