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.mjs
CHANGED
|
@@ -26,6 +26,16 @@ function parseConfig(configContent) {
|
|
|
26
26
|
rootFolderId: parsed.google_drive.root_folder_id || process.env.HAZO_GOOGLE_DRIVE_ROOT_FOLDER_ID
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
if (parsed.dropbox) {
|
|
30
|
+
config.dropbox = {
|
|
31
|
+
clientId: parsed.dropbox.client_id || process.env.HAZO_DROPBOX_CLIENT_ID || "",
|
|
32
|
+
clientSecret: parsed.dropbox.client_secret || process.env.HAZO_DROPBOX_CLIENT_SECRET || "",
|
|
33
|
+
redirectUri: parsed.dropbox.redirect_uri || process.env.HAZO_DROPBOX_REDIRECT_URI || "",
|
|
34
|
+
refreshToken: parsed.dropbox.refresh_token || process.env.HAZO_DROPBOX_REFRESH_TOKEN,
|
|
35
|
+
accessToken: parsed.dropbox.access_token || process.env.HAZO_DROPBOX_ACCESS_TOKEN,
|
|
36
|
+
rootPath: parsed.dropbox.root_path || process.env.HAZO_DROPBOX_ROOT_PATH
|
|
37
|
+
};
|
|
38
|
+
}
|
|
29
39
|
return config;
|
|
30
40
|
}
|
|
31
41
|
function loadConfig(configPath) {
|
|
@@ -87,6 +97,18 @@ refresh_token =
|
|
|
87
97
|
access_token =
|
|
88
98
|
; Optional: Root folder ID to use as base (empty = root of Drive)
|
|
89
99
|
root_folder_id =
|
|
100
|
+
|
|
101
|
+
[dropbox]
|
|
102
|
+
; Dropbox OAuth credentials
|
|
103
|
+
; These can also be set via environment variables:
|
|
104
|
+
; HAZO_DROPBOX_CLIENT_ID, HAZO_DROPBOX_CLIENT_SECRET, etc.
|
|
105
|
+
client_id =
|
|
106
|
+
client_secret =
|
|
107
|
+
redirect_uri = http://localhost:3000/api/auth/dropbox/callback
|
|
108
|
+
refresh_token =
|
|
109
|
+
access_token =
|
|
110
|
+
; Optional: Root path to use as base (empty = root of Dropbox)
|
|
111
|
+
root_path =
|
|
90
112
|
`;
|
|
91
113
|
}
|
|
92
114
|
async function saveConfig(config, configPath) {
|
|
@@ -113,6 +135,16 @@ async function saveConfig(config, configPath) {
|
|
|
113
135
|
root_folder_id: config.google_drive.rootFolderId || ""
|
|
114
136
|
};
|
|
115
137
|
}
|
|
138
|
+
if (config.dropbox) {
|
|
139
|
+
iniConfig.dropbox = {
|
|
140
|
+
client_id: config.dropbox.clientId || "",
|
|
141
|
+
client_secret: config.dropbox.clientSecret || "",
|
|
142
|
+
redirect_uri: config.dropbox.redirectUri || "",
|
|
143
|
+
refresh_token: config.dropbox.refreshToken || "",
|
|
144
|
+
access_token: config.dropbox.accessToken || "",
|
|
145
|
+
root_path: config.dropbox.rootPath || ""
|
|
146
|
+
};
|
|
147
|
+
}
|
|
116
148
|
const content = ini.stringify(iniConfig);
|
|
117
149
|
await fs.promises.writeFile(resolvedPath, content, "utf-8");
|
|
118
150
|
}
|
|
@@ -1650,10 +1682,641 @@ function createGoogleDriveModule() {
|
|
|
1650
1682
|
return new GoogleDriveModule();
|
|
1651
1683
|
}
|
|
1652
1684
|
|
|
1685
|
+
// src/modules/dropbox/index.ts
|
|
1686
|
+
import { Dropbox } from "dropbox";
|
|
1687
|
+
|
|
1688
|
+
// src/modules/dropbox/auth.ts
|
|
1689
|
+
var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
|
|
1690
|
+
var DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
|
|
1691
|
+
var DROPBOX_REVOKE_URL = "https://api.dropboxapi.com/2/auth/token/revoke";
|
|
1692
|
+
var DropboxAuth = class {
|
|
1693
|
+
constructor(config, callbacks = {}) {
|
|
1694
|
+
this.tokens = null;
|
|
1695
|
+
this.config = config;
|
|
1696
|
+
this.callbacks = callbacks;
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Generate the authorization URL for OAuth consent
|
|
1700
|
+
*/
|
|
1701
|
+
getAuthUrl(state) {
|
|
1702
|
+
const params = new URLSearchParams({
|
|
1703
|
+
client_id: this.config.clientId,
|
|
1704
|
+
redirect_uri: this.config.redirectUri,
|
|
1705
|
+
response_type: "code",
|
|
1706
|
+
token_access_type: "offline"
|
|
1707
|
+
});
|
|
1708
|
+
if (state) {
|
|
1709
|
+
params.set("state", state);
|
|
1710
|
+
}
|
|
1711
|
+
return `${DROPBOX_AUTH_URL}?${params.toString()}`;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Exchange authorization code for tokens
|
|
1715
|
+
*/
|
|
1716
|
+
async exchangeCodeForTokens(code) {
|
|
1717
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
1718
|
+
method: "POST",
|
|
1719
|
+
headers: {
|
|
1720
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1721
|
+
},
|
|
1722
|
+
body: new URLSearchParams({
|
|
1723
|
+
code,
|
|
1724
|
+
grant_type: "authorization_code",
|
|
1725
|
+
client_id: this.config.clientId,
|
|
1726
|
+
client_secret: this.config.clientSecret,
|
|
1727
|
+
redirect_uri: this.config.redirectUri
|
|
1728
|
+
})
|
|
1729
|
+
});
|
|
1730
|
+
if (!response.ok) {
|
|
1731
|
+
const errorData = await response.text();
|
|
1732
|
+
throw new Error(`Failed to exchange code for tokens: ${errorData}`);
|
|
1733
|
+
}
|
|
1734
|
+
const data = await response.json();
|
|
1735
|
+
this.tokens = {
|
|
1736
|
+
accessToken: data.access_token,
|
|
1737
|
+
refreshToken: data.refresh_token,
|
|
1738
|
+
expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
1739
|
+
};
|
|
1740
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1741
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1742
|
+
}
|
|
1743
|
+
return this.tokens;
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Set tokens directly (e.g., from stored tokens)
|
|
1747
|
+
*/
|
|
1748
|
+
async setTokens(tokens) {
|
|
1749
|
+
this.tokens = tokens;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Load tokens from storage using callback
|
|
1753
|
+
*/
|
|
1754
|
+
async loadStoredTokens() {
|
|
1755
|
+
if (!this.callbacks.getStoredTokens) {
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
const tokens = await this.callbacks.getStoredTokens();
|
|
1759
|
+
if (tokens) {
|
|
1760
|
+
await this.setTokens(tokens);
|
|
1761
|
+
return true;
|
|
1762
|
+
}
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Check if authenticated
|
|
1767
|
+
*/
|
|
1768
|
+
isAuthenticated() {
|
|
1769
|
+
return this.tokens !== null && !!this.tokens.accessToken;
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Get current tokens
|
|
1773
|
+
*/
|
|
1774
|
+
getTokens() {
|
|
1775
|
+
return this.tokens;
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Get current access token
|
|
1779
|
+
*/
|
|
1780
|
+
getAccessToken() {
|
|
1781
|
+
return this.tokens?.accessToken ?? null;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Refresh the access token
|
|
1785
|
+
*/
|
|
1786
|
+
async refreshAccessToken() {
|
|
1787
|
+
if (!this.tokens?.refreshToken) {
|
|
1788
|
+
throw new Error("No refresh token available");
|
|
1789
|
+
}
|
|
1790
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
1791
|
+
method: "POST",
|
|
1792
|
+
headers: {
|
|
1793
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1794
|
+
},
|
|
1795
|
+
body: new URLSearchParams({
|
|
1796
|
+
grant_type: "refresh_token",
|
|
1797
|
+
refresh_token: this.tokens.refreshToken,
|
|
1798
|
+
client_id: this.config.clientId,
|
|
1799
|
+
client_secret: this.config.clientSecret
|
|
1800
|
+
})
|
|
1801
|
+
});
|
|
1802
|
+
if (!response.ok) {
|
|
1803
|
+
const errorData = await response.text();
|
|
1804
|
+
throw new Error(`Failed to refresh token: ${errorData}`);
|
|
1805
|
+
}
|
|
1806
|
+
const data = await response.json();
|
|
1807
|
+
this.tokens = {
|
|
1808
|
+
...this.tokens,
|
|
1809
|
+
accessToken: data.access_token,
|
|
1810
|
+
expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
1811
|
+
};
|
|
1812
|
+
if (this.callbacks.onTokensUpdated) {
|
|
1813
|
+
await this.callbacks.onTokensUpdated(this.tokens);
|
|
1814
|
+
}
|
|
1815
|
+
return this.tokens;
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Revoke access (disconnect)
|
|
1819
|
+
*/
|
|
1820
|
+
async revokeAccess() {
|
|
1821
|
+
if (this.tokens?.accessToken) {
|
|
1822
|
+
try {
|
|
1823
|
+
await fetch(DROPBOX_REVOKE_URL, {
|
|
1824
|
+
method: "POST",
|
|
1825
|
+
headers: {
|
|
1826
|
+
Authorization: `Bearer ${this.tokens.accessToken}`
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
this.tokens = null;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Check if token is expired or will expire soon
|
|
1836
|
+
*/
|
|
1837
|
+
isTokenExpired(bufferSeconds = 300) {
|
|
1838
|
+
if (!this.tokens?.expiryDate) {
|
|
1839
|
+
return false;
|
|
1840
|
+
}
|
|
1841
|
+
const now = Date.now();
|
|
1842
|
+
const expiry = this.tokens.expiryDate;
|
|
1843
|
+
return now >= expiry - bufferSeconds * 1e3;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Ensure valid access token (refresh if needed)
|
|
1847
|
+
*/
|
|
1848
|
+
async ensureValidToken() {
|
|
1849
|
+
if (this.isTokenExpired()) {
|
|
1850
|
+
await this.refreshAccessToken();
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
function createDropboxAuth(config, callbacks) {
|
|
1855
|
+
return new DropboxAuth(config, callbacks);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// src/modules/dropbox/index.ts
|
|
1859
|
+
var MAX_UPLOAD_SIZE = 150 * 1024 * 1024;
|
|
1860
|
+
var DropboxModule = class extends BaseStorageModule {
|
|
1861
|
+
constructor() {
|
|
1862
|
+
super(...arguments);
|
|
1863
|
+
this.provider = "dropbox";
|
|
1864
|
+
this.auth = null;
|
|
1865
|
+
this.dbx = null;
|
|
1866
|
+
this.rootPath = "";
|
|
1867
|
+
this.authCallbacks = {};
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Set authentication callbacks for token persistence
|
|
1871
|
+
*/
|
|
1872
|
+
setAuthCallbacks(callbacks) {
|
|
1873
|
+
this.authCallbacks = callbacks;
|
|
1874
|
+
}
|
|
1875
|
+
async initialize(config) {
|
|
1876
|
+
await super.initialize(config);
|
|
1877
|
+
const dropboxConfig = this.getProviderConfig();
|
|
1878
|
+
if (!dropboxConfig.clientId || !dropboxConfig.clientSecret) {
|
|
1879
|
+
throw new AuthenticationError("dropbox", "Missing client ID or client secret");
|
|
1880
|
+
}
|
|
1881
|
+
this.auth = createDropboxAuth(
|
|
1882
|
+
{
|
|
1883
|
+
clientId: dropboxConfig.clientId,
|
|
1884
|
+
clientSecret: dropboxConfig.clientSecret,
|
|
1885
|
+
redirectUri: dropboxConfig.redirectUri
|
|
1886
|
+
},
|
|
1887
|
+
this.authCallbacks
|
|
1888
|
+
);
|
|
1889
|
+
if (dropboxConfig.refreshToken || dropboxConfig.accessToken) {
|
|
1890
|
+
await this.auth.setTokens({
|
|
1891
|
+
accessToken: dropboxConfig.accessToken || "",
|
|
1892
|
+
refreshToken: dropboxConfig.refreshToken || ""
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
this.rootPath = dropboxConfig.rootPath || "";
|
|
1896
|
+
this.createDropboxClient();
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Create/recreate the Dropbox SDK client with the current access token
|
|
1900
|
+
*/
|
|
1901
|
+
createDropboxClient() {
|
|
1902
|
+
const accessToken = this.auth?.getAccessToken();
|
|
1903
|
+
if (accessToken) {
|
|
1904
|
+
this.dbx = new Dropbox({ accessToken });
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Get the auth instance for OAuth flow
|
|
1909
|
+
*/
|
|
1910
|
+
getAuth() {
|
|
1911
|
+
if (!this.auth) {
|
|
1912
|
+
throw new AuthenticationError("dropbox", "Module not initialized");
|
|
1913
|
+
}
|
|
1914
|
+
return this.auth;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Check if user is authenticated
|
|
1918
|
+
*/
|
|
1919
|
+
isAuthenticated() {
|
|
1920
|
+
return this.auth?.isAuthenticated() ?? false;
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Authenticate with provided tokens
|
|
1924
|
+
*/
|
|
1925
|
+
async authenticate(tokens) {
|
|
1926
|
+
if (!this.auth) {
|
|
1927
|
+
throw new AuthenticationError("dropbox", "Module not initialized");
|
|
1928
|
+
}
|
|
1929
|
+
await this.auth.setTokens(tokens);
|
|
1930
|
+
this.createDropboxClient();
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Ensure authenticated before operations
|
|
1934
|
+
*/
|
|
1935
|
+
async ensureAuthenticated() {
|
|
1936
|
+
this.ensureInitialized();
|
|
1937
|
+
if (!this.isAuthenticated()) {
|
|
1938
|
+
throw new AuthenticationError("dropbox", "Not authenticated. Please connect your Dropbox.");
|
|
1939
|
+
}
|
|
1940
|
+
await this.auth.ensureValidToken();
|
|
1941
|
+
this.createDropboxClient();
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Convert virtual path to Dropbox path
|
|
1945
|
+
* Virtual: /folder/file.txt -> Dropbox: /folder/file.txt (or /rootPath/folder/file.txt)
|
|
1946
|
+
* Dropbox root is empty string "", not "/"
|
|
1947
|
+
*/
|
|
1948
|
+
toDropboxPath(virtualPath) {
|
|
1949
|
+
const normalized = this.normalizePath(virtualPath);
|
|
1950
|
+
if (this.rootPath) {
|
|
1951
|
+
if (normalized === "/") {
|
|
1952
|
+
return `/${this.rootPath}`;
|
|
1953
|
+
}
|
|
1954
|
+
return `/${this.rootPath}${normalized}`;
|
|
1955
|
+
}
|
|
1956
|
+
if (normalized === "/") {
|
|
1957
|
+
return "";
|
|
1958
|
+
}
|
|
1959
|
+
return normalized;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Convert Dropbox metadata to FileSystemItem
|
|
1963
|
+
*/
|
|
1964
|
+
metadataToItem(entry, virtualPath) {
|
|
1965
|
+
const isFolder2 = entry[".tag"] === "folder";
|
|
1966
|
+
const path3 = virtualPath || this.toVirtualPath(entry.path_display || entry.name);
|
|
1967
|
+
if (isFolder2) {
|
|
1968
|
+
return createFolderItem({
|
|
1969
|
+
id: entry.id,
|
|
1970
|
+
name: entry.name,
|
|
1971
|
+
path: path3,
|
|
1972
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1973
|
+
modifiedAt: /* @__PURE__ */ new Date(),
|
|
1974
|
+
metadata: {
|
|
1975
|
+
dropboxId: entry.id,
|
|
1976
|
+
pathDisplay: entry.path_display
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
const fileEntry = entry;
|
|
1981
|
+
return createFileItem({
|
|
1982
|
+
id: fileEntry.id,
|
|
1983
|
+
name: fileEntry.name,
|
|
1984
|
+
path: path3,
|
|
1985
|
+
size: fileEntry.size,
|
|
1986
|
+
mimeType: getMimeType(fileEntry.name),
|
|
1987
|
+
createdAt: new Date(fileEntry.client_modified),
|
|
1988
|
+
modifiedAt: new Date(fileEntry.server_modified),
|
|
1989
|
+
metadata: {
|
|
1990
|
+
dropboxId: fileEntry.id,
|
|
1991
|
+
pathDisplay: fileEntry.path_display
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Convert a Dropbox path_display to virtual path
|
|
1997
|
+
*/
|
|
1998
|
+
toVirtualPath(dropboxPath) {
|
|
1999
|
+
if (this.rootPath && dropboxPath.toLowerCase().startsWith(`/${this.rootPath.toLowerCase()}`)) {
|
|
2000
|
+
const stripped = dropboxPath.substring(this.rootPath.length + 1);
|
|
2001
|
+
return stripped || "/";
|
|
2002
|
+
}
|
|
2003
|
+
return dropboxPath || "/";
|
|
2004
|
+
}
|
|
2005
|
+
async createDirectory(virtualPath) {
|
|
2006
|
+
try {
|
|
2007
|
+
await this.ensureAuthenticated();
|
|
2008
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2009
|
+
const response = await this.dbx.filesCreateFolderV2({
|
|
2010
|
+
path: dbxPath,
|
|
2011
|
+
autorename: false
|
|
2012
|
+
});
|
|
2013
|
+
const metadata = response.result.metadata;
|
|
2014
|
+
const item = this.metadataToItem(
|
|
2015
|
+
{ ".tag": "folder", id: metadata.id, name: metadata.name, path_display: metadata.path_display, path_lower: metadata.path_lower },
|
|
2016
|
+
this.normalizePath(virtualPath)
|
|
2017
|
+
);
|
|
2018
|
+
return this.successResult(item);
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
if (error instanceof AuthenticationError) {
|
|
2021
|
+
return this.errorResult(error.message);
|
|
2022
|
+
}
|
|
2023
|
+
const errMsg = error.message || String(error);
|
|
2024
|
+
if (errMsg.includes("path/conflict")) {
|
|
2025
|
+
return this.errorResult(`Directory already exists: ${virtualPath}`);
|
|
2026
|
+
}
|
|
2027
|
+
return this.errorResult(`Failed to create directory: ${errMsg}`);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
async removeDirectory(virtualPath, recursive = false) {
|
|
2031
|
+
try {
|
|
2032
|
+
await this.ensureAuthenticated();
|
|
2033
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2034
|
+
if (!recursive) {
|
|
2035
|
+
const listResponse = await this.dbx.filesListFolder({
|
|
2036
|
+
path: dbxPath,
|
|
2037
|
+
limit: 1
|
|
2038
|
+
});
|
|
2039
|
+
if (listResponse.result.entries.length > 0) {
|
|
2040
|
+
return this.errorResult(`Directory is not empty: ${virtualPath}`);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
await this.dbx.filesDeleteV2({ path: dbxPath });
|
|
2044
|
+
return this.successResult();
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
const errMsg = error.message || String(error);
|
|
2047
|
+
if (errMsg.includes("path_lookup/not_found") || errMsg.includes("not_found")) {
|
|
2048
|
+
return this.errorResult(`Directory not found: ${virtualPath}`);
|
|
2049
|
+
}
|
|
2050
|
+
return this.errorResult(`Failed to remove directory: ${errMsg}`);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
async uploadFile(source, remotePath, options = {}) {
|
|
2054
|
+
try {
|
|
2055
|
+
await this.ensureAuthenticated();
|
|
2056
|
+
const normalized = this.normalizePath(remotePath);
|
|
2057
|
+
const dbxPath = this.toDropboxPath(remotePath);
|
|
2058
|
+
let contents;
|
|
2059
|
+
if (typeof source === "string") {
|
|
2060
|
+
const fs3 = await import("fs");
|
|
2061
|
+
contents = await fs3.promises.readFile(source);
|
|
2062
|
+
} else if (Buffer.isBuffer(source)) {
|
|
2063
|
+
contents = source;
|
|
2064
|
+
} else {
|
|
2065
|
+
const chunks = [];
|
|
2066
|
+
const reader = source.getReader();
|
|
2067
|
+
let done = false;
|
|
2068
|
+
while (!done) {
|
|
2069
|
+
const result = await reader.read();
|
|
2070
|
+
done = result.done;
|
|
2071
|
+
if (result.value) {
|
|
2072
|
+
chunks.push(result.value);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
contents = Buffer.concat(chunks);
|
|
2076
|
+
}
|
|
2077
|
+
if (contents.length > MAX_UPLOAD_SIZE) {
|
|
2078
|
+
throw new FileTooLargeError(this.getBaseName(remotePath), contents.length, MAX_UPLOAD_SIZE);
|
|
2079
|
+
}
|
|
2080
|
+
if (!options.overwrite) {
|
|
2081
|
+
try {
|
|
2082
|
+
await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2083
|
+
throw new FileExistsError(remotePath);
|
|
2084
|
+
} catch (err) {
|
|
2085
|
+
if (err instanceof FileExistsError) throw err;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
const response = await this.dbx.filesUpload({
|
|
2089
|
+
path: dbxPath,
|
|
2090
|
+
contents,
|
|
2091
|
+
mode: options.overwrite ? { ".tag": "overwrite" } : { ".tag": "add" },
|
|
2092
|
+
autorename: false
|
|
2093
|
+
});
|
|
2094
|
+
if (options.onProgress) {
|
|
2095
|
+
options.onProgress(100, contents.length, contents.length);
|
|
2096
|
+
}
|
|
2097
|
+
const metadata = response.result;
|
|
2098
|
+
const item = this.metadataToItem(
|
|
2099
|
+
{
|
|
2100
|
+
".tag": "file",
|
|
2101
|
+
id: metadata.id,
|
|
2102
|
+
name: metadata.name,
|
|
2103
|
+
path_display: metadata.path_display,
|
|
2104
|
+
path_lower: metadata.path_lower,
|
|
2105
|
+
size: metadata.size,
|
|
2106
|
+
client_modified: metadata.client_modified,
|
|
2107
|
+
server_modified: metadata.server_modified
|
|
2108
|
+
},
|
|
2109
|
+
normalized
|
|
2110
|
+
);
|
|
2111
|
+
return this.successResult(item);
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
if (error instanceof AuthenticationError || error instanceof FileExistsError || error instanceof FileTooLargeError) {
|
|
2114
|
+
return this.errorResult(error.message);
|
|
2115
|
+
}
|
|
2116
|
+
return this.errorResult(`Failed to upload file: ${error.message}`);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
async downloadFile(remotePath, localPath, options = {}) {
|
|
2120
|
+
try {
|
|
2121
|
+
await this.ensureAuthenticated();
|
|
2122
|
+
const dbxPath = this.toDropboxPath(remotePath);
|
|
2123
|
+
const response = await this.dbx.filesDownload({ path: dbxPath });
|
|
2124
|
+
const result = response.result;
|
|
2125
|
+
const buffer = Buffer.from(result.fileBinary);
|
|
2126
|
+
if (options.onProgress) {
|
|
2127
|
+
options.onProgress(100, buffer.length, buffer.length);
|
|
2128
|
+
}
|
|
2129
|
+
if (localPath) {
|
|
2130
|
+
const fs3 = await import("fs");
|
|
2131
|
+
const path3 = await import("path");
|
|
2132
|
+
await fs3.promises.mkdir(path3.dirname(localPath), { recursive: true });
|
|
2133
|
+
await fs3.promises.writeFile(localPath, buffer);
|
|
2134
|
+
return this.successResult(localPath);
|
|
2135
|
+
}
|
|
2136
|
+
return this.successResult(buffer);
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
const errMsg = error.message || String(error);
|
|
2139
|
+
if (errMsg.includes("path/not_found") || errMsg.includes("path_lookup/not_found")) {
|
|
2140
|
+
return this.errorResult(`File not found: ${remotePath}`);
|
|
2141
|
+
}
|
|
2142
|
+
return this.errorResult(`Failed to download file: ${errMsg}`);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
async moveItem(sourcePath, destinationPath, _options = {}) {
|
|
2146
|
+
try {
|
|
2147
|
+
await this.ensureAuthenticated();
|
|
2148
|
+
const fromPath = this.toDropboxPath(sourcePath);
|
|
2149
|
+
const toPath = this.toDropboxPath(destinationPath);
|
|
2150
|
+
const response = await this.dbx.filesMoveV2({
|
|
2151
|
+
from_path: fromPath,
|
|
2152
|
+
to_path: toPath,
|
|
2153
|
+
autorename: false
|
|
2154
|
+
});
|
|
2155
|
+
const metadata = response.result.metadata;
|
|
2156
|
+
const item = this.metadataToItem(metadata, this.normalizePath(destinationPath));
|
|
2157
|
+
return this.successResult(item);
|
|
2158
|
+
} catch (error) {
|
|
2159
|
+
const errMsg = error.message || String(error);
|
|
2160
|
+
if (errMsg.includes("not_found")) {
|
|
2161
|
+
return this.errorResult(`Item not found: ${sourcePath}`);
|
|
2162
|
+
}
|
|
2163
|
+
return this.errorResult(`Failed to move item: ${errMsg}`);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
async deleteFile(virtualPath) {
|
|
2167
|
+
try {
|
|
2168
|
+
await this.ensureAuthenticated();
|
|
2169
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2170
|
+
await this.dbx.filesDeleteV2({ path: dbxPath });
|
|
2171
|
+
return this.successResult();
|
|
2172
|
+
} catch (error) {
|
|
2173
|
+
const errMsg = error.message || String(error);
|
|
2174
|
+
if (errMsg.includes("not_found")) {
|
|
2175
|
+
return this.errorResult(`File not found: ${virtualPath}`);
|
|
2176
|
+
}
|
|
2177
|
+
return this.errorResult(`Failed to delete file: ${errMsg}`);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
async renameFile(virtualPath, newName, _options = {}) {
|
|
2181
|
+
try {
|
|
2182
|
+
await this.ensureAuthenticated();
|
|
2183
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
2184
|
+
const newVirtualPath = this.joinPath(parentPath, newName);
|
|
2185
|
+
const fromPath = this.toDropboxPath(virtualPath);
|
|
2186
|
+
const toPath = this.toDropboxPath(newVirtualPath);
|
|
2187
|
+
const response = await this.dbx.filesMoveV2({
|
|
2188
|
+
from_path: fromPath,
|
|
2189
|
+
to_path: toPath,
|
|
2190
|
+
autorename: false
|
|
2191
|
+
});
|
|
2192
|
+
const metadata = response.result.metadata;
|
|
2193
|
+
const item = this.metadataToItem(metadata, newVirtualPath);
|
|
2194
|
+
return this.successResult(item);
|
|
2195
|
+
} catch (error) {
|
|
2196
|
+
const errMsg = error.message || String(error);
|
|
2197
|
+
if (errMsg.includes("not_found")) {
|
|
2198
|
+
return this.errorResult(`File not found: ${virtualPath}`);
|
|
2199
|
+
}
|
|
2200
|
+
return this.errorResult(`Failed to rename file: ${errMsg}`);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
async renameFolder(virtualPath, newName, _options = {}) {
|
|
2204
|
+
try {
|
|
2205
|
+
await this.ensureAuthenticated();
|
|
2206
|
+
const parentPath = this.getParentPath(virtualPath);
|
|
2207
|
+
const newVirtualPath = this.joinPath(parentPath, newName);
|
|
2208
|
+
const fromPath = this.toDropboxPath(virtualPath);
|
|
2209
|
+
const toPath = this.toDropboxPath(newVirtualPath);
|
|
2210
|
+
const response = await this.dbx.filesMoveV2({
|
|
2211
|
+
from_path: fromPath,
|
|
2212
|
+
to_path: toPath,
|
|
2213
|
+
autorename: false
|
|
2214
|
+
});
|
|
2215
|
+
const metadata = response.result.metadata;
|
|
2216
|
+
const item = this.metadataToItem(metadata, newVirtualPath);
|
|
2217
|
+
return this.successResult(item);
|
|
2218
|
+
} catch (error) {
|
|
2219
|
+
const errMsg = error.message || String(error);
|
|
2220
|
+
if (errMsg.includes("not_found")) {
|
|
2221
|
+
return this.errorResult(`Folder not found: ${virtualPath}`);
|
|
2222
|
+
}
|
|
2223
|
+
return this.errorResult(`Failed to rename folder: ${errMsg}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
async listDirectory(virtualPath, options = {}) {
|
|
2227
|
+
try {
|
|
2228
|
+
await this.ensureAuthenticated();
|
|
2229
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2230
|
+
const items = [];
|
|
2231
|
+
let hasMore = true;
|
|
2232
|
+
let cursor;
|
|
2233
|
+
const firstResponse = await this.dbx.filesListFolder({
|
|
2234
|
+
path: dbxPath,
|
|
2235
|
+
limit: 100
|
|
2236
|
+
});
|
|
2237
|
+
let entries = firstResponse.result.entries;
|
|
2238
|
+
hasMore = firstResponse.result.has_more;
|
|
2239
|
+
cursor = firstResponse.result.cursor;
|
|
2240
|
+
const processEntries = async (entryList) => {
|
|
2241
|
+
for (const entry of entryList) {
|
|
2242
|
+
if (!options.includeHidden && entry.name.startsWith(".")) {
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
const itemPath = this.joinPath(virtualPath, entry.name);
|
|
2246
|
+
const item = this.metadataToItem(entry, itemPath);
|
|
2247
|
+
if (options.filter && !options.filter(item)) {
|
|
2248
|
+
continue;
|
|
2249
|
+
}
|
|
2250
|
+
items.push(item);
|
|
2251
|
+
if (options.recursive && entry[".tag"] === "folder") {
|
|
2252
|
+
const subResult = await this.listDirectory(itemPath, options);
|
|
2253
|
+
if (subResult.success && subResult.data) {
|
|
2254
|
+
items.push(...subResult.data);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
await processEntries(entries);
|
|
2260
|
+
while (hasMore && cursor) {
|
|
2261
|
+
const continueResponse = await this.dbx.filesListFolderContinue({ cursor });
|
|
2262
|
+
entries = continueResponse.result.entries;
|
|
2263
|
+
hasMore = continueResponse.result.has_more;
|
|
2264
|
+
cursor = continueResponse.result.cursor;
|
|
2265
|
+
await processEntries(entries);
|
|
2266
|
+
}
|
|
2267
|
+
return this.successResult(items);
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
const errMsg = error.message || String(error);
|
|
2270
|
+
if (errMsg.includes("path/not_found") || errMsg.includes("not_found")) {
|
|
2271
|
+
return this.errorResult(`Directory not found: ${virtualPath}`);
|
|
2272
|
+
}
|
|
2273
|
+
return this.errorResult(`Failed to list directory: ${errMsg}`);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
async getItem(virtualPath) {
|
|
2277
|
+
try {
|
|
2278
|
+
await this.ensureAuthenticated();
|
|
2279
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2280
|
+
const response = await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2281
|
+
const metadata = response.result;
|
|
2282
|
+
const item = this.metadataToItem(metadata, this.normalizePath(virtualPath));
|
|
2283
|
+
return this.successResult(item);
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
const errMsg = error.message || String(error);
|
|
2286
|
+
if (errMsg.includes("not_found")) {
|
|
2287
|
+
return this.errorResult(`Item not found: ${virtualPath}`);
|
|
2288
|
+
}
|
|
2289
|
+
return this.errorResult(`Failed to get item: ${errMsg}`);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
async exists(virtualPath) {
|
|
2293
|
+
try {
|
|
2294
|
+
await this.ensureAuthenticated();
|
|
2295
|
+
const dbxPath = this.toDropboxPath(virtualPath);
|
|
2296
|
+
await this.dbx.filesGetMetadata({ path: dbxPath });
|
|
2297
|
+
return true;
|
|
2298
|
+
} catch {
|
|
2299
|
+
return false;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
async getFolderTree(path3 = "/", depth = 3) {
|
|
2303
|
+
try {
|
|
2304
|
+
await this.ensureAuthenticated();
|
|
2305
|
+
return super.getFolderTree(path3, depth);
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
return this.errorResult(`Failed to get folder tree: ${error.message}`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
function createDropboxModule() {
|
|
2312
|
+
return new DropboxModule();
|
|
2313
|
+
}
|
|
2314
|
+
|
|
1653
2315
|
// src/modules/index.ts
|
|
1654
2316
|
var moduleRegistry = {
|
|
1655
2317
|
local: createLocalModule,
|
|
1656
|
-
google_drive: createGoogleDriveModule
|
|
2318
|
+
google_drive: createGoogleDriveModule,
|
|
2319
|
+
dropbox: createDropboxModule
|
|
1657
2320
|
};
|
|
1658
2321
|
function getRegisteredProviders() {
|
|
1659
2322
|
return Object.keys(moduleRegistry);
|
|
@@ -2219,6 +2882,7 @@ var FileMetadataService = class {
|
|
|
2219
2882
|
if (input.scope_id !== void 0) record.scope_id = input.scope_id;
|
|
2220
2883
|
if (input.uploaded_by !== void 0) record.uploaded_by = input.uploaded_by;
|
|
2221
2884
|
if (input.original_filename !== void 0) record.original_filename = input.original_filename;
|
|
2885
|
+
if (input.content_tag !== void 0) record.content_tag = input.content_tag;
|
|
2222
2886
|
const results = await this.crud.insert(record);
|
|
2223
2887
|
this.logger?.debug?.("Recorded file upload", { path: input.file_path });
|
|
2224
2888
|
return results[0] || null;
|
|
@@ -3596,10 +4260,18 @@ function createNamingConventionService(crudService, options) {
|
|
|
3596
4260
|
|
|
3597
4261
|
// src/services/llm-extraction-service.ts
|
|
3598
4262
|
var LLMExtractionService = class {
|
|
3599
|
-
constructor(
|
|
3600
|
-
this.llmFactory =
|
|
4263
|
+
constructor(factoryConfig, defaultProvider = "gemini") {
|
|
4264
|
+
this.llmFactory = factoryConfig.create;
|
|
4265
|
+
this.cacheInvalidator = factoryConfig.invalidateCache;
|
|
3601
4266
|
this.defaultProvider = defaultProvider;
|
|
3602
4267
|
}
|
|
4268
|
+
/**
|
|
4269
|
+
* Invalidate the LLM prompt cache
|
|
4270
|
+
* Passthrough to hazo_llm_api's invalidate_prompt_cache when configured
|
|
4271
|
+
*/
|
|
4272
|
+
invalidatePromptCache(area, key) {
|
|
4273
|
+
this.cacheInvalidator?.(area, key);
|
|
4274
|
+
}
|
|
3603
4275
|
/**
|
|
3604
4276
|
* Extract data from a document
|
|
3605
4277
|
*
|
|
@@ -3726,8 +4398,8 @@ var LLMExtractionService = class {
|
|
|
3726
4398
|
};
|
|
3727
4399
|
}
|
|
3728
4400
|
};
|
|
3729
|
-
function createLLMExtractionService(
|
|
3730
|
-
return new LLMExtractionService(
|
|
4401
|
+
function createLLMExtractionService(factoryConfig, defaultProvider) {
|
|
4402
|
+
return new LLMExtractionService(factoryConfig, defaultProvider);
|
|
3731
4403
|
}
|
|
3732
4404
|
|
|
3733
4405
|
// src/common/naming-utils.ts
|
|
@@ -4053,10 +4725,11 @@ function generatePreviewName(pattern, userVariables, options = {}) {
|
|
|
4053
4725
|
|
|
4054
4726
|
// src/services/upload-extract-service.ts
|
|
4055
4727
|
var UploadExtractService = class {
|
|
4056
|
-
constructor(fileManager, namingService, extractionService) {
|
|
4728
|
+
constructor(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
4057
4729
|
this.fileManager = fileManager;
|
|
4058
4730
|
this.namingService = namingService;
|
|
4059
4731
|
this.extractionService = extractionService;
|
|
4732
|
+
this.defaultContentTagConfig = defaultContentTagConfig;
|
|
4060
4733
|
}
|
|
4061
4734
|
/**
|
|
4062
4735
|
* Upload a file with optional extraction and naming convention
|
|
@@ -4133,11 +4806,12 @@ var UploadExtractService = class {
|
|
|
4133
4806
|
metadata.extraction_id = extractionData.id;
|
|
4134
4807
|
metadata.extraction_source = extractionData.source;
|
|
4135
4808
|
}
|
|
4809
|
+
const effectiveContentTagConfig = options.contentTagConfig ?? this.defaultContentTagConfig;
|
|
4810
|
+
const needsContentTagging = effectiveContentTagConfig?.content_tag_set_by_llm && this.extractionService && this.fileManager.isTrackingActive();
|
|
4136
4811
|
const uploadResult = await this.fileManager.uploadFile(source, fullPath, {
|
|
4137
4812
|
...options,
|
|
4138
4813
|
metadata,
|
|
4139
|
-
awaitRecording: !!extractionData
|
|
4140
|
-
// Await recording when extraction needs to be added
|
|
4814
|
+
awaitRecording: !!extractionData || !!needsContentTagging
|
|
4141
4815
|
});
|
|
4142
4816
|
if (!uploadResult.success) {
|
|
4143
4817
|
return {
|
|
@@ -4161,13 +4835,23 @@ var UploadExtractService = class {
|
|
|
4161
4835
|
);
|
|
4162
4836
|
}
|
|
4163
4837
|
}
|
|
4838
|
+
let contentTag;
|
|
4839
|
+
if (needsContentTagging && effectiveContentTagConfig) {
|
|
4840
|
+
contentTag = await this.performContentTagging(
|
|
4841
|
+
source,
|
|
4842
|
+
mimeType,
|
|
4843
|
+
effectiveContentTagConfig,
|
|
4844
|
+
fullPath
|
|
4845
|
+
);
|
|
4846
|
+
}
|
|
4164
4847
|
return {
|
|
4165
4848
|
success: true,
|
|
4166
4849
|
file: uploadResult.data,
|
|
4167
4850
|
extraction: extractionData,
|
|
4168
4851
|
generatedPath: fullPath,
|
|
4169
4852
|
generatedFolderPath: generatedFolderPath || void 0,
|
|
4170
|
-
originalFileName
|
|
4853
|
+
originalFileName,
|
|
4854
|
+
contentTag
|
|
4171
4855
|
};
|
|
4172
4856
|
} catch (error) {
|
|
4173
4857
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4265,6 +4949,76 @@ var UploadExtractService = class {
|
|
|
4265
4949
|
folderPath: folderPath || void 0
|
|
4266
4950
|
};
|
|
4267
4951
|
}
|
|
4952
|
+
/**
|
|
4953
|
+
* Perform content tagging via LLM extraction.
|
|
4954
|
+
* Calls the LLM with the configured prompt, extracts the specified field,
|
|
4955
|
+
* and writes it to the content_tag column.
|
|
4956
|
+
*/
|
|
4957
|
+
async performContentTagging(buffer, mimeType, config, filePath) {
|
|
4958
|
+
try {
|
|
4959
|
+
if (!this.extractionService) return void 0;
|
|
4960
|
+
const result = await this.extractionService.extract(buffer, mimeType, {
|
|
4961
|
+
promptArea: config.content_tag_prompt_area,
|
|
4962
|
+
promptKey: config.content_tag_prompt_key,
|
|
4963
|
+
promptVariables: config.content_tag_prompt_variables
|
|
4964
|
+
});
|
|
4965
|
+
if (!result.success || !result.data) return void 0;
|
|
4966
|
+
const tagValue = result.data[config.content_tag_prompt_return_fieldname];
|
|
4967
|
+
if (typeof tagValue !== "string" || !tagValue) return void 0;
|
|
4968
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4969
|
+
if (metadataService) {
|
|
4970
|
+
const storageType = this.fileManager.getProvider() || "local";
|
|
4971
|
+
const record = await metadataService.findByPath(filePath, storageType);
|
|
4972
|
+
if (record) {
|
|
4973
|
+
await metadataService.updateFields(record.id, { content_tag: tagValue });
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
return tagValue;
|
|
4977
|
+
} catch {
|
|
4978
|
+
return void 0;
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
/**
|
|
4982
|
+
* Manually tag a file's content via LLM.
|
|
4983
|
+
* Works with existing DB records, resolving the file path internally.
|
|
4984
|
+
*
|
|
4985
|
+
* @param fileId - Database record ID of the file
|
|
4986
|
+
* @param config - Content tag config (falls back to default if not provided)
|
|
4987
|
+
* @returns OperationResult with the tag value
|
|
4988
|
+
*/
|
|
4989
|
+
async tagFileContent(fileId, config) {
|
|
4990
|
+
const effectiveConfig = config ?? this.defaultContentTagConfig;
|
|
4991
|
+
if (!effectiveConfig || !effectiveConfig.content_tag_set_by_llm) {
|
|
4992
|
+
return { success: false, error: "Content tagging is not configured or disabled" };
|
|
4993
|
+
}
|
|
4994
|
+
if (!this.extractionService) {
|
|
4995
|
+
return { success: false, error: "Extraction service not available" };
|
|
4996
|
+
}
|
|
4997
|
+
const metadataService = this.fileManager.getMetadataService();
|
|
4998
|
+
if (!metadataService) {
|
|
4999
|
+
return { success: false, error: "Metadata service not available (tracking not enabled)" };
|
|
5000
|
+
}
|
|
5001
|
+
const record = await metadataService.findById(fileId);
|
|
5002
|
+
if (!record) {
|
|
5003
|
+
return { success: false, error: `File record not found: ${fileId}` };
|
|
5004
|
+
}
|
|
5005
|
+
const downloadResult = await this.fileManager.downloadFile(record.file_path);
|
|
5006
|
+
if (!downloadResult.success || !downloadResult.data) {
|
|
5007
|
+
return { success: false, error: `Failed to download file: ${downloadResult.error}` };
|
|
5008
|
+
}
|
|
5009
|
+
const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : Buffer.from(downloadResult.data);
|
|
5010
|
+
const mimeType = getMimeType(record.filename);
|
|
5011
|
+
const tagValue = await this.performContentTagging(
|
|
5012
|
+
buffer,
|
|
5013
|
+
mimeType,
|
|
5014
|
+
effectiveConfig,
|
|
5015
|
+
record.file_path
|
|
5016
|
+
);
|
|
5017
|
+
if (!tagValue) {
|
|
5018
|
+
return { success: false, error: "Content tagging did not produce a result" };
|
|
5019
|
+
}
|
|
5020
|
+
return { success: true, data: tagValue };
|
|
5021
|
+
}
|
|
4268
5022
|
/**
|
|
4269
5023
|
* Get the file manager
|
|
4270
5024
|
*/
|
|
@@ -4284,8 +5038,8 @@ var UploadExtractService = class {
|
|
|
4284
5038
|
return this.extractionService;
|
|
4285
5039
|
}
|
|
4286
5040
|
};
|
|
4287
|
-
function createUploadExtractService(fileManager, namingService, extractionService) {
|
|
4288
|
-
return new UploadExtractService(fileManager, namingService, extractionService);
|
|
5041
|
+
function createUploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig) {
|
|
5042
|
+
return new UploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig);
|
|
4289
5043
|
}
|
|
4290
5044
|
|
|
4291
5045
|
// src/schema/index.ts
|
|
@@ -4312,7 +5066,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4312
5066
|
uploaded_by TEXT,
|
|
4313
5067
|
storage_verified_at TEXT,
|
|
4314
5068
|
deleted_at TEXT,
|
|
4315
|
-
original_filename TEXT
|
|
5069
|
+
original_filename TEXT,
|
|
5070
|
+
content_tag TEXT
|
|
4316
5071
|
)`,
|
|
4317
5072
|
indexes: [
|
|
4318
5073
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4322,7 +5077,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4322
5077
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4323
5078
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4324
5079
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4325
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
5080
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
5081
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4326
5082
|
]
|
|
4327
5083
|
},
|
|
4328
5084
|
postgres: {
|
|
@@ -4345,7 +5101,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4345
5101
|
uploaded_by UUID,
|
|
4346
5102
|
storage_verified_at TIMESTAMP WITH TIME ZONE,
|
|
4347
5103
|
deleted_at TIMESTAMP WITH TIME ZONE,
|
|
4348
|
-
original_filename TEXT
|
|
5104
|
+
original_filename TEXT,
|
|
5105
|
+
content_tag TEXT
|
|
4349
5106
|
)`,
|
|
4350
5107
|
indexes: [
|
|
4351
5108
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
|
|
@@ -4355,7 +5112,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4355
5112
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
|
|
4356
5113
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
|
|
4357
5114
|
"CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
|
|
4358
|
-
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
|
|
5115
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
|
|
5116
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
4359
5117
|
]
|
|
4360
5118
|
},
|
|
4361
5119
|
columns: [
|
|
@@ -4377,7 +5135,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
4377
5135
|
"uploaded_by",
|
|
4378
5136
|
"storage_verified_at",
|
|
4379
5137
|
"deleted_at",
|
|
4380
|
-
"original_filename"
|
|
5138
|
+
"original_filename",
|
|
5139
|
+
"content_tag"
|
|
4381
5140
|
]
|
|
4382
5141
|
};
|
|
4383
5142
|
function getSchemaForTable(tableName, dbType) {
|
|
@@ -4518,6 +5277,45 @@ function getNamingSchemaForTable(tableName, dbType) {
|
|
|
4518
5277
|
)
|
|
4519
5278
|
};
|
|
4520
5279
|
}
|
|
5280
|
+
var HAZO_FILES_MIGRATION_V3 = {
|
|
5281
|
+
tableName: HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
5282
|
+
sqlite: {
|
|
5283
|
+
alterStatements: [
|
|
5284
|
+
"ALTER TABLE hazo_files ADD COLUMN content_tag TEXT"
|
|
5285
|
+
],
|
|
5286
|
+
indexes: [
|
|
5287
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
5288
|
+
],
|
|
5289
|
+
backfill: ""
|
|
5290
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
5291
|
+
},
|
|
5292
|
+
postgres: {
|
|
5293
|
+
alterStatements: [
|
|
5294
|
+
"ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT"
|
|
5295
|
+
],
|
|
5296
|
+
indexes: [
|
|
5297
|
+
"CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
|
|
5298
|
+
],
|
|
5299
|
+
backfill: ""
|
|
5300
|
+
// No backfill needed — column is nullable, defaults to NULL
|
|
5301
|
+
},
|
|
5302
|
+
newColumns: [
|
|
5303
|
+
"content_tag"
|
|
5304
|
+
]
|
|
5305
|
+
};
|
|
5306
|
+
function getMigrationV3ForTable(tableName, dbType) {
|
|
5307
|
+
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5308
|
+
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5309
|
+
return {
|
|
5310
|
+
alterStatements: migration.alterStatements.map(
|
|
5311
|
+
(stmt) => stmt.replace(new RegExp(defaultName, "g"), tableName)
|
|
5312
|
+
),
|
|
5313
|
+
indexes: migration.indexes.map(
|
|
5314
|
+
(idx) => idx.replace(new RegExp(defaultName, "g"), tableName)
|
|
5315
|
+
),
|
|
5316
|
+
backfill: migration.backfill
|
|
5317
|
+
};
|
|
5318
|
+
}
|
|
4521
5319
|
|
|
4522
5320
|
// src/migrations/add-reference-tracking.ts
|
|
4523
5321
|
async function migrateToV2(executor, dbType, tableName) {
|
|
@@ -4536,6 +5334,20 @@ async function backfillV2Defaults(executor, dbType, tableName) {
|
|
|
4536
5334
|
const migration = tableName ? getMigrationForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V2[dbType];
|
|
4537
5335
|
await executor.run(migration.backfill);
|
|
4538
5336
|
}
|
|
5337
|
+
|
|
5338
|
+
// src/migrations/add-content-tag.ts
|
|
5339
|
+
async function migrateToV3(executor, dbType, tableName) {
|
|
5340
|
+
const migration = tableName ? getMigrationV3ForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V3[dbType];
|
|
5341
|
+
for (const stmt of migration.alterStatements) {
|
|
5342
|
+
try {
|
|
5343
|
+
await executor.run(stmt);
|
|
5344
|
+
} catch {
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
for (const idx of migration.indexes) {
|
|
5348
|
+
await executor.run(idx);
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
4539
5351
|
export {
|
|
4540
5352
|
ALL_SYSTEM_VARIABLES,
|
|
4541
5353
|
AuthenticationError,
|
|
@@ -4544,6 +5356,8 @@ export {
|
|
|
4544
5356
|
DirectoryExistsError,
|
|
4545
5357
|
DirectoryNotEmptyError,
|
|
4546
5358
|
DirectoryNotFoundError,
|
|
5359
|
+
DropboxAuth,
|
|
5360
|
+
DropboxModule,
|
|
4547
5361
|
FileExistsError,
|
|
4548
5362
|
FileManager,
|
|
4549
5363
|
FileMetadataService,
|
|
@@ -4553,6 +5367,7 @@ export {
|
|
|
4553
5367
|
GoogleDriveModule,
|
|
4554
5368
|
HAZO_FILES_DEFAULT_TABLE_NAME,
|
|
4555
5369
|
HAZO_FILES_MIGRATION_V2,
|
|
5370
|
+
HAZO_FILES_MIGRATION_V3,
|
|
4556
5371
|
HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
|
|
4557
5372
|
HAZO_FILES_NAMING_TABLE_SCHEMA,
|
|
4558
5373
|
HAZO_FILES_TABLE_SCHEMA,
|
|
@@ -4579,6 +5394,8 @@ export {
|
|
|
4579
5394
|
computeFileHashSync,
|
|
4580
5395
|
computeFileInfo,
|
|
4581
5396
|
createAndInitializeModule,
|
|
5397
|
+
createDropboxAuth,
|
|
5398
|
+
createDropboxModule,
|
|
4582
5399
|
createEmptyFileDataStructure,
|
|
4583
5400
|
createEmptyNamingRuleSchema,
|
|
4584
5401
|
createFileItem,
|
|
@@ -4622,6 +5439,7 @@ export {
|
|
|
4622
5439
|
getFileMetadataValues,
|
|
4623
5440
|
getMergedData,
|
|
4624
5441
|
getMigrationForTable,
|
|
5442
|
+
getMigrationV3ForTable,
|
|
4625
5443
|
getMimeType,
|
|
4626
5444
|
getNameWithoutExtension,
|
|
4627
5445
|
getNamingSchemaForTable,
|
|
@@ -4654,6 +5472,7 @@ export {
|
|
|
4654
5472
|
loadConfig,
|
|
4655
5473
|
loadConfigAsync,
|
|
4656
5474
|
migrateToV2,
|
|
5475
|
+
migrateToV3,
|
|
4657
5476
|
normalizePath,
|
|
4658
5477
|
parseConfig,
|
|
4659
5478
|
parseFileData,
|