hazo_files 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -9
- package/dist/index.d.mts +288 -8
- package/dist/index.d.ts +288 -8
- package/dist/index.js +842 -16
- package/dist/index.mjs +835 -16
- package/dist/server/index.d.mts +298 -11
- package/dist/server/index.d.ts +298 -11
- package/dist/server/index.js +849 -19
- package/dist/server/index.mjs +842 -19
- package/package.json +5 -6
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ __export(index_exports, {
|
|
|
37
37
|
DirectoryExistsError: () => DirectoryExistsError,
|
|
38
38
|
DirectoryNotEmptyError: () => DirectoryNotEmptyError,
|
|
39
39
|
DirectoryNotFoundError: () => DirectoryNotFoundError,
|
|
40
|
+
DropboxAuth: () => DropboxAuth,
|
|
41
|
+
DropboxModule: () => DropboxModule,
|
|
40
42
|
FileExistsError: () => FileExistsError,
|
|
41
43
|
FileManager: () => FileManager,
|
|
42
44
|
FileMetadataService: () => FileMetadataService,
|
|
@@ -46,6 +48,7 @@ __export(index_exports, {
|
|
|
46
48
|
GoogleDriveModule: () => GoogleDriveModule,
|
|
47
49
|
HAZO_FILES_DEFAULT_TABLE_NAME: () => HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
48
50
|
HAZO_FILES_MIGRATION_V2: () => HAZO_FILES_MIGRATION_V2,
|
|
51
|
+
HAZO_FILES_MIGRATION_V3: () => HAZO_FILES_MIGRATION_V3,
|
|
49
52
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME: () => HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
50
53
|
HAZO_FILES_NAMING_TABLE_SCHEMA: () => HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
51
54
|
HAZO_FILES_TABLE_SCHEMA: () => HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -72,6 +75,8 @@ __export(index_exports, {
|
|
|
72
75
|
computeFileHashSync: () => computeFileHashSync,
|
|
73
76
|
computeFileInfo: () => computeFileInfo,
|
|
74
77
|
createAndInitializeModule: () => createAndInitializeModule,
|
|
78
|
+
createDropboxAuth: () => createDropboxAuth,
|
|
79
|
+
createDropboxModule: () => createDropboxModule,
|
|
75
80
|
createEmptyFileDataStructure: () => createEmptyFileDataStructure,
|
|
76
81
|
createEmptyNamingRuleSchema: () => createEmptyNamingRuleSchema,
|
|
77
82
|
createFileItem: () => createFileItem,
|
|
@@ -115,6 +120,7 @@ __export(index_exports, {
|
|
|
115
120
|
getFileMetadataValues: () => getFileMetadataValues,
|
|
116
121
|
getMergedData: () => getMergedData,
|
|
117
122
|
getMigrationForTable: () => getMigrationForTable,
|
|
123
|
+
getMigrationV3ForTable: () => getMigrationV3ForTable,
|
|
118
124
|
getMimeType: () => getMimeType,
|
|
119
125
|
getNameWithoutExtension: () => getNameWithoutExtension,
|
|
120
126
|
getNamingSchemaForTable: () => getNamingSchemaForTable,
|
|
@@ -147,6 +153,7 @@ __export(index_exports, {
|
|
|
147
153
|
loadConfig: () => loadConfig,
|
|
148
154
|
loadConfigAsync: () => loadConfigAsync,
|
|
149
155
|
migrateToV2: () => migrateToV2,
|
|
156
|
+
migrateToV3: () => migrateToV3,
|
|
150
157
|
normalizePath: () => normalizePath,
|
|
151
158
|
parseConfig: () => parseConfig,
|
|
152
159
|
parseFileData: () => parseFileData,
|
|
@@ -202,6 +209,16 @@ function parseConfig(configContent) {
|
|
|
202
209
|
rootFolderId: parsed.google_drive.root_folder_id || process.env.HAZO_GOOGLE_DRIVE_ROOT_FOLDER_ID
|
|
203
210
|
};
|
|
204
211
|
}
|
|
212
|
+
if (parsed.dropbox) {
|
|
213
|
+
config.dropbox = {
|
|
214
|
+
clientId: parsed.dropbox.client_id || process.env.HAZO_DROPBOX_CLIENT_ID || "",
|
|
215
|
+
clientSecret: parsed.dropbox.client_secret || process.env.HAZO_DROPBOX_CLIENT_SECRET || "",
|
|
216
|
+
redirectUri: parsed.dropbox.redirect_uri || process.env.HAZO_DROPBOX_REDIRECT_URI || "",
|
|
217
|
+
refreshToken: parsed.dropbox.refresh_token || process.env.HAZO_DROPBOX_REFRESH_TOKEN,
|
|
218
|
+
accessToken: parsed.dropbox.access_token || process.env.HAZO_DROPBOX_ACCESS_TOKEN,
|
|
219
|
+
rootPath: parsed.dropbox.root_path || process.env.HAZO_DROPBOX_ROOT_PATH
|
|
220
|
+
};
|
|
221
|
+
}
|
|
205
222
|
return config;
|
|
206
223
|
}
|
|
207
224
|
function loadConfig(configPath) {
|
|
@@ -263,6 +280,18 @@ refresh_token =
|
|
|
263
280
|
access_token =
|
|
264
281
|
; Optional: Root folder ID to use as base (empty = root of Drive)
|
|
265
282
|
root_folder_id =
|
|
283
|
+
|
|
284
|
+
[dropbox]
|
|
285
|
+
; Dropbox OAuth credentials
|
|
286
|
+
; These can also be set via environment variables:
|
|
287
|
+
; HAZO_DROPBOX_CLIENT_ID, HAZO_DROPBOX_CLIENT_SECRET, etc.
|
|
288
|
+
client_id =
|
|
289
|
+
client_secret =
|
|
290
|
+
redirect_uri = http://localhost:3000/api/auth/dropbox/callback
|
|
291
|
+
refresh_token =
|
|
292
|
+
access_token =
|
|
293
|
+
; Optional: Root path to use as base (empty = root of Dropbox)
|
|
294
|
+
root_path =
|
|
266
295
|
`;
|
|
267
296
|
}
|
|
268
297
|
async function saveConfig(config, configPath) {
|
|
@@ -289,6 +318,16 @@ async function saveConfig(config, configPath) {
|
|
|
289
318
|
root_folder_id: config.google_drive.rootFolderId || ""
|
|
290
319
|
};
|
|
291
320
|
}
|
|
321
|
+
if (config.dropbox) {
|
|
322
|
+
iniConfig.dropbox = {
|
|
323
|
+
client_id: config.dropbox.clientId || "",
|
|
324
|
+
client_secret: config.dropbox.clientSecret || "",
|
|
325
|
+
redirect_uri: config.dropbox.redirectUri || "",
|
|
326
|
+
refresh_token: config.dropbox.refreshToken || "",
|
|
327
|
+
access_token: config.dropbox.accessToken || "",
|
|
328
|
+
root_path: config.dropbox.rootPath || ""
|
|
329
|
+
};
|
|
330
|
+
}
|
|
292
331
|
const content = ini.stringify(iniConfig);
|
|
293
332
|
await fs.promises.writeFile(resolvedPath, content, "utf-8");
|
|
294
333
|
}
|
|
@@ -1826,10 +1865,641 @@ function createGoogleDriveModule() {
|
|
|
1826
1865
|
return new GoogleDriveModule();
|
|
1827
1866
|
}
|
|
1828
1867
|
|
|
1868
|
+
// src/modules/dropbox/index.ts
|
|
1869
|
+
var import_dropbox = require("dropbox");
|
|
1870
|
+
|
|
1871
|
+
// src/modules/dropbox/auth.ts
|
|
1872
|
+
var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
|
|
1873
|
+
var DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
|
|
1874
|
+
var DROPBOX_REVOKE_URL = "https://api.dropboxapi.com/2/auth/token/revoke";
|
|
1875
|
+
var DropboxAuth = class {
|
|
1876
|
+
constructor(config, callbacks = {}) {
|
|
1877
|
+
this.tokens = null;
|
|
1878
|
+
this.config = config;
|
|
1879
|
+
this.callbacks = callbacks;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Generate the authorization URL for OAuth consent
|
|
1883
|
+
*/
|
|
1884
|
+
getAuthUrl(state) {
|
|
1885
|
+
const params = new URLSearchParams({
|
|
1886
|
+
client_id: this.config.clientId,
|
|
1887
|
+
redirect_uri: this.config.redirectUri,
|
|
1888
|
+
response_type: "code",
|
|
1889
|
+
token_access_type: "offline"
|
|
1890
|
+
});
|
|
1891
|
+
if (state) {
|
|
1892
|
+
params.set("state", state);
|
|
1893
|
+
}
|
|
1894
|
+
return `${DROPBOX_AUTH_URL}?${params.toString()}`;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Exchange authorization code for tokens
|
|
1898
|
+
*/
|
|
1899
|
+
async exchangeCodeForTokens(code) {
|
|
1900
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
1901
|
+
method: "POST",
|
|
1902
|
+
headers: {
|
|
1903
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1904
|
+
},
|
|
1905
|
+
body: new URLSearchParams({
|
|
1906
|
+
code,
|
|
1907
|
+
grant_type: "authorization_code",
|
|
1908
|
+
client_id: this.config.clientId,
|
|
1909
|
+
client_secret: this.config.clientSecret,
|
|
1910
|
+
redirect_uri: this.config.redirectUri
|
|
1911
|
+
})
|
|
1912
|
+
});
|
|
1913
|
+
if (!response.ok) {
|
|
1914
|
+
const errorData = await response.text();
|
|
1915
|
+
throw new Error(`Failed to exchange code for tokens: ${errorData}`);
|
|
1916
|
+
}
|
|
1917
|
+
const data = await response.json();
|
|
1918
|
+
this.tokens = {
|
|
1919
|
+
accessToken: data.access_token,
|
|
1920
|
+
refreshToken: data.refresh_token,
|
|
1921
|
+
expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
1922
|
+
};
|
|
1923
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1924
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1925
|
+
}
|
|
1926
|
+
return this.tokens;
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Set tokens directly (e.g., from stored tokens)
|
|
1930
|
+
*/
|
|
1931
|
+
async setTokens(tokens) {
|
|
1932
|
+
this.tokens = tokens;
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Load tokens from storage using callback
|
|
1936
|
+
*/
|
|
1937
|
+
async loadStoredTokens() {
|
|
1938
|
+
if (!this.callbacks.getStoredTokens) {
|
|
1939
|
+
return false;
|
|
1940
|
+
}
|
|
1941
|
+
const tokens = await this.callbacks.getStoredTokens();
|
|
1942
|
+
if (tokens) {
|
|
1943
|
+
await this.setTokens(tokens);
|
|
1944
|
+
return true;
|
|
1945
|
+
}
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Check if authenticated
|
|
1950
|
+
*/
|
|
1951
|
+
isAuthenticated() {
|
|
1952
|
+
return this.tokens !== null && !!this.tokens.accessToken;
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Get current tokens
|
|
1956
|
+
*/
|
|
1957
|
+
getTokens() {
|
|
1958
|
+
return this.tokens;
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Get current access token
|
|
1962
|
+
*/
|
|
1963
|
+
getAccessToken() {
|
|
1964
|
+
return this.tokens?.accessToken ?? null;
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Refresh the access token
|
|
1968
|
+
*/
|
|
1969
|
+
async refreshAccessToken() {
|
|
1970
|
+
if (!this.tokens?.refreshToken) {
|
|
1971
|
+
throw new Error("No refresh token available");
|
|
1972
|
+
}
|
|
1973
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
1974
|
+
method: "POST",
|
|
1975
|
+
headers: {
|
|
1976
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1977
|
+
},
|
|
1978
|
+
body: new URLSearchParams({
|
|
1979
|
+
grant_type: "refresh_token",
|
|
1980
|
+
refresh_token: this.tokens.refreshToken,
|
|
1981
|
+
client_id: this.config.clientId,
|
|
1982
|
+
client_secret: this.config.clientSecret
|
|
1983
|
+
})
|
|
1984
|
+
});
|
|
1985
|
+
if (!response.ok) {
|
|
1986
|
+
const errorData = await response.text();
|
|
1987
|
+
throw new Error(`Failed to refresh token: ${errorData}`);
|
|
1988
|
+
}
|
|
1989
|
+
const data = await response.json();
|
|
1990
|
+
this.tokens = {
|
|
1991
|
+
...this.tokens,
|
|
1992
|
+
accessToken: data.access_token,
|
|
1993
|
+
expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
1994
|
+
};
|
|
1995
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1996
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1997
|
+
}
|
|
1998
|
+
return this.tokens;
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Revoke access (disconnect)
|
|
2002
|
+
*/
|
|
2003
|
+
async revokeAccess() {
|
|
2004
|
+
if (this.tokens?.accessToken) {
|
|
2005
|
+
try {
|
|
2006
|
+
await fetch(DROPBOX_REVOKE_URL, {
|
|
2007
|
+
method: "POST",
|
|
2008
|
+
headers: {
|
|
2009
|
+
Authorization: `Bearer ${this.tokens.accessToken}`
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
2012
|
+
} catch {
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
this.tokens = null;
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Check if token is expired or will expire soon
|
|
2019
|
+
*/
|
|
2020
|
+
isTokenExpired(bufferSeconds = 300) {
|
|
2021
|
+
if (!this.tokens?.expiryDate) {
|
|
2022
|
+
return false;
|
|
2023
|
+
}
|
|
2024
|
+
const now = Date.now();
|
|
2025
|
+
const expiry = this.tokens.expiryDate;
|
|
2026
|
+
return now >= expiry - bufferSeconds * 1e3;
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Ensure valid access token (refresh if needed)
|
|
2030
|
+
*/
|
|
2031
|
+
async ensureValidToken() {
|
|
2032
|
+
if (this.isTokenExpired()) {
|
|
2033
|
+
await this.refreshAccessToken();
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
function createDropboxAuth(config, callbacks) {
|
|
2038
|
+
return new DropboxAuth(config, callbacks);
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/modules/dropbox/index.ts
|
|
2042
|
+
var MAX_UPLOAD_SIZE = 150 * 1024 * 1024;
|
|
2043
|
+
var DropboxModule = class extends BaseStorageModule {
|
|
2044
|
+
constructor() {
|
|
2045
|
+
super(...arguments);
|
|
2046
|
+
this.provider = "dropbox";
|
|
2047
|
+
this.auth = null;
|
|
2048
|
+
this.dbx = null;
|
|
2049
|
+
this.rootPath = "";
|
|
2050
|
+
this.authCallbacks = {};
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Set authentication callbacks for token persistence
|
|
2054
|
+
*/
|
|
2055
|
+
setAuthCallbacks(callbacks) {
|
|
2056
|
+
this.authCallbacks = callbacks;
|
|
2057
|
+
}
|
|
2058
|
+
async initialize(config) {
|
|
2059
|
+
await super.initialize(config);
|
|
2060
|
+
const dropboxConfig = this.getProviderConfig();
|
|
2061
|
+
if (!dropboxConfig.clientId || !dropboxConfig.clientSecret) {
|
|
2062
|
+
throw new AuthenticationError("dropbox", "Missing client ID or client secret");
|
|
2063
|
+
}
|
|
2064
|
+
this.auth = createDropboxAuth(
|
|
2065
|
+
{
|
|
2066
|
+
clientId: dropboxConfig.clientId,
|
|
2067
|
+
clientSecret: dropboxConfig.clientSecret,
|
|
2068
|
+
redirectUri: dropboxConfig.redirectUri
|
|
2069
|
+
},
|
|
2070
|
+
this.authCallbacks
|
|
2071
|
+
);
|
|
2072
|
+
if (dropboxConfig.refreshToken || dropboxConfig.accessToken) {
|
|
2073
|
+
await this.auth.setTokens({
|
|
2074
|
+
accessToken: dropboxConfig.accessToken || "",
|
|
2075
|
+
refreshToken: dropboxConfig.refreshToken || ""
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
this.rootPath = dropboxConfig.rootPath || "";
|
|
2079
|
+
this.createDropboxClient();
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Create/recreate the Dropbox SDK client with the current access token
|
|
2083
|
+
*/
|
|
2084
|
+
createDropboxClient() {
|
|
2085
|
+
const accessToken = this.auth?.getAccessToken();
|
|
2086
|
+
if (accessToken) {
|
|
2087
|
+
this.dbx = new import_dropbox.Dropbox({ accessToken });
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Get the auth instance for OAuth flow
|
|
2092
|
+
*/
|
|
2093
|
+
getAuth() {
|
|
2094
|
+
if (!this.auth) {
|
|
2095
|
+
throw new AuthenticationError("dropbox", "Module not initialized");
|
|
2096
|
+
}
|
|
2097
|
+
return this.auth;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Check if user is authenticated
|
|
2101
|
+
*/
|
|
2102
|
+
isAuthenticated() {
|
|
2103
|
+
return this.auth?.isAuthenticated() ?? false;
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Authenticate with provided tokens
|
|
2107
|
+
*/
|
|
2108
|
+
async authenticate(tokens) {
|
|
2109
|
+
if (!this.auth) {
|
|
2110
|
+
throw new AuthenticationError("dropbox", "Module not initialized");
|
|
2111
|
+
}
|
|
2112
|
+
await this.auth.setTokens(tokens);
|
|
2113
|
+
this.createDropboxClient();
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Ensure authenticated before operations
|
|
2117
|
+
*/
|
|
2118
|
+
async ensureAuthenticated() {
|
|
2119
|
+
this.ensureInitialized();
|
|
2120
|
+
if (!this.isAuthenticated()) {
|
|
2121
|
+
throw new AuthenticationError("dropbox", "Not authenticated. Please connect your Dropbox.");
|
|
2122
|
+
}
|
|
2123
|
+
await this.auth.ensureValidToken();
|
|
2124
|
+
this.createDropboxClient();
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Convert virtual path to Dropbox path
|
|
2128
|
+
* Virtual: /folder/file.txt -> Dropbox: /folder/file.txt (or /rootPath/folder/file.txt)
|
|
2129
|
+
* Dropbox root is empty string "", not "/"
|
|
2130
|
+
*/
|
|
2131
|
+
toDropboxPath(virtualPath) {
|
|
2132
|
+
const normalized = this.normalizePath(virtualPath);
|
|
2133
|
+
if (this.rootPath) {
|
|
2134
|
+
if (normalized === "/") {
|
|
2135
|
+
return `/${this.rootPath}`;
|
|
2136
|
+
}
|
|
2137
|
+
return `/${this.rootPath}${normalized}`;
|
|
2138
|
+
}
|
|
2139
|
+
if (normalized === "/") {
|
|
2140
|
+
return "";
|
|
2141
|
+
}
|
|
2142
|
+
return normalized;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Convert Dropbox metadata to FileSystemItem
|
|
2146
|
+
*/
|
|
2147
|
+
metadataToItem(entry, virtualPath) {
|
|
2148
|
+
const isFolder2 = entry[".tag"] === "folder";
|
|
2149
|
+
const path3 = virtualPath || this.toVirtualPath(entry.path_display || entry.name);
|
|
2150
|
+
if (isFolder2) {
|
|
2151
|
+
return createFolderItem({
|
|
2152
|
+
id: entry.id,
|
|
2153
|
+
name: entry.name,
|
|
2154
|
+
path: path3,
|
|
2155
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2156
|
+
modifiedAt: /* @__PURE__ */ new Date(),
|
|
2157
|
+
metadata: {
|
|
2158
|
+
dropboxId: entry.id,
|
|
2159
|
+
pathDisplay: entry.path_display
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
const fileEntry = entry;
|
|
2164
|
+
return createFileItem({
|
|
2165
|
+
id: fileEntry.id,
|
|
2166
|
+
name: fileEntry.name,
|
|
2167
|
+
path: path3,
|
|
2168
|
+
size: fileEntry.size,
|
|
2169
|
+
mimeType: getMimeType(fileEntry.name),
|
|
2170
|
+
createdAt: new Date(fileEntry.client_modified),
|
|
2171
|
+
modifiedAt: new Date(fileEntry.server_modified),
|
|
2172
|
+
metadata: {
|
|
2173
|
+
dropboxId: fileEntry.id,
|
|
2174
|
+
pathDisplay: fileEntry.path_display
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Convert a Dropbox path_display to virtual path
|
|
2180
|
+
*/
|
|
2181
|
+
toVirtualPath(dropboxPath) {
|
|
2182
|
+
if (this.rootPath && dropboxPath.toLowerCase().startsWith(`/${this.rootPath.toLowerCase()}`)) {
|
|
2183
|
+
const stripped = dropboxPath.substring(this.rootPath.length + 1);
|
|
2184
|
+
return stripped || "/";
|
|
2185
|
+
}
|
|
2186
|
+
return dropboxPath || "/";
|
|
2187
|
+
}
|
|
2188
|
+
async createDirectory(virtualPath) {
|
|
2189
|
+
try {
|
|
2190
|
+
await this.ensureAuthenticated();
|
|
2191
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2192
|
+
const response = await this.dbx.filesCreateFolderV2({
|
|
2193
|
+
path: dbxPath,
|
|
2194
|
+
autorename: false
|
|
2195
|
+
});
|
|
2196
|
+
const metadata = response.result.metadata;
|
|
2197
|
+
const item = this.metadataToItem(
|
|
2198
|
+
{ ".tag": "folder", id: metadata.id, name: metadata.name, path_display: metadata.path_display, path_lower: metadata.path_lower },
|
|
2199
|
+
this.normalizePath(virtualPath)
|
|
2200
|
+
);
|
|
2201
|
+
return this.successResult(item);
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
if (error instanceof AuthenticationError) {
|
|
2204
|
+
return this.errorResult(error.message);
|
|
2205
|
+
}
|
|
2206
|
+
const errMsg = error.message || String(error);
|
|
2207
|
+
if (errMsg.includes("path/conflict")) {
|
|
2208
|
+
return this.errorResult(`Directory already exists: ${virtualPath}`);
|
|
2209
|
+
}
|
|
2210
|
+
return this.errorResult(`Failed to create directory: ${errMsg}`);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
async removeDirectory(virtualPath, recursive = false) {
|
|
2214
|
+
try {
|
|
2215
|
+
await this.ensureAuthenticated();
|
|
2216
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2217
|
+
if (!recursive) {
|
|
2218
|
+
const listResponse = await this.dbx.filesListFolder({
|
|
2219
|
+
path: dbxPath,
|
|
2220
|
+
limit: 1
|
|
2221
|
+
});
|
|
2222
|
+
if (listResponse.result.entries.length > 0) {
|
|
2223
|
+
return this.errorResult(`Directory is not empty: ${virtualPath}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
await this.dbx.filesDeleteV2({ path: dbxPath });
|
|
2227
|
+
return this.successResult();
|
|
2228
|
+
} catch (error) {
|
|
2229
|
+
const errMsg = error.message || String(error);
|
|
2230
|
+
if (errMsg.includes("path_lookup/not_found") || errMsg.includes("not_found")) {
|
|
2231
|
+
return this.errorResult(`Directory not found: ${virtualPath}`);
|
|
2232
|
+
}
|
|
2233
|
+
return this.errorResult(`Failed to remove directory: ${errMsg}`);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
async uploadFile(source, remotePath, options = {}) {
|
|
2237
|
+
try {
|
|
2238
|
+
await this.ensureAuthenticated();
|
|
2239
|
+
const normalized = this.normalizePath(remotePath);
|
|
2240
|
+
const dbxPath = this.toDropboxPath(remotePath);
|
|
2241
|
+
let contents;
|
|
2242
|
+
if (typeof source === "string") {
|
|
2243
|
+
const fs3 = await import("fs");
|
|
2244
|
+
contents = await fs3.promises.readFile(source);
|
|
2245
|
+
} else if (Buffer.isBuffer(source)) {
|
|
2246
|
+
contents = source;
|
|
2247
|
+
} else {
|
|
2248
|
+
const chunks = [];
|
|
2249
|
+
const reader = source.getReader();
|
|
2250
|
+
let done = false;
|
|
2251
|
+
while (!done) {
|
|
2252
|
+
const result = await reader.read();
|
|
2253
|
+
done = result.done;
|
|
2254
|
+
if (result.value) {
|
|
2255
|
+
chunks.push(result.value);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
contents = Buffer.concat(chunks);
|
|
2259
|
+
}
|
|
2260
|
+
if (contents.length > MAX_UPLOAD_SIZE) {
|
|
2261
|
+
throw new FileTooLargeError(this.getBaseName(remotePath), contents.length, MAX_UPLOAD_SIZE);
|
|
2262
|
+
}
|
|
2263
|
+
if (!options.overwrite) {
|
|
2264
|
+
try {
|
|
2265
|
+
await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2266
|
+
throw new FileExistsError(remotePath);
|
|
2267
|
+
} catch (err) {
|
|
2268
|
+
if (err instanceof FileExistsError) throw err;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
const response = await this.dbx.filesUpload({
|
|
2272
|
+
path: dbxPath,
|
|
2273
|
+
contents,
|
|
2274
|
+
mode: options.overwrite ? { ".tag": "overwrite" } : { ".tag": "add" },
|
|
2275
|
+
autorename: false
|
|
2276
|
+
});
|
|
2277
|
+
if (options.onProgress) {
|
|
2278
|
+
options.onProgress(100, contents.length, contents.length);
|
|
2279
|
+
}
|
|
2280
|
+
const metadata = response.result;
|
|
2281
|
+
const item = this.metadataToItem(
|
|
2282
|
+
{
|
|
2283
|
+
".tag": "file",
|
|
2284
|
+
id: metadata.id,
|
|
2285
|
+
name: metadata.name,
|
|
2286
|
+
path_display: metadata.path_display,
|
|
2287
|
+
path_lower: metadata.path_lower,
|
|
2288
|
+
size: metadata.size,
|
|
2289
|
+
client_modified: metadata.client_modified,
|
|
2290
|
+
server_modified: metadata.server_modified
|
|
2291
|
+
},
|
|
2292
|
+
normalized
|
|
2293
|
+
);
|
|
2294
|
+
return this.successResult(item);
|
|
2295
|
+
} catch (error) {
|
|
2296
|
+
if (error instanceof AuthenticationError || error instanceof FileExistsError || error instanceof FileTooLargeError) {
|
|
2297
|
+
return this.errorResult(error.message);
|
|
2298
|
+
}
|
|
2299
|
+
return this.errorResult(`Failed to upload file: ${error.message}`);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
async downloadFile(remotePath, localPath, options = {}) {
|
|
2303
|
+
try {
|
|
2304
|
+
await this.ensureAuthenticated();
|
|
2305
|
+
const dbxPath = this.toDropboxPath(remotePath);
|
|
2306
|
+
const response = await this.dbx.filesDownload({ path: dbxPath });
|
|
2307
|
+
const result = response.result;
|
|
2308
|
+
const buffer = Buffer.from(result.fileBinary);
|
|
2309
|
+
if (options.onProgress) {
|
|
2310
|
+
options.onProgress(100, buffer.length, buffer.length);
|
|
2311
|
+
}
|
|
2312
|
+
if (localPath) {
|
|
2313
|
+
const fs3 = await import("fs");
|
|
2314
|
+
const path3 = await import("path");
|
|
2315
|
+
await fs3.promises.mkdir(path3.dirname(localPath), { recursive: true });
|
|
2316
|
+
await fs3.promises.writeFile(localPath, buffer);
|
|
2317
|
+
return this.successResult(localPath);
|
|
2318
|
+
}
|
|
2319
|
+
return this.successResult(buffer);
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
const errMsg = error.message || String(error);
|
|
2322
|
+
if (errMsg.includes("path/not_found") || errMsg.includes("path_lookup/not_found")) {
|
|
2323
|
+
return this.errorResult(`File not found: ${remotePath}`);
|
|
2324
|
+
}
|
|
2325
|
+
return this.errorResult(`Failed to download file: ${errMsg}`);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
async moveItem(sourcePath, destinationPath, _options = {}) {
|
|
2329
|
+
try {
|
|
2330
|
+
await this.ensureAuthenticated();
|
|
2331
|
+
const fromPath = this.toDropboxPath(sourcePath);
|
|
2332
|
+
const toPath = this.toDropboxPath(destinationPath);
|
|
2333
|
+
const response = await this.dbx.filesMoveV2({
|
|
2334
|
+
from_path: fromPath,
|
|
2335
|
+
to_path: toPath,
|
|
2336
|
+
autorename: false
|
|
2337
|
+
});
|
|
2338
|
+
const metadata = response.result.metadata;
|
|
2339
|
+
const item = this.metadataToItem(metadata, this.normalizePath(destinationPath));
|
|
2340
|
+
return this.successResult(item);
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
const errMsg = error.message || String(error);
|
|
2343
|
+
if (errMsg.includes("not_found")) {
|
|
2344
|
+
return this.errorResult(`Item not found: ${sourcePath}`);
|
|
2345
|
+
}
|
|
2346
|
+
return this.errorResult(`Failed to move item: ${errMsg}`);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
async deleteFile(virtualPath) {
|
|
2350
|
+
try {
|
|
2351
|
+
await this.ensureAuthenticated();
|
|
2352
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2353
|
+
await this.dbx.filesDeleteV2({ path: dbxPath });
|
|
2354
|
+
return this.successResult();
|
|
2355
|
+
} catch (error) {
|
|
2356
|
+
const errMsg = error.message || String(error);
|
|
2357
|
+
if (errMsg.includes("not_found")) {
|
|
2358
|
+
return this.errorResult(`File not found: ${virtualPath}`);
|
|
2359
|
+
}
|
|
2360
|
+
return this.errorResult(`Failed to delete file: ${errMsg}`);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
async renameFile(virtualPath, newName, _options = {}) {
|
|
2364
|
+
try {
|
|
2365
|
+
await this.ensureAuthenticated();
|
|
2366
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
2367
|
+
const newVirtualPath = this.joinPath(parentPath, newName);
|
|
2368
|
+
const fromPath = this.toDropboxPath(virtualPath);
|
|
2369
|
+
const toPath = this.toDropboxPath(newVirtualPath);
|
|
2370
|
+
const response = await this.dbx.filesMoveV2({
|
|
2371
|
+
from_path: fromPath,
|
|
2372
|
+
to_path: toPath,
|
|
2373
|
+
autorename: false
|
|
2374
|
+
});
|
|
2375
|
+
const metadata = response.result.metadata;
|
|
2376
|
+
const item = this.metadataToItem(metadata, newVirtualPath);
|
|
2377
|
+
return this.successResult(item);
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
const errMsg = error.message || String(error);
|
|
2380
|
+
if (errMsg.includes("not_found")) {
|
|
2381
|
+
return this.errorResult(`File not found: ${virtualPath}`);
|
|
2382
|
+
}
|
|
2383
|
+
return this.errorResult(`Failed to rename file: ${errMsg}`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
async renameFolder(virtualPath, newName, _options = {}) {
|
|
2387
|
+
try {
|
|
2388
|
+
await this.ensureAuthenticated();
|
|
2389
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
2390
|
+
const newVirtualPath = this.joinPath(parentPath, newName);
|
|
2391
|
+
const fromPath = this.toDropboxPath(virtualPath);
|
|
2392
|
+
const toPath = this.toDropboxPath(newVirtualPath);
|
|
2393
|
+
const response = await this.dbx.filesMoveV2({
|
|
2394
|
+
from_path: fromPath,
|
|
2395
|
+
to_path: toPath,
|
|
2396
|
+
autorename: false
|
|
2397
|
+
});
|
|
2398
|
+
const metadata = response.result.metadata;
|
|
2399
|
+
const item = this.metadataToItem(metadata, newVirtualPath);
|
|
2400
|
+
return this.successResult(item);
|
|
2401
|
+
} catch (error) {
|
|
2402
|
+
const errMsg = error.message || String(error);
|
|
2403
|
+
if (errMsg.includes("not_found")) {
|
|
2404
|
+
return this.errorResult(`Folder not found: ${virtualPath}`);
|
|
2405
|
+
}
|
|
2406
|
+
return this.errorResult(`Failed to rename folder: ${errMsg}`);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
async listDirectory(virtualPath, options = {}) {
|
|
2410
|
+
try {
|
|
2411
|
+
await this.ensureAuthenticated();
|
|
2412
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2413
|
+
const items = [];
|
|
2414
|
+
let hasMore = true;
|
|
2415
|
+
let cursor;
|
|
2416
|
+
const firstResponse = await this.dbx.filesListFolder({
|
|
2417
|
+
path: dbxPath,
|
|
2418
|
+
limit: 100
|
|
2419
|
+
});
|
|
2420
|
+
let entries = firstResponse.result.entries;
|
|
2421
|
+
hasMore = firstResponse.result.has_more;
|
|
2422
|
+
cursor = firstResponse.result.cursor;
|
|
2423
|
+
const processEntries = async (entryList) => {
|
|
2424
|
+
for (const entry of entryList) {
|
|
2425
|
+
if (!options.includeHidden && entry.name.startsWith(".")) {
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
const itemPath = this.joinPath(virtualPath, entry.name);
|
|
2429
|
+
const item = this.metadataToItem(entry, itemPath);
|
|
2430
|
+
if (options.filter && !options.filter(item)) {
|
|
2431
|
+
continue;
|
|
2432
|
+
}
|
|
2433
|
+
items.push(item);
|
|
2434
|
+
if (options.recursive && entry[".tag"] === "folder") {
|
|
2435
|
+
const subResult = await this.listDirectory(itemPath, options);
|
|
2436
|
+
if (subResult.success && subResult.data) {
|
|
2437
|
+
items.push(...subResult.data);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
await processEntries(entries);
|
|
2443
|
+
while (hasMore && cursor) {
|
|
2444
|
+
const continueResponse = await this.dbx.filesListFolderContinue({ cursor });
|
|
2445
|
+
entries = continueResponse.result.entries;
|
|
2446
|
+
hasMore = continueResponse.result.has_more;
|
|
2447
|
+
cursor = continueResponse.result.cursor;
|
|
2448
|
+
await processEntries(entries);
|
|
2449
|
+
}
|
|
2450
|
+
return this.successResult(items);
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
const errMsg = error.message || String(error);
|
|
2453
|
+
if (errMsg.includes("path/not_found") || errMsg.includes("not_found")) {
|
|
2454
|
+
return this.errorResult(`Directory not found: ${virtualPath}`);
|
|
2455
|
+
}
|
|
2456
|
+
return this.errorResult(`Failed to list directory: ${errMsg}`);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
async getItem(virtualPath) {
|
|
2460
|
+
try {
|
|
2461
|
+
await this.ensureAuthenticated();
|
|
2462
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2463
|
+
const response = await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2464
|
+
const metadata = response.result;
|
|
2465
|
+
const item = this.metadataToItem(metadata, this.normalizePath(virtualPath));
|
|
2466
|
+
return this.successResult(item);
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
const errMsg = error.message || String(error);
|
|
2469
|
+
if (errMsg.includes("not_found")) {
|
|
2470
|
+
return this.errorResult(`Item not found: ${virtualPath}`);
|
|
2471
|
+
}
|
|
2472
|
+
return this.errorResult(`Failed to get item: ${errMsg}`);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
async exists(virtualPath) {
|
|
2476
|
+
try {
|
|
2477
|
+
await this.ensureAuthenticated();
|
|
2478
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2479
|
+
await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2480
|
+
return true;
|
|
2481
|
+
} catch {
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
async getFolderTree(path3 = "/", depth = 3) {
|
|
2486
|
+
try {
|
|
2487
|
+
await this.ensureAuthenticated();
|
|
2488
|
+
return super.getFolderTree(path3, depth);
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
return this.errorResult(`Failed to get folder tree: ${error.message}`);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
function createDropboxModule() {
|
|
2495
|
+
return new DropboxModule();
|
|
2496
|
+
}
|
|
2497
|
+
|
|
1829
2498
|
// src/modules/index.ts
|
|
1830
2499
|
var moduleRegistry = {
|
|
1831
2500
|
local: createLocalModule,
|
|
1832
|
-
google_drive: createGoogleDriveModule
|
|
2501
|
+
google_drive: createGoogleDriveModule,
|
|
2502
|
+
dropbox: createDropboxModule
|
|
1833
2503
|
};
|
|
1834
2504
|
function getRegisteredProviders() {
|
|
1835
2505
|
return Object.keys(moduleRegistry);
|
|
@@ -2395,6 +3065,7 @@ var FileMetadataService = class {
|
|
|
2395
3065
|
if (input.scope_id !== void 0) record.scope_id = input.scope_id;
|
|
2396
3066
|
if (input.uploaded_by !== void 0) record.uploaded_by = input.uploaded_by;
|
|
2397
3067
|
if (input.original_filename !== void 0) record.original_filename = input.original_filename;
|
|
3068
|
+
if (input.content_tag !== void 0) record.content_tag = input.content_tag;
|
|
2398
3069
|
const results = await this.crud.insert(record);
|
|
2399
3070
|
this.logger?.debug?.("Recorded file upload", { path: input.file_path });
|
|
2400
3071
|
return results[0] || null;
|
|
@@ -3772,10 +4443,18 @@ function createNamingConventionService(crudService, options) {
|
|
|
3772
4443
|
|
|
3773
4444
|
// src/services/llm-extraction-service.ts
|
|
3774
4445
|
var LLMExtractionService = class {
|
|
3775
|
-
constructor(
|
|
3776
|
-
this.llmFactory =
|
|
4446
|
+
constructor(factoryConfig, defaultProvider = "gemini") {
|
|
4447
|
+
this.llmFactory = factoryConfig.create;
|
|
4448
|
+
this.cacheInvalidator = factoryConfig.invalidateCache;
|
|
3777
4449
|
this.defaultProvider = defaultProvider;
|
|
3778
4450
|
}
|
|
4451
|
+
/**
|
|
4452
|
+
* Invalidate the LLM prompt cache
|
|
4453
|
+
* Passthrough to hazo_llm_api's invalidate_prompt_cache when configured
|
|
4454
|
+
*/
|
|
4455
|
+
invalidatePromptCache(area, key) {
|
|
4456
|
+
this.cacheInvalidator?.(area, key);
|
|
4457
|
+
}
|
|
3779
4458
|
/**
|
|
3780
4459
|
* Extract data from a document
|
|
3781
4460
|
*
|
|
@@ -3902,8 +4581,8 @@ var LLMExtractionService = class {
|
|
|
3902
4581
|
};
|
|
3903
4582
|
}
|
|
3904
4583
|
};
|
|
3905
|
-
function createLLMExtractionService(
|
|
3906
|
-
return new LLMExtractionService(
|
|
4584
|
+
function createLLMExtractionService(factoryConfig, defaultProvider) {
|
|
4585
|
+
return new LLMExtractionService(factoryConfig, defaultProvider);
|
|
3907
4586
|
}
|
|
3908
4587
|
|
|
3909
4588
|
// src/common/naming-utils.ts
|
|
@@ -4229,10 +4908,11 @@ function generatePreviewName(pattern, userVariables, options = {}) {
|
|
|
4229
4908
|
|
|
4230
4909
|
// src/services/upload-extract-service.ts
|
|
4231
4910
|
var UploadExtractService = class {
|
|
4232
|
-
constructor(fileManager, namingService, extractionService) {
|
|
4911
|
+
constructor(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4233
4912
|
this.fileManager = fileManager;
|
|
4234
4913
|
this.namingService = namingService;
|
|
4235
4914
|
this.extractionService = extractionService;
|
|
4915
|
+
this.defaultContentTagConfig = defaultContentTagConfig;
|
|
4236
4916
|
}
|
|
4237
4917
|
/**
|
|
4238
4918
|
* Upload a file with optional extraction and naming convention
|
|
@@ -4309,11 +4989,12 @@ var UploadExtractService = class {
|
|
|
4309
4989
|
metadata.extraction_id = extractionData.id;
|
|
4310
4990
|
metadata.extraction_source = extractionData.source;
|
|
4311
4991
|
}
|
|
4992
|
+
const effectiveContentTagConfig = options.contentTagConfig ?? this.defaultContentTagConfig;
|
|
4993
|
+
const needsContentTagging = effectiveContentTagConfig?.content_tag_set_by_llm && this.extractionService && this.fileManager.isTrackingActive();
|
|
4312
4994
|
const uploadResult = await this.fileManager.uploadFile(source, fullPath, {
|
|
4313
4995
|
...options,
|
|
4314
4996
|
metadata,
|
|
4315
|
-
awaitRecording: !!extractionData
|
|
4316
|
-
// Await recording when extraction needs to be added
|
|
4997
|
+
awaitRecording: !!extractionData || !!needsContentTagging
|
|
4317
4998
|
});
|
|
4318
4999
|
if (!uploadResult.success) {
|
|
4319
5000
|
return {
|
|
@@ -4337,13 +5018,23 @@ var UploadExtractService = class {
|
|
|
4337
5018
|
);
|
|
4338
5019
|
}
|
|
4339
5020
|
}
|
|
5021
|
+
let contentTag;
|
|
5022
|
+
if (needsContentTagging && effectiveContentTagConfig) {
|
|
5023
|
+
contentTag = await this.performContentTagging(
|
|
5024
|
+
source,
|
|
5025
|
+
mimeType,
|
|
5026
|
+
effectiveContentTagConfig,
|
|
5027
|
+
fullPath
|
|
5028
|
+
);
|
|
5029
|
+
}
|
|
4340
5030
|
return {
|
|
4341
5031
|
success: true,
|
|
4342
5032
|
file: uploadResult.data,
|
|
4343
5033
|
extraction: extractionData,
|
|
4344
5034
|
generatedPath: fullPath,
|
|
4345
5035
|
generatedFolderPath: generatedFolderPath || void 0,
|
|
4346
|
-
originalFileName
|
|
5036
|
+
originalFileName,
|
|
5037
|
+
contentTag
|
|
4347
5038
|
};
|
|
4348
5039
|
} catch (error) {
|
|
4349
5040
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4441,6 +5132,76 @@ var UploadExtractService = class {
|
|
|
4441
5132
|
folderPath: folderPath || void 0
|
|
4442
5133
|
};
|
|
4443
5134
|
}
|
|
5135
|
+
/**
|
|
5136
|
+
* Perform content tagging via LLM extraction.
|
|
5137
|
+
* Calls the LLM with the configured prompt, extracts the specified field,
|
|
5138
|
+
* and writes it to the content_tag column.
|
|
5139
|
+
*/
|
|
5140
|
+
async performContentTagging(buffer, mimeType, config, filePath) {
|
|
5141
|
+
try {
|
|
5142
|
+
if (!this.extractionService) return void 0;
|
|
5143
|
+
const result = await this.extractionService.extract(buffer, mimeType, {
|
|
5144
|
+
promptArea: config.content_tag_prompt_area,
|
|
5145
|
+
promptKey: config.content_tag_prompt_key,
|
|
5146
|
+
promptVariables: config.content_tag_prompt_variables
|
|
5147
|
+
});
|
|
5148
|
+
if (!result.success || !result.data) return void 0;
|
|
5149
|
+
const tagValue = result.data[config.content_tag_prompt_return_fieldname];
|
|
5150
|
+
if (typeof tagValue !== "string" || !tagValue) return void 0;
|
|
5151
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
5152
|
+
if (metadataService) {
|
|
5153
|
+
const storageType = this.fileManager.getProvider() || "local";
|
|
5154
|
+
const record = await metadataService.findByPath(filePath, storageType);
|
|
5155
|
+
if (record) {
|
|
5156
|
+
await metadataService.updateFields(record.id, { content_tag: tagValue });
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
return tagValue;
|
|
5160
|
+
} catch {
|
|
5161
|
+
return void 0;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
/**
|
|
5165
|
+
* Manually tag a file's content via LLM.
|
|
5166
|
+
* Works with existing DB records, resolving the file path internally.
|
|
5167
|
+
*
|
|
5168
|
+
* @param fileId - Database record ID of the file
|
|
5169
|
+
* @param config - Content tag config (falls back to default if not provided)
|
|
5170
|
+
* @returns OperationResult with the tag value
|
|
5171
|
+
*/
|
|
5172
|
+
async tagFileContent(fileId, config) {
|
|
5173
|
+
const effectiveConfig = config ?? this.defaultContentTagConfig;
|
|
5174
|
+
if (!effectiveConfig || !effectiveConfig.content_tag_set_by_llm) {
|
|
5175
|
+
return { success: false, error: "Content tagging is not configured or disabled" };
|
|
5176
|
+
}
|
|
5177
|
+
if (!this.extractionService) {
|
|
5178
|
+
return { success: false, error: "Extraction service not available" };
|
|
5179
|
+
}
|
|
5180
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
5181
|
+
if (!metadataService) {
|
|
5182
|
+
return { success: false, error: "Metadata service not available (tracking not enabled)" };
|
|
5183
|
+
}
|
|
5184
|
+
const record = await metadataService.findById(fileId);
|
|
5185
|
+
if (!record) {
|
|
5186
|
+
return { success: false, error: `File record not found: ${fileId}` };
|
|
5187
|
+
}
|
|
5188
|
+
const downloadResult = await this.fileManager.downloadFile(record.file_path);
|
|
5189
|
+
if (!downloadResult.success || !downloadResult.data) {
|
|
5190
|
+
return { success: false, error: `Failed to download file: ${downloadResult.error}` };
|
|
5191
|
+
}
|
|
5192
|
+
const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : Buffer.from(downloadResult.data);
|
|
5193
|
+
const mimeType = getMimeType(record.filename);
|
|
5194
|
+
const tagValue = await this.performContentTagging(
|
|
5195
|
+
buffer,
|
|
5196
|
+
mimeType,
|
|
5197
|
+
effectiveConfig,
|
|
5198
|
+
record.file_path
|
|
5199
|
+
);
|
|
5200
|
+
if (!tagValue) {
|
|
5201
|
+
return { success: false, error: "Content tagging did not produce a result" };
|
|
5202
|
+
}
|
|
5203
|
+
return { success: true, data: tagValue };
|
|
5204
|
+
}
|
|
4444
5205
|
/**
|
|
4445
5206
|
* Get the file manager
|
|
4446
5207
|
*/
|
|
@@ -4460,8 +5221,8 @@ var UploadExtractService = class {
|
|
|
4460
5221
|
return this.extractionService;
|
|
4461
5222
|
}
|
|
4462
5223
|
};
|
|
4463
|
-
function createUploadExtractService(fileManager, namingService, extractionService) {
|
|
4464
|
-
return new UploadExtractService(fileManager, namingService, extractionService);
|
|
5224
|
+
function createUploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
5225
|
+
return new UploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig);
|
|
4465
5226
|
}
|
|
4466
5227
|
|
|
4467
5228
|
// src/schema/index.ts
|
|
@@ -4488,7 +5249,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4488
5249
|
uploaded_by TEXT,
|
|
4489
5250
|
storage_verified_at TEXT,
|
|
4490
5251
|
deleted_at TEXT,
|
|
4491
|
-
original_filename TEXT
|
|
5252
|
+
original_filename TEXT,
|
|
5253
|
+
content_tag TEXT
|
|
4492
5254
|
)`,
|
|
4493
5255
|
indexes: [
|
|
4494
5256
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4498,7 +5260,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4498
5260
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4499
5261
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4500
5262
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4501
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
5263
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
5264
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4502
5265
|
]
|
|
4503
5266
|
},
|
|
4504
5267
|
postgres: {
|
|
@@ -4521,7 +5284,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4521
5284
|
uploaded_by UUID,
|
|
4522
5285
|
storage_verified_at TIMESTAMP WITH TIME ZONE,
|
|
4523
5286
|
deleted_at TIMESTAMP WITH TIME ZONE,
|
|
4524
|
-
original_filename TEXT
|
|
5287
|
+
original_filename TEXT,
|
|
5288
|
+
content_tag TEXT
|
|
4525
5289
|
)`,
|
|
4526
5290
|
indexes: [
|
|
4527
5291
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4531,7 +5295,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4531
5295
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4532
5296
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4533
5297
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4534
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
5298
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
5299
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4535
5300
|
]
|
|
4536
5301
|
},
|
|
4537
5302
|
columns: [
|
|
@@ -4553,7 +5318,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4553
5318
|
"uploaded_by",
|
|
4554
5319
|
"storage_verified_at",
|
|
4555
5320
|
"deleted_at",
|
|
4556
|
-
"original_filename"
|
|
5321
|
+
"original_filename",
|
|
5322
|
+
"content_tag"
|
|
4557
5323
|
]
|
|
4558
5324
|
};
|
|
4559
5325
|
function getSchemaForTable(tableName, dbType) {
|
|
@@ -4694,6 +5460,45 @@ function getNamingSchemaForTable(tableName, dbType) {
|
|
|
4694
5460
|
)
|
|
4695
5461
|
};
|
|
4696
5462
|
}
|
|
5463
|
+
var HAZO_FILES_MIGRATION_V3 = {
|
|
5464
|
+
tableName: HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
5465
|
+
sqlite: {
|
|
5466
|
+
alterStatements: [
|
|
5467
|
+
"ALTER TABLE hazo_files ADD COLUMN content_tag TEXT"
|
|
5468
|
+
],
|
|
5469
|
+
indexes: [
|
|
5470
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
5471
|
+
],
|
|
5472
|
+
backfill: ""
|
|
5473
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
5474
|
+
},
|
|
5475
|
+
postgres: {
|
|
5476
|
+
alterStatements: [
|
|
5477
|
+
"ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT"
|
|
5478
|
+
],
|
|
5479
|
+
indexes: [
|
|
5480
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
5481
|
+
],
|
|
5482
|
+
backfill: ""
|
|
5483
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
5484
|
+
},
|
|
5485
|
+
newColumns: [
|
|
5486
|
+
"content_tag"
|
|
5487
|
+
]
|
|
5488
|
+
};
|
|
5489
|
+
function getMigrationV3ForTable(tableName, dbType) {
|
|
5490
|
+
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5491
|
+
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5492
|
+
return {
|
|
5493
|
+
alterStatements: migration.alterStatements.map(
|
|
5494
|
+
(stmt) => stmt.replace(new RegExp(defaultName, "g"), tableName)
|
|
5495
|
+
),
|
|
5496
|
+
indexes: migration.indexes.map(
|
|
5497
|
+
(idx) => idx.replace(new RegExp(defaultName, "g"), tableName)
|
|
5498
|
+
),
|
|
5499
|
+
backfill: migration.backfill
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
4697
5502
|
|
|
4698
5503
|
// src/migrations/add-reference-tracking.ts
|
|
4699
5504
|
async function migrateToV2(executor, dbType, tableName) {
|
|
@@ -4712,6 +5517,20 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4712
5517
|
const migration = tableName ? getMigrationForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V2[dbType];
|
|
4713
5518
|
await executor.run(migration.backfill);
|
|
4714
5519
|
}
|
|
5520
|
+
|
|
5521
|
+
// src/migrations/add-content-tag.ts
|
|
5522
|
+
async function migrateToV3(executor, dbType, tableName) {
|
|
5523
|
+
const migration = tableName ? getMigrationV3ForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V3[dbType];
|
|
5524
|
+
for (const stmt of migration.alterStatements) {
|
|
5525
|
+
try {
|
|
5526
|
+
await executor.run(stmt);
|
|
5527
|
+
} catch {
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
for (const idx of migration.indexes) {
|
|
5531
|
+
await executor.run(idx);
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
4715
5534
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4716
5535
|
0 && (module.exports = {
|
|
4717
5536
|
ALL_SYSTEM_VARIABLES,
|
|
@@ -4721,6 +5540,8 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4721
5540
|
DirectoryExistsError,
|
|
4722
5541
|
DirectoryNotEmptyError,
|
|
4723
5542
|
DirectoryNotFoundError,
|
|
5543
|
+
DropboxAuth,
|
|
5544
|
+
DropboxModule,
|
|
4724
5545
|
FileExistsError,
|
|
4725
5546
|
FileManager,
|
|
4726
5547
|
FileMetadataService,
|
|
@@ -4730,6 +5551,7 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4730
5551
|
GoogleDriveModule,
|
|
4731
5552
|
HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4732
5553
|
HAZO_FILES_MIGRATION_V2,
|
|
5554
|
+
HAZO_FILES_MIGRATION_V3,
|
|
4733
5555
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
4734
5556
|
HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
4735
5557
|
HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -4756,6 +5578,8 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4756
5578
|
computeFileHashSync,
|
|
4757
5579
|
computeFileInfo,
|
|
4758
5580
|
createAndInitializeModule,
|
|
5581
|
+
createDropboxAuth,
|
|
5582
|
+
createDropboxModule,
|
|
4759
5583
|
createEmptyFileDataStructure,
|
|
4760
5584
|
createEmptyNamingRuleSchema,
|
|
4761
5585
|
createFileItem,
|
|
@@ -4799,6 +5623,7 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4799
5623
|
getFileMetadataValues,
|
|
4800
5624
|
getMergedData,
|
|
4801
5625
|
getMigrationForTable,
|
|
5626
|
+
getMigrationV3ForTable,
|
|
4802
5627
|
getMimeType,
|
|
4803
5628
|
getNameWithoutExtension,
|
|
4804
5629
|
getNamingSchemaForTable,
|
|
@@ -4831,6 +5656,7 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4831
5656
|
loadConfig,
|
|
4832
5657
|
loadConfigAsync,
|
|
4833
5658
|
migrateToV2,
|
|
5659
|
+
migrateToV3,
|
|
4834
5660
|
normalizePath,
|
|
4835
5661
|
parseConfig,
|
|
4836
5662
|
parseFileData,
|