@xuda.io/drive_module 1.1.1487 → 1.1.1489
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/index.mjs +70 -100
- package/index.mjs.r2bak +3544 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1803,97 +1803,76 @@ export const get_drive_size = async (req) => {
|
|
|
1803
1803
|
}
|
|
1804
1804
|
};
|
|
1805
1805
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1806
|
+
// --- Object storage (S3 / R2) helpers ------------------------------------
|
|
1807
|
+
// Credentials: prefer R2 (post-migration), fall back to DO Spaces so the same
|
|
1808
|
+
// code runs before and after the cutover.
|
|
1809
|
+
const _storage_credentials = () => {
|
|
1810
|
+
if (_conf.r2?.access_key_id && _conf.r2?.secret_access_key) {
|
|
1811
|
+
return { accessKeyId: _conf.r2.access_key_id, secretAccessKey: _conf.r2.secret_access_key };
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
accessKeyId: _conf.digitalocean?.spaces_access_key,
|
|
1815
|
+
secretAccessKey: _conf.digitalocean?.spaces_secret_key,
|
|
1816
|
+
};
|
|
1817
|
+
};
|
|
1816
1818
|
|
|
1817
|
-
|
|
1819
|
+
// R2 addresses buckets path-style against the account endpoint; DO Spaces uses
|
|
1820
|
+
// virtual-hosted style.
|
|
1821
|
+
const _is_r2 = () => !!(_conf.r2?.access_key_id);
|
|
1818
1822
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1823
|
+
// Public URL for a stored object, ALWAYS rebuilt from the Key. Post-migration
|
|
1824
|
+
// every bucket is served from one custom domain (r2.public_base); pre-migration
|
|
1825
|
+
// we reconstruct the virtual-hosted DO Spaces URL. Rebuilding from the Key means
|
|
1826
|
+
// existing docs that still carry old DO Locations resolve to the live backend.
|
|
1827
|
+
const _public_url_for = (region_host, key) => {
|
|
1828
|
+
const k = String(key || '').replace(/^\/+/, '');
|
|
1829
|
+
if (!k) return null;
|
|
1830
|
+
if (_conf.r2?.public_base) return `${String(_conf.r2.public_base).replace(/\/+$/, '')}/${k}`;
|
|
1831
|
+
const conf = _conf.storage_bucket?.[region_host];
|
|
1832
|
+
if (!conf) return null;
|
|
1833
|
+
const host = String(conf.endpoint || '').replace(/^https?:\/\//, '').replace(/\/+$/, '');
|
|
1834
|
+
return host ? `https://${conf.name}.${host}/${k}` : null;
|
|
1835
|
+
};
|
|
1825
1836
|
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1837
|
+
const upload_file_to_spaces = async (tempPath, ref, drive_type, file_name) => {
|
|
1838
|
+
const region_host = process.env.XUDA_HOSTNAME;
|
|
1839
|
+
const conf = _conf.storage_bucket?.[region_host];
|
|
1840
|
+
const s3 = _drive_s3_for(region_host);
|
|
1841
|
+
if (!s3 || !conf) {
|
|
1842
|
+
console.error('upload_file_to_spaces: no storage config for', region_host);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const Bucket = conf.name;
|
|
1846
|
+
const Key = path.join(region_host, drive_type, ref, _utils.UUID() + '_' + file_name);
|
|
1832
1847
|
|
|
1833
1848
|
try {
|
|
1834
1849
|
const upload = new Upload({
|
|
1835
|
-
client:
|
|
1836
|
-
params:
|
|
1850
|
+
client: s3,
|
|
1851
|
+
params: { Bucket, Key, Body: fs.createReadStream(tempPath), ContentType: _drive_content_type_for(file_name) },
|
|
1837
1852
|
});
|
|
1838
1853
|
|
|
1839
1854
|
const data = await upload.done();
|
|
1840
|
-
|
|
1855
|
+
// Override the SDK Location with the canonical public URL (custom domain on
|
|
1856
|
+
// R2), and pin Bucket/Key so the stored doc.bucket entry is backend-correct.
|
|
1857
|
+
return { ...data, Location: _public_url_for(region_host, Key), Bucket, Key, key: Key };
|
|
1841
1858
|
} catch (err) {
|
|
1842
1859
|
console.error('Error', err);
|
|
1843
1860
|
}
|
|
1844
1861
|
};
|
|
1845
1862
|
|
|
1863
|
+
// Retired under the single R2 namespace: every region resolves to the same
|
|
1864
|
+
// bucket, so there is nothing to copy. Kept as a no-op so existing callers keep
|
|
1865
|
+
// working — returns a backend-correct location rebuilt from the Key.
|
|
1846
1866
|
export const copy_file_to_another_bucket = async (source_region, target_region, file_name) => {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
forcePathStyle: false,
|
|
1855
|
-
});
|
|
1856
|
-
|
|
1857
|
-
const targetS3 = new S3Client({
|
|
1858
|
-
endpoint: _conf.storage_bucket[target_region].endpoint,
|
|
1859
|
-
region: _conf.storage_bucket[target_region].region,
|
|
1860
|
-
credentials: {
|
|
1861
|
-
accessKeyId: _conf.digitalocean.spaces_access_key,
|
|
1862
|
-
secretAccessKey: _conf.digitalocean.spaces_secret_key,
|
|
1867
|
+
return {
|
|
1868
|
+
code: 1,
|
|
1869
|
+
data: {
|
|
1870
|
+
Location: _public_url_for(target_region, file_name),
|
|
1871
|
+
key: file_name,
|
|
1872
|
+
Key: file_name,
|
|
1873
|
+
Bucket: _conf.storage_bucket?.[target_region]?.name,
|
|
1863
1874
|
},
|
|
1864
|
-
|
|
1865
|
-
});
|
|
1866
|
-
|
|
1867
|
-
try {
|
|
1868
|
-
const { Body } = await sourceS3.send(
|
|
1869
|
-
new GetObjectCommand({
|
|
1870
|
-
Bucket: source_region,
|
|
1871
|
-
Key: file_name,
|
|
1872
|
-
}),
|
|
1873
|
-
);
|
|
1874
|
-
|
|
1875
|
-
const upload = new Upload({
|
|
1876
|
-
client: targetS3,
|
|
1877
|
-
params: {
|
|
1878
|
-
Bucket: target_region,
|
|
1879
|
-
Key: file_name,
|
|
1880
|
-
Body,
|
|
1881
|
-
ACL: 'public-read',
|
|
1882
|
-
},
|
|
1883
|
-
});
|
|
1884
|
-
|
|
1885
|
-
const ret = await upload.done();
|
|
1886
|
-
|
|
1887
|
-
ret.Location = `${_conf.storage_bucket[target_region].endpoint}/${target_region}/${file_name}`;
|
|
1888
|
-
ret.key = file_name;
|
|
1889
|
-
ret.Key = file_name;
|
|
1890
|
-
ret.Bucket = target_region;
|
|
1891
|
-
|
|
1892
|
-
return { code: 1, data: ret };
|
|
1893
|
-
} catch (err) {
|
|
1894
|
-
console.error('Error copying file:', err);
|
|
1895
|
-
return { code: -1, data: err.message };
|
|
1896
|
-
}
|
|
1875
|
+
};
|
|
1897
1876
|
};
|
|
1898
1877
|
|
|
1899
1878
|
const get_folder_size = async (dir, drive_type, app_id, uid) => {
|
|
@@ -1976,19 +1955,17 @@ const get_file_info = async (file_path) => {
|
|
|
1976
1955
|
};
|
|
1977
1956
|
|
|
1978
1957
|
export const delete_file_from_bucket = async (bucket) => {
|
|
1979
|
-
const s3 = new S3Client({
|
|
1980
|
-
endpoint: _conf.storage_bucket[process.env.XUDA_HOSTNAME].endpoint,
|
|
1981
|
-
region: _conf.storage_bucket[process.env.XUDA_HOSTNAME].region,
|
|
1982
|
-
credentials: {
|
|
1983
|
-
accessKeyId: _conf.digitalocean.spaces_access_key,
|
|
1984
|
-
secretAccessKey: _conf.digitalocean.spaces_secret_key,
|
|
1985
|
-
},
|
|
1986
|
-
forcePathStyle: false,
|
|
1987
|
-
});
|
|
1988
|
-
|
|
1989
1958
|
try {
|
|
1990
|
-
for await (let [
|
|
1991
|
-
|
|
1959
|
+
for await (let [region_host, val] of Object.entries(bucket || {})) {
|
|
1960
|
+
const Key = val?.key || val?.Key;
|
|
1961
|
+
if (!Key) continue;
|
|
1962
|
+
// Resolve the client + bucket NAME from config for the file's region (fall
|
|
1963
|
+
// back to this server's region). Never trust the stored val.Bucket — after
|
|
1964
|
+
// the R2 cutover that name (e.g. xuda-bucket-nyc) no longer exists.
|
|
1965
|
+
const s3 = _drive_s3_for(region_host) || _drive_s3_for(process.env.XUDA_HOSTNAME);
|
|
1966
|
+
const Bucket = _conf.storage_bucket?.[region_host]?.name || _conf.storage_bucket?.[process.env.XUDA_HOSTNAME]?.name;
|
|
1967
|
+
if (!s3 || !Bucket) continue;
|
|
1968
|
+
await s3.send(new DeleteObjectCommand({ Bucket, Key }));
|
|
1992
1969
|
}
|
|
1993
1970
|
} catch (err) {
|
|
1994
1971
|
console.error('Error', err);
|
|
@@ -2920,11 +2897,8 @@ const _drive_s3_for = (region_host) => {
|
|
|
2920
2897
|
_drive_s3_cache[region_host] = new S3Client({
|
|
2921
2898
|
endpoint: conf.endpoint,
|
|
2922
2899
|
region: conf.region,
|
|
2923
|
-
credentials:
|
|
2924
|
-
|
|
2925
|
-
secretAccessKey: _conf.digitalocean.spaces_secret_key,
|
|
2926
|
-
},
|
|
2927
|
-
forcePathStyle: false,
|
|
2900
|
+
credentials: _storage_credentials(),
|
|
2901
|
+
forcePathStyle: _is_r2(),
|
|
2928
2902
|
});
|
|
2929
2903
|
}
|
|
2930
2904
|
return _drive_s3_cache[region_host];
|
|
@@ -3044,10 +3018,6 @@ export const put_site_files = async (req) => {
|
|
|
3044
3018
|
const Bucket = conf.name;
|
|
3045
3019
|
if (!s3 || !Bucket) return { code: -1, data: `no S3 client for region ${region_host}` };
|
|
3046
3020
|
|
|
3047
|
-
const endpoint_host = String(conf.endpoint || '')
|
|
3048
|
-
.replace(/^https?:\/\//, '')
|
|
3049
|
-
.replace(/\/+$/, '');
|
|
3050
|
-
|
|
3051
3021
|
const manifest = {};
|
|
3052
3022
|
let bytes = 0;
|
|
3053
3023
|
for (const f of files) {
|
|
@@ -3062,11 +3032,11 @@ export const put_site_files = async (req) => {
|
|
|
3062
3032
|
const Key = `sites/${site_id}/${version}${norm}`;
|
|
3063
3033
|
const up = new Upload({
|
|
3064
3034
|
client: s3,
|
|
3065
|
-
params: { Bucket, Key, Body: body,
|
|
3035
|
+
params: { Bucket, Key, Body: body, ContentType: content_type },
|
|
3066
3036
|
});
|
|
3067
|
-
|
|
3068
|
-
//
|
|
3069
|
-
const location =
|
|
3037
|
+
await up.done();
|
|
3038
|
+
// Canonical public URL, rebuilt from the Key (custom domain on R2).
|
|
3039
|
+
const location = _public_url_for(region_host, Key);
|
|
3070
3040
|
manifest[norm] = { key: Key, bucket: Bucket, region: region_host, location, content_type, bytes: body.length };
|
|
3071
3041
|
bytes += body.length;
|
|
3072
3042
|
}
|