Initial commit
This commit is contained in:
17
src/main.ts
Normal file
17
src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useNodesStore } from '@/stores/nodesStore'
|
||||
import { useProvidersStore } from '@/stores/providersStore'
|
||||
import { useServicesStore } from '@/stores/servicesStore'
|
||||
|
||||
/**
|
||||
* File Manager Module Boot Script
|
||||
*
|
||||
* This script is executed when the file_manager module is loaded.
|
||||
* It initializes the stores which manage file nodes (files and folders) state.
|
||||
*/
|
||||
|
||||
console.log('[FileManager] Booting File Manager module...')
|
||||
|
||||
console.log('[FileManager] File Manager module booted successfully')
|
||||
|
||||
// Export stores for external use if needed
|
||||
export { useNodesStore, useProvidersStore, useServicesStore }
|
||||
132
src/models/collection.ts
Normal file
132
src/models/collection.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Class model for FileCollection Interface
|
||||
*/
|
||||
import type { FileCollection } from "@/types/node";
|
||||
|
||||
export class FileCollectionObject implements FileCollection {
|
||||
|
||||
_data!: FileCollection;
|
||||
|
||||
constructor() {
|
||||
this._data = {
|
||||
'@type': 'files.collection',
|
||||
in: null,
|
||||
id: '',
|
||||
createdBy: '',
|
||||
createdOn: '',
|
||||
modifiedBy: '',
|
||||
modifiedOn: '',
|
||||
owner: '',
|
||||
signature: '',
|
||||
label: '',
|
||||
};
|
||||
}
|
||||
|
||||
fromJson(data: FileCollection): FileCollectionObject {
|
||||
this._data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson(): FileCollection {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
clone(): FileCollectionObject {
|
||||
const cloned = new FileCollectionObject();
|
||||
cloned._data = JSON.parse(JSON.stringify(this._data));
|
||||
return cloned;
|
||||
}
|
||||
|
||||
/** Properties */
|
||||
|
||||
get '@type'(): 'files.collection' {
|
||||
return this._data['@type'];
|
||||
}
|
||||
|
||||
get in(): string | null {
|
||||
return this._data.in;
|
||||
}
|
||||
|
||||
set in(value: string | null) {
|
||||
this._data.in = value;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._data.id;
|
||||
}
|
||||
|
||||
set id(value: string) {
|
||||
this._data.id = value;
|
||||
}
|
||||
|
||||
get createdBy(): string {
|
||||
return this._data.createdBy;
|
||||
}
|
||||
|
||||
set createdBy(value: string) {
|
||||
this._data.createdBy = value;
|
||||
}
|
||||
|
||||
get createdOn(): string {
|
||||
return this._data.createdOn;
|
||||
}
|
||||
|
||||
set createdOn(value: string) {
|
||||
this._data.createdOn = value;
|
||||
}
|
||||
|
||||
get modifiedBy(): string {
|
||||
return this._data.modifiedBy;
|
||||
}
|
||||
|
||||
set modifiedBy(value: string) {
|
||||
this._data.modifiedBy = value;
|
||||
}
|
||||
|
||||
get modifiedOn(): string {
|
||||
return this._data.modifiedOn;
|
||||
}
|
||||
|
||||
set modifiedOn(value: string) {
|
||||
this._data.modifiedOn = value;
|
||||
}
|
||||
|
||||
get owner(): string {
|
||||
return this._data.owner;
|
||||
}
|
||||
|
||||
set owner(value: string) {
|
||||
this._data.owner = value;
|
||||
}
|
||||
|
||||
get signature(): string {
|
||||
return this._data.signature;
|
||||
}
|
||||
|
||||
set signature(value: string) {
|
||||
this._data.signature = value;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._data.label;
|
||||
}
|
||||
|
||||
set label(value: string) {
|
||||
this._data.label = value;
|
||||
}
|
||||
|
||||
/** Helper methods */
|
||||
|
||||
get isRoot(): boolean {
|
||||
return this._data.id === '00000000-0000-0000-0000-000000000000';
|
||||
}
|
||||
|
||||
get createdOnDate(): Date | null {
|
||||
return this._data.createdOn ? new Date(this._data.createdOn) : null;
|
||||
}
|
||||
|
||||
get modifiedOnDate(): Date | null {
|
||||
return this._data.modifiedOn ? new Date(this._data.modifiedOn) : null;
|
||||
}
|
||||
|
||||
}
|
||||
200
src/models/entity.ts
Normal file
200
src/models/entity.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Class model for FileEntity Interface
|
||||
*/
|
||||
import type { FileEntity } from "@/types/node";
|
||||
|
||||
export class FileEntityObject implements FileEntity {
|
||||
|
||||
_data!: FileEntity;
|
||||
|
||||
constructor() {
|
||||
this._data = {
|
||||
'@type': 'files.entity',
|
||||
in: null,
|
||||
id: '',
|
||||
createdBy: '',
|
||||
createdOn: '',
|
||||
modifiedBy: '',
|
||||
modifiedOn: '',
|
||||
owner: '',
|
||||
signature: '',
|
||||
label: '',
|
||||
size: 0,
|
||||
mime: '',
|
||||
format: '',
|
||||
encoding: '',
|
||||
};
|
||||
}
|
||||
|
||||
fromJson(data: FileEntity): FileEntityObject {
|
||||
this._data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson(): FileEntity {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
clone(): FileEntityObject {
|
||||
const cloned = new FileEntityObject();
|
||||
cloned._data = JSON.parse(JSON.stringify(this._data));
|
||||
return cloned;
|
||||
}
|
||||
|
||||
/** Properties */
|
||||
|
||||
get '@type'(): 'files.entity' {
|
||||
return this._data['@type'];
|
||||
}
|
||||
|
||||
get in(): string | null {
|
||||
return this._data.in;
|
||||
}
|
||||
|
||||
set in(value: string | null) {
|
||||
this._data.in = value;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._data.id;
|
||||
}
|
||||
|
||||
set id(value: string) {
|
||||
this._data.id = value;
|
||||
}
|
||||
|
||||
get createdBy(): string {
|
||||
return this._data.createdBy;
|
||||
}
|
||||
|
||||
set createdBy(value: string) {
|
||||
this._data.createdBy = value;
|
||||
}
|
||||
|
||||
get createdOn(): string {
|
||||
return this._data.createdOn;
|
||||
}
|
||||
|
||||
set createdOn(value: string) {
|
||||
this._data.createdOn = value;
|
||||
}
|
||||
|
||||
get modifiedBy(): string {
|
||||
return this._data.modifiedBy;
|
||||
}
|
||||
|
||||
set modifiedBy(value: string) {
|
||||
this._data.modifiedBy = value;
|
||||
}
|
||||
|
||||
get modifiedOn(): string {
|
||||
return this._data.modifiedOn;
|
||||
}
|
||||
|
||||
set modifiedOn(value: string) {
|
||||
this._data.modifiedOn = value;
|
||||
}
|
||||
|
||||
get owner(): string {
|
||||
return this._data.owner;
|
||||
}
|
||||
|
||||
set owner(value: string) {
|
||||
this._data.owner = value;
|
||||
}
|
||||
|
||||
get signature(): string {
|
||||
return this._data.signature;
|
||||
}
|
||||
|
||||
set signature(value: string) {
|
||||
this._data.signature = value;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._data.label;
|
||||
}
|
||||
|
||||
set label(value: string) {
|
||||
this._data.label = value;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._data.size;
|
||||
}
|
||||
|
||||
set size(value: number) {
|
||||
this._data.size = value;
|
||||
}
|
||||
|
||||
get mime(): string {
|
||||
return this._data.mime;
|
||||
}
|
||||
|
||||
set mime(value: string) {
|
||||
this._data.mime = value;
|
||||
}
|
||||
|
||||
get format(): string {
|
||||
return this._data.format;
|
||||
}
|
||||
|
||||
set format(value: string) {
|
||||
this._data.format = value;
|
||||
}
|
||||
|
||||
get encoding(): string {
|
||||
return this._data.encoding;
|
||||
}
|
||||
|
||||
set encoding(value: string) {
|
||||
this._data.encoding = value;
|
||||
}
|
||||
|
||||
/** Helper methods */
|
||||
|
||||
get createdOnDate(): Date | null {
|
||||
return this._data.createdOn ? new Date(this._data.createdOn) : null;
|
||||
}
|
||||
|
||||
get modifiedOnDate(): Date | null {
|
||||
return this._data.modifiedOn ? new Date(this._data.modifiedOn) : null;
|
||||
}
|
||||
|
||||
get extension(): string {
|
||||
const parts = this._data.label.split('.');
|
||||
return parts.length > 1 ? parts[parts.length - 1] : '';
|
||||
}
|
||||
|
||||
get sizeFormatted(): string {
|
||||
const bytes = this._data.size;
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
get isImage(): boolean {
|
||||
return this._data.mime.startsWith('image/');
|
||||
}
|
||||
|
||||
get isVideo(): boolean {
|
||||
return this._data.mime.startsWith('video/');
|
||||
}
|
||||
|
||||
get isAudio(): boolean {
|
||||
return this._data.mime.startsWith('audio/');
|
||||
}
|
||||
|
||||
get isText(): boolean {
|
||||
return this._data.mime.startsWith('text/') ||
|
||||
this._data.mime === 'application/json' ||
|
||||
this._data.mime === 'application/xml';
|
||||
}
|
||||
|
||||
get isPdf(): boolean {
|
||||
return this._data.mime === 'application/pdf';
|
||||
}
|
||||
|
||||
}
|
||||
8
src/models/index.ts
Normal file
8
src/models/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Central export point for all File Manager models
|
||||
*/
|
||||
|
||||
export { FileCollectionObject } from './collection';
|
||||
export { FileEntityObject } from './entity';
|
||||
export { ProviderObject } from './provider';
|
||||
export { ServiceObject } from './service';
|
||||
63
src/models/provider.ts
Normal file
63
src/models/provider.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Class model for Provider Interface
|
||||
*/
|
||||
import type { ProviderCapabilitiesInterface, ProviderInterface } from "@/types/provider";
|
||||
|
||||
export class ProviderObject implements ProviderInterface {
|
||||
|
||||
_data!: ProviderInterface;
|
||||
|
||||
constructor() {
|
||||
this._data = {
|
||||
'@type': 'files:provider',
|
||||
id: '',
|
||||
label: '',
|
||||
capabilities: {},
|
||||
};
|
||||
}
|
||||
|
||||
fromJson(data: ProviderInterface): ProviderObject {
|
||||
this._data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson(): ProviderInterface {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
clone(): ProviderObject {
|
||||
const cloned = new ProviderObject();
|
||||
cloned._data = JSON.parse(JSON.stringify(this._data));
|
||||
return cloned;
|
||||
}
|
||||
|
||||
capable(capability: keyof ProviderCapabilitiesInterface): boolean {
|
||||
return !!(this._data.capabilities && this._data.capabilities[capability]);
|
||||
}
|
||||
|
||||
capability(capability: keyof ProviderCapabilitiesInterface): boolean | string[] | Record<string, string> | Record<string, string[]> | undefined {
|
||||
if (this._data.capabilities) {
|
||||
return this._data.capabilities[capability];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Immutable Properties */
|
||||
|
||||
get '@type'(): string {
|
||||
return this._data['@type'];
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._data.id;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._data.label;
|
||||
}
|
||||
|
||||
get capabilities(): ProviderCapabilitiesInterface {
|
||||
return this._data.capabilities;
|
||||
}
|
||||
|
||||
}
|
||||
57
src/models/service.ts
Normal file
57
src/models/service.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Class model for Service Interface
|
||||
*/
|
||||
import type { ServiceInterface } from "@/types/service";
|
||||
|
||||
export class ServiceObject implements ServiceInterface {
|
||||
|
||||
_data!: ServiceInterface;
|
||||
|
||||
constructor() {
|
||||
this._data = {
|
||||
'@type': 'files:service',
|
||||
id: '',
|
||||
provider: '',
|
||||
label: '',
|
||||
rootId: '',
|
||||
};
|
||||
}
|
||||
|
||||
fromJson(data: ServiceInterface): ServiceObject {
|
||||
this._data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
toJson(): ServiceInterface {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
clone(): ServiceObject {
|
||||
const cloned = new ServiceObject();
|
||||
cloned._data = JSON.parse(JSON.stringify(this._data));
|
||||
return cloned;
|
||||
}
|
||||
|
||||
/** Immutable Properties */
|
||||
|
||||
get '@type'(): string {
|
||||
return this._data['@type'];
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._data.id;
|
||||
}
|
||||
|
||||
get provider(): string {
|
||||
return this._data.provider;
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this._data.label;
|
||||
}
|
||||
|
||||
get rootId(): string {
|
||||
return this._data.rootId;
|
||||
}
|
||||
|
||||
}
|
||||
72
src/services/api.ts
Normal file
72
src/services/api.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* File Manager API Service
|
||||
* Central service for making API calls to the file manager backend
|
||||
*/
|
||||
|
||||
import { createFetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper-core';
|
||||
|
||||
const fetchWrapper = createFetchWrapper();
|
||||
|
||||
const BASE_URL = '/m/file_manager/v1';
|
||||
|
||||
interface ApiRequest {
|
||||
version: number;
|
||||
transaction: string;
|
||||
operation: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ApiSuccessResponse<T> {
|
||||
version: number;
|
||||
transaction: string;
|
||||
operation: string;
|
||||
status: 'success';
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface ApiErrorResponse {
|
||||
version: number;
|
||||
transaction: string;
|
||||
operation: string;
|
||||
status: 'error';
|
||||
error: {
|
||||
code: number;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
type ApiResponseRaw<T> = ApiSuccessResponse<T> | ApiErrorResponse;
|
||||
|
||||
/**
|
||||
* Generate a unique transaction ID
|
||||
*/
|
||||
function generateTransactionId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API operation
|
||||
*/
|
||||
async function execute<T>(operation: string, data: Record<string, unknown> = {}): Promise<T> {
|
||||
const request: ApiRequest = {
|
||||
version: 1,
|
||||
transaction: generateTransactionId(),
|
||||
operation,
|
||||
data,
|
||||
};
|
||||
|
||||
const response: ApiResponseRaw<T> = await fetchWrapper.post(BASE_URL, request);
|
||||
|
||||
if (response.status === 'error') {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const fileManagerApi = {
|
||||
execute,
|
||||
generateTransactionId,
|
||||
};
|
||||
|
||||
export default fileManagerApi;
|
||||
195
src/services/collectionService.ts
Normal file
195
src/services/collectionService.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Collection management service
|
||||
*/
|
||||
|
||||
import { fileManagerApi } from './api';
|
||||
import type { FilterCondition, SortCondition } from '@/types/common';
|
||||
import type { FileCollection } from '@/types/node';
|
||||
|
||||
export const collectionService = {
|
||||
|
||||
/**
|
||||
* List collections within a location
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param location - Parent collection ID (null for root)
|
||||
* @param filter - Optional filter conditions
|
||||
* @param sort - Optional sort conditions
|
||||
* @returns Promise with collection list
|
||||
*/
|
||||
async list(
|
||||
provider: string,
|
||||
service: string,
|
||||
location?: string | null,
|
||||
filter?: FilterCondition[] | null,
|
||||
sort?: SortCondition[] | null
|
||||
): Promise<FileCollection[]> {
|
||||
return await fileManagerApi.execute<FileCollection[]>('collection.list', {
|
||||
provider,
|
||||
service,
|
||||
location: location ?? null,
|
||||
filter: filter ?? null,
|
||||
sort: sort ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a collection exists
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param identifier - Collection identifier
|
||||
* @returns Promise with extant status
|
||||
*/
|
||||
async extant(
|
||||
provider: string,
|
||||
service: string,
|
||||
identifier: string
|
||||
): Promise<boolean> {
|
||||
const result = await fileManagerApi.execute<{ extant: boolean }>('collection.extant', {
|
||||
provider,
|
||||
service,
|
||||
identifier,
|
||||
});
|
||||
return result.extant;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a specific collection
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param identifier - Collection identifier
|
||||
* @returns Promise with collection details
|
||||
*/
|
||||
async fetch(
|
||||
provider: string,
|
||||
service: string,
|
||||
identifier: string
|
||||
): Promise<FileCollection> {
|
||||
return await fileManagerApi.execute<FileCollection>('collection.fetch', {
|
||||
provider,
|
||||
service,
|
||||
identifier,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new collection (folder)
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param location - Parent collection ID (null for root)
|
||||
* @param data - Collection data (label, etc.)
|
||||
* @param options - Additional options
|
||||
* @returns Promise with created collection
|
||||
*/
|
||||
async create(
|
||||
provider: string,
|
||||
service: string,
|
||||
location: string | null,
|
||||
data: Partial<FileCollection>,
|
||||
options?: Record<string, unknown>
|
||||
): Promise<FileCollection> {
|
||||
return await fileManagerApi.execute<FileCollection>('collection.create', {
|
||||
provider,
|
||||
service,
|
||||
location,
|
||||
data,
|
||||
options: options ?? {},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Modify an existing collection
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param identifier - Collection identifier
|
||||
* @param data - Data to modify
|
||||
* @returns Promise with modified collection
|
||||
*/
|
||||
async modify(
|
||||
provider: string,
|
||||
service: string,
|
||||
identifier: string,
|
||||
data: Partial<FileCollection>
|
||||
): Promise<FileCollection> {
|
||||
return await fileManagerApi.execute<FileCollection>('collection.modify', {
|
||||
provider,
|
||||
service,
|
||||
identifier,
|
||||
data,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a collection
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param identifier - Collection identifier
|
||||
* @returns Promise with success status
|
||||
*/
|
||||
async destroy(
|
||||
provider: string,
|
||||
service: string,
|
||||
identifier: string
|
||||
): Promise<boolean> {
|
||||
const result = await fileManagerApi.execute<{ success: boolean }>('collection.destroy', {
|
||||
provider,
|
||||
service,
|
||||
identifier,
|
||||
});
|
||||
return result.success;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy a collection to a new location
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param identifier - Collection identifier to copy
|
||||
* @param location - Destination parent collection ID (null for root)
|
||||
* @returns Promise with copied collection
|
||||
*/
|
||||
async copy(
|
||||
provider: string,
|
||||
service: string,
|
||||
identifier: string,
|
||||
location?: string | null
|
||||
): Promise<FileCollection> {
|
||||
return await fileManagerApi.execute<FileCollection>('collection.copy', {
|
||||
provider,
|
||||
service,
|
||||
identifier,
|
||||
location: location ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Move a collection to a new location
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param identifier - Collection identifier to move
|
||||
* @param location - Destination parent collection ID (null for root)
|
||||
* @returns Promise with moved collection
|
||||
*/
|
||||
async move(
|
||||
provider: string,
|
||||
service: string,
|
||||
identifier: string,
|
||||
location?: string | null
|
||||
): Promise<FileCollection> {
|
||||
return await fileManagerApi.execute<FileCollection>('collection.move', {
|
||||
provider,
|
||||
service,
|
||||
identifier,
|
||||
location: location ?? null,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default collectionService;
|
||||
293
src/services/entityService.ts
Normal file
293
src/services/entityService.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* Entity (file) management service
|
||||
*/
|
||||
|
||||
import { fileManagerApi } from './api';
|
||||
import type { FilterCondition, SortCondition, RangeCondition } from '@/types/common';
|
||||
import type { FileEntity } from '@/types/node';
|
||||
import type { EntityDeltaResult } from '@/types/api';
|
||||
|
||||
export const entityService = {
|
||||
|
||||
/**
|
||||
* List entities within a collection
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier
|
||||
* @param filter - Optional filter conditions
|
||||
* @param sort - Optional sort conditions
|
||||
* @param range - Optional range/pagination conditions
|
||||
* @returns Promise with entity list
|
||||
*/
|
||||
async list(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string,
|
||||
filter?: FilterCondition[] | null,
|
||||
sort?: SortCondition[] | null,
|
||||
range?: RangeCondition | null
|
||||
): Promise<FileEntity[]> {
|
||||
return await fileManagerApi.execute<FileEntity[]>('entity.list', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
filter: filter ?? null,
|
||||
sort: sort ?? null,
|
||||
range: range ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get delta changes for entities since a signature
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier
|
||||
* @param signature - Previous sync signature
|
||||
* @param detail - Detail level ('ids' or 'full')
|
||||
* @returns Promise with delta changes
|
||||
*/
|
||||
async delta(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string,
|
||||
signature: string,
|
||||
detail: 'ids' | 'full' = 'ids'
|
||||
): Promise<EntityDeltaResult> {
|
||||
return await fileManagerApi.execute<EntityDeltaResult>('entity.delta', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
signature,
|
||||
detail,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check which entities exist
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier
|
||||
* @param identifiers - Entity identifiers to check
|
||||
* @returns Promise with existence map
|
||||
*/
|
||||
async extant(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string,
|
||||
identifiers: string[]
|
||||
): Promise<Record<string, boolean>> {
|
||||
return await fileManagerApi.execute<Record<string, boolean>>('entity.extant', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifiers,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch specific entities
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier
|
||||
* @param identifiers - Entity identifiers to fetch
|
||||
* @returns Promise with entity list
|
||||
*/
|
||||
async fetch(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string,
|
||||
identifiers: string[]
|
||||
): Promise<FileEntity[]> {
|
||||
return await fileManagerApi.execute<FileEntity[]>('entity.fetch', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifiers,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Read entity content
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier
|
||||
* @param identifier - Entity identifier
|
||||
* @returns Promise with base64 encoded content
|
||||
*/
|
||||
async read(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string,
|
||||
identifier: string
|
||||
): Promise<{ content: string | null; encoding: 'base64' }> {
|
||||
return await fileManagerApi.execute<{ content: string | null; encoding: 'base64' }>('entity.read', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifier,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new entity (file)
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier (null for root)
|
||||
* @param data - Entity data (label, mime, etc.)
|
||||
* @param options - Additional options
|
||||
* @returns Promise with created entity
|
||||
*/
|
||||
async create(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string | null,
|
||||
data: Partial<FileEntity>,
|
||||
options?: Record<string, unknown>
|
||||
): Promise<FileEntity> {
|
||||
return await fileManagerApi.execute<FileEntity>('entity.create', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
data,
|
||||
options: options ?? {},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Modify an existing entity
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier (can be null)
|
||||
* @param identifier - Entity identifier
|
||||
* @param data - Data to modify
|
||||
* @returns Promise with modified entity
|
||||
*/
|
||||
async modify(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
data: Partial<FileEntity>
|
||||
): Promise<FileEntity> {
|
||||
return await fileManagerApi.execute<FileEntity>('entity.modify', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifier,
|
||||
data,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete an entity
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier (can be null)
|
||||
* @param identifier - Entity identifier
|
||||
* @returns Promise with success status
|
||||
*/
|
||||
async destroy(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string | null,
|
||||
identifier: string
|
||||
): Promise<boolean> {
|
||||
const result = await fileManagerApi.execute<{ success: boolean }>('entity.destroy', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifier,
|
||||
});
|
||||
return result.success;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy an entity to a new location
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Source collection identifier (can be null)
|
||||
* @param identifier - Entity identifier to copy
|
||||
* @param destination - Destination collection ID (null for root)
|
||||
* @returns Promise with copied entity
|
||||
*/
|
||||
async copy(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
destination?: string | null
|
||||
): Promise<FileEntity> {
|
||||
return await fileManagerApi.execute<FileEntity>('entity.copy', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifier,
|
||||
destination: destination ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Move an entity to a new location
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Source collection identifier (can be null)
|
||||
* @param identifier - Entity identifier to move
|
||||
* @param destination - Destination collection ID (null for root)
|
||||
* @returns Promise with moved entity
|
||||
*/
|
||||
async move(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
destination?: string | null
|
||||
): Promise<FileEntity> {
|
||||
return await fileManagerApi.execute<FileEntity>('entity.move', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifier,
|
||||
destination: destination ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Write content to an entity
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param collection - Collection identifier (can be null)
|
||||
* @param identifier - Entity identifier
|
||||
* @param content - Content to write (base64 encoded)
|
||||
* @returns Promise with bytes written
|
||||
*/
|
||||
async write(
|
||||
provider: string,
|
||||
service: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
content: string
|
||||
): Promise<number> {
|
||||
const result = await fileManagerApi.execute<{ bytesWritten: number }>('entity.write', {
|
||||
provider,
|
||||
service,
|
||||
collection,
|
||||
identifier,
|
||||
content,
|
||||
encoding: 'base64',
|
||||
});
|
||||
return result.bytesWritten;
|
||||
},
|
||||
};
|
||||
|
||||
export default entityService;
|
||||
10
src/services/index.ts
Normal file
10
src/services/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Central export point for all File Manager services
|
||||
*/
|
||||
|
||||
export { fileManagerApi } from './api';
|
||||
export { providerService } from './providerService';
|
||||
export { serviceService } from './serviceService';
|
||||
export { collectionService } from './collectionService';
|
||||
export { entityService } from './entityService';
|
||||
export { nodeService } from './nodeService';
|
||||
74
src/services/nodeService.ts
Normal file
74
src/services/nodeService.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Node (unified collection/entity) management service
|
||||
*/
|
||||
|
||||
import { fileManagerApi } from './api';
|
||||
import type { FilterCondition, SortCondition, RangeCondition } from '@/types/common';
|
||||
import type { FileNode } from '@/types/node';
|
||||
import type { NodeDeltaResult } from '@/types/api';
|
||||
|
||||
export const nodeService = {
|
||||
|
||||
/**
|
||||
* List all nodes (collections and entities) within a location
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param location - Parent collection ID (null for root)
|
||||
* @param recursive - Whether to list recursively
|
||||
* @param filter - Optional filter conditions
|
||||
* @param sort - Optional sort conditions
|
||||
* @param range - Optional range/pagination conditions
|
||||
* @returns Promise with node list
|
||||
*/
|
||||
async list(
|
||||
provider: string,
|
||||
service: string,
|
||||
location?: string | null,
|
||||
recursive: boolean = false,
|
||||
filter?: FilterCondition[] | null,
|
||||
sort?: SortCondition[] | null,
|
||||
range?: RangeCondition | null
|
||||
): Promise<FileNode[]> {
|
||||
return await fileManagerApi.execute<FileNode[]>('node.list', {
|
||||
provider,
|
||||
service,
|
||||
location: location ?? null,
|
||||
recursive,
|
||||
filter: filter ?? null,
|
||||
sort: sort ?? null,
|
||||
range: range ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get delta changes for nodes since a signature
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param service - Service identifier
|
||||
* @param location - Parent collection ID (null for root)
|
||||
* @param signature - Previous sync signature
|
||||
* @param recursive - Whether to get delta recursively
|
||||
* @param detail - Detail level ('ids' or 'full')
|
||||
* @returns Promise with delta changes
|
||||
*/
|
||||
async delta(
|
||||
provider: string,
|
||||
service: string,
|
||||
location: string | null,
|
||||
signature: string,
|
||||
recursive: boolean = false,
|
||||
detail: 'ids' | 'full' = 'ids'
|
||||
): Promise<NodeDeltaResult> {
|
||||
return await fileManagerApi.execute<NodeDeltaResult>('node.delta', {
|
||||
provider,
|
||||
service,
|
||||
location,
|
||||
signature,
|
||||
recursive,
|
||||
detail,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default nodeService;
|
||||
34
src/services/providerService.ts
Normal file
34
src/services/providerService.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Provider management service
|
||||
*/
|
||||
|
||||
import { fileManagerApi } from './api';
|
||||
import type { SourceSelector } from '@/types/common';
|
||||
import type { ProviderRecord } from '@/types/provider';
|
||||
|
||||
export const providerService = {
|
||||
|
||||
/**
|
||||
* List all available providers
|
||||
*
|
||||
* @param sources - Optional source selector to filter providers
|
||||
* @returns Promise with provider list keyed by provider ID
|
||||
*/
|
||||
async list(sources?: SourceSelector): Promise<ProviderRecord> {
|
||||
return await fileManagerApi.execute<ProviderRecord>('provider.list', {
|
||||
sources: sources || null
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check which providers exist/are available
|
||||
*
|
||||
* @param sources - Source selector with provider IDs to check
|
||||
* @returns Promise with provider availability status
|
||||
*/
|
||||
async extant(sources: SourceSelector): Promise<Record<string, boolean>> {
|
||||
return await fileManagerApi.execute<Record<string, boolean>>('provider.extant', { sources });
|
||||
},
|
||||
};
|
||||
|
||||
export default providerService;
|
||||
48
src/services/serviceService.ts
Normal file
48
src/services/serviceService.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Service management service
|
||||
*/
|
||||
|
||||
import { fileManagerApi } from './api';
|
||||
import type { SourceSelector } from '@/types/common';
|
||||
import type { ServiceInterface, ServiceRecord } from '@/types/service';
|
||||
|
||||
export const serviceService = {
|
||||
|
||||
/**
|
||||
* List all available services
|
||||
*
|
||||
* @param sources - Optional source selector to filter services
|
||||
* @returns Promise with service list grouped by provider
|
||||
*/
|
||||
async list(sources?: SourceSelector): Promise<ServiceRecord> {
|
||||
return await fileManagerApi.execute<ServiceRecord>('service.list', {
|
||||
sources: sources || null
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check which services exist/are available
|
||||
*
|
||||
* @param sources - Source selector with service IDs to check
|
||||
* @returns Promise with service availability status
|
||||
*/
|
||||
async extant(sources: SourceSelector): Promise<Record<string, boolean>> {
|
||||
return await fileManagerApi.execute<Record<string, boolean>>('service.extant', { sources });
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a specific service
|
||||
*
|
||||
* @param provider - Provider identifier
|
||||
* @param identifier - Service identifier
|
||||
* @returns Promise with service details
|
||||
*/
|
||||
async fetch(provider: string, identifier: string): Promise<ServiceInterface> {
|
||||
return await fileManagerApi.execute<ServiceInterface>('service.fetch', {
|
||||
provider,
|
||||
identifier
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default serviceService;
|
||||
687
src/stores/nodesStore.ts
Normal file
687
src/stores/nodesStore.ts
Normal file
@@ -0,0 +1,687 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { FileNode, FileCollection, FileEntity } from '../types/node'
|
||||
import type { FilterCondition, SortCondition, RangeCondition } from '../types/common'
|
||||
import { isFileCollection } from '../types/node'
|
||||
import { collectionService } from '../services/collectionService'
|
||||
import { entityService } from '../services/entityService'
|
||||
import { nodeService } from '../services/nodeService'
|
||||
import { FileCollectionObject } from '../models/collection'
|
||||
import { FileEntityObject } from '../models/entity'
|
||||
|
||||
// Root collection constant
|
||||
export const ROOT_ID = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
// Store structure: provider -> service -> nodeId -> node (either collection or entity object)
|
||||
type NodeRecord = FileCollectionObject | FileEntityObject
|
||||
type ServiceNodeStore = Record<string, NodeRecord>
|
||||
type ProviderNodeStore = Record<string, ServiceNodeStore>
|
||||
type NodeStore = Record<string, ProviderNodeStore>
|
||||
|
||||
export const useNodesStore = defineStore('fileNodes', () => {
|
||||
const nodes: Ref<NodeStore> = ref({})
|
||||
const syncTokens: Ref<Record<string, Record<string, string>>> = ref({}) // provider -> service -> token
|
||||
const loading = ref(false)
|
||||
const error: Ref<string | null> = ref(null)
|
||||
|
||||
// Computed: flat list of all nodes
|
||||
const nodeList: ComputedRef<NodeRecord[]> = computed(() => {
|
||||
const result: NodeRecord[] = []
|
||||
Object.values(nodes.value).forEach(providerNodes => {
|
||||
Object.values(providerNodes).forEach(serviceNodes => {
|
||||
result.push(...Object.values(serviceNodes))
|
||||
})
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
// Computed: all collections (folders)
|
||||
const collectionList: ComputedRef<FileCollectionObject[]> = computed(() => {
|
||||
return nodeList.value.filter(
|
||||
(node): node is FileCollectionObject => node['@type'] === 'files.collection'
|
||||
)
|
||||
})
|
||||
|
||||
// Computed: all entities (files)
|
||||
const entityList: ComputedRef<FileEntityObject[]> = computed(() => {
|
||||
return nodeList.value.filter(
|
||||
(node): node is FileEntityObject => node['@type'] === 'files.entity'
|
||||
)
|
||||
})
|
||||
|
||||
// Get a specific node
|
||||
const getNode = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
nodeId: string
|
||||
): NodeRecord | undefined => {
|
||||
return nodes.value[providerId]?.[serviceId]?.[nodeId]
|
||||
}
|
||||
|
||||
// Get all nodes for a service
|
||||
const getServiceNodes = (
|
||||
providerId: string,
|
||||
serviceId: string
|
||||
): NodeRecord[] => {
|
||||
return Object.values(nodes.value[providerId]?.[serviceId] || {})
|
||||
}
|
||||
|
||||
// Get children of a parent node (or root nodes if parentId is null/ROOT_ID)
|
||||
const getChildren = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
parentId: string | null
|
||||
): NodeRecord[] => {
|
||||
const serviceNodes = nodes.value[providerId]?.[serviceId] || {}
|
||||
const targetParent = parentId === ROOT_ID ? ROOT_ID : parentId
|
||||
return Object.values(serviceNodes).filter(node => node.in === targetParent)
|
||||
}
|
||||
|
||||
// Get child collections (folders)
|
||||
const getChildCollections = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
parentId: string | null
|
||||
): FileCollectionObject[] => {
|
||||
return getChildren(providerId, serviceId, parentId).filter(
|
||||
(node): node is FileCollectionObject => node['@type'] === 'files.collection'
|
||||
)
|
||||
}
|
||||
|
||||
// Get child entities (files)
|
||||
const getChildEntities = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
parentId: string | null
|
||||
): FileEntityObject[] => {
|
||||
return getChildren(providerId, serviceId, parentId).filter(
|
||||
(node): node is FileEntityObject => node['@type'] === 'files.entity'
|
||||
)
|
||||
}
|
||||
|
||||
// Get path to root (ancestors)
|
||||
const getPath = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
nodeId: string
|
||||
): NodeRecord[] => {
|
||||
const path: NodeRecord[] = []
|
||||
let currentNode = getNode(providerId, serviceId, nodeId)
|
||||
|
||||
while (currentNode) {
|
||||
path.unshift(currentNode)
|
||||
if (currentNode.in === null || currentNode.in === ROOT_ID || currentNode.id === ROOT_ID) {
|
||||
break
|
||||
}
|
||||
currentNode = getNode(providerId, serviceId, currentNode.in)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// Check if a node is the root
|
||||
const isRoot = (nodeId: string): boolean => {
|
||||
return nodeId === ROOT_ID
|
||||
}
|
||||
|
||||
// Helper to hydrate a node based on its type
|
||||
const hydrateNode = (data: FileNode): NodeRecord => {
|
||||
if (isFileCollection(data)) {
|
||||
return new FileCollectionObject().fromJson(data)
|
||||
} else {
|
||||
return new FileEntityObject().fromJson(data as FileEntity)
|
||||
}
|
||||
}
|
||||
|
||||
// Set all nodes for a provider/service
|
||||
const setNodes = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
data: FileNode[]
|
||||
) => {
|
||||
if (!nodes.value[providerId]) {
|
||||
nodes.value[providerId] = {}
|
||||
}
|
||||
const hydrated: ServiceNodeStore = {}
|
||||
for (const nodeData of data) {
|
||||
hydrated[nodeData.id] = hydrateNode(nodeData)
|
||||
}
|
||||
nodes.value[providerId][serviceId] = hydrated
|
||||
}
|
||||
|
||||
// Add/update a single node
|
||||
const addNode = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
node: FileNode
|
||||
) => {
|
||||
if (!nodes.value[providerId]) {
|
||||
nodes.value[providerId] = {}
|
||||
}
|
||||
if (!nodes.value[providerId][serviceId]) {
|
||||
nodes.value[providerId][serviceId] = {}
|
||||
}
|
||||
nodes.value[providerId][serviceId][node.id] = hydrateNode(node)
|
||||
}
|
||||
|
||||
// Add multiple nodes (handles both array and object formats from API)
|
||||
const addNodes = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
data: FileNode[] | Record<string, FileNode>
|
||||
) => {
|
||||
if (!nodes.value[providerId]) {
|
||||
nodes.value[providerId] = {}
|
||||
}
|
||||
if (!nodes.value[providerId][serviceId]) {
|
||||
nodes.value[providerId][serviceId] = {}
|
||||
}
|
||||
// Handle both array and object (keyed by ID) formats
|
||||
const nodeArray = Array.isArray(data) ? data : Object.values(data)
|
||||
for (const nodeData of nodeArray) {
|
||||
nodes.value[providerId][serviceId][nodeData.id] = hydrateNode(nodeData)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a node
|
||||
const removeNode = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
nodeId: string
|
||||
) => {
|
||||
if (nodes.value[providerId]?.[serviceId]) {
|
||||
delete nodes.value[providerId][serviceId][nodeId]
|
||||
}
|
||||
}
|
||||
|
||||
// Remove multiple nodes
|
||||
const removeNodes = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
nodeIds: string[]
|
||||
) => {
|
||||
if (nodes.value[providerId]?.[serviceId]) {
|
||||
for (const id of nodeIds) {
|
||||
delete nodes.value[providerId][serviceId][id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all nodes for a service
|
||||
const clearServiceNodes = (
|
||||
providerId: string,
|
||||
serviceId: string
|
||||
) => {
|
||||
if (nodes.value[providerId]) {
|
||||
delete nodes.value[providerId][serviceId]
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all nodes
|
||||
const clearNodes = () => {
|
||||
nodes.value = {}
|
||||
}
|
||||
|
||||
// Sync token management
|
||||
const getSyncToken = (
|
||||
providerId: string,
|
||||
serviceId: string
|
||||
): string | undefined => {
|
||||
return syncTokens.value[providerId]?.[serviceId]
|
||||
}
|
||||
|
||||
const setSyncToken = (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
token: string
|
||||
) => {
|
||||
if (!syncTokens.value[providerId]) {
|
||||
syncTokens.value[providerId] = {}
|
||||
}
|
||||
syncTokens.value[providerId][serviceId] = token
|
||||
}
|
||||
|
||||
// ==================== API Actions ====================
|
||||
|
||||
// Fetch nodes (collections and entities) for a location
|
||||
const fetchNodes = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
location?: string | null,
|
||||
recursive: boolean = false,
|
||||
filter?: FilterCondition[] | null,
|
||||
sort?: SortCondition[] | null,
|
||||
range?: RangeCondition | null
|
||||
): Promise<NodeRecord[]> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await nodeService.list(
|
||||
providerId,
|
||||
serviceId,
|
||||
location,
|
||||
recursive,
|
||||
filter,
|
||||
sort,
|
||||
range
|
||||
)
|
||||
// API returns object keyed by ID, convert to array
|
||||
const nodeArray = Array.isArray(data) ? data : Object.values(data)
|
||||
addNodes(providerId, serviceId, nodeArray)
|
||||
return nodeArray.map(hydrateNode)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to fetch nodes'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch collections (folders) for a location
|
||||
const fetchCollections = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
location?: string | null,
|
||||
filter?: FilterCondition[] | null,
|
||||
sort?: SortCondition[] | null
|
||||
): Promise<FileCollectionObject[]> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await collectionService.list(providerId, serviceId, location, filter, sort)
|
||||
// API returns object keyed by ID, convert to array
|
||||
const collectionArray = Array.isArray(data) ? data : Object.values(data)
|
||||
addNodes(providerId, serviceId, collectionArray)
|
||||
return collectionArray.map(c => new FileCollectionObject().fromJson(c))
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to fetch collections'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch entities (files) for a collection
|
||||
const fetchEntities = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string,
|
||||
filter?: FilterCondition[] | null,
|
||||
sort?: SortCondition[] | null,
|
||||
range?: RangeCondition | null
|
||||
): Promise<FileEntityObject[]> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await entityService.list(providerId, serviceId, collection, filter, sort, range)
|
||||
// API returns object keyed by ID, convert to array
|
||||
const entityArray = Array.isArray(data) ? data : Object.values(data)
|
||||
addNodes(providerId, serviceId, entityArray)
|
||||
return entityArray.map(e => new FileEntityObject().fromJson(e))
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to fetch entities'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Create a collection (folder)
|
||||
const createCollection = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
location: string | null,
|
||||
data: Partial<FileCollection>,
|
||||
options?: Record<string, unknown>
|
||||
): Promise<FileCollectionObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const created = await collectionService.create(providerId, serviceId, location, data, options)
|
||||
addNode(providerId, serviceId, created)
|
||||
return new FileCollectionObject().fromJson(created)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to create collection'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Create an entity (file)
|
||||
const createEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string | null,
|
||||
data: Partial<FileEntity>,
|
||||
options?: Record<string, unknown>
|
||||
): Promise<FileEntityObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const created = await entityService.create(providerId, serviceId, collection, data, options)
|
||||
addNode(providerId, serviceId, created)
|
||||
return new FileEntityObject().fromJson(created)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to create entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Modify a collection
|
||||
const modifyCollection = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
identifier: string,
|
||||
data: Partial<FileCollection>
|
||||
): Promise<FileCollectionObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const modified = await collectionService.modify(providerId, serviceId, identifier, data)
|
||||
addNode(providerId, serviceId, modified)
|
||||
return new FileCollectionObject().fromJson(modified)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to modify collection'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Modify an entity
|
||||
const modifyEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
data: Partial<FileEntity>
|
||||
): Promise<FileEntityObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const modified = await entityService.modify(providerId, serviceId, collection, identifier, data)
|
||||
addNode(providerId, serviceId, modified)
|
||||
return new FileEntityObject().fromJson(modified)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to modify entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy a collection
|
||||
const destroyCollection = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
identifier: string
|
||||
): Promise<boolean> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const success = await collectionService.destroy(providerId, serviceId, identifier)
|
||||
if (success) {
|
||||
removeNode(providerId, serviceId, identifier)
|
||||
}
|
||||
return success
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to destroy collection'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an entity
|
||||
const destroyEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string | null,
|
||||
identifier: string
|
||||
): Promise<boolean> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const success = await entityService.destroy(providerId, serviceId, collection, identifier)
|
||||
if (success) {
|
||||
removeNode(providerId, serviceId, identifier)
|
||||
}
|
||||
return success
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to destroy entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Copy a collection
|
||||
const copyCollection = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
identifier: string,
|
||||
location?: string | null
|
||||
): Promise<FileCollectionObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const copied = await collectionService.copy(providerId, serviceId, identifier, location)
|
||||
addNode(providerId, serviceId, copied)
|
||||
return new FileCollectionObject().fromJson(copied)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to copy collection'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Copy an entity
|
||||
const copyEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
destination?: string | null
|
||||
): Promise<FileEntityObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const copied = await entityService.copy(providerId, serviceId, collection, identifier, destination)
|
||||
addNode(providerId, serviceId, copied)
|
||||
return new FileEntityObject().fromJson(copied)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to copy entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Move a collection
|
||||
const moveCollection = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
identifier: string,
|
||||
location?: string | null
|
||||
): Promise<FileCollectionObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const moved = await collectionService.move(providerId, serviceId, identifier, location)
|
||||
addNode(providerId, serviceId, moved)
|
||||
return new FileCollectionObject().fromJson(moved)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to move collection'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Move an entity
|
||||
const moveEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
destination?: string | null
|
||||
): Promise<FileEntityObject> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const moved = await entityService.move(providerId, serviceId, collection, identifier, destination)
|
||||
addNode(providerId, serviceId, moved)
|
||||
return new FileEntityObject().fromJson(moved)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to move entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Read entity content
|
||||
const readEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string,
|
||||
identifier: string
|
||||
): Promise<string | null> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const result = await entityService.read(providerId, serviceId, collection, identifier)
|
||||
return result.content
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to read entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Write entity content
|
||||
const writeEntity = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
collection: string | null,
|
||||
identifier: string,
|
||||
content: string
|
||||
): Promise<number> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
return await entityService.write(providerId, serviceId, collection, identifier, content)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to write entity'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Sync delta changes
|
||||
const syncDelta = async (
|
||||
providerId: string,
|
||||
serviceId: string,
|
||||
location: string | null,
|
||||
signature: string,
|
||||
recursive: boolean = false,
|
||||
detail: 'ids' | 'full' = 'full'
|
||||
): Promise<void> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const delta = await nodeService.delta(providerId, serviceId, location, signature, recursive, detail)
|
||||
|
||||
// Handle removed nodes
|
||||
if (delta.removed.length > 0) {
|
||||
removeNodes(providerId, serviceId, delta.removed)
|
||||
}
|
||||
|
||||
// Handle added/modified nodes
|
||||
if (detail === 'full') {
|
||||
const addedNodes = delta.added as FileNode[]
|
||||
const modifiedNodes = delta.modified as FileNode[]
|
||||
if (addedNodes.length > 0) {
|
||||
addNodes(providerId, serviceId, addedNodes)
|
||||
}
|
||||
if (modifiedNodes.length > 0) {
|
||||
addNodes(providerId, serviceId, modifiedNodes)
|
||||
}
|
||||
}
|
||||
|
||||
// Update sync token
|
||||
if (delta.signature) {
|
||||
setSyncToken(providerId, serviceId, delta.signature)
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to sync delta'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
nodes,
|
||||
syncTokens,
|
||||
loading,
|
||||
error,
|
||||
// Constants
|
||||
ROOT_ID,
|
||||
// Computed
|
||||
nodeList,
|
||||
collectionList,
|
||||
entityList,
|
||||
// Getters
|
||||
getNode,
|
||||
getServiceNodes,
|
||||
getChildren,
|
||||
getChildCollections,
|
||||
getChildEntities,
|
||||
getPath,
|
||||
isRoot,
|
||||
// Setters
|
||||
setNodes,
|
||||
addNode,
|
||||
addNodes,
|
||||
removeNode,
|
||||
removeNodes,
|
||||
clearServiceNodes,
|
||||
clearNodes,
|
||||
// Sync
|
||||
getSyncToken,
|
||||
setSyncToken,
|
||||
// API Actions - Fetch
|
||||
fetchNodes,
|
||||
fetchCollections,
|
||||
fetchEntities,
|
||||
// API Actions - Create
|
||||
createCollection,
|
||||
createEntity,
|
||||
// API Actions - Modify
|
||||
modifyCollection,
|
||||
modifyEntity,
|
||||
// API Actions - Destroy
|
||||
destroyCollection,
|
||||
destroyEntity,
|
||||
// API Actions - Copy
|
||||
copyCollection,
|
||||
copyEntity,
|
||||
// API Actions - Move
|
||||
moveCollection,
|
||||
moveEntity,
|
||||
// API Actions - Content
|
||||
readEntity,
|
||||
writeEntity,
|
||||
// API Actions - Sync
|
||||
syncDelta,
|
||||
}
|
||||
})
|
||||
104
src/stores/providersStore.ts
Normal file
104
src/stores/providersStore.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { ProviderInterface, ProviderRecord, ProviderCapabilitiesInterface } from '../types/provider'
|
||||
import type { SourceSelector } from '../types/common'
|
||||
import { providerService } from '../services/providerService'
|
||||
import { ProviderObject } from '../models/provider'
|
||||
|
||||
export const useProvidersStore = defineStore('fileProviders', () => {
|
||||
const providers: Ref<Record<string, ProviderObject>> = ref({})
|
||||
const loading = ref(false)
|
||||
const error: Ref<string | null> = ref(null)
|
||||
const initialized = ref(false)
|
||||
|
||||
const providerList: ComputedRef<ProviderObject[]> = computed(() =>
|
||||
Object.values(providers.value)
|
||||
)
|
||||
|
||||
const providerIds: ComputedRef<string[]> = computed(() =>
|
||||
Object.keys(providers.value)
|
||||
)
|
||||
|
||||
const getProvider = (id: string): ProviderObject | undefined => {
|
||||
return providers.value[id]
|
||||
}
|
||||
|
||||
const hasProvider = (id: string): boolean => {
|
||||
return id in providers.value
|
||||
}
|
||||
|
||||
const isCapable = (providerId: string, capability: keyof ProviderCapabilitiesInterface): boolean => {
|
||||
const provider = providers.value[providerId]
|
||||
return provider ? provider.capable(capability) : false
|
||||
}
|
||||
|
||||
const setProviders = (data: ProviderRecord) => {
|
||||
const hydrated: Record<string, ProviderObject> = {}
|
||||
for (const [id, providerData] of Object.entries(data)) {
|
||||
hydrated[id] = new ProviderObject().fromJson(providerData)
|
||||
}
|
||||
providers.value = hydrated
|
||||
initialized.value = true
|
||||
}
|
||||
|
||||
const addProvider = (id: string, provider: ProviderInterface) => {
|
||||
providers.value[id] = new ProviderObject().fromJson(provider)
|
||||
}
|
||||
|
||||
const removeProvider = (id: string) => {
|
||||
delete providers.value[id]
|
||||
}
|
||||
|
||||
const clearProviders = () => {
|
||||
providers.value = {}
|
||||
initialized.value = false
|
||||
}
|
||||
|
||||
// API actions
|
||||
const fetchProviders = async (sources?: SourceSelector): Promise<void> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await providerService.list(sources)
|
||||
setProviders(data)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to fetch providers'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const checkProviderExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
|
||||
try {
|
||||
return await providerService.extant(sources)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to check providers'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
providers,
|
||||
loading,
|
||||
error,
|
||||
initialized,
|
||||
// Computed
|
||||
providerList,
|
||||
providerIds,
|
||||
// Getters
|
||||
getProvider,
|
||||
hasProvider,
|
||||
isCapable,
|
||||
// Setters
|
||||
setProviders,
|
||||
addProvider,
|
||||
removeProvider,
|
||||
clearProviders,
|
||||
// Actions
|
||||
fetchProviders,
|
||||
checkProviderExtant,
|
||||
}
|
||||
})
|
||||
131
src/stores/servicesStore.ts
Normal file
131
src/stores/servicesStore.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { ServiceInterface, ServiceRecord } from '../types/service'
|
||||
import type { SourceSelector } from '../types/common'
|
||||
import { serviceService } from '../services/serviceService'
|
||||
import { ServiceObject } from '../models/service'
|
||||
|
||||
// Nested structure: provider -> service -> ServiceObject
|
||||
type ServiceStore = Record<string, Record<string, ServiceObject>>
|
||||
|
||||
export const useServicesStore = defineStore('fileServices', () => {
|
||||
const services: Ref<ServiceStore> = ref({})
|
||||
const loading = ref(false)
|
||||
const error: Ref<string | null> = ref(null)
|
||||
const initialized = ref(false)
|
||||
|
||||
const serviceList: ComputedRef<ServiceObject[]> = computed(() => {
|
||||
const result: ServiceObject[] = []
|
||||
Object.values(services.value).forEach(providerServices => {
|
||||
result.push(...Object.values(providerServices))
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const getService = (providerId: string, serviceId: string): ServiceObject | undefined => {
|
||||
return services.value[providerId]?.[serviceId]
|
||||
}
|
||||
|
||||
const hasService = (providerId: string, serviceId: string): boolean => {
|
||||
return !!services.value[providerId]?.[serviceId]
|
||||
}
|
||||
|
||||
const getProviderServices = (providerId: string): ServiceObject[] => {
|
||||
return Object.values(services.value[providerId] || {})
|
||||
}
|
||||
|
||||
const getRootId = (providerId: string, serviceId: string): string | undefined => {
|
||||
return services.value[providerId]?.[serviceId]?.rootId
|
||||
}
|
||||
|
||||
const setServices = (data: ServiceRecord) => {
|
||||
const hydrated: ServiceStore = {}
|
||||
for (const [id, serviceData] of Object.entries(data)) {
|
||||
const providerId = serviceData.provider
|
||||
if (!hydrated[providerId]) {
|
||||
hydrated[providerId] = {}
|
||||
}
|
||||
hydrated[providerId][id] = new ServiceObject().fromJson(serviceData)
|
||||
}
|
||||
services.value = hydrated
|
||||
initialized.value = true
|
||||
}
|
||||
|
||||
const addService = (providerId: string, serviceId: string, service: ServiceInterface) => {
|
||||
if (!services.value[providerId]) {
|
||||
services.value[providerId] = {}
|
||||
}
|
||||
services.value[providerId][serviceId] = new ServiceObject().fromJson(service)
|
||||
}
|
||||
|
||||
const removeService = (providerId: string, serviceId: string) => {
|
||||
if (services.value[providerId]) {
|
||||
delete services.value[providerId][serviceId]
|
||||
}
|
||||
}
|
||||
|
||||
const clearServices = () => {
|
||||
services.value = {}
|
||||
initialized.value = false
|
||||
}
|
||||
|
||||
// API actions
|
||||
const fetchServices = async (sources?: SourceSelector): Promise<void> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await serviceService.list(sources)
|
||||
setServices(data)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to fetch services'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const checkServiceExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
|
||||
try {
|
||||
return await serviceService.extant(sources)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to check services'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const fetchService = async (providerId: string, serviceId: string): Promise<ServiceObject> => {
|
||||
try {
|
||||
const data = await serviceService.fetch(providerId, serviceId)
|
||||
addService(providerId, serviceId, data)
|
||||
return services.value[providerId][serviceId]
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to fetch service'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
services,
|
||||
loading,
|
||||
error,
|
||||
initialized,
|
||||
// Computed
|
||||
serviceList,
|
||||
// Getters
|
||||
getService,
|
||||
hasService,
|
||||
getProviderServices,
|
||||
getRootId,
|
||||
// Setters
|
||||
setServices,
|
||||
addService,
|
||||
removeService,
|
||||
clearServices,
|
||||
// Actions
|
||||
fetchServices,
|
||||
checkServiceExtant,
|
||||
fetchService,
|
||||
}
|
||||
})
|
||||
262
src/types/api.ts
Normal file
262
src/types/api.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* File Manager API Types - Request and Response interfaces
|
||||
*/
|
||||
|
||||
import type { SourceSelector, FilterCondition, SortCondition, RangeCondition, ApiResponse } from '@/types/common';
|
||||
import type { ProviderRecord } from '@/types/provider';
|
||||
import type { ServiceInterface, ServiceRecord } from '@/types/service';
|
||||
import type { FileCollection, FileEntity, FileNode } from '@/types/node';
|
||||
|
||||
// ==================== Provider Types ====================
|
||||
|
||||
export type ProviderListResponse = ApiResponse<ProviderRecord>;
|
||||
|
||||
export interface ProviderExtantRequest {
|
||||
sources: SourceSelector;
|
||||
}
|
||||
|
||||
export type ProviderExtantResponse = ApiResponse<Record<string, boolean>>;
|
||||
|
||||
// ==================== Service Types ====================
|
||||
|
||||
export type ServiceListResponse = ApiResponse<ServiceRecord>;
|
||||
|
||||
export interface ServiceExtantRequest {
|
||||
sources: SourceSelector;
|
||||
}
|
||||
|
||||
export type ServiceExtantResponse = ApiResponse<Record<string, boolean>>;
|
||||
|
||||
export interface ServiceFetchRequest {
|
||||
provider: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export type ServiceFetchResponse = ApiResponse<ServiceInterface>;
|
||||
|
||||
// ==================== Collection Types ====================
|
||||
|
||||
export interface CollectionListRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
location?: string | null;
|
||||
filter?: FilterCondition[] | null;
|
||||
sort?: SortCondition[] | null;
|
||||
}
|
||||
|
||||
export type CollectionListResponse = ApiResponse<FileCollection[]>;
|
||||
|
||||
export interface CollectionExtantRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export type CollectionExtantResponse = ApiResponse<{ extant: boolean }>;
|
||||
|
||||
export interface CollectionFetchRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export type CollectionFetchResponse = ApiResponse<FileCollection>;
|
||||
|
||||
export interface CollectionCreateRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
location?: string | null;
|
||||
data: Partial<FileCollection>;
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type CollectionCreateResponse = ApiResponse<FileCollection>;
|
||||
|
||||
export interface CollectionModifyRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
data: Partial<FileCollection>;
|
||||
}
|
||||
|
||||
export type CollectionModifyResponse = ApiResponse<FileCollection>;
|
||||
|
||||
export interface CollectionDestroyRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export type CollectionDestroyResponse = ApiResponse<{ success: boolean }>;
|
||||
|
||||
export interface CollectionCopyRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
location?: string | null;
|
||||
}
|
||||
|
||||
export type CollectionCopyResponse = ApiResponse<FileCollection>;
|
||||
|
||||
export interface CollectionMoveRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
identifier: string;
|
||||
location?: string | null;
|
||||
}
|
||||
|
||||
export type CollectionMoveResponse = ApiResponse<FileCollection>;
|
||||
|
||||
// ==================== Entity Types ====================
|
||||
|
||||
export interface EntityListRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection: string;
|
||||
filter?: FilterCondition[] | null;
|
||||
sort?: SortCondition[] | null;
|
||||
range?: RangeCondition | null;
|
||||
}
|
||||
|
||||
export type EntityListResponse = ApiResponse<FileEntity[]>;
|
||||
|
||||
export interface EntityDeltaRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection: string;
|
||||
signature: string;
|
||||
detail?: 'ids' | 'full';
|
||||
}
|
||||
|
||||
export interface EntityDeltaResult {
|
||||
added: string[] | FileEntity[];
|
||||
modified: string[] | FileEntity[];
|
||||
removed: string[];
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export type EntityDeltaResponse = ApiResponse<EntityDeltaResult>;
|
||||
|
||||
export interface EntityExtantRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection: string;
|
||||
identifiers: string[];
|
||||
}
|
||||
|
||||
export type EntityExtantResponse = ApiResponse<Record<string, boolean>>;
|
||||
|
||||
export interface EntityFetchRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection: string;
|
||||
identifiers: string[];
|
||||
}
|
||||
|
||||
export type EntityFetchResponse = ApiResponse<FileEntity[]>;
|
||||
|
||||
export interface EntityReadRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export interface EntityReadResult {
|
||||
content: string | null;
|
||||
encoding: 'base64';
|
||||
}
|
||||
|
||||
export type EntityReadResponse = ApiResponse<EntityReadResult>;
|
||||
|
||||
export interface EntityCreateRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection?: string | null;
|
||||
data: Partial<FileEntity>;
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type EntityCreateResponse = ApiResponse<FileEntity>;
|
||||
|
||||
export interface EntityModifyRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection?: string | null;
|
||||
identifier: string;
|
||||
data: Partial<FileEntity>;
|
||||
}
|
||||
|
||||
export type EntityModifyResponse = ApiResponse<FileEntity>;
|
||||
|
||||
export interface EntityDestroyRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection?: string | null;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export type EntityDestroyResponse = ApiResponse<{ success: boolean }>;
|
||||
|
||||
export interface EntityCopyRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection?: string | null;
|
||||
identifier: string;
|
||||
destination?: string | null;
|
||||
}
|
||||
|
||||
export type EntityCopyResponse = ApiResponse<FileEntity>;
|
||||
|
||||
export interface EntityMoveRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection?: string | null;
|
||||
identifier: string;
|
||||
destination?: string | null;
|
||||
}
|
||||
|
||||
export type EntityMoveResponse = ApiResponse<FileEntity>;
|
||||
|
||||
export interface EntityWriteRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
collection?: string | null;
|
||||
identifier: string;
|
||||
content: string;
|
||||
encoding?: 'base64';
|
||||
}
|
||||
|
||||
export type EntityWriteResponse = ApiResponse<{ bytesWritten: number }>;
|
||||
|
||||
// ==================== Node Types (Unified/Recursive) ====================
|
||||
|
||||
export interface NodeListRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
location?: string | null;
|
||||
recursive?: boolean;
|
||||
filter?: FilterCondition[] | null;
|
||||
sort?: SortCondition[] | null;
|
||||
range?: RangeCondition | null;
|
||||
}
|
||||
|
||||
export type NodeListResponse = ApiResponse<FileNode[]>;
|
||||
|
||||
export interface NodeDeltaRequest {
|
||||
provider: string;
|
||||
service: string;
|
||||
location?: string | null;
|
||||
signature: string;
|
||||
recursive?: boolean;
|
||||
detail?: 'ids' | 'full';
|
||||
}
|
||||
|
||||
export interface NodeDeltaResult {
|
||||
added: string[] | FileNode[];
|
||||
modified: string[] | FileNode[];
|
||||
removed: string[];
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export type NodeDeltaResponse = ApiResponse<NodeDeltaResult>;
|
||||
85
src/types/common.ts
Normal file
85
src/types/common.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Common types for file manager
|
||||
*/
|
||||
|
||||
export type SourceSelector = {
|
||||
[provider: string]: boolean | ServiceSelector;
|
||||
};
|
||||
|
||||
export type ServiceSelector = {
|
||||
[service: string]: boolean;
|
||||
};
|
||||
|
||||
export const SortDirection = {
|
||||
Ascending: 'asc',
|
||||
Descending: 'desc'
|
||||
} as const;
|
||||
|
||||
export type SortDirection = typeof SortDirection[keyof typeof SortDirection];
|
||||
|
||||
export const RangeType = {
|
||||
Tally: 'tally',
|
||||
Date: 'date'
|
||||
} as const;
|
||||
|
||||
export type RangeType = typeof RangeType[keyof typeof RangeType];
|
||||
|
||||
export const RangeAnchorType = {
|
||||
Absolute: 'absolute',
|
||||
Relative: 'relative'
|
||||
} as const;
|
||||
|
||||
export type RangeAnchorType = typeof RangeAnchorType[keyof typeof RangeAnchorType];
|
||||
|
||||
export const FilterOperator = {
|
||||
Equals: 'eq',
|
||||
NotEquals: 'ne',
|
||||
GreaterThan: 'gt',
|
||||
LessThan: 'lt',
|
||||
GreaterThanOrEquals: 'gte',
|
||||
LessThanOrEquals: 'lte',
|
||||
Contains: 'contains',
|
||||
StartsWith: 'startsWith',
|
||||
EndsWith: 'endsWith',
|
||||
In: 'in',
|
||||
NotIn: 'notIn'
|
||||
} as const;
|
||||
|
||||
export type FilterOperator = typeof FilterOperator[keyof typeof FilterOperator];
|
||||
|
||||
export interface FilterCondition {
|
||||
attribute: string;
|
||||
value: unknown;
|
||||
operator?: FilterOperator;
|
||||
}
|
||||
|
||||
export interface SortCondition {
|
||||
attribute: string;
|
||||
direction: SortDirection;
|
||||
}
|
||||
|
||||
export interface RangeCondition {
|
||||
type: RangeType;
|
||||
anchor?: RangeAnchorType;
|
||||
position?: string | number;
|
||||
tally?: number;
|
||||
}
|
||||
|
||||
export interface ApiRequest {
|
||||
version: number;
|
||||
transaction: string;
|
||||
operation: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = unknown> {
|
||||
version: number;
|
||||
transaction: string;
|
||||
operation: string;
|
||||
status: 'success' | 'error';
|
||||
data?: T;
|
||||
error?: {
|
||||
code: number;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
9
src/types/index.ts
Normal file
9
src/types/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* File manager types barrel export
|
||||
*/
|
||||
|
||||
export * from './common';
|
||||
export * from './provider';
|
||||
export * from './service';
|
||||
export * from './node';
|
||||
export * from './api';
|
||||
65
src/types/node.ts
Normal file
65
src/types/node.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Node types for file manager (collections and entities)
|
||||
*/
|
||||
|
||||
export type NodeType = 'files.collection' | 'files.entity';
|
||||
|
||||
export interface NodeBase {
|
||||
'@type': NodeType;
|
||||
in: string | null;
|
||||
id: string;
|
||||
createdBy: string;
|
||||
createdOn: string;
|
||||
modifiedBy: string;
|
||||
modifiedOn: string;
|
||||
owner: string;
|
||||
signature: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface FileCollection extends NodeBase {
|
||||
'@type': 'files.collection';
|
||||
}
|
||||
|
||||
export interface FileEntity extends NodeBase {
|
||||
'@type': 'files.entity';
|
||||
size: number;
|
||||
mime: string;
|
||||
format: string;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
export type FileNode = FileCollection | FileEntity;
|
||||
|
||||
export interface NodeListResult {
|
||||
items: FileNode[];
|
||||
total: number;
|
||||
hasMore?: boolean;
|
||||
}
|
||||
|
||||
export interface EntityListResult {
|
||||
items: FileEntity[];
|
||||
total: number;
|
||||
hasMore?: boolean;
|
||||
}
|
||||
|
||||
export interface CollectionListResult {
|
||||
items: FileCollection[];
|
||||
total: number;
|
||||
hasMore?: boolean;
|
||||
}
|
||||
|
||||
export interface DeltaResult {
|
||||
added: FileNode[];
|
||||
modified: FileNode[];
|
||||
removed: string[];
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export function isFileCollection(node: FileNode): node is FileCollection {
|
||||
return node['@type'] === 'files.collection';
|
||||
}
|
||||
|
||||
export function isFileEntity(node: FileNode): node is FileEntity {
|
||||
return node['@type'] === 'files.entity';
|
||||
}
|
||||
49
src/types/provider.ts
Normal file
49
src/types/provider.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Provider types for file manager
|
||||
*/
|
||||
|
||||
export interface ProviderCapabilitiesInterface {
|
||||
CollectionList?: boolean;
|
||||
CollectionListFilter?: boolean | Record<string, string>;
|
||||
CollectionListSort?: boolean | string[];
|
||||
CollectionExtant?: boolean;
|
||||
CollectionFetch?: boolean;
|
||||
CollectionCreate?: boolean;
|
||||
CollectionModify?: boolean;
|
||||
CollectionDestroy?: boolean;
|
||||
CollectionCopy?: boolean;
|
||||
CollectionMove?: boolean;
|
||||
EntityList?: boolean;
|
||||
EntityListFilter?: boolean | Record<string, string>;
|
||||
EntityListSort?: boolean | string[];
|
||||
EntityListRange?: boolean | Record<string, string[]>;
|
||||
EntityDelta?: boolean;
|
||||
EntityExtant?: boolean;
|
||||
EntityFetch?: boolean;
|
||||
EntityRead?: boolean;
|
||||
EntityReadStream?: boolean;
|
||||
EntityReadChunk?: boolean;
|
||||
EntityCreate?: boolean;
|
||||
EntityModify?: boolean;
|
||||
EntityDestroy?: boolean;
|
||||
EntityCopy?: boolean;
|
||||
EntityMove?: boolean;
|
||||
EntityWrite?: boolean;
|
||||
EntityWriteStream?: boolean;
|
||||
EntityWriteChunk?: boolean;
|
||||
NodeList?: boolean;
|
||||
NodeListFilter?: boolean | Record<string, string>;
|
||||
NodeListSort?: boolean | string[];
|
||||
NodeListRange?: boolean | Record<string, string[]>;
|
||||
NodeDelta?: boolean;
|
||||
[key: string]: boolean | string[] | Record<string, string> | Record<string, string[]> | undefined;
|
||||
}
|
||||
|
||||
export interface ProviderInterface {
|
||||
'@type': string;
|
||||
id: string;
|
||||
label: string;
|
||||
capabilities: ProviderCapabilitiesInterface;
|
||||
}
|
||||
|
||||
export type ProviderRecord = Record<string, ProviderInterface>;
|
||||
13
src/types/service.ts
Normal file
13
src/types/service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Service types for file manager
|
||||
*/
|
||||
|
||||
export interface ServiceInterface {
|
||||
'@type': string;
|
||||
id: string;
|
||||
provider: string;
|
||||
label: string;
|
||||
rootId: string;
|
||||
}
|
||||
|
||||
export type ServiceRecord = Record<string, ServiceInterface>;
|
||||
Reference in New Issue
Block a user