@xuda.io/drive_module 1.1.1488 → 1.1.1490

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 CHANGED
@@ -19,11 +19,7 @@ import { Upload } from '@aws-sdk/lib-storage';
19
19
 
20
20
  console.log('Drive Module loaded...');
21
21
 
22
- global._conf = (
23
- await import(path.join(process.env.XUDA_HOME, process.env.XUDA_CONFIG), {
24
- with: { type: 'json' },
25
- })
26
- ).default;
22
+ global._conf = (await import(path.join(process.env.XUDA_HOME, "common", "load_conf.mjs"))).loadConf();
27
23
 
28
24
  const _common = await import(path.join(process.env.XUDA_HOME, 'common', 'xuda_node_common.mjs'));
29
25
  const _utils = await import(path.join(process.env.XUDA_HOME, 'common', 'xuda-cpi-utils.mjs'));
@@ -1803,97 +1799,76 @@ export const get_drive_size = async (req) => {
1803
1799
  }
1804
1800
  };
1805
1801
 
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
- });
1802
+ // --- Object storage (S3 / R2) helpers ------------------------------------
1803
+ // Credentials: prefer R2 (post-migration), fall back to DO Spaces so the same
1804
+ // code runs before and after the cutover.
1805
+ const _storage_credentials = () => {
1806
+ if (_conf.r2?.access_key_id && _conf.r2?.secret_access_key) {
1807
+ return { accessKeyId: _conf.r2.access_key_id, secretAccessKey: _conf.r2.secret_access_key };
1808
+ }
1809
+ return {
1810
+ accessKeyId: _conf.digitalocean?.spaces_access_key,
1811
+ secretAccessKey: _conf.digitalocean?.spaces_secret_key,
1812
+ };
1813
+ };
1816
1814
 
1817
- const fileContent = fs.createReadStream(tempPath);
1815
+ // R2 addresses buckets path-style against the account endpoint; DO Spaces uses
1816
+ // virtual-hosted style.
1817
+ const _is_r2 = () => !!(_conf.r2?.access_key_id);
1818
1818
 
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
- // };
1819
+ // Public URL for a stored object, ALWAYS rebuilt from the Key. Post-migration
1820
+ // every bucket is served from one custom domain (r2.public_base); pre-migration
1821
+ // we reconstruct the virtual-hosted DO Spaces URL. Rebuilding from the Key means
1822
+ // existing docs that still carry old DO Locations resolve to the live backend.
1823
+ const _public_url_for = (region_host, key) => {
1824
+ const k = String(key || '').replace(/^\/+/, '');
1825
+ if (!k) return null;
1826
+ if (_conf.r2?.public_base) return `${String(_conf.r2.public_base).replace(/\/+$/, '')}/${k}`;
1827
+ const conf = _conf.storage_bucket?.[region_host];
1828
+ if (!conf) return null;
1829
+ const host = String(conf.endpoint || '').replace(/^https?:\/\//, '').replace(/\/+$/, '');
1830
+ return host ? `https://${conf.name}.${host}/${k}` : null;
1831
+ };
1825
1832
 
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
- };
1833
+ const upload_file_to_spaces = async (tempPath, ref, drive_type, file_name) => {
1834
+ const region_host = process.env.XUDA_HOSTNAME;
1835
+ const conf = _conf.storage_bucket?.[region_host];
1836
+ const s3 = _drive_s3_for(region_host);
1837
+ if (!s3 || !conf) {
1838
+ console.error('upload_file_to_spaces: no storage config for', region_host);
1839
+ return;
1840
+ }
1841
+ const Bucket = conf.name;
1842
+ const Key = path.join(region_host, drive_type, ref, _utils.UUID() + '_' + file_name);
1832
1843
 
1833
1844
  try {
1834
1845
  const upload = new Upload({
1835
- client: spacesEndpoint,
1836
- params: uploadParams,
1846
+ client: s3,
1847
+ params: { Bucket, Key, Body: fs.createReadStream(tempPath), ContentType: _drive_content_type_for(file_name) },
1837
1848
  });
1838
1849
 
1839
1850
  const data = await upload.done();
1840
- return data;
1851
+ // Override the SDK Location with the canonical public URL (custom domain on
1852
+ // R2), and pin Bucket/Key so the stored doc.bucket entry is backend-correct.
1853
+ return { ...data, Location: _public_url_for(region_host, Key), Bucket, Key, key: Key };
1841
1854
  } catch (err) {
1842
1855
  console.error('Error', err);
1843
1856
  }
1844
1857
  };
