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/server/index.mjs
CHANGED
|
@@ -33,6 +33,16 @@ function parseConfig(configContent) {
|
|
|
33
33
|
rootFolderId: parsed.google_drive.root_folder_id || process.env.HAZO_GOOGLE_DRIVE_ROOT_FOLDER_ID
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
if (parsed.dropbox) {
|
|
37
|
+
config.dropbox = {
|
|
38
|
+
clientId: parsed.dropbox.client_id || process.env.HAZO_DROPBOX_CLIENT_ID || "",
|
|
39
|
+
clientSecret: parsed.dropbox.client_secret || process.env.HAZO_DROPBOX_CLIENT_SECRET || "",
|
|
40
|
+
redirectUri: parsed.dropbox.redirect_uri || process.env.HAZO_DROPBOX_REDIRECT_URI || "",
|
|
41
|
+
refreshToken: parsed.dropbox.refresh_token || process.env.HAZO_DROPBOX_REFRESH_TOKEN,
|
|
42
|
+
accessToken: parsed.dropbox.access_token || process.env.HAZO_DROPBOX_ACCESS_TOKEN,
|
|
43
|
+
rootPath: parsed.dropbox.root_path || process.env.HAZO_DROPBOX_ROOT_PATH
|
|
44
|
+
};
|
|
45
|
+
}
|
|
36
46
|
return config;
|
|
37
47
|
}
|
|
38
48
|
function loadConfig(configPath) {
|
|
@@ -94,6 +104,18 @@ refresh_token =
|
|
|
94
104
|
access_token =
|
|
95
105
|
; Optional: Root folder ID to use as base (empty = root of Drive)
|
|
96
106
|
root_folder_id =
|
|
107
|
+
|
|
108
|
+
[dropbox]
|
|
109
|
+
; Dropbox OAuth credentials
|
|
110
|
+
; These can also be set via environment variables:
|
|
111
|
+
; HAZO_DROPBOX_CLIENT_ID, HAZO_DROPBOX_CLIENT_SECRET, etc.
|
|
112
|
+
client_id =
|
|
113
|
+
client_secret =
|
|
114
|
+
redirect_uri = http://localhost:3000/api/auth/dropbox/callback
|
|
115
|
+
refresh_token =
|
|
116
|
+
access_token =
|
|
117
|
+
; Optional: Root path to use as base (empty = root of Dropbox)
|
|
118
|
+
root_path =
|
|
97
119
|
`;
|
|
98
120
|
}
|
|
99
121
|
async function saveConfig(config, configPath) {
|
|
@@ -120,6 +142,16 @@ async function saveConfig(config, configPath) {
|
|
|
120
142
|
root_folder_id: config.google_drive.rootFolderId || ""
|
|
121
143
|
};
|
|
122
144
|
}
|
|
145
|
+
if (config.dropbox) {
|
|
146
|
+
iniConfig.dropbox = {
|
|
147
|
+
client_id: config.dropbox.clientId || "",
|
|
148
|
+
client_secret: config.dropbox.clientSecret || "",
|
|
149
|
+
redirect_uri: config.dropbox.redirectUri || "",
|
|
150
|
+
refresh_token: config.dropbox.refreshToken || "",
|
|
151
|
+
access_token: config.dropbox.accessToken || "",
|
|
152
|
+
root_path: config.dropbox.rootPath || ""
|
|
153
|
+
};
|
|
154
|
+
}
|
|
123
155
|
const content = ini.stringify(iniConfig);
|
|
124
156
|
await fs.promises.writeFile(resolvedPath, content, "utf-8");
|
|
125
157
|
}
|
|
@@ -1657,10 +1689,641 @@ function createGoogleDriveModule() {
|
|
|
1657
1689
|
return new GoogleDriveModule();
|
|
1658
1690
|
}
|
|
1659
1691
|
|
|
1692
|
+
// src/modules/dropbox/index.ts
|
|
1693
|
+
import { Dropbox } from "dropbox";
|
|
1694
|
+
|
|
1695
|
+
// src/modules/dropbox/auth.ts
|
|
1696
|
+
var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
|
|
1697
|
+
var DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
|
|
1698
|
+
var DROPBOX_REVOKE_URL = "https://api.dropboxapi.com/2/auth/token/revoke";
|
|
1699
|
+
var DropboxAuth = class {
|
|
1700
|
+
constructor(config, callbacks = {}) {
|
|
1701
|
+
this.tokens = null;
|
|
1702
|
+
this.config = config;
|
|
1703
|
+
this.callbacks = callbacks;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Generate the authorization URL for OAuth consent
|
|
1707
|
+
*/
|
|
1708
|
+
getAuthUrl(state) {
|
|
1709
|
+
const params = new URLSearchParams({
|
|
1710
|
+
client_id: this.config.clientId,
|
|
1711
|
+
redirect_uri: this.config.redirectUri,
|
|
1712
|
+
response_type: "code",
|
|
1713
|
+
token_access_type: "offline"
|
|
1714
|
+
});
|
|
1715
|
+
if (state) {
|
|
1716
|
+
params.set("state", state);
|
|
1717
|
+
}
|
|
1718
|
+
return `${DROPBOX_AUTH_URL}?${params.toString()}`;
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Exchange authorization code for tokens
|
|
1722
|
+
*/
|
|
1723
|
+
async exchangeCodeForTokens(code) {
|
|
1724
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
1725
|
+
method: "POST",
|
|
1726
|
+
headers: {
|
|
1727
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1728
|
+
},
|
|
1729
|
+
body: new URLSearchParams({
|
|
1730
|
+
code,
|
|
1731
|
+
grant_type: "authorization_code",
|
|
1732
|
+
client_id: this.config.clientId,
|
|
1733
|
+
client_secret: this.config.clientSecret,
|
|
1734
|
+
redirect_uri: this.config.redirectUri
|
|
1735
|
+
})
|
|
1736
|
+
});
|
|
1737
|
+
if (!response.ok) {
|
|
1738
|
+
const errorData = await response.text();
|
|
1739
|
+
throw new Error(`Failed to exchange code for tokens: ${errorData}`);
|
|
1740
|
+
}
|
|
1741
|
+
const data = await response.json();
|
|
1742
|
+
this.tokens = {
|
|
1743
|
+
accessToken: data.access_token,
|
|
1744
|
+
refreshToken: data.refresh_token,
|
|
1745
|
+
expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
1746
|
+
};
|
|
1747
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1748
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1749
|
+
}
|
|
1750
|
+
return this.tokens;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Set tokens directly (e.g., from stored tokens)
|
|
1754
|
+
*/
|
|
1755
|
+
async setTokens(tokens) {
|
|
1756
|
+
this.tokens = tokens;
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Load tokens from storage using callback
|
|
1760
|
+
*/
|
|
1761
|
+
async loadStoredTokens() {
|
|
1762
|
+
if (!this.callbacks.getStoredTokens) {
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
const tokens = await this.callbacks.getStoredTokens();
|
|
1766
|
+
if (tokens) {
|
|
1767
|
+
await this.setTokens(tokens);
|
|
1768
|
+
return true;
|
|
1769
|
+
}
|
|
1770
|
+
return false;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Check if authenticated
|
|
1774
|
+
*/
|
|
1775
|
+
isAuthenticated() {
|
|
1776
|
+
return this.tokens !== null && !!this.tokens.accessToken;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Get current tokens
|
|
1780
|
+
*/
|
|
1781
|
+
getTokens() {
|
|
1782
|
+
return this.tokens;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Get current access token
|
|
1786
|
+
*/
|
|
1787
|
+
getAccessToken() {
|
|
1788
|
+
return this.tokens?.accessToken ?? null;
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Refresh the access token
|
|
1792
|
+
*/
|
|
1793
|
+
async refreshAccessToken() {
|
|
1794
|
+
if (!this.tokens?.refreshToken) {
|
|
1795
|
+
throw new Error("No refresh token available");
|
|
1796
|
+
}
|
|
1797
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
1798
|
+
method: "POST",
|
|
1799
|
+
headers: {
|
|
1800
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1801
|
+
},
|
|
1802
|
+
body: new URLSearchParams({
|
|
1803
|
+
grant_type: "refresh_token",
|
|
1804
|
+
refresh_token: this.tokens.refreshToken,
|
|
1805
|
+
client_id: this.config.clientId,
|
|
1806
|
+
client_secret: this.config.clientSecret
|
|
1807
|
+
})
|
|
1808
|
+
});
|
|
1809
|
+
if (!response.ok) {
|
|
1810
|
+
const errorData = await response.text();
|
|
1811
|
+
throw new Error(`Failed to refresh token: ${errorData}`);
|
|
1812
|
+
}
|
|
1813
|
+
const data = await response.json();
|
|
1814
|
+
this.tokens = {
|
|
1815
|
+
...this.tokens,
|
|
1816
|
+
accessToken: data.access_token,
|
|
1817
|
+
expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
1818
|
+
};
|
|
1819
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1820
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1821
|
+
}
|
|
1822
|
+
return this.tokens;
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Revoke access (disconnect)
|
|
1826
|
+
*/
|
|
1827
|
+
async revokeAccess() {
|
|
1828
|
+
if (this.tokens?.accessToken) {
|
|
1829
|
+
try {
|
|
1830
|
+
await fetch(DROPBOX_REVOKE_URL, {
|
|
1831
|
+
method: "POST",
|
|
1832
|
+
headers: {
|
|
1833
|
+
Authorization: `Bearer ${this.tokens.accessToken}`
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
} catch {
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
this.tokens = null;
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Check if token is expired or will expire soon
|
|
1843
|
+
*/
|
|
1844
|
+
isTokenExpired(bufferSeconds = 300) {
|
|
1845
|
+
if (!this.tokens?.expiryDate) {
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
const now = Date.now();
|
|
1849
|
+
const expiry = this.tokens.expiryDate;
|
|
1850
|
+
return now >= expiry - bufferSeconds * 1e3;
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Ensure valid access token (refresh if needed)
|
|
1854
|
+
*/
|
|
1855
|
+
async ensureValidToken() {
|
|
1856
|
+
if (this.isTokenExpired()) {
|
|
1857
|
+
await this.refreshAccessToken();
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
function createDropboxAuth(config, callbacks) {
|
|
1862
|
+
return new DropboxAuth(config, callbacks);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// src/modules/dropbox/index.ts
|
|
1866
|
+
var MAX_UPLOAD_SIZE = 150 * 1024 * 1024;
|
|
1867
|
+
var DropboxModule = class extends BaseStorageModule {
|
|
1868
|
+
constructor() {
|
|
1869
|
+
super(...arguments);
|
|
1870
|
+
this.provider = "dropbox";
|
|
1871
|
+
this.auth = null;
|
|
1872
|
+
this.dbx = null;
|
|
1873
|
+
this.rootPath = "";
|
|
1874
|
+
this.authCallbacks = {};
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* Set authentication callbacks for token persistence
|
|
1878
|
+
*/
|
|
1879
|
+
setAuthCallbacks(callbacks) {
|
|
1880
|
+
this.authCallbacks = callbacks;
|
|
1881
|
+
}
|
|
1882
|
+
async initialize(config) {
|
|
1883
|
+
await super.initialize(config);
|
|
1884
|
+
const dropboxConfig = this.getProviderConfig();
|
|
1885
|
+
if (!dropboxConfig.clientId || !dropboxConfig.clientSecret) {
|
|
1886
|
+
throw new AuthenticationError("dropbox", "Missing client ID or client secret");
|
|
1887
|
+
}
|
|
1888
|
+
this.auth = createDropboxAuth(
|
|
1889
|
+
{
|
|
1890
|
+
clientId: dropboxConfig.clientId,
|
|
1891
|
+
clientSecret: dropboxConfig.clientSecret,
|
|
1892
|
+
redirectUri: dropboxConfig.redirectUri
|
|
1893
|
+
},
|
|
1894
|
+
this.authCallbacks
|
|
1895
|
+
);
|
|
1896
|
+
if (dropboxConfig.refreshToken || dropboxConfig.accessToken) {
|
|
1897
|
+
await this.auth.setTokens({
|
|
1898
|
+
accessToken: dropboxConfig.accessToken || "",
|
|
1899
|
+
refreshToken: dropboxConfig.refreshToken || ""
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
this.rootPath = dropboxConfig.rootPath || "";
|
|
1903
|
+
this.createDropboxClient();
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Create/recreate the Dropbox SDK client with the current access token
|
|
1907
|
+
*/
|
|
1908
|
+
createDropboxClient() {
|
|
1909
|
+
const accessToken = this.auth?.getAccessToken();
|
|
1910
|
+
if (accessToken) {
|
|
1911
|
+
this.dbx = new Dropbox({ accessToken });
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Get the auth instance for OAuth flow
|
|
1916
|
+
*/
|
|
1917
|
+
getAuth() {
|
|
1918
|
+
if (!this.auth) {
|
|
1919
|
+
throw new AuthenticationError("dropbox", "Module not initialized");
|
|
1920
|
+
}
|
|
1921
|
+
return this.auth;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Check if user is authenticated
|
|
1925
|
+
*/
|
|
1926
|
+
isAuthenticated() {
|
|
1927
|
+
return this.auth?.isAuthenticated() ?? false;
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Authenticate with provided tokens
|
|
1931
|
+
*/
|
|
1932
|
+
async authenticate(tokens) {
|
|
1933
|
+
if (!this.auth) {
|
|
1934
|
+
throw new AuthenticationError("dropbox", "Module not initialized");
|
|
1935
|
+
}
|
|
1936
|
+
await this.auth.setTokens(tokens);
|
|
1937
|
+
this.createDropboxClient();
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Ensure authenticated before operations
|
|
1941
|
+
*/
|
|
1942
|
+
async ensureAuthenticated() {
|
|
1943
|
+
this.ensureInitialized();
|
|
1944
|
+
if (!this.isAuthenticated()) {
|
|
1945
|
+
throw new AuthenticationError("dropbox", "Not authenticated. Please connect your Dropbox.");
|
|
1946
|
+
}
|
|
1947
|
+
await this.auth.ensureValidToken();
|
|
1948
|
+
this.createDropboxClient();
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Convert virtual path to Dropbox path
|
|
1952
|
+
* Virtual: /folder/file.txt -> Dropbox: /folder/file.txt (or /rootPath/folder/file.txt)
|
|
1953
|
+
* Dropbox root is empty string "", not "/"
|
|
1954
|
+
*/
|
|
1955
|
+
toDropboxPath(virtualPath) {
|
|
1956
|
+
const normalized = this.normalizePath(virtualPath);
|
|
1957
|
+
if (this.rootPath) {
|
|
1958
|
+
if (normalized === "/") {
|
|
1959
|
+
return `/${this.rootPath}`;
|
|
1960
|
+
}
|
|
1961
|
+
return `/${this.rootPath}${normalized}`;
|
|
1962
|
+
}
|
|
1963
|
+
if (normalized === "/") {
|
|
1964
|
+
return "";
|
|
1965
|
+
}
|
|
1966
|
+
return normalized;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Convert Dropbox metadata to FileSystemItem
|
|
1970
|
+
*/
|
|
1971
|
+
metadataToItem(entry, virtualPath) {
|
|
1972
|
+
const isFolder2 = entry[".tag"] === "folder";
|
|
1973
|
+
const path3 = virtualPath || this.toVirtualPath(entry.path_display || entry.name);
|
|
1974
|
+
if (isFolder2) {
|
|
1975
|
+
return createFolderItem({
|
|
1976
|
+
id: entry.id,
|
|
1977
|
+
name: entry.name,
|
|
1978
|
+
path: path3,
|
|
1979
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1980
|
+
modifiedAt: /* @__PURE__ */ new Date(),
|
|
1981
|
+
metadata: {
|
|
1982
|
+
dropboxId: entry.id,
|
|
1983
|
+
pathDisplay: entry.path_display
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
const fileEntry = entry;
|
|
1988
|
+
return createFileItem({
|
|
1989
|
+
id: fileEntry.id,
|
|
1990
|
+
name: fileEntry.name,
|
|
1991
|
+
path: path3,
|
|
1992
|
+
size: fileEntry.size,
|
|
1993
|
+
mimeType: getMimeType(fileEntry.name),
|
|
1994
|
+
createdAt: new Date(fileEntry.client_modified),
|
|
1995
|
+
modifiedAt: new Date(fileEntry.server_modified),
|
|
1996
|
+
metadata: {
|
|
1997
|
+
dropboxId: fileEntry.id,
|
|
1998
|
+
pathDisplay: fileEntry.path_display
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Convert a Dropbox path_display to virtual path
|
|
2004
|
+
*/
|
|
2005
|
+
toVirtualPath(dropboxPath) {
|
|
2006
|
+
if (this.rootPath && dropboxPath.toLowerCase().startsWith(`/${this.rootPath.toLowerCase()}`)) {
|
|
2007
|
+
const stripped = dropboxPath.substring(this.rootPath.length + 1);
|
|
2008
|
+
return stripped || "/";
|
|
2009
|
+
}
|
|
2010
|
+
return dropboxPath || "/";
|
|
2011
|
+
}
|
|
2012
|
+
async createDirectory(virtualPath) {
|
|
2013
|
+
try {
|
|
2014
|
+
await this.ensureAuthenticated();
|
|
2015
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2016
|
+
const response = await this.dbx.filesCreateFolderV2({
|
|
2017
|
+
path: dbxPath,
|
|
2018
|
+
autorename: false
|
|
2019
|
+
});
|
|
2020
|
+
const metadata = response.result.metadata;
|
|
2021
|
+
const item = this.metadataToItem(
|
|
2022
|
+
{ ".tag": "folder", id: metadata.id, name: metadata.name, path_display: metadata.path_display, path_lower: metadata.path_lower },
|
|
2023
|
+
this.normalizePath(virtualPath)
|
|
2024
|
+
);
|
|
2025
|
+
return this.successResult(item);
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
if (error instanceof AuthenticationError) {
|
|
2028
|
+
return this.errorResult(error.message);
|
|
2029
|
+
}
|
|
2030
|
+
const errMsg = error.message || String(error);
|
|
2031
|
+
if (errMsg.includes("path/conflict")) {
|
|
2032
|
+
return this.errorResult(`Directory already exists: ${virtualPath}`);
|
|
2033
|
+
}
|
|
2034
|
+
return this.errorResult(`Failed to create directory: ${errMsg}`);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
async removeDirectory(virtualPath, recursive = false) {
|
|
2038
|
+
try {
|
|
2039
|
+
await this.ensureAuthenticated();
|
|
2040
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2041
|
+
if (!recursive) {
|
|
2042
|
+
const listResponse = await this.dbx.filesListFolder({
|
|
2043
|
+
path: dbxPath,
|
|
2044
|
+
limit: 1
|
|
2045
|
+
});
|
|
2046
|
+
if (listResponse.result.entries.length > 0) {
|
|
2047
|
+
return this.errorResult(`Directory is not empty: ${virtualPath}`);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
await this.dbx.filesDeleteV2({ path: dbxPath });
|
|
2051
|
+
return this.successResult();
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
const errMsg = error.message || String(error);
|
|
2054
|
+
if (errMsg.includes("path_lookup/not_found") || errMsg.includes("not_found")) {
|
|
2055
|
+
return this.errorResult(`Directory not found: ${virtualPath}`);
|
|
2056
|
+
}
|
|
2057
|
+
return this.errorResult(`Failed to remove directory: ${errMsg}`);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
async uploadFile(source, remotePath, options = {}) {
|
|
2061
|
+
try {
|
|
2062
|
+
await this.ensureAuthenticated();
|
|
2063
|
+
const normalized = this.normalizePath(remotePath);
|
|
2064
|
+
const dbxPath = this.toDropboxPath(remotePath);
|
|
2065
|
+
let contents;
|
|
2066
|
+
if (typeof source === "string") {
|
|
2067
|
+
const fs3 = await import("fs");
|
|
2068
|
+
contents = await fs3.promises.readFile(source);
|
|
2069
|
+
} else if (Buffer.isBuffer(source)) {
|
|
2070
|
+
contents = source;
|
|
2071
|
+
} else {
|
|
2072
|
+
const chunks = [];
|
|
2073
|
+
const reader = source.getReader();
|
|
2074
|
+
let done = false;
|
|
2075
|
+
while (!done) {
|
|
2076
|
+
const result = await reader.read();
|
|
2077
|
+
done = result.done;
|
|
2078
|
+
if (result.value) {
|
|
2079
|
+
chunks.push(result.value);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
contents = Buffer.concat(chunks);
|
|
2083
|
+
}
|
|
2084
|
+
if (contents.length > MAX_UPLOAD_SIZE) {
|
|
2085
|
+
throw new FileTooLargeError(this.getBaseName(remotePath), contents.length, MAX_UPLOAD_SIZE);
|
|
2086
|
+
}
|
|
2087
|
+
if (!options.overwrite) {
|
|
2088
|
+
try {
|
|
2089
|
+
await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2090
|
+
throw new FileExistsError(remotePath);
|
|
2091
|
+
} catch (err) {
|
|
2092
|
+
if (err instanceof FileExistsError) throw err;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
const response = await this.dbx.filesUpload({
|
|
2096
|
+
path: dbxPath,
|
|
2097
|
+
contents,
|
|
2098
|
+
mode: options.overwrite ? { ".tag": "overwrite" } : { ".tag": "add" },
|
|
2099
|
+
autorename: false
|
|
2100
|
+
});
|
|
2101
|
+
if (options.onProgress) {
|
|
2102
|
+
options.onProgress(100, contents.length, contents.length);
|
|
2103
|
+
}
|
|
2104
|
+
const metadata = response.result;
|
|
2105
|
+
const item = this.metadataToItem(
|
|
2106
|
+
{
|
|
2107
|
+
".tag": "file",
|
|
2108
|
+
id: metadata.id,
|
|
2109
|
+
name: metadata.name,
|
|
2110
|
+
path_display: metadata.path_display,
|
|
2111
|
+
path_lower: metadata.path_lower,
|
|
2112
|
+
size: metadata.size,
|
|
2113
|
+
client_modified: metadata.client_modified,
|
|
2114
|
+
server_modified: metadata.server_modified
|
|
2115
|
+
},
|
|
2116
|
+
normalized
|
|
2117
|
+
);
|
|
2118
|
+
return this.successResult(item);
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
if (error instanceof AuthenticationError || error instanceof FileExistsError || error instanceof FileTooLargeError) {
|
|
2121
|
+
return this.errorResult(error.message);
|
|
2122
|
+
}
|
|
2123
|
+
return this.errorResult(`Failed to upload file: ${error.message}`);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
async downloadFile(remotePath, localPath, options = {}) {
|
|
2127
|
+
try {
|
|
2128
|
+
await this.ensureAuthenticated();
|
|
2129
|
+
const dbxPath = this.toDropboxPath(remotePath);
|
|
2130
|
+
const response = await this.dbx.filesDownload({ path: dbxPath });
|
|
2131
|
+
const result = response.result;
|
|
2132
|
+
const buffer = Buffer.from(result.fileBinary);
|
|
2133
|
+
if (options.onProgress) {
|
|
2134
|
+
options.onProgress(100, buffer.length, buffer.length);
|
|
2135
|
+
}
|
|
2136
|
+
if (localPath) {
|
|
2137
|
+
const fs3 = await import("fs");
|
|
2138
|
+
const path3 = await import("path");
|
|
2139
|
+
await fs3.promises.mkdir(path3.dirname(localPath), { recursive: true });
|
|
2140
|
+
await fs3.promises.writeFile(localPath, buffer);
|
|
2141
|
+
return this.successResult(localPath);
|
|
2142
|
+
}
|
|
2143
|
+
return this.successResult(buffer);
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
const errMsg = error.message || String(error);
|
|
2146
|
+
if (errMsg.includes("path/not_found") || errMsg.includes("path_lookup/not_found")) {
|
|
2147
|
+
return this.errorResult(`File not found: ${remotePath}`);
|
|
2148
|
+
}
|
|
2149
|
+
return this.errorResult(`Failed to download file: ${errMsg}`);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
async moveItem(sourcePath, destinationPath, _options = {}) {
|
|
2153
|
+
try {
|
|
2154
|
+
await this.ensureAuthenticated();
|
|
2155
|
+
const fromPath = this.toDropboxPath(sourcePath);
|
|
2156
|
+
const toPath = this.toDropboxPath(destinationPath);
|
|
2157
|
+
const response = await this.dbx.filesMoveV2({
|
|
2158
|
+
from_path: fromPath,
|
|
2159
|
+
to_path: toPath,
|
|
2160
|
+
autorename: false
|
|
2161
|
+
});
|
|
2162
|
+
const metadata = response.result.metadata;
|
|
2163
|
+
const item = this.metadataToItem(metadata, this.normalizePath(destinationPath));
|
|
2164
|
+
return this.successResult(item);
|
|
2165
|
+
} catch (error) {
|
|
2166
|
+
const errMsg = error.message || String(error);
|
|
2167
|
+
if (errMsg.includes("not_found")) {
|
|
2168
|
+
return this.errorResult(`Item not found: ${sourcePath}`);
|
|
2169
|
+
}
|
|
2170
|
+
return this.errorResult(`Failed to move item: ${errMsg}`);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
async deleteFile(virtualPath) {
|
|
2174
|
+
try {
|
|
2175
|
+
await this.ensureAuthenticated();
|
|
2176
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2177
|
+
await this.dbx.filesDeleteV2({ path: dbxPath });
|
|
2178
|
+
return this.successResult();
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
const errMsg = error.message || String(error);
|
|
2181
|
+
if (errMsg.includes("not_found")) {
|
|
2182
|
+
return this.errorResult(`File not found: ${virtualPath}`);
|
|
2183
|
+
}
|
|
2184
|
+
return this.errorResult(`Failed to delete file: ${errMsg}`);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
async renameFile(virtualPath, newName, _options = {}) {
|
|
2188
|
+
try {
|
|
2189
|
+
await this.ensureAuthenticated();
|
|
2190
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
2191
|
+
const newVirtualPath = this.joinPath(parentPath, newName);
|
|
2192
|
+
const fromPath = this.toDropboxPath(virtualPath);
|
|
2193
|
+
const toPath = this.toDropboxPath(newVirtualPath);
|
|
2194
|
+
const response = await this.dbx.filesMoveV2({
|
|
2195
|
+
from_path: fromPath,
|
|
2196
|
+
to_path: toPath,
|
|
2197
|
+
autorename: false
|
|
2198
|
+
});
|
|
2199
|
+
const metadata = response.result.metadata;
|
|
2200
|
+
const item = this.metadataToItem(metadata, newVirtualPath);
|
|
2201
|
+
return this.successResult(item);
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
const errMsg = error.message || String(error);
|
|
2204
|
+
if (errMsg.includes("not_found")) {
|
|
2205
|
+
return this.errorResult(`File not found: ${virtualPath}`);
|
|
2206
|
+
}
|
|
2207
|
+
return this.errorResult(`Failed to rename file: ${errMsg}`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
async renameFolder(virtualPath, newName, _options = {}) {
|
|
2211
|
+
try {
|
|
2212
|
+
await this.ensureAuthenticated();
|
|
2213
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
2214
|
+
const newVirtualPath = this.joinPath(parentPath, newName);
|
|
2215
|
+
const fromPath = this.toDropboxPath(virtualPath);
|
|
2216
|
+
const toPath = this.toDropboxPath(newVirtualPath);
|
|
2217
|
+
const response = await this.dbx.filesMoveV2({
|
|
2218
|
+
from_path: fromPath,
|
|
2219
|
+
to_path: toPath,
|
|
2220
|
+
autorename: false
|
|
2221
|
+
});
|
|
2222
|
+
const metadata = response.result.metadata;
|
|
2223
|
+
const item = this.metadataToItem(metadata, newVirtualPath);
|
|
2224
|
+
return this.successResult(item);
|
|
2225
|
+
} catch (error) {
|
|
2226
|
+
const errMsg = error.message || String(error);
|
|
2227
|
+
if (errMsg.includes("not_found")) {
|
|
2228
|
+
return this.errorResult(`Folder not found: ${virtualPath}`);
|
|
2229
|
+
}
|
|
2230
|
+
return this.errorResult(`Failed to rename folder: ${errMsg}`);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
async listDirectory(virtualPath, options = {}) {
|
|
2234
|
+
try {
|
|
2235
|
+
await this.ensureAuthenticated();
|
|
2236
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2237
|
+
const items = [];
|
|
2238
|
+
let hasMore = true;
|
|
2239
|
+
let cursor;
|
|
2240
|
+
const firstResponse = await this.dbx.filesListFolder({
|
|
2241
|
+
path: dbxPath,
|
|
2242
|
+
limit: 100
|
|
2243
|
+
});
|
|
2244
|
+
let entries = firstResponse.result.entries;
|
|
2245
|
+
hasMore = firstResponse.result.has_more;
|
|
2246
|
+
cursor = firstResponse.result.cursor;
|
|
2247
|
+
const processEntries = async (entryList) => {
|
|
2248
|
+
for (const entry of entryList) {
|
|
2249
|
+
if (!options.includeHidden && entry.name.startsWith(".")) {
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
const itemPath = this.joinPath(virtualPath, entry.name);
|
|
2253
|
+
const item = this.metadataToItem(entry, itemPath);
|
|
2254
|
+
if (options.filter && !options.filter(item)) {
|
|
2255
|
+
continue;
|
|
2256
|
+
}
|
|
2257
|
+
items.push(item);
|
|
2258
|
+
if (options.recursive && entry[".tag"] === "folder") {
|
|
2259
|
+
const subResult = await this.listDirectory(itemPath, options);
|
|
2260
|
+
if (subResult.success && subResult.data) {
|
|
2261
|
+
items.push(...subResult.data);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
await processEntries(entries);
|
|
2267
|
+
while (hasMore && cursor) {
|
|
2268
|
+
const continueResponse = await this.dbx.filesListFolderContinue({ cursor });
|
|
2269
|
+
entries = continueResponse.result.entries;
|
|
2270
|
+
hasMore = continueResponse.result.has_more;
|
|
2271
|
+
cursor = continueResponse.result.cursor;
|
|
2272
|
+
await processEntries(entries);
|
|
2273
|
+
}
|
|
2274
|
+
return this.successResult(items);
|
|
2275
|
+
} catch (error) {
|
|
2276
|
+
const errMsg = error.message || String(error);
|
|
2277
|
+
if (errMsg.includes("path/not_found") || errMsg.includes("not_found")) {
|
|
2278
|
+
return this.errorResult(`Directory not found: ${virtualPath}`);
|
|
2279
|
+
}
|
|
2280
|
+
return this.errorResult(`Failed to list directory: ${errMsg}`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
async getItem(virtualPath) {
|
|
2284
|
+
try {
|
|
2285
|
+
await this.ensureAuthenticated();
|
|
2286
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2287
|
+
const response = await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2288
|
+
const metadata = response.result;
|
|
2289
|
+
const item = this.metadataToItem(metadata, this.normalizePath(virtualPath));
|
|
2290
|
+
return this.successResult(item);
|
|
2291
|
+
} catch (error) {
|
|
2292
|
+
const errMsg = error.message || String(error);
|
|
2293
|
+
if (errMsg.includes("not_found")) {
|
|
2294
|
+
return this.errorResult(`Item not found: ${virtualPath}`);
|
|
2295
|
+
}
|
|
2296
|
+
return this.errorResult(`Failed to get item: ${errMsg}`);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
async exists(virtualPath) {
|
|
2300
|
+
try {
|
|
2301
|
+
await this.ensureAuthenticated();
|
|
2302
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2303
|
+
await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2304
|
+
return true;
|
|
2305
|
+
} catch {
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
async getFolderTree(path3 = "/", depth = 3) {
|
|
2310
|
+
try {
|
|
2311
|
+
await this.ensureAuthenticated();
|
|
2312
|
+
return super.getFolderTree(path3, depth);
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
return this.errorResult(`Failed to get folder tree: ${error.message}`);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
function createDropboxModule() {
|
|
2319
|
+
return new DropboxModule();
|
|
2320
|
+
}
|
|
2321
|
+
|
|
1660
2322
|
// src/modules/index.ts
|
|
1661
2323
|
var moduleRegistry = {
|
|
1662
2324
|
local: createLocalModule,
|
|
1663
|
-
google_drive: createGoogleDriveModule
|
|
2325
|
+
google_drive: createGoogleDriveModule,
|
|
2326
|
+
dropbox: createDropboxModule
|
|
1664
2327
|
};
|
|
1665
2328
|
function getRegisteredProviders() {
|
|
1666
2329
|
return Object.keys(moduleRegistry);
|
|
@@ -2226,6 +2889,7 @@ var FileMetadataService = class {
|
|
|
2226
2889
|
if (input.scope_id !== void 0) record.scope_id = input.scope_id;
|
|
2227
2890
|
if (input.uploaded_by !== void 0) record.uploaded_by = input.uploaded_by;
|
|
2228
2891
|
if (input.original_filename !== void 0) record.original_filename = input.original_filename;
|
|
2892
|
+
if (input.content_tag !== void 0) record.content_tag = input.content_tag;
|
|
2229
2893
|
const results = await this.crud.insert(record);
|
|
2230
2894
|
this.logger?.debug?.("Recorded file upload", { path: input.file_path });
|
|
2231
2895
|
return results[0] || null;
|
|
@@ -3603,10 +4267,18 @@ function createNamingConventionService(crudService, options) {
|
|
|
3603
4267
|
|
|
3604
4268
|
// src/services/llm-extraction-service.ts
|
|
3605
4269
|
var LLMExtractionService = class {
|
|
3606
|
-
constructor(
|
|
3607
|
-
this.llmFactory =
|
|
4270
|
+
constructor(factoryConfig, defaultProvider = "gemini") {
|
|
4271
|
+
this.llmFactory = factoryConfig.create;
|
|
4272
|
+
this.cacheInvalidator = factoryConfig.invalidateCache;
|
|
3608
4273
|
this.defaultProvider = defaultProvider;
|
|
3609
4274
|
}
|
|
4275
|
+
/**
|
|
4276
|
+
* Invalidate the LLM prompt cache
|
|
4277
|
+
* Passthrough to hazo_llm_api's invalidate_prompt_cache when configured
|
|
4278
|
+
*/
|
|
4279
|
+
invalidatePromptCache(area, key) {
|
|
4280
|
+
this.cacheInvalidator?.(area, key);
|
|
4281
|
+
}
|
|
3610
4282
|
/**
|
|
3611
4283
|
* Extract data from a document
|
|
3612
4284
|
*
|
|
@@ -3733,8 +4405,8 @@ var LLMExtractionService = class {
|
|
|
3733
4405
|
};
|
|
3734
4406
|
}
|
|
3735
4407
|
};
|
|
3736
|
-
function createLLMExtractionService(
|
|
3737
|
-
return new LLMExtractionService(
|
|
4408
|
+
function createLLMExtractionService(factoryConfig, defaultProvider) {
|
|
4409
|
+
return new LLMExtractionService(factoryConfig, defaultProvider);
|
|
3738
4410
|
}
|
|
3739
4411
|
|
|
3740
4412
|
// src/common/naming-utils.ts
|
|
@@ -4060,10 +4732,11 @@ function generatePreviewName(pattern, userVariables, options = {}) {
|
|
|
4060
4732
|
|
|
4061
4733
|
// src/services/upload-extract-service.ts
|
|
4062
4734
|
var UploadExtractService = class {
|
|
4063
|
-
constructor(fileManager, namingService, extractionService) {
|
|
4735
|
+
constructor(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4064
4736
|
this.fileManager = fileManager;
|
|
4065
4737
|
this.namingService = namingService;
|
|
4066
4738
|
this.extractionService = extractionService;
|
|
4739
|
+
this.defaultContentTagConfig = defaultContentTagConfig;
|
|
4067
4740
|
}
|
|
4068
4741
|
/**
|
|
4069
4742
|
* Upload a file with optional extraction and naming convention
|
|
@@ -4140,11 +4813,12 @@ var UploadExtractService = class {
|
|
|
4140
4813
|
metadata.extraction_id = extractionData.id;
|
|
4141
4814
|
metadata.extraction_source = extractionData.source;
|
|
4142
4815
|
}
|
|
4816
|
+
const effectiveContentTagConfig = options.contentTagConfig ?? this.defaultContentTagConfig;
|
|
4817
|
+
const needsContentTagging = effectiveContentTagConfig?.content_tag_set_by_llm && this.extractionService && this.fileManager.isTrackingActive();
|
|
4143
4818
|
const uploadResult = await this.fileManager.uploadFile(source, fullPath, {
|
|
4144
4819
|
...options,
|
|
4145
4820
|
metadata,
|
|
4146
|
-
awaitRecording: !!extractionData
|
|
4147
|
-
// Await recording when extraction needs to be added
|
|
4821
|
+
awaitRecording: !!extractionData || !!needsContentTagging
|
|
4148
4822
|
});
|
|
4149
4823
|
if (!uploadResult.success) {
|
|
4150
4824
|
return {
|
|
@@ -4168,13 +4842,23 @@ var UploadExtractService = class {
|
|
|
4168
4842
|
);
|
|
4169
4843
|
}
|
|
4170
4844
|
}
|
|
4845
|
+
let contentTag;
|
|
4846
|
+
if (needsContentTagging && effectiveContentTagConfig) {
|
|
4847
|
+
contentTag = await this.performContentTagging(
|
|
4848
|
+
source,
|
|
4849
|
+
mimeType,
|
|
4850
|
+
effectiveContentTagConfig,
|
|
4851
|
+
fullPath
|
|
4852
|
+
);
|
|
4853
|
+
}
|
|
4171
4854
|
return {
|
|
4172
4855
|
success: true,
|
|
4173
4856
|
file: uploadResult.data,
|
|
4174
4857
|
extraction: extractionData,
|
|
4175
4858
|
generatedPath: fullPath,
|
|
4176
4859
|
generatedFolderPath: generatedFolderPath || void 0,
|
|
4177
|
-
originalFileName
|
|
4860
|
+
originalFileName,
|
|
4861
|
+
contentTag
|
|
4178
4862
|
};
|
|
4179
4863
|
} catch (error) {
|
|
4180
4864
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4272,6 +4956,76 @@ var UploadExtractService = class {
|
|
|
4272
4956
|
folderPath: folderPath || void 0
|
|
4273
4957
|
};
|
|
4274
4958
|
}
|
|
4959
|
+
/**
|
|
4960
|
+
* Perform content tagging via LLM extraction.
|
|
4961
|
+
* Calls the LLM with the configured prompt, extracts the specified field,
|
|
4962
|
+
* and writes it to the content_tag column.
|
|
4963
|
+
*/
|
|
4964
|
+
async performContentTagging(buffer, mimeType, config, filePath) {
|
|
4965
|
+
try {
|
|
4966
|
+
if (!this.extractionService) return void 0;
|
|
4967
|
+
const result = await this.extractionService.extract(buffer, mimeType, {
|
|
4968
|
+
promptArea: config.content_tag_prompt_area,
|
|
4969
|
+
promptKey: config.content_tag_prompt_key,
|
|
4970
|
+
promptVariables: config.content_tag_prompt_variables
|
|
4971
|
+
});
|
|
4972
|
+
if (!result.success || !result.data) return void 0;
|
|
4973
|
+
const tagValue = result.data[config.content_tag_prompt_return_fieldname];
|
|
4974
|
+
if (typeof tagValue !== "string" || !tagValue) return void 0;
|
|
4975
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4976
|
+
if (metadataService) {
|
|
4977
|
+
const storageType = this.fileManager.getProvider() || "local";
|
|
4978
|
+
const record = await metadataService.findByPath(filePath, storageType);
|
|
4979
|
+
if (record) {
|
|
4980
|
+
await metadataService.updateFields(record.id, { content_tag: tagValue });
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
return tagValue;
|
|
4984
|
+
} catch {
|
|
4985
|
+
return void 0;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
/**
|
|
4989
|
+
* Manually tag a file's content via LLM.
|
|
4990
|
+
* Works with existing DB records, resolving the file path internally.
|
|
4991
|
+
*
|
|
4992
|
+
* @param fileId - Database record ID of the file
|
|
4993
|
+
* @param config - Content tag config (falls back to default if not provided)
|
|
4994
|
+
* @returns OperationResult with the tag value
|
|
4995
|
+
*/
|
|
4996
|
+
async tagFileContent(fileId, config) {
|
|
4997
|
+
const effectiveConfig = config ?? this.defaultContentTagConfig;
|
|
4998
|
+
if (!effectiveConfig || !effectiveConfig.content_tag_set_by_llm) {
|
|
4999
|
+
return { success: false, error: "Content tagging is not configured or disabled" };
|
|
5000
|
+
}
|
|
5001
|
+
if (!this.extractionService) {
|
|
5002
|
+
return { success: false, error: "Extraction service not available" };
|
|
5003
|
+
}
|
|
5004
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
5005
|
+
if (!metadataService) {
|
|
5006
|
+
return { success: false, error: "Metadata service not available (tracking not enabled)" };
|
|
5007
|
+
}
|
|
5008
|
+
const record = await metadataService.findById(fileId);
|
|
5009
|
+
if (!record) {
|
|
5010
|
+
return { success: false, error: `File record not found: ${fileId}` };
|
|
5011
|
+
}
|
|
5012
|
+
const downloadResult = await this.fileManager.downloadFile(record.file_path);
|
|
5013
|
+
if (!downloadResult.success || !downloadResult.data) {
|
|
5014
|
+
return { success: false, error: `Failed to download file: ${downloadResult.error}` };
|
|
5015
|
+
}
|
|
5016
|
+
const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : Buffer.from(downloadResult.data);
|
|
5017
|
+
const mimeType = getMimeType(record.filename);
|
|
5018
|
+
const tagValue = await this.performContentTagging(
|
|
5019
|
+
buffer,
|
|
5020
|
+
mimeType,
|
|
5021
|
+
effectiveConfig,
|
|
5022
|
+
record.file_path
|
|
5023
|
+
);
|
|
5024
|
+
if (!tagValue) {
|
|
5025
|
+
return { success: false, error: "Content tagging did not produce a result" };
|
|
5026
|
+
}
|
|
5027
|
+
return { success: true, data: tagValue };
|
|
5028
|
+
}
|
|
4275
5029
|
/**
|
|
4276
5030
|
* Get the file manager
|
|
4277
5031
|
*/
|
|
@@ -4291,8 +5045,8 @@ var UploadExtractService = class {
|
|
|
4291
5045
|
return this.extractionService;
|
|
4292
5046
|
}
|
|
4293
5047
|
};
|
|
4294
|
-
function createUploadExtractService(fileManager, namingService, extractionService) {
|
|
4295
|
-
return new UploadExtractService(fileManager, namingService, extractionService);
|
|
5048
|
+
function createUploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
5049
|
+
return new UploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig);
|
|
4296
5050
|
}
|
|
4297
5051
|
|
|
4298
5052
|
// src/server/factory.ts
|
|
@@ -4308,7 +5062,8 @@ async function createHazoFilesServer(options = {}) {
|
|
|
4308
5062
|
metadataTableName = "hazo_files",
|
|
4309
5063
|
namingTableName = "hazo_files_naming",
|
|
4310
5064
|
enableTracking = !!crudService,
|
|
4311
|
-
trackDownloads = true
|
|
5065
|
+
trackDownloads = true,
|
|
5066
|
+
defaultContentTagConfig
|
|
4312
5067
|
} = options;
|
|
4313
5068
|
const fileManagerOptions = {
|
|
4314
5069
|
config,
|
|
@@ -4337,14 +5092,17 @@ async function createHazoFilesServer(options = {}) {
|
|
|
4337
5092
|
const uploadExtractService = new UploadExtractService(
|
|
4338
5093
|
fileManager,
|
|
4339
5094
|
namingService || void 0,
|
|
4340
|
-
extractionService || void 0
|
|
5095
|
+
extractionService || void 0,
|
|
5096
|
+
defaultContentTagConfig
|
|
4341
5097
|
);
|
|
5098
|
+
const invalidatePromptCache = extractionService ? (area, key) => extractionService.invalidatePromptCache(area, key) : void 0;
|
|
4342
5099
|
return {
|
|
4343
5100
|
fileManager,
|
|
4344
5101
|
metadataService,
|
|
4345
5102
|
namingService,
|
|
4346
5103
|
extractionService,
|
|
4347
|
-
uploadExtractService
|
|
5104
|
+
uploadExtractService,
|
|
5105
|
+
invalidatePromptCache
|
|
4348
5106
|
};
|
|
4349
5107
|
}
|
|
4350
5108
|
async function createBasicFileManager(config, crudService) {
|
|
@@ -4383,7 +5141,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4383
5141
|
uploaded_by TEXT,
|
|
4384
5142
|
storage_verified_at TEXT,
|
|
4385
5143
|
deleted_at TEXT,
|
|
4386
|
-
original_filename TEXT
|
|
5144
|
+
original_filename TEXT,
|
|
5145
|
+
content_tag TEXT
|
|
4387
5146
|
)`,
|
|
4388
5147
|
indexes: [
|
|
4389
5148
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4393,7 +5152,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4393
5152
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4394
5153
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4395
5154
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4396
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
5155
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
5156
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4397
5157
|
]
|
|
4398
5158
|
},
|
|
4399
5159
|
postgres: {
|
|
@@ -4416,7 +5176,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4416
5176
|
uploaded_by UUID,
|
|
4417
5177
|
storage_verified_at TIMESTAMP WITH TIME ZONE,
|
|
4418
5178
|
deleted_at TIMESTAMP WITH TIME ZONE,
|
|
4419
|
-
original_filename TEXT
|
|
5179
|
+
original_filename TEXT,
|
|
5180
|
+
content_tag TEXT
|
|
4420
5181
|
)`,
|
|
4421
5182
|
indexes: [
|
|
4422
5183
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4426,7 +5187,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4426
5187
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4427
5188
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4428
5189
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4429
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
5190
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
5191
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4430
5192
|
]
|
|
4431
5193
|
},
|
|
4432
5194
|
columns: [
|
|
@@ -4448,7 +5210,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4448
5210
|
"uploaded_by",
|
|
4449
5211
|
"storage_verified_at",
|
|
4450
5212
|
"deleted_at",
|
|
4451
|
-
"original_filename"
|
|
5213
|
+
"original_filename",
|
|
5214
|
+
"content_tag"
|
|
4452
5215
|
]
|
|
4453
5216
|
};
|
|
4454
5217
|
function getSchemaForTable(tableName, dbType) {
|
|
@@ -4589,6 +5352,45 @@ function getNamingSchemaForTable(tableName, dbType) {
|
|
|
4589
5352
|
)
|
|
4590
5353
|
};
|
|
4591
5354
|
}
|
|
5355
|
+
var HAZO_FILES_MIGRATION_V3 = {
|
|
5356
|
+
tableName: HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
5357
|
+
sqlite: {
|
|
5358
|
+
alterStatements: [
|
|
5359
|
+
"ALTER TABLE hazo_files ADD COLUMN content_tag TEXT"
|
|
5360
|
+
],
|
|
5361
|
+
indexes: [
|
|
5362
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
5363
|
+
],
|
|
5364
|
+
backfill: ""
|
|
5365
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
5366
|
+
},
|
|
5367
|
+
postgres: {
|
|
5368
|
+
alterStatements: [
|
|
5369
|
+
"ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT"
|
|
5370
|
+
],
|
|
5371
|
+
indexes: [
|
|
5372
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
5373
|
+
],
|
|
5374
|
+
backfill: ""
|
|
5375
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
5376
|
+
},
|
|
5377
|
+
newColumns: [
|
|
5378
|
+
"content_tag"
|
|
5379
|
+
]
|
|
5380
|
+
};
|
|
5381
|
+
function getMigrationV3ForTable(tableName, dbType) {
|
|
5382
|
+
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5383
|
+
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5384
|
+
return {
|
|
5385
|
+
alterStatements: migration.alterStatements.map(
|
|
5386
|
+
(stmt) => stmt.replace(new RegExp(defaultName, "g"), tableName)
|
|
5387
|
+
),
|
|
5388
|
+
indexes: migration.indexes.map(
|
|
5389
|
+
(idx) => idx.replace(new RegExp(defaultName, "g"), tableName)
|
|
5390
|
+
),
|
|
5391
|
+
backfill: migration.backfill
|
|
5392
|
+
};
|
|
5393
|
+
}
|
|
4592
5394
|
|
|
4593
5395
|
// src/migrations/add-reference-tracking.ts
|
|
4594
5396
|
async function migrateToV2(executor, dbType, tableName) {
|
|
@@ -4608,6 +5410,20 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4608
5410
|
await executor.run(migration.backfill);
|
|
4609
5411
|
}
|
|
4610
5412
|
|
|
5413
|
+
// src/migrations/add-content-tag.ts
|
|
5414
|
+
async function migrateToV3(executor, dbType, tableName) {
|
|
5415
|
+
const migration = tableName ? getMigrationV3ForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V3[dbType];
|
|
5416
|
+
for (const stmt of migration.alterStatements) {
|
|
5417
|
+
try {
|
|
5418
|
+
await executor.run(stmt);
|
|
5419
|
+
} catch {
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
for (const idx of migration.indexes) {
|
|
5423
|
+
await executor.run(idx);
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
|
|
4611
5427
|
// src/server/index.ts
|
|
4612
5428
|
try {
|
|
4613
5429
|
__require("server-only");
|
|
@@ -4621,6 +5437,8 @@ export {
|
|
|
4621
5437
|
DirectoryExistsError,
|
|
4622
5438
|
DirectoryNotEmptyError,
|
|
4623
5439
|
DirectoryNotFoundError,
|
|
5440
|
+
DropboxAuth,
|
|
5441
|
+
DropboxModule,
|
|
4624
5442
|
FileExistsError,
|
|
4625
5443
|
FileManager,
|
|
4626
5444
|
FileMetadataService,
|
|
@@ -4630,6 +5448,7 @@ export {
|
|
|
4630
5448
|
GoogleDriveModule,
|
|
4631
5449
|
HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4632
5450
|
HAZO_FILES_MIGRATION_V2,
|
|
5451
|
+
HAZO_FILES_MIGRATION_V3,
|
|
4633
5452
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
4634
5453
|
HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
4635
5454
|
HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -4657,6 +5476,8 @@ export {
|
|
|
4657
5476
|
computeFileInfo,
|
|
4658
5477
|
createAndInitializeModule,
|
|
4659
5478
|
createBasicFileManager,
|
|
5479
|
+
createDropboxAuth,
|
|
5480
|
+
createDropboxModule,
|
|
4660
5481
|
createEmptyFileDataStructure,
|
|
4661
5482
|
createEmptyNamingRuleSchema,
|
|
4662
5483
|
createFileItem,
|
|
@@ -4701,6 +5522,7 @@ export {
|
|
|
4701
5522
|
getFileMetadataValues,
|
|
4702
5523
|
getMergedData,
|
|
4703
5524
|
getMigrationForTable,
|
|
5525
|
+
getMigrationV3ForTable,
|
|
4704
5526
|
getMimeType,
|
|
4705
5527
|
getNameWithoutExtension,
|
|
4706
5528
|
getNamingSchemaForTable,
|
|
@@ -4733,6 +5555,7 @@ export {
|
|
|
4733
5555
|
loadConfig,
|
|
4734
5556
|
loadConfigAsync,
|
|
4735
5557
|
migrateToV2,
|
|
5558
|
+
migrateToV3,
|
|
4736
5559
|
normalizePath,
|
|
4737
5560
|
parseConfig,
|
|
4738
5561
|
parseFileData,
|