mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 22:23:49 +08:00
feat(account): add client account api foundation
This commit is contained in:
@@ -20,11 +20,13 @@ Object.defineProperty(window, "ipcRenderer", {
|
|||||||
invoke: vi.fn(),
|
invoke: vi.fn(),
|
||||||
removeListener: vi.fn(),
|
removeListener: vi.fn(),
|
||||||
},
|
},
|
||||||
|
writable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock window.apm_store
|
// Mock window.apm_store
|
||||||
Object.defineProperty(window, "apm_store", {
|
Object.defineProperty(window, "apm_store", {
|
||||||
value: {
|
value: {
|
||||||
arch: "amd64-store",
|
arch: "amd64",
|
||||||
},
|
},
|
||||||
|
writable: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FLARUM_BASE_URL,
|
||||||
|
FLARUM_REGISTER_URL,
|
||||||
|
SPARK_BACKEND_BASE_URL,
|
||||||
|
} from "@/global/storeConfig";
|
||||||
|
import type {
|
||||||
|
DownloadedAppRecord,
|
||||||
|
FavoriteFolder,
|
||||||
|
FavoriteItem,
|
||||||
|
ReviewTags,
|
||||||
|
SparkUser,
|
||||||
|
SyncedAppListItem,
|
||||||
|
} from "@/global/typedefinition";
|
||||||
|
|
||||||
|
describe("account shared types", () => {
|
||||||
|
it("exports backend/forum config and account shapes", () => {
|
||||||
|
const user: SparkUser = {
|
||||||
|
id: 1,
|
||||||
|
flarumUserId: "123",
|
||||||
|
username: "momen",
|
||||||
|
displayName: "Momen",
|
||||||
|
avatarUrl: "https://bbs.spark-app.store/avatar.png",
|
||||||
|
forumLevel: "管理员",
|
||||||
|
forumGroups: ["管理员"],
|
||||||
|
};
|
||||||
|
const folder: FavoriteFolder = {
|
||||||
|
id: 1,
|
||||||
|
name: "默认收藏夹",
|
||||||
|
itemCount: 1,
|
||||||
|
createdAt: "2026-05-18T00:00:00Z",
|
||||||
|
updatedAt: "2026-05-18T00:00:00Z",
|
||||||
|
};
|
||||||
|
const favorite: FavoriteItem = {
|
||||||
|
id: 2,
|
||||||
|
appKey: "app:office:wps",
|
||||||
|
pkgname: "wps",
|
||||||
|
name: "WPS",
|
||||||
|
category: "office",
|
||||||
|
iconUrl: "https://example.invalid/wps.png",
|
||||||
|
createdAt: "2026-05-18T00:00:00Z",
|
||||||
|
};
|
||||||
|
const download: DownloadedAppRecord = {
|
||||||
|
id: 3,
|
||||||
|
appKey: "app:office:wps",
|
||||||
|
pkgname: "wps",
|
||||||
|
name: "WPS",
|
||||||
|
category: "office",
|
||||||
|
selectedOrigin: "apm",
|
||||||
|
version: "1.0.0",
|
||||||
|
packageArch: "amd64",
|
||||||
|
downloadedAt: "2026-05-18T00:00:00Z",
|
||||||
|
};
|
||||||
|
const syncItem: SyncedAppListItem = {
|
||||||
|
pkgname: "wps",
|
||||||
|
origin: "apm",
|
||||||
|
category: "office",
|
||||||
|
version: "1.0.0",
|
||||||
|
packageArch: "amd64",
|
||||||
|
appName: "WPS",
|
||||||
|
iconUrl: "https://example.invalid/wps.png",
|
||||||
|
};
|
||||||
|
const tags: ReviewTags = {
|
||||||
|
origin: "apm",
|
||||||
|
category: "office",
|
||||||
|
pkgname: "wps",
|
||||||
|
version: "1.0.0",
|
||||||
|
packageArch: "amd64",
|
||||||
|
clientArch: "amd64",
|
||||||
|
distro: "deepin 25",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(typeof SPARK_BACKEND_BASE_URL).toBe("string");
|
||||||
|
expect(FLARUM_BASE_URL).toContain("bbs.spark-app.store");
|
||||||
|
expect(FLARUM_REGISTER_URL).toContain("register");
|
||||||
|
expect(user.forumGroups).toEqual(["管理员"]);
|
||||||
|
expect(folder.itemCount).toBe(1);
|
||||||
|
expect(favorite.appKey).toBe("app:office:wps");
|
||||||
|
expect(download.selectedOrigin).toBe("apm");
|
||||||
|
expect(syncItem.origin).toBe("apm");
|
||||||
|
expect(tags.packageArch).toBe("amd64");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,6 +7,14 @@ export const APM_STORE_BASE_URL: string =
|
|||||||
export const APM_STORE_STATS_BASE_URL: string =
|
export const APM_STORE_STATS_BASE_URL: string =
|
||||||
import.meta.env.VITE_APM_STORE_STATS_BASE_URL || "";
|
import.meta.env.VITE_APM_STORE_STATS_BASE_URL || "";
|
||||||
|
|
||||||
|
export const SPARK_BACKEND_BASE_URL: string =
|
||||||
|
import.meta.env.VITE_SPARK_BACKEND_BASE_URL || "";
|
||||||
|
|
||||||
|
export const FLARUM_BASE_URL = "https://bbs.spark-app.store";
|
||||||
|
export const FLARUM_REGISTER_URL = `${FLARUM_BASE_URL}/register`;
|
||||||
|
export const FLARUM_PROFILE_URL = `${FLARUM_BASE_URL}/u`;
|
||||||
|
export const FLARUM_SETTINGS_URL = `${FLARUM_BASE_URL}/settings`;
|
||||||
|
|
||||||
// 下面的变量用于存储当前应用的信息,其实用在多个组件中
|
// 下面的变量用于存储当前应用的信息,其实用在多个组件中
|
||||||
export const currentApp = ref<App | null>(null);
|
export const currentApp = ref<App | null>(null);
|
||||||
export const currentAppSparkInstalled = ref(false);
|
export const currentAppSparkInstalled = ref(false);
|
||||||
|
|||||||
@@ -249,3 +249,130 @@ export interface SidebarEntry {
|
|||||||
type?: "category" | "search" | "link";
|
type?: "category" | "search" | "link";
|
||||||
value?: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SparkUser {
|
||||||
|
id: number;
|
||||||
|
flarumUserId: string;
|
||||||
|
username: string;
|
||||||
|
displayName: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
forumLevel: string;
|
||||||
|
forumGroups: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthSession {
|
||||||
|
accessToken: string;
|
||||||
|
tokenType: "bearer";
|
||||||
|
user: SparkUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlarumLoginPayload {
|
||||||
|
identification: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReviewTags {
|
||||||
|
origin: "spark" | "apm";
|
||||||
|
category: string;
|
||||||
|
pkgname: string;
|
||||||
|
version: string;
|
||||||
|
packageArch: string;
|
||||||
|
clientArch: string;
|
||||||
|
distro: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RatingSummary {
|
||||||
|
averageRating: number;
|
||||||
|
reviewCount: number;
|
||||||
|
starCounts: Record<number, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppReview {
|
||||||
|
id: number;
|
||||||
|
rating: number;
|
||||||
|
content: string;
|
||||||
|
version: string;
|
||||||
|
packageArch: string;
|
||||||
|
clientArch: string;
|
||||||
|
distro: string;
|
||||||
|
origin: "spark" | "apm";
|
||||||
|
category: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
userDisplayName: string;
|
||||||
|
userAvatarUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FavoriteFolder {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
itemCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FavoriteItem {
|
||||||
|
id: number;
|
||||||
|
appKey: string;
|
||||||
|
pkgname: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
iconUrl: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FavoriteAvailabilityStatus =
|
||||||
|
| "installable"
|
||||||
|
| "installed"
|
||||||
|
| "platform-unavailable"
|
||||||
|
| "arch-unavailable"
|
||||||
|
| "downlisted";
|
||||||
|
|
||||||
|
export interface ResolvedFavoriteItem {
|
||||||
|
item: FavoriteItem;
|
||||||
|
status: FavoriteAvailabilityStatus;
|
||||||
|
reason: string;
|
||||||
|
selectedApp: App | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadedAppRecord {
|
||||||
|
id: number;
|
||||||
|
appKey: string;
|
||||||
|
pkgname: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
selectedOrigin: "spark" | "apm";
|
||||||
|
version: string;
|
||||||
|
packageArch: string;
|
||||||
|
downloadedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadedAppList {
|
||||||
|
items: DownloadedAppRecord[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedAppListItem {
|
||||||
|
id?: number;
|
||||||
|
pkgname: string;
|
||||||
|
origin: "spark" | "apm";
|
||||||
|
category: string;
|
||||||
|
version: string;
|
||||||
|
packageArch: string;
|
||||||
|
appName: string;
|
||||||
|
iconUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedAppList {
|
||||||
|
snapshotName: string;
|
||||||
|
clientArch: string;
|
||||||
|
distro: string;
|
||||||
|
updatedAt: string;
|
||||||
|
items: SyncedAppListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SystemInfo {
|
||||||
|
distro: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { SPARK_BACKEND_BASE_URL } from "@/global/storeConfig";
|
||||||
|
import type {
|
||||||
|
AppReview,
|
||||||
|
AuthSession,
|
||||||
|
DownloadedAppList,
|
||||||
|
DownloadedAppRecord,
|
||||||
|
FavoriteFolder,
|
||||||
|
FavoriteItem,
|
||||||
|
RatingSummary,
|
||||||
|
ReviewTags,
|
||||||
|
SparkUser,
|
||||||
|
SyncedAppList,
|
||||||
|
SyncedAppListItem,
|
||||||
|
} from "@/global/typedefinition";
|
||||||
|
|
||||||
|
const backend = axios.create({
|
||||||
|
baseURL: SPARK_BACKEND_BASE_URL,
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
type ApiRecord = Record<string, unknown>;
|
||||||
|
|
||||||
|
const asApiRecord = (value: unknown): ApiRecord => {
|
||||||
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||||
|
return value as ApiRecord;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const asApiRecordArray = (value: unknown): ApiRecord[] => {
|
||||||
|
if (!Array.isArray(value)) return [];
|
||||||
|
return value.map(asApiRecord);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseForumGroups = (raw: unknown): string[] => {
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
return raw.filter((item): item is string => typeof item === "string");
|
||||||
|
}
|
||||||
|
if (typeof raw !== "string" || raw.length === 0) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed: unknown = JSON.parse(raw);
|
||||||
|
return Array.isArray(parsed)
|
||||||
|
? parsed.filter((item): item is string => typeof item === "string")
|
||||||
|
: [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toUser = (raw: ApiRecord): SparkUser => ({
|
||||||
|
id: Number(raw.id),
|
||||||
|
flarumUserId: String(raw.flarum_user_id || ""),
|
||||||
|
username: String(raw.username || ""),
|
||||||
|
displayName: String(raw.display_name || raw.username || ""),
|
||||||
|
avatarUrl: String(raw.avatar_url || ""),
|
||||||
|
forumLevel: String(raw.forum_level || "论坛用户"),
|
||||||
|
forumGroups: parseForumGroups(raw.forum_groups),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toReview = (raw: ApiRecord): AppReview => ({
|
||||||
|
id: Number(raw.id),
|
||||||
|
rating: Number(raw.rating),
|
||||||
|
content: String(raw.content || ""),
|
||||||
|
version: String(raw.version || "unknown"),
|
||||||
|
packageArch: String(raw.package_arch || "unknown"),
|
||||||
|
clientArch: String(raw.client_arch || "unknown"),
|
||||||
|
distro: String(raw.distro || "unknown"),
|
||||||
|
origin: raw.origin === "spark" ? "spark" : "apm",
|
||||||
|
category: String(raw.category || ""),
|
||||||
|
createdAt: String(raw.created_at || ""),
|
||||||
|
updatedAt: String(raw.updated_at || ""),
|
||||||
|
userDisplayName: String(raw.user_display_name || ""),
|
||||||
|
userAvatarUrl: String(raw.user_avatar_url || ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toFavoriteFolder = (raw: ApiRecord): FavoriteFolder => ({
|
||||||
|
id: Number(raw.id),
|
||||||
|
name: String(raw.name || ""),
|
||||||
|
itemCount: Number(raw.item_count || 0),
|
||||||
|
createdAt: String(raw.created_at || ""),
|
||||||
|
updatedAt: String(raw.updated_at || ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toFavoriteItem = (raw: ApiRecord): FavoriteItem => ({
|
||||||
|
id: Number(raw.id),
|
||||||
|
appKey: String(raw.app_key || ""),
|
||||||
|
pkgname: String(raw.pkgname || ""),
|
||||||
|
name: String(raw.name || ""),
|
||||||
|
category: String(raw.category || ""),
|
||||||
|
iconUrl: String(raw.icon_url || ""),
|
||||||
|
createdAt: String(raw.created_at || ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toDownloadedApp = (raw: ApiRecord): DownloadedAppRecord => ({
|
||||||
|
id: Number(raw.id),
|
||||||
|
appKey: String(raw.app_key || ""),
|
||||||
|
pkgname: String(raw.pkgname || ""),
|
||||||
|
name: String(raw.name || ""),
|
||||||
|
category: String(raw.category || ""),
|
||||||
|
selectedOrigin: raw.selected_origin === "spark" ? "spark" : "apm",
|
||||||
|
version: String(raw.version || ""),
|
||||||
|
packageArch: String(raw.package_arch || "unknown"),
|
||||||
|
downloadedAt: String(raw.downloaded_at || ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toSyncedAppListItem = (raw: ApiRecord): SyncedAppListItem => ({
|
||||||
|
id: raw.id === undefined ? undefined : Number(raw.id),
|
||||||
|
pkgname: String(raw.pkgname || ""),
|
||||||
|
origin: raw.origin === "spark" ? "spark" : "apm",
|
||||||
|
category: String(raw.category || ""),
|
||||||
|
version: String(raw.version || ""),
|
||||||
|
packageArch: String(raw.package_arch || "unknown"),
|
||||||
|
appName: String(raw.app_name || ""),
|
||||||
|
iconUrl: String(raw.icon_url || ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toSyncedAppList = (
|
||||||
|
raw: ApiRecord,
|
||||||
|
fallback?: { clientArch: string; distro: string; items: SyncedAppListItem[] },
|
||||||
|
): SyncedAppList => ({
|
||||||
|
snapshotName: String(raw.snapshot_name || "默认列表"),
|
||||||
|
clientArch: String(raw.client_arch || fallback?.clientArch || "unknown"),
|
||||||
|
distro: String(raw.distro || fallback?.distro || "unknown"),
|
||||||
|
updatedAt: String(raw.updated_at || ""),
|
||||||
|
items: raw.items
|
||||||
|
? asApiRecordArray(raw.items).map(toSyncedAppListItem)
|
||||||
|
: fallback?.items || [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setBackendToken = (token: string | null): void => {
|
||||||
|
if (token) backend.defaults.headers.common.Authorization = `Bearer ${token}`;
|
||||||
|
else delete backend.defaults.headers.common.Authorization;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exchangeFlarumToken = async (payload: {
|
||||||
|
flarumUserId: string;
|
||||||
|
flarumToken: string;
|
||||||
|
}): Promise<AuthSession> => {
|
||||||
|
const response = await backend.post("/auth/flarum", {
|
||||||
|
flarum_user_id: payload.flarumUserId,
|
||||||
|
flarum_token: payload.flarumToken,
|
||||||
|
});
|
||||||
|
const data = asApiRecord(response.data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: String(data.access_token || ""),
|
||||||
|
tokenType: "bearer",
|
||||||
|
user: toUser(asApiRecord(data.user)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchMe = async (): Promise<SparkUser> => {
|
||||||
|
const response = await backend.get("/me");
|
||||||
|
return toUser(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchRatingSummary = async (
|
||||||
|
appKey: string,
|
||||||
|
): Promise<RatingSummary> => {
|
||||||
|
const response = await backend.get(
|
||||||
|
`/apps/${encodeURIComponent(appKey)}/rating-summary`,
|
||||||
|
);
|
||||||
|
const data = asApiRecord(response.data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
averageRating: Number(data.average_rating || 0),
|
||||||
|
reviewCount: Number(data.review_count || 0),
|
||||||
|
starCounts: Object.fromEntries(
|
||||||
|
Object.entries(asApiRecord(data.star_counts)).map(([key, value]) => [
|
||||||
|
Number(key),
|
||||||
|
Number(value),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchReviews = async (appKey: string): Promise<AppReview[]> => {
|
||||||
|
const response = await backend.get(
|
||||||
|
`/apps/${encodeURIComponent(appKey)}/reviews`,
|
||||||
|
);
|
||||||
|
return asApiRecordArray(response.data).map(toReview);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const submitReview = async (
|
||||||
|
appKey: string,
|
||||||
|
payload: { rating: number; content: string; tags: ReviewTags },
|
||||||
|
): Promise<AppReview> => {
|
||||||
|
const response = await backend.post(
|
||||||
|
`/apps/${encodeURIComponent(appKey)}/reviews`,
|
||||||
|
{
|
||||||
|
rating: payload.rating,
|
||||||
|
content: payload.content,
|
||||||
|
tags: {
|
||||||
|
origin: payload.tags.origin,
|
||||||
|
category: payload.tags.category,
|
||||||
|
pkgname: payload.tags.pkgname,
|
||||||
|
version: payload.tags.version,
|
||||||
|
package_arch: payload.tags.packageArch,
|
||||||
|
client_arch: payload.tags.clientArch,
|
||||||
|
distro: payload.tags.distro,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return toReview(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listFavoriteFolders = async (): Promise<FavoriteFolder[]> => {
|
||||||
|
const response = await backend.get("/me/favorite-folders");
|
||||||
|
return asApiRecordArray(response.data).map(toFavoriteFolder);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFavoriteFolder = async (
|
||||||
|
name: string,
|
||||||
|
): Promise<FavoriteFolder> => {
|
||||||
|
const response = await backend.post("/me/favorite-folders", { name });
|
||||||
|
return toFavoriteFolder(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renameFavoriteFolder = async (
|
||||||
|
folderId: number,
|
||||||
|
name: string,
|
||||||
|
): Promise<FavoriteFolder> => {
|
||||||
|
const response = await backend.patch(`/me/favorite-folders/${folderId}`, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
return toFavoriteFolder(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFavoriteFolder = async (folderId: number): Promise<void> => {
|
||||||
|
await backend.delete(`/me/favorite-folders/${folderId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listFavoriteItems = async (
|
||||||
|
folderId: number,
|
||||||
|
): Promise<FavoriteItem[]> => {
|
||||||
|
const response = await backend.get(`/me/favorite-folders/${folderId}/items`);
|
||||||
|
return asApiRecordArray(response.data).map(toFavoriteItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addFavoriteItem = async (
|
||||||
|
folderId: number | "default",
|
||||||
|
item: Omit<FavoriteItem, "id" | "createdAt">,
|
||||||
|
): Promise<FavoriteItem> => {
|
||||||
|
const response = await backend.post(
|
||||||
|
`/me/favorite-folders/${folderId}/items`,
|
||||||
|
{
|
||||||
|
app_key: item.appKey,
|
||||||
|
pkgname: item.pkgname,
|
||||||
|
name: item.name,
|
||||||
|
category: item.category,
|
||||||
|
icon_url: item.iconUrl,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return toFavoriteItem(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFavoriteItem = async (
|
||||||
|
folderId: number,
|
||||||
|
itemId: number,
|
||||||
|
): Promise<void> => {
|
||||||
|
await backend.delete(`/me/favorite-folders/${folderId}/items/${itemId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bulkDeleteFavoriteItems = async (
|
||||||
|
folderId: number,
|
||||||
|
itemIds: number[],
|
||||||
|
): Promise<number> => {
|
||||||
|
const response = await backend.post(
|
||||||
|
`/me/favorite-folders/${folderId}/items/bulk-delete`,
|
||||||
|
{ item_ids: itemIds },
|
||||||
|
);
|
||||||
|
return Number(asApiRecord(response.data).deleted_count || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listDownloadedApps = async (
|
||||||
|
page = 1,
|
||||||
|
pageSize = 20,
|
||||||
|
): Promise<DownloadedAppList> => {
|
||||||
|
const response = await backend.get("/me/downloaded-apps", {
|
||||||
|
params: { page, page_size: pageSize },
|
||||||
|
});
|
||||||
|
const data = asApiRecord(response.data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: asApiRecordArray(data.items).map(toDownloadedApp),
|
||||||
|
total: Number(data.total || 0),
|
||||||
|
page: Number(data.page || page),
|
||||||
|
pageSize: Number(data.page_size || pageSize),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const recordDownloadedApp = async (
|
||||||
|
item: Omit<DownloadedAppRecord, "id" | "downloadedAt">,
|
||||||
|
): Promise<DownloadedAppRecord> => {
|
||||||
|
const response = await backend.post("/me/downloaded-apps", {
|
||||||
|
app_key: item.appKey,
|
||||||
|
pkgname: item.pkgname,
|
||||||
|
name: item.name,
|
||||||
|
category: item.category,
|
||||||
|
selected_origin: item.selectedOrigin,
|
||||||
|
version: item.version,
|
||||||
|
package_arch: item.packageArch,
|
||||||
|
});
|
||||||
|
return toDownloadedApp(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchSyncedAppList = async (): Promise<SyncedAppList | null> => {
|
||||||
|
const response = await backend.get("/me/app-list");
|
||||||
|
if (!response.data) return null;
|
||||||
|
return toSyncedAppList(asApiRecord(response.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadSyncedAppList = async (payload: {
|
||||||
|
clientArch: string;
|
||||||
|
distro: string;
|
||||||
|
items: SyncedAppListItem[];
|
||||||
|
}): Promise<SyncedAppList> => {
|
||||||
|
const response = await backend.put("/me/app-list", {
|
||||||
|
client_arch: payload.clientArch,
|
||||||
|
distro: payload.distro,
|
||||||
|
items: payload.items.map((item) => ({
|
||||||
|
pkgname: item.pkgname,
|
||||||
|
origin: item.origin,
|
||||||
|
category: item.category,
|
||||||
|
version: item.version,
|
||||||
|
package_arch: item.packageArch,
|
||||||
|
app_name: item.appName,
|
||||||
|
icon_url: item.iconUrl,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toSyncedAppList(asApiRecord(response.data), payload);
|
||||||
|
};
|
||||||
Vendored
+4
@@ -10,6 +10,10 @@ declare module "*.vue" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_SPARK_BACKEND_BASE_URL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
// expose in the `electron/preload/index.ts`
|
// expose in the `electron/preload/index.ts`
|
||||||
ipcRenderer: IpcRendererFacade;
|
ipcRenderer: IpcRendererFacade;
|
||||||
|
|||||||
Reference in New Issue
Block a user