1845
1858
 
1859
+ // Retired under the single R2 namespace: every region resolves to the same
1860
+ // bucket, so there is nothing to copy. Kept as a no-op so existing callers keep
1861
+ // working — returns a backend-correct location rebuilt from the Key.
1846
1862
  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,
1863
+ return {
1864
+ code: 1,
1865
+ data: {
1866
+ Location: _public_url_for(target_region, file_name),
1867
+ key: file_name,
1868
+ Key: file_name,
1869
+ Bucket: _conf.storage_bucket?.[target_region]?.name,
1863
1870
  },
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
- }
1871
+ };
1897
1872
  };
1898
1873
 
1899
1874
  const get_folder_size = async (dir, drive_type, app_id, uid) => {
@@ -1976,19 +1951,17 @@ const get_file_info = async (file_path) => {
1976
1951
  };
1977
1952
 
1978
1953
  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
1954
  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 }));
1955
+ for await (let [region_host, val] of Object.entries(bucket || {})) {
1956
+ const Key = val?.key || val?.Key;
1957
+ if (!Key) continue;
1958
+ // Resolve the client + bucket NAME from config for the file's region (fall
1959
+ // back to this server's region). Never trust the stored val.Bucket — after
1960
+ // the R2 cutover that name (e.g. xuda-bucket-nyc) no longer exists.
1961
+ const s3 = _drive_s3_for(region_host) || _drive_s3_for(process.env.XUDA_HOSTNAME);
1962
+ const Bucket = _conf.storage_bucket?.[region_host]?.name || _conf.storage_bucket?.[process.env.XUDA_HOSTNAME]?.name;
1963
+ if (!s3 || !Bucket) continue;
1964
+ await s3.send(new DeleteObjectCommand({ Bucket, Key }));
1992
1965
  }
1993
1966
  } catch (err) {
1994
1967
  console.error('Error', err);
@@ -2920,11 +2893,8 @@ const _drive_s3_for = (region_host) => {
2920
2893
  _drive_s3_cache[region_host] = new S3Client({
2921
2894
  endpoint: conf.endpoint,
2922
2895
  region: conf.region,
2923
- credentials: {
2924
- accessKeyId: _conf.digitalocean.spaces_access_key,
2925
- secretAccessKey: _conf.digitalocean.spaces_secret_key,
2926
- },
2927
- forcePathStyle: false,
2896
+ credentials: _storage_credentials(),
2897
+ forcePathStyle: _is_r2(),
2928
2898
  });
2929
2899
  }
2930
2900
  return _drive_s3_cache[region_host];
@@ -3044,10 +3014,6 @@ export const put_site_files = async (req) => {
3044
3014
  const Bucket = conf.name;
3045
3015
  if (!s3 || !Bucket) return { code: -1, data: `no S3 client for region ${region_host}` };
3046
3016
 
3047
- const endpoint_host = String(conf.endpoint || '')
3048
- .replace(/^https?:\/\//, '')
3049
- .replace(/\/+$/, '');
3050
-
3051
3017
  const manifest = {};
3052
3018
  let bytes = 0;
3053
3019
  for (const f of files) {
@@ -3062,11 +3028,11 @@ export const put_site_files = async (req) => {
3062
3028
  const Key = `sites/${site_id}/${version}${norm}`;
3063
3029
  const up = new Upload({
3064
3030
  client: s3,
3065
- params: { Bucket, Key, Body: body, ACL: 'public-read', ContentType: content_type },
3031
+ params: { Bucket, Key, Body: body, ContentType: content_type },
3066
3032
  });
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);
3033
+ await up.done();
3034
+ // Canonical public URL, rebuilt from the Key (custom domain on R2).
3035
+ const location = _public_url_for(region_host, Key);
3070
3036
  manifest[norm] = { key: Key, bucket: Bucket, region: region_host, location, content_type, bytes: body.length };
3071
3037
  bytes += body.length;
3072
3038
  }