This commit is contained in:
2026-03-12 17:42:00 +08:00
parent 914ca64c10
commit 78f31d44ec
6 changed files with 88 additions and 143 deletions

View File

@@ -2,13 +2,14 @@ import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import type { KintoneRestAPIError } from "@kintone/rest-api-client";
import type { DomainWithPassword } from "@shared/types/domain";
import type {
KintoneApp,
AppResponse,
AppsResponse,
AppCustomizeResponse,
AppDetail,
FileContent,
AppCustomizationConfig,
KintoneApiError,
JSFileConfig,
CSSFileConfig,
FileConfig,
} from "@shared/types/kintone";
/**
@@ -32,12 +33,6 @@ export class KintoneError extends Error {
}
}
// Use typeof to get SDK method return types
type KintoneClient = KintoneRestAPIClient;
type AppResponse = ReturnType<KintoneClient["app"]["getApp"]>;
type AppsResponse = ReturnType<KintoneClient["app"]["getApps"]>;
type AppCustomizeResponse = ReturnType<KintoneClient["app"]["getAppCustomize"]>;
/**
* Kintone REST API Client
*/
@@ -87,48 +82,24 @@ export class SelfKintoneClient {
}
}
private mapApp(app: Awaited<AppResponse>): KintoneApp {
return {
appId: app.appId,
name: app.name,
code: app.code,
spaceId: app.spaceId || undefined,
createdAt: app.createdAt,
creator: app.creator,
modifiedAt: app.modifiedAt,
modifier: app.modifier,
};
}
private mapResource(
resource:
| Awaited<AppCustomizeResponse>["desktop"]["js"][number]
| Awaited<AppCustomizeResponse>["desktop"]["css"][number],
): JSFileConfig | CSSFileConfig {
if (resource.type === "FILE") {
return {
type: "FILE",
file: {
fileKey: resource.file.fileKey,
name: resource.file.name,
size: parseInt(resource.file.size, 10),
},
};
}
return {
type: "URL",
url: resource.url,
};
): FileConfig {
// Return SDK type directly - no conversion needed
return resource;
}
private buildCustomizeSection(
js?: JSFileConfig[],
css?: CSSFileConfig[],
): Parameters<KintoneClient["app"]["updateAppCustomize"]>[0]["desktop"] {
js?: FileConfig[],
css?: FileConfig[],
): Parameters<
KintoneRestAPIClient["app"]["updateAppCustomize"]
>[0]["desktop"] {
if (!js && !css) return undefined;
const mapItem = (item: JSFileConfig | CSSFileConfig) => {
const mapItem = (item: FileConfig) => {
if (item.type === "FILE" && item.file) {
return { type: "FILE" as const, file: { fileKey: item.file.fileKey } };
}
@@ -150,7 +121,7 @@ export class SelfKintoneClient {
async getApps(options?: {
limit?: number;
offset?: number;
}): Promise<KintoneApp[]> {
}): Promise<AppResponse[]> {
return this.withErrorHandling(async () => {
// If pagination options provided, use them directly
if (options?.limit !== undefined || options?.offset !== undefined) {
@@ -158,11 +129,11 @@ export class SelfKintoneClient {
if (options.limit) params.limit = options.limit;
if (options.offset) params.offset = options.offset;
const response = await this.client.app.getApps(params);
return response.apps.map((app) => this.mapApp(app));
return response.apps;
}
// Otherwise, fetch all apps (pagination handled internally)
const allApps: Awaited<AppsResponse>["apps"] = [];
const allApps: AppResponse[] = [];
const limit = 100; // Max allowed by Kintone API
let offset = 0;
let hasMore = true;
@@ -179,7 +150,7 @@ export class SelfKintoneClient {
}
}
return allApps.map((app) => this.mapApp(app));
return allApps;
});
}
@@ -203,7 +174,6 @@ export class SelfKintoneClient {
mobile:
customizeInfo.mobile.css?.map((css) => this.mapResource(css)) || [],
},
plugins: [],
};
return {

View File

@@ -16,9 +16,10 @@ import type {
} from "@shared/types/ipc";
import type { Domain, DomainWithStatus } from "@shared/types/domain";
import type {
KintoneApp,
AppResponse,
AppDetail,
FileContent,
KintoneSpace,
} from "@shared/types/kintone";
import type { Version } from "@shared/types/version";
@@ -39,10 +40,13 @@ export interface SelfAPI {
updateDomain: (params: UpdateDomainParams) => Promise<Result<Domain>>;
deleteDomain: (id: string) => Promise<Result<void>>;
testConnection: (id: string) => Promise<Result<DomainWithStatus>>;
testDomainConnection: (params: TestDomainConnectionParams) => Promise<Result<boolean>>;
testDomainConnection: (
params: TestDomainConnectionParams,
) => Promise<Result<boolean>>;
// ==================== Browse ====================
getApps: (params: GetAppsParams) => Promise<Result<KintoneApp[]>>;
getSpaces: (params: { domainId: string }) => Promise<Result<KintoneSpace[]>>;
getApps: (params: GetAppsParams) => Promise<Result<AppResponse[]>>;
getAppDetail: (params: GetAppDetailParams) => Promise<Result<AppDetail>>;
getFileContent: (
params: GetFileContentParams,

View File

@@ -24,7 +24,7 @@ import { createStyles } from "antd-style";
import { useAppStore } from "@renderer/stores";
import { useDomainStore } from "@renderer/stores";
import { CodeViewer } from "../CodeViewer";
import { JSFileConfig, CSSFileConfig } from "@shared/types/kintone";
import { FileConfig } from "@shared/types/kintone";
const useStyles = createStyles(({ token, css }) => ({
container: css`
@@ -168,7 +168,7 @@ const AppDetail: React.FC = () => {
}
const renderFileList = (
files: (JSFileConfig | CSSFileConfig)[] | undefined,
files: (FileConfig)[] | undefined,
type: "js" | "css",
) => {
if (!files || files.length === 0) {

View File

@@ -6,11 +6,11 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { KintoneApp, AppDetail } from "@shared/types/kintone";
import type { AppResponse, AppDetail } from "@shared/types/kintone";
interface AppState {
// State
apps: KintoneApp[];
apps: AppResponse[];
currentApp: AppDetail | null;
selectedAppId: string | null;
loading: boolean;
@@ -27,7 +27,7 @@ interface AppState {
loadedAt: string | null;
// Actions
setApps: (apps: KintoneApp[]) => void;
setApps: (apps: AppResponse[]) => void;
setCurrentApp: (app: AppDetail | null) => void;
setSelectedAppId: (id: string | null) => void;
setLoading: (loading: boolean) => void;

View File

@@ -5,8 +5,8 @@
import type { Domain, DomainWithPassword, DomainWithStatus } from "./domain";
import type {
AppResponse,
KintoneSpace,
KintoneApp,
AppDetail,
FileContent,
} from "./kintone";
@@ -136,11 +136,13 @@ export interface ElectronAPI {
updateDomain: (params: UpdateDomainParams) => Promise<Result<Domain>>;
deleteDomain: (id: string) => Promise<Result<void>>;
testConnection: (id: string) => Promise<Result<DomainWithStatus>>;
testDomainConnection: (params: TestDomainConnectionParams) => Promise<Result<boolean>>;
testDomainConnection: (
params: TestDomainConnectionParams,
) => Promise<Result<boolean>>;
// Browse
getSpaces: (params: GetSpacesParams) => Promise<Result<KintoneSpace[]>>;
getApps: (params: GetAppsParams) => Promise<Result<KintoneApp[]>>;
getApps: (params: GetAppsParams) => Promise<Result<AppResponse[]>>;
getAppDetail: (params: GetAppDetailParams) => Promise<Result<AppDetail>>;
getFileContent: (
params: GetFileContentParams,

View File

@@ -1,100 +1,45 @@
/**
* Kintone API response types
* Based on REQUIREMENTS.md:331-345
* Using SDK types where possible, custom types only for business-layer aggregation
*/
// Space types
export interface KintoneSpace {
id: string;
name: string;
code: string;
createdAt?: string;
creator?: {
code: string;
name: string;
};
}
import type { KintoneRestAPIClient } from "@kintone/rest-api-client";
// App types
export interface KintoneApp {
appId: string;
name: string;
code?: string;
spaceId?: string;
createdAt: string;
creator?: {
code: string;
name: string;
};
modifiedAt?: string;
modifier?: {
code: string;
name: string;
};
}
// ==================== SDK Type Extraction ====================
// Use typeof + ReturnType to extract types from SDK methods
type KintoneClient = KintoneRestAPIClient;
// File configuration types
export interface JSFileConfig {
type: "FILE" | "URL";
file?: {
fileKey: string;
name?: string; // Optional for deploy, filled by Kintone
size?: number; // Optional for deploy, filled by Kintone
};
url?: string;
}
/** App response from getApp/getApps */
export type AppResponse = Awaited<ReturnType<KintoneClient["app"]["getApp"]>>;
export interface CSSFileConfig {
type: "FILE" | "URL";
file?: {
fileKey: string;
name?: string; // Optional for deploy, filled by Kintone
size?: number; // Optional for deploy, filled by Kintone
};
url?: string;
}
/** Apps list response */
export type AppsResponse = Awaited<ReturnType<KintoneClient["app"]["getApps"]>>;
// App customization config
export interface AppCustomizationConfig {
javascript?: {
pc?: JSFileConfig[];
mobile?: JSFileConfig[];
};
stylesheet?: {
pc?: CSSFileConfig[];
mobile?: CSSFileConfig[];
};
plugins?: AppPlugin[];
}
/** App customization response */
export type AppCustomizeResponse = Awaited<
ReturnType<KintoneClient["app"]["getAppCustomize"]>
>;
export interface AppPlugin {
id: string;
name: string;
enabled: boolean;
}
/** File upload response */
export type FileUploadResponse = Awaited<
ReturnType<KintoneClient["file"]["uploadFile"]>
>;
// App detail response
export interface AppDetail {
appId: string;
name: string;
code?: string;
description?: string;
spaceId?: string;
spaceName?: string;
createdAt: string;
creator: {
code: string;
name: string;
};
modifiedAt: string;
modifier: {
code: string;
name: string;
};
// ==================== Custom Business Types ====================
// These types represent business-layer aggregation or additional metadata
/**
* App detail - combines app info + customization
* This is a business-layer type that aggregates data from multiple API calls
*/
export interface AppDetail extends AppResponse {
customization?: AppCustomizationConfig;
}
// File content
/**
* File content - file metadata + content
* SDK's downloadFile only returns ArrayBuffer, we add metadata
*/
export interface FileContent {
fileKey: string;
name: string;
@@ -103,7 +48,31 @@ export interface FileContent {
content?: string; // Base64 encoded or text
}
// API Error
/**
* App customization config
* Uses SDK's customize types structure (desktop/mobile) but simplified for internal use
* Note: SDK uses { js: [], css: [] } but we use { javascript: { pc, mobile } } for easier UI binding
*/
export interface AppCustomizationConfig {
javascript?: {
pc?: FileConfig[];
mobile?: FileConfig[];
};
stylesheet?: {
pc?: FileConfig[];
mobile?: FileConfig[];
};
}
/**
* File config for customization
* Using SDK's type directly from AppCustomizeResponse
*/
export type FileConfig = Awaited<AppCustomizeResponse>["desktop"]["js"][number];
/**
* API Error - simplified from SDK's KintoneRestAPIError
*/
export interface KintoneApiError {
code: string;
message: string;