@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.
Files changed (3) hide show
  1. package/index.mjs +70 -100
  2. package/index.mjs.r2bak +3544 -0
  3. 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
- const upload_file_to_spaces = async (tempPath, ref, drive_type, file_name) => {
1807
- const spacesEndpoint = new S3Client({
1808
- endpoint: _conf.storage_bucket[process.env.XUDA_HOSTNAME].endpoint,
1809
- region: _conf.storage_bucket[process.env.XUDA_HOSTNAME].region,
1810
- credentials: {
1811
- accessKeyId: _conf.digitalocean.spaces_access_key,
1812
- secretAccessKey: _conf.digitalocean.spaces_secret_key,
1813
- },
1814
- forcePathStyle: false,
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
- const fileContent = fs.createReadStream(tempPath);
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
- // const uploadParams = {
1820
- // Bucket: `${process.env.XUDA_HOSTNAME}/${drive_type}`,
1821
- // Key: path.join(ref, _utils.UUID() + '_' + file_name),
1822
- // Body: fileContent,
1823
- // ACL: 'public-read',
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
- const uploadParams = {
1827
- Bucket: _conf.storage_bucket[process.env.XUDA_HOSTNAME].name,
1828
- Key: path.join(process.env.XUDA_HOSTNAME, drive_type, ref, _utils.UUID() + '_' + file_name),
1829
- Body: fileContent,
1830
- ACL: 'public-read',
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: spacesEndpoint,
1836
- params: uploadParams,
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
- return data;
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
- const sourceS3 = new S3Client({
1848
- endpoint: _conf.storage_bucket[source_region].endpoint,
1849
- region: _conf.storage_bucket[source_region].region,
1850
- credentials: {
1851
- accessKeyId: _conf.digitalocean.spaces_access_key,
1852
- secretAccessKey: _conf.digitalocean.spaces_secret_key,
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
- forcePathStyle: false,
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 [key, val] of Object.entries(bucket)) {
1991
- await s3.send(new DeleteObjectCommand({ Bucket: val.Bucket, Key: val.key || val.Key }));
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
- accessKeyId: _conf.digitalocean.spaces_access_key,
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, ACL: 'public-read', ContentType: content_type },
3035
+ params: { Bucket, Key, Body: body, ContentType: content_type },
3066
3036
  });
3067
- const res = await up.done();
3068
- // Prefer the SDK-reported Location; reconstruct a virtual-hosted URL if absent.
3069
- const location = res?.Location || (endpoint_host ? `https://${Bucket}.${endpoint_host}/${Key}` : null);
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
  }