@tom2012/cc-web 1.5.10 → 1.5.14
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 +23 -3
- package/backend/dist/backup/config.d.ts +8 -0
- package/backend/dist/backup/config.d.ts.map +1 -0
- package/backend/dist/backup/config.js +104 -0
- package/backend/dist/backup/config.js.map +1 -0
- package/backend/dist/backup/engine.d.ts +22 -0
- package/backend/dist/backup/engine.d.ts.map +1 -0
- package/backend/dist/backup/engine.js +280 -0
- package/backend/dist/backup/engine.js.map +1 -0
- package/backend/dist/backup/providers/dropbox.d.ts +25 -0
- package/backend/dist/backup/providers/dropbox.d.ts.map +1 -0
- package/backend/dist/backup/providers/dropbox.js +319 -0
- package/backend/dist/backup/providers/dropbox.js.map +1 -0
- package/backend/dist/backup/providers/google-drive.d.ts +29 -0
- package/backend/dist/backup/providers/google-drive.d.ts.map +1 -0
- package/backend/dist/backup/providers/google-drive.js +305 -0
- package/backend/dist/backup/providers/google-drive.js.map +1 -0
- package/backend/dist/backup/providers/index.d.ts +3 -0
- package/backend/dist/backup/providers/index.d.ts.map +1 -0
- package/backend/dist/backup/providers/index.js +19 -0
- package/backend/dist/backup/providers/index.js.map +1 -0
- package/backend/dist/backup/providers/onedrive.d.ts +29 -0
- package/backend/dist/backup/providers/onedrive.d.ts.map +1 -0
- package/backend/dist/backup/providers/onedrive.js +316 -0
- package/backend/dist/backup/providers/onedrive.js.map +1 -0
- package/backend/dist/backup/scheduler.d.ts +6 -0
- package/backend/dist/backup/scheduler.d.ts.map +1 -0
- package/backend/dist/backup/scheduler.js +53 -0
- package/backend/dist/backup/scheduler.js.map +1 -0
- package/backend/dist/backup/types.d.ts +68 -0
- package/backend/dist/backup/types.d.ts.map +1 -0
- package/backend/dist/backup/types.js +4 -0
- package/backend/dist/backup/types.js.map +1 -0
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +74 -10
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/routes/backup.d.ts +4 -0
- package/backend/dist/routes/backup.d.ts.map +1 -0
- package/backend/dist/routes/backup.js +175 -0
- package/backend/dist/routes/backup.js.map +1 -0
- package/backend/dist/routes/projects.d.ts.map +1 -1
- package/backend/dist/routes/projects.js +14 -0
- package/backend/dist/routes/projects.js.map +1 -1
- package/backend/dist/routes/sounds.d.ts +3 -0
- package/backend/dist/routes/sounds.d.ts.map +1 -0
- package/backend/dist/routes/sounds.js +283 -0
- package/backend/dist/routes/sounds.js.map +1 -0
- package/backend/package-lock.json +662 -20
- package/backend/package.json +8 -0
- package/bin/ccweb.js +33 -0
- package/frontend/dist/assets/index-D7gjxQKB.css +32 -0
- package/frontend/dist/assets/index-Dg1NcsQH.js +489 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-CQjbS4zv.css +0 -32
- package/frontend/dist/assets/index-CtyR65A4.js +0 -434
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// backend/src/backup/providers/dropbox.ts
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.DropboxProvider = void 0;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const dropbox_1 = require("dropbox");
|
|
41
|
+
const TOKEN_ENDPOINT = 'https://api.dropboxapi.com/oauth2/token';
|
|
42
|
+
// 150 MB threshold: above this use chunked upload session
|
|
43
|
+
const SIMPLE_UPLOAD_MAX = 150 * 1024 * 1024;
|
|
44
|
+
// 8 MB chunk size for upload sessions
|
|
45
|
+
const CHUNK_SIZE = 8 * 1024 * 1024;
|
|
46
|
+
class DropboxProvider {
|
|
47
|
+
constructor(config) {
|
|
48
|
+
this.dbx = null;
|
|
49
|
+
this.config = config;
|
|
50
|
+
if (config.tokens) {
|
|
51
|
+
this.initClient(config.tokens.access_token);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
initClient(accessToken) {
|
|
55
|
+
this.dbx = new dropbox_1.Dropbox({ accessToken });
|
|
56
|
+
}
|
|
57
|
+
client() {
|
|
58
|
+
if (!this.dbx) {
|
|
59
|
+
throw new Error('Dropbox client is not initialized');
|
|
60
|
+
}
|
|
61
|
+
return this.dbx;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Normalize a path to Dropbox format:
|
|
65
|
+
* - Root is empty string ''
|
|
66
|
+
* - All other paths start with '/' and have no trailing slash
|
|
67
|
+
*/
|
|
68
|
+
normPath(remotePath) {
|
|
69
|
+
// Strip leading/trailing slashes then re-add leading slash if non-empty
|
|
70
|
+
const stripped = remotePath.replace(/^\/+|\/+$/g, '');
|
|
71
|
+
if (!stripped)
|
|
72
|
+
return '';
|
|
73
|
+
return '/' + stripped;
|
|
74
|
+
}
|
|
75
|
+
getAuthUrl(redirectUri) {
|
|
76
|
+
const params = new URLSearchParams({
|
|
77
|
+
client_id: this.config.clientId,
|
|
78
|
+
response_type: 'code',
|
|
79
|
+
redirect_uri: redirectUri,
|
|
80
|
+
token_access_type: 'offline',
|
|
81
|
+
});
|
|
82
|
+
return `https://www.dropbox.com/oauth2/authorize?${params.toString()}`;
|
|
83
|
+
}
|
|
84
|
+
async handleCallback(code, redirectUri) {
|
|
85
|
+
const body = new URLSearchParams({
|
|
86
|
+
code,
|
|
87
|
+
grant_type: 'authorization_code',
|
|
88
|
+
redirect_uri: redirectUri,
|
|
89
|
+
});
|
|
90
|
+
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
91
|
+
const res = await fetch(TOKEN_ENDPOINT, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
95
|
+
Authorization: `Basic ${credentials}`,
|
|
96
|
+
},
|
|
97
|
+
body: body.toString(),
|
|
98
|
+
});
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
const text = await res.text();
|
|
101
|
+
throw new Error(`Dropbox token exchange failed: ${res.status} ${text}`);
|
|
102
|
+
}
|
|
103
|
+
const data = (await res.json());
|
|
104
|
+
if (!data.access_token || !data.refresh_token) {
|
|
105
|
+
throw new Error('Missing tokens in Dropbox OAuth2 callback response');
|
|
106
|
+
}
|
|
107
|
+
const providerTokens = {
|
|
108
|
+
access_token: data.access_token,
|
|
109
|
+
refresh_token: data.refresh_token,
|
|
110
|
+
expiry: new Date(Date.now() + data.expires_in * 1000).toISOString(),
|
|
111
|
+
};
|
|
112
|
+
this.config.tokens = providerTokens;
|
|
113
|
+
this.initClient(providerTokens.access_token);
|
|
114
|
+
return providerTokens;
|
|
115
|
+
}
|
|
116
|
+
async refreshToken() {
|
|
117
|
+
if (!this.config.tokens?.refresh_token) {
|
|
118
|
+
throw new Error('No refresh token available for Dropbox provider');
|
|
119
|
+
}
|
|
120
|
+
const body = new URLSearchParams({
|
|
121
|
+
grant_type: 'refresh_token',
|
|
122
|
+
refresh_token: this.config.tokens.refresh_token,
|
|
123
|
+
});
|
|
124
|
+
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
125
|
+
const res = await fetch(TOKEN_ENDPOINT, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
129
|
+
Authorization: `Basic ${credentials}`,
|
|
130
|
+
},
|
|
131
|
+
body: body.toString(),
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
const text = await res.text();
|
|
135
|
+
throw new Error(`Dropbox token refresh failed: ${res.status} ${text}`);
|
|
136
|
+
}
|
|
137
|
+
const data = (await res.json());
|
|
138
|
+
if (!data.access_token) {
|
|
139
|
+
throw new Error('Failed to refresh Dropbox access token');
|
|
140
|
+
}
|
|
141
|
+
const providerTokens = {
|
|
142
|
+
access_token: data.access_token,
|
|
143
|
+
refresh_token: data.refresh_token ?? this.config.tokens.refresh_token,
|
|
144
|
+
expiry: new Date(Date.now() + data.expires_in * 1000).toISOString(),
|
|
145
|
+
};
|
|
146
|
+
this.config.tokens = providerTokens;
|
|
147
|
+
this.initClient(providerTokens.access_token);
|
|
148
|
+
return providerTokens;
|
|
149
|
+
}
|
|
150
|
+
isAuthorized() {
|
|
151
|
+
return !!(this.config.tokens?.access_token && this.config.tokens?.refresh_token);
|
|
152
|
+
}
|
|
153
|
+
async ensureAuth() {
|
|
154
|
+
if (!this.isAuthorized()) {
|
|
155
|
+
throw new Error('Dropbox provider is not authorized');
|
|
156
|
+
}
|
|
157
|
+
const tokens = this.config.tokens;
|
|
158
|
+
const expiryMs = new Date(tokens.expiry).getTime();
|
|
159
|
+
const nowMs = Date.now();
|
|
160
|
+
// Refresh if within 60 seconds of expiry
|
|
161
|
+
if (expiryMs - nowMs < 60 * 1000) {
|
|
162
|
+
await this.refreshToken();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async listFiles(remotePath) {
|
|
166
|
+
await this.ensureAuth();
|
|
167
|
+
const dbxPath = this.normPath(remotePath);
|
|
168
|
+
const results = [];
|
|
169
|
+
let response = await this.client().filesListFolder({ path: dbxPath });
|
|
170
|
+
while (true) {
|
|
171
|
+
for (const entry of response.result.entries) {
|
|
172
|
+
const isDirectory = entry['.tag'] === 'folder';
|
|
173
|
+
const entryPath = entry.path_display ?? entry.path_lower ?? entry.name;
|
|
174
|
+
// Return path relative to remotePath base (strip leading slash)
|
|
175
|
+
const normalizedBase = remotePath.replace(/^\/+|\/+$/g, '');
|
|
176
|
+
const relPath = normalizedBase
|
|
177
|
+
? `${normalizedBase}/${entry.name}`
|
|
178
|
+
: entry.name;
|
|
179
|
+
results.push({
|
|
180
|
+
name: entry.name,
|
|
181
|
+
path: relPath,
|
|
182
|
+
isDirectory,
|
|
183
|
+
size: isDirectory ? 0 : (entry.size ?? 0),
|
|
184
|
+
modifiedTime: isDirectory
|
|
185
|
+
? new Date().toISOString()
|
|
186
|
+
: (entry.server_modified ?? new Date().toISOString()),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (!response.result.has_more)
|
|
190
|
+
break;
|
|
191
|
+
response = await this.client().filesListFolderContinue({
|
|
192
|
+
cursor: response.result.cursor,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return results;
|
|
196
|
+
}
|
|
197
|
+
async uploadFile(localPath, remotePath) {
|
|
198
|
+
await this.ensureAuth();
|
|
199
|
+
const dbxPath = this.normPath(remotePath);
|
|
200
|
+
const stat = fs.statSync(localPath);
|
|
201
|
+
const fileSize = stat.size;
|
|
202
|
+
if (fileSize < SIMPLE_UPLOAD_MAX) {
|
|
203
|
+
// Simple upload
|
|
204
|
+
const fileContent = fs.readFileSync(localPath);
|
|
205
|
+
await this.client().filesUpload({
|
|
206
|
+
path: dbxPath,
|
|
207
|
+
mode: { '.tag': 'overwrite' },
|
|
208
|
+
contents: fileContent,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Chunked upload session
|
|
213
|
+
const fd = fs.openSync(localPath, 'r');
|
|
214
|
+
try {
|
|
215
|
+
// Start upload session with first chunk
|
|
216
|
+
let offset = 0;
|
|
217
|
+
const firstChunkSize = Math.min(CHUNK_SIZE, fileSize);
|
|
218
|
+
const firstChunk = Buffer.alloc(firstChunkSize);
|
|
219
|
+
fs.readSync(fd, firstChunk, 0, firstChunkSize, 0);
|
|
220
|
+
offset += firstChunkSize;
|
|
221
|
+
const startRes = await this.client().filesUploadSessionStart({
|
|
222
|
+
close: false,
|
|
223
|
+
contents: firstChunk,
|
|
224
|
+
});
|
|
225
|
+
const sessionId = startRes.result.session_id;
|
|
226
|
+
// Append remaining chunks
|
|
227
|
+
while (offset < fileSize) {
|
|
228
|
+
const chunkSize = Math.min(CHUNK_SIZE, fileSize - offset);
|
|
229
|
+
const chunk = Buffer.alloc(chunkSize);
|
|
230
|
+
fs.readSync(fd, chunk, 0, chunkSize, offset);
|
|
231
|
+
const isLast = offset + chunkSize >= fileSize;
|
|
232
|
+
if (isLast) {
|
|
233
|
+
// Finish the session
|
|
234
|
+
await this.client().filesUploadSessionFinish({
|
|
235
|
+
cursor: { session_id: sessionId, offset },
|
|
236
|
+
commit: {
|
|
237
|
+
path: dbxPath,
|
|
238
|
+
mode: { '.tag': 'overwrite' },
|
|
239
|
+
},
|
|
240
|
+
contents: chunk,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
await this.client().filesUploadSessionAppendV2({
|
|
245
|
+
cursor: { session_id: sessionId, offset },
|
|
246
|
+
close: false,
|
|
247
|
+
contents: chunk,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
offset += chunkSize;
|
|
251
|
+
}
|
|
252
|
+
// Edge case: file exactly one chunk — finish with empty content
|
|
253
|
+
if (fileSize === firstChunkSize) {
|
|
254
|
+
await this.client().filesUploadSessionFinish({
|
|
255
|
+
cursor: { session_id: sessionId, offset },
|
|
256
|
+
commit: {
|
|
257
|
+
path: dbxPath,
|
|
258
|
+
mode: { '.tag': 'overwrite' },
|
|
259
|
+
},
|
|
260
|
+
contents: Buffer.alloc(0),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
fs.closeSync(fd);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async deleteFile(remotePath) {
|
|
270
|
+
await this.ensureAuth();
|
|
271
|
+
const dbxPath = this.normPath(remotePath);
|
|
272
|
+
try {
|
|
273
|
+
await this.client().filesDeleteV2({ path: dbxPath });
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
// Ignore path_lookup errors (file not found) — treat as success
|
|
277
|
+
const errObj = err;
|
|
278
|
+
const summary = errObj?.error?.error_summary ?? '';
|
|
279
|
+
if (!summary.startsWith('path_lookup/')) {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async mkdir(remotePath) {
|
|
285
|
+
await this.ensureAuth();
|
|
286
|
+
const dbxPath = this.normPath(remotePath);
|
|
287
|
+
if (!dbxPath)
|
|
288
|
+
return; // root always exists
|
|
289
|
+
try {
|
|
290
|
+
await this.client().filesCreateFolderV2({ path: dbxPath });
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
// Ignore path/conflict errors (folder already exists)
|
|
294
|
+
const errObj = err;
|
|
295
|
+
const summary = errObj?.error?.error_summary ?? '';
|
|
296
|
+
if (!summary.startsWith('path/conflict')) {
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async downloadFile(remotePath, localPath) {
|
|
302
|
+
await this.ensureAuth();
|
|
303
|
+
const dbxPath = this.normPath(remotePath);
|
|
304
|
+
const res = await this.client().filesDownload({ path: dbxPath });
|
|
305
|
+
// The SDK returns the binary content as `fileBinary` in the result
|
|
306
|
+
const fileBinary = res.result.fileBinary;
|
|
307
|
+
if (!fileBinary) {
|
|
308
|
+
throw new Error(`Dropbox filesDownload returned no fileBinary for: ${remotePath}`);
|
|
309
|
+
}
|
|
310
|
+
// Ensure parent directory exists
|
|
311
|
+
const parentDir = path.dirname(localPath);
|
|
312
|
+
if (!fs.existsSync(parentDir)) {
|
|
313
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
314
|
+
}
|
|
315
|
+
fs.writeFileSync(localPath, fileBinary);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
exports.DropboxProvider = DropboxProvider;
|
|
319
|
+
//# sourceMappingURL=dropbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropbox.js","sourceRoot":"","sources":["../../../src/backup/providers/dropbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CloudProvider, ProviderConfig, ProviderTokens, RemoteFile } from '../types';
|
|
2
|
+
export declare class GoogleDriveProvider implements CloudProvider {
|
|
3
|
+
config: ProviderConfig;
|
|
4
|
+
private oauth2Client;
|
|
5
|
+
private folderIdCache;
|
|
6
|
+
constructor(config: ProviderConfig);
|
|
7
|
+
getAuthUrl(redirectUri: string): string;
|
|
8
|
+
handleCallback(code: string, redirectUri: string): Promise<ProviderTokens>;
|
|
9
|
+
refreshToken(): Promise<ProviderTokens>;
|
|
10
|
+
isAuthorized(): boolean;
|
|
11
|
+
ensureAuth(): Promise<void>;
|
|
12
|
+
private drive;
|
|
13
|
+
/**
|
|
14
|
+
* Walk path segments and return the Drive folder ID, creating any missing folders.
|
|
15
|
+
* Empty path or '/' returns 'root'.
|
|
16
|
+
*/
|
|
17
|
+
private getOrCreateFolder;
|
|
18
|
+
/**
|
|
19
|
+
* Find the Drive file ID for a given remote path.
|
|
20
|
+
* Returns null if not found.
|
|
21
|
+
*/
|
|
22
|
+
private findFileId;
|
|
23
|
+
listFiles(remotePath: string): Promise<RemoteFile[]>;
|
|
24
|
+
uploadFile(localPath: string, remotePath: string): Promise<void>;
|
|
25
|
+
downloadFile(remotePath: string, localPath: string): Promise<void>;
|
|
26
|
+
deleteFile(remotePath: string): Promise<void>;
|
|
27
|
+
mkdir(remotePath: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=google-drive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-drive.d.ts","sourceRoot":"","sources":["../../../src/backup/providers/google-drive.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAErF,qBAAa,mBAAoB,YAAW,aAAa;IACvD,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,aAAa,CAAkC;gBAE3C,MAAM,EAAE,cAAc;IAgBlC,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAajC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA0B1E,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC;IAwB7C,YAAY,IAAI,OAAO;IAIjB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAajC,OAAO,CAAC,KAAK;IAIb;;;OAGG;YACW,iBAAiB;IAmD/B;;;OAGG;YACW,UAAU;IA0BlB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAoCpD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4ChE,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBlE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB7C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI/C"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// backend/src/backup/providers/google-drive.ts
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.GoogleDriveProvider = void 0;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const googleapis_1 = require("googleapis");
|
|
40
|
+
class GoogleDriveProvider {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
this.folderIdCache = new Map();
|
|
43
|
+
this.config = config;
|
|
44
|
+
this.oauth2Client = new googleapis_1.google.auth.OAuth2(config.clientId, config.clientSecret);
|
|
45
|
+
if (config.tokens) {
|
|
46
|
+
this.oauth2Client.setCredentials({
|
|
47
|
+
access_token: config.tokens.access_token,
|
|
48
|
+
refresh_token: config.tokens.refresh_token,
|
|
49
|
+
expiry_date: new Date(config.tokens.expiry).getTime(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
getAuthUrl(redirectUri) {
|
|
54
|
+
const client = new googleapis_1.google.auth.OAuth2(this.config.clientId, this.config.clientSecret, redirectUri);
|
|
55
|
+
return client.generateAuthUrl({
|
|
56
|
+
access_type: 'offline',
|
|
57
|
+
prompt: 'consent',
|
|
58
|
+
scope: ['https://www.googleapis.com/auth/drive.file'],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async handleCallback(code, redirectUri) {
|
|
62
|
+
const client = new googleapis_1.google.auth.OAuth2(this.config.clientId, this.config.clientSecret, redirectUri);
|
|
63
|
+
const { tokens } = await client.getToken(code);
|
|
64
|
+
if (!tokens.access_token || !tokens.refresh_token) {
|
|
65
|
+
throw new Error('Missing tokens in OAuth2 callback response');
|
|
66
|
+
}
|
|
67
|
+
const providerTokens = {
|
|
68
|
+
access_token: tokens.access_token,
|
|
69
|
+
refresh_token: tokens.refresh_token,
|
|
70
|
+
expiry: tokens.expiry_date
|
|
71
|
+
? new Date(tokens.expiry_date).toISOString()
|
|
72
|
+
: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
73
|
+
};
|
|
74
|
+
this.oauth2Client.setCredentials({
|
|
75
|
+
access_token: providerTokens.access_token,
|
|
76
|
+
refresh_token: providerTokens.refresh_token,
|
|
77
|
+
expiry_date: new Date(providerTokens.expiry).getTime(),
|
|
78
|
+
});
|
|
79
|
+
this.config.tokens = providerTokens;
|
|
80
|
+
return providerTokens;
|
|
81
|
+
}
|
|
82
|
+
async refreshToken() {
|
|
83
|
+
const { credentials } = await this.oauth2Client.refreshAccessToken();
|
|
84
|
+
if (!credentials.access_token) {
|
|
85
|
+
throw new Error('Failed to refresh access token');
|
|
86
|
+
}
|
|
87
|
+
const providerTokens = {
|
|
88
|
+
access_token: credentials.access_token,
|
|
89
|
+
refresh_token: credentials.refresh_token ??
|
|
90
|
+
this.config.tokens?.refresh_token ??
|
|
91
|
+
'',
|
|
92
|
+
expiry: credentials.expiry_date
|
|
93
|
+
? new Date(credentials.expiry_date).toISOString()
|
|
94
|
+
: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
95
|
+
};
|
|
96
|
+
this.oauth2Client.setCredentials({
|
|
97
|
+
access_token: providerTokens.access_token,
|
|
98
|
+
refresh_token: providerTokens.refresh_token,
|
|
99
|
+
expiry_date: new Date(providerTokens.expiry).getTime(),
|
|
100
|
+
});
|
|
101
|
+
this.config.tokens = providerTokens;
|
|
102
|
+
return providerTokens;
|
|
103
|
+
}
|
|
104
|
+
isAuthorized() {
|
|
105
|
+
return !!(this.config.tokens?.access_token && this.config.tokens?.refresh_token);
|
|
106
|
+
}
|
|
107
|
+
async ensureAuth() {
|
|
108
|
+
if (!this.isAuthorized()) {
|
|
109
|
+
throw new Error('Google Drive provider is not authorized');
|
|
110
|
+
}
|
|
111
|
+
const tokens = this.config.tokens;
|
|
112
|
+
const expiryMs = new Date(tokens.expiry).getTime();
|
|
113
|
+
const nowMs = Date.now();
|
|
114
|
+
// Refresh if within 60 seconds of expiry
|
|
115
|
+
if (expiryMs - nowMs < 60 * 1000) {
|
|
116
|
+
await this.refreshToken();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
drive() {
|
|
120
|
+
return googleapis_1.google.drive({ version: 'v3', auth: this.oauth2Client });
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Walk path segments and return the Drive folder ID, creating any missing folders.
|
|
124
|
+
* Empty path or '/' returns 'root'.
|
|
125
|
+
*/
|
|
126
|
+
async getOrCreateFolder(folderPath) {
|
|
127
|
+
const normalized = folderPath.replace(/^\/+|\/+$/g, '');
|
|
128
|
+
if (!normalized)
|
|
129
|
+
return 'root';
|
|
130
|
+
if (this.folderIdCache.has(normalized)) {
|
|
131
|
+
return this.folderIdCache.get(normalized);
|
|
132
|
+
}
|
|
133
|
+
const segments = normalized.split('/');
|
|
134
|
+
let parentId = 'root';
|
|
135
|
+
for (let i = 0; i < segments.length; i++) {
|
|
136
|
+
const partialPath = segments.slice(0, i + 1).join('/');
|
|
137
|
+
if (this.folderIdCache.has(partialPath)) {
|
|
138
|
+
parentId = this.folderIdCache.get(partialPath);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const segment = segments[i];
|
|
142
|
+
const drive = this.drive();
|
|
143
|
+
// Search for existing folder
|
|
144
|
+
const res = await drive.files.list({
|
|
145
|
+
q: `name = '${segment.replace(/'/g, "\\'")}' and mimeType = 'application/vnd.google-apps.folder' and '${parentId}' in parents and trashed = false`,
|
|
146
|
+
fields: 'files(id)',
|
|
147
|
+
pageSize: 1,
|
|
148
|
+
});
|
|
149
|
+
let folderId;
|
|
150
|
+
if (res.data.files && res.data.files.length > 0) {
|
|
151
|
+
folderId = res.data.files[0].id;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Create the folder
|
|
155
|
+
const created = await drive.files.create({
|
|
156
|
+
requestBody: {
|
|
157
|
+
name: segment,
|
|
158
|
+
mimeType: 'application/vnd.google-apps.folder',
|
|
159
|
+
parents: [parentId],
|
|
160
|
+
},
|
|
161
|
+
fields: 'id',
|
|
162
|
+
});
|
|
163
|
+
folderId = created.data.id;
|
|
164
|
+
}
|
|
165
|
+
this.folderIdCache.set(partialPath, folderId);
|
|
166
|
+
parentId = folderId;
|
|
167
|
+
}
|
|
168
|
+
return parentId;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Find the Drive file ID for a given remote path.
|
|
172
|
+
* Returns null if not found.
|
|
173
|
+
*/
|
|
174
|
+
async findFileId(remotePath) {
|
|
175
|
+
const normalized = remotePath.replace(/^\/+/, '');
|
|
176
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
177
|
+
const parentPath = lastSlash >= 0 ? normalized.slice(0, lastSlash) : '';
|
|
178
|
+
const fileName = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
|
|
179
|
+
let parentId;
|
|
180
|
+
try {
|
|
181
|
+
parentId = await this.getOrCreateFolder(parentPath);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const drive = this.drive();
|
|
187
|
+
const res = await drive.files.list({
|
|
188
|
+
q: `name = '${fileName.replace(/'/g, "\\'")}' and '${parentId}' in parents and trashed = false`,
|
|
189
|
+
fields: 'files(id)',
|
|
190
|
+
pageSize: 1,
|
|
191
|
+
});
|
|
192
|
+
if (res.data.files && res.data.files.length > 0) {
|
|
193
|
+
return res.data.files[0].id;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
async listFiles(remotePath) {
|
|
198
|
+
await this.ensureAuth();
|
|
199
|
+
const folderId = await this.getOrCreateFolder(remotePath);
|
|
200
|
+
const drive = this.drive();
|
|
201
|
+
const results = [];
|
|
202
|
+
let pageToken;
|
|
203
|
+
const normalizedBase = remotePath.replace(/^\/+|\/+$/g, '');
|
|
204
|
+
do {
|
|
205
|
+
const res = await drive.files.list({
|
|
206
|
+
q: `'${folderId}' in parents and trashed = false`,
|
|
207
|
+
fields: 'nextPageToken, files(id, name, mimeType, size, modifiedTime)',
|
|
208
|
+
pageSize: 1000,
|
|
209
|
+
...(pageToken ? { pageToken } : {}),
|
|
210
|
+
});
|
|
211
|
+
for (const file of res.data.files ?? []) {
|
|
212
|
+
const isDirectory = file.mimeType === 'application/vnd.google-apps.folder';
|
|
213
|
+
const filePath = normalizedBase ? `${normalizedBase}/${file.name}` : file.name;
|
|
214
|
+
results.push({
|
|
215
|
+
name: file.name,
|
|
216
|
+
path: filePath,
|
|
217
|
+
isDirectory,
|
|
218
|
+
size: isDirectory ? 0 : parseInt(file.size ?? '0', 10),
|
|
219
|
+
modifiedTime: file.modifiedTime ?? new Date().toISOString(),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
pageToken = res.data.nextPageToken ?? undefined;
|
|
223
|
+
} while (pageToken);
|
|
224
|
+
return results;
|
|
225
|
+
}
|
|
226
|
+
async uploadFile(localPath, remotePath) {
|
|
227
|
+
await this.ensureAuth();
|
|
228
|
+
const normalized = remotePath.replace(/^\/+/, '');
|
|
229
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
230
|
+
const parentPath = lastSlash >= 0 ? normalized.slice(0, lastSlash) : '';
|
|
231
|
+
const fileName = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
|
|
232
|
+
const parentId = await this.getOrCreateFolder(parentPath);
|
|
233
|
+
const drive = this.drive();
|
|
234
|
+
const fileStream = fs.createReadStream(localPath);
|
|
235
|
+
const stat = fs.statSync(localPath);
|
|
236
|
+
// Check if file already exists
|
|
237
|
+
const existingId = await this.findFileId(remotePath);
|
|
238
|
+
if (existingId) {
|
|
239
|
+
// Update existing file
|
|
240
|
+
await drive.files.update({
|
|
241
|
+
fileId: existingId,
|
|
242
|
+
requestBody: {},
|
|
243
|
+
media: {
|
|
244
|
+
body: fileStream,
|
|
245
|
+
},
|
|
246
|
+
fields: 'id',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Create new file
|
|
251
|
+
await drive.files.create({
|
|
252
|
+
requestBody: {
|
|
253
|
+
name: fileName,
|
|
254
|
+
parents: [parentId],
|
|
255
|
+
},
|
|
256
|
+
media: {
|
|
257
|
+
body: fileStream,
|
|
258
|
+
},
|
|
259
|
+
fields: 'id',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Suppress unused variable warning
|
|
263
|
+
void stat;
|
|
264
|
+
}
|
|
265
|
+
async downloadFile(remotePath, localPath) {
|
|
266
|
+
await this.ensureAuth();
|
|
267
|
+
const fileId = await this.findFileId(remotePath);
|
|
268
|
+
if (!fileId) {
|
|
269
|
+
throw new Error(`File not found on Google Drive: ${remotePath}`);
|
|
270
|
+
}
|
|
271
|
+
const drive = this.drive();
|
|
272
|
+
const res = await drive.files.get({ fileId, alt: 'media' }, { responseType: 'stream' });
|
|
273
|
+
await new Promise((resolve, reject) => {
|
|
274
|
+
const dest = fs.createWriteStream(localPath);
|
|
275
|
+
res.data
|
|
276
|
+
.on('error', reject)
|
|
277
|
+
.pipe(dest)
|
|
278
|
+
.on('error', reject)
|
|
279
|
+
.on('finish', resolve);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
async deleteFile(remotePath) {
|
|
283
|
+
await this.ensureAuth();
|
|
284
|
+
const fileId = await this.findFileId(remotePath);
|
|
285
|
+
if (!fileId) {
|
|
286
|
+
// Already gone — treat as success
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const drive = this.drive();
|
|
290
|
+
await drive.files.delete({ fileId });
|
|
291
|
+
// Invalidate cache entries that include this path
|
|
292
|
+
const normalized = remotePath.replace(/^\/+|\/+$/g, '');
|
|
293
|
+
for (const key of this.folderIdCache.keys()) {
|
|
294
|
+
if (key === normalized || key.startsWith(normalized + '/')) {
|
|
295
|
+
this.folderIdCache.delete(key);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async mkdir(remotePath) {
|
|
300
|
+
await this.ensureAuth();
|
|
301
|
+
await this.getOrCreateFolder(remotePath);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
exports.GoogleDriveProvider = GoogleDriveProvider;
|
|
305
|
+
//# sourceMappingURL=google-drive.js.map
|