feat: migrate to PostgreSQL GORM and MinIO file storage
Use UUID models and soft deletion, persist file relationships with deduplicated monthly MinIO objects, and add admin file management. Update mini-program media handling and add local dev configuration.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const loader = require('./load-module.cjs');
|
||||
test('development requests and file previews use local backend, trial and release use production', () => {
|
||||
for (const version of ['develop','trial','release']) {
|
||||
const wx = {getAccountInfoSync:()=>({miniProgram:{envVersion:version}})};
|
||||
const load = loader({wx,console});
|
||||
const env = load(path.join(__dirname,'../config/env.js'));
|
||||
const {API_BASE,getImageUrl} = load(path.join(__dirname,'../utils/request.js'));
|
||||
assert.equal(env.env, version === 'develop' ? 'dev' : version);
|
||||
assert.equal(API_BASE, version === 'develop' ? 'http://127.0.0.1:8080/api' : 'https://past.sundynix.cn/api');
|
||||
assert.equal(getImageUrl('/api/files/6083e119-6b26-441b-8b70-44d224366238'),API_BASE+'/files/6083e119-6b26-441b-8b70-44d224366238');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const createLoader = require('./load-module.cjs');
|
||||
const {uploadReference} = require('../utils/file-media');
|
||||
const id = '6083e119-6b26-441b-8b70-44d224366238';
|
||||
const id2 = '7083e119-6b26-441b-8b70-44d224366238';
|
||||
function client(extra = {}) {
|
||||
const calls = [];
|
||||
const load = createLoader({console, Promise, Date, wx: {
|
||||
getStorageSync: () => 'token', hideLoading() {},
|
||||
request(o) { calls.push(o); o.success({statusCode:200, data:{success:true}}); }, ...extra
|
||||
}});
|
||||
return {...load(path.join(__dirname, '../utils/request.js')), calls};
|
||||
}
|
||||
test('business requests persist IDs, preserve order and distinguish clear from omitted fields', async () => {
|
||||
const c = client();
|
||||
await c.api.diaryUpdate({id:'diary',images:['https://past.sundynix.cn/api/files/'+id, id2]});
|
||||
await c.api.albumUpdate({id:'album',cover:''});
|
||||
await c.api.updateProfile({nickname:'test'});
|
||||
await c.api.photoAdd({album_id:'album',path:'/api/files/'+id});
|
||||
await c.api.checkinDo({id:'checkin',photo:id});
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(c.calls[0].data)), {id:'diary',imageFileIds:[id,id2]});
|
||||
assert.equal(c.calls[1].data.coverFileId,'');
|
||||
assert.equal('avatarFileId' in c.calls[2].data,false);
|
||||
assert.equal(c.calls[3].data.fileId,id);
|
||||
assert.equal(c.calls[4].data.photoFileId,id);
|
||||
});
|
||||
test('duplicate and ID-only uploads keep stable display URLs and file metadata', async () => {
|
||||
const c = client({compressImage(o){o.fail();},uploadFile(o){o.success({statusCode:200,data:JSON.stringify({success:true,duplicate:true,file:{id,url:'https://minio/raw-object'}})});}});
|
||||
const result = await c.uploadFileCompat({url:c.API_BASE+'/upload',filePath:'http://tmp/new.jpg'});
|
||||
assert.equal(uploadReference(result),id);
|
||||
assert.equal(result.duplicate,true);
|
||||
assert.equal(result.file.url,'https://minio/raw-object');
|
||||
assert.equal(c.getImageUrl(result.path),'https://past.sundynix.cn/api/files/'+id);
|
||||
assert.throws(()=>uploadReference({success:true,path:'http://tmp/new.jpg'}),/文件 ID/);
|
||||
});
|
||||
test('diary edit uploads HTTP temporary images, retains existing IDs and stops on upload failure', async () => {
|
||||
let definition, saved, uploads=[];
|
||||
const c = client();
|
||||
const load = createLoader({console, Date, Promise, setTimeout(){}, Page(p){definition=p;}, wx:{showLoading(){},hideLoading(){},showToast(){}}}, {
|
||||
'../../utils/request':{getImageUrl:c.getImageUrl,api:{async diaryUpdate(data){saved=data;}}}
|
||||
});
|
||||
load(path.join(__dirname,'../pages/diary_edit/diary_edit.js'));
|
||||
const p = {...definition, data:{loading:false,form:{content:'text'},existingImages:[id],tempImages:[c.getImageUrl(id),'http://tmp/new.jpg']},setData(v){Object.assign(this.data,v);},async _uploadImage(v){uploads.push(v);return id2;}};
|
||||
await p.submit();
|
||||
assert.deepEqual(uploads,['http://tmp/new.jpg']);
|
||||
assert.deepEqual(Array.from(saved.images),[id,id2]);
|
||||
saved=null;p._uploadImage=async()=>{throw Error('network');};
|
||||
await p.submit();assert.equal(saved,null);
|
||||
});
|
||||
|
||||
test('media display keeps local assets and temporary paths without a backend directory prefix', () => {
|
||||
const c = client();
|
||||
assert.equal(c.getImageUrl('/images/default-avatar.png'), '/images/default-avatar.png');
|
||||
assert.equal(c.getImageUrl('wxfile://tmp/photo.jpg'), 'wxfile://tmp/photo.jpg');
|
||||
assert.equal(c.getImageUrl('http://tmp/photo.jpg'), 'http://tmp/photo.jpg');
|
||||
assert.equal(c.getImageUrl(id), 'https://past.sundynix.cn/api/files/' + id);
|
||||
});
|
||||
@@ -35,7 +35,7 @@ test('server relation changes discard cached data before another read',async()=>
|
||||
await api.diaryList();await api.diaryList();assert.equal(reads,1);
|
||||
await api.checkAuth();assert.equal(storage.get('coupleId'),'22');assert.equal(redirects,1);
|
||||
const fresh=await api.diaryList();assert.equal(reads,2);assert.equal(fresh.items[0].title,'new space');
|
||||
assert.equal(getImageUrl('/backend/uploads/a.jpg?media_sig=abc'),'https://past.sundynix.cn/backend/uploads/a.jpg?media_sig=abc');
|
||||
assert.equal(getImageUrl('/api/files/6083e119-6b26-441b-8b70-44d224366238'),'https://past.sundynix.cn/api/files/6083e119-6b26-441b-8b70-44d224366238');
|
||||
});
|
||||
|
||||
test('settings and profile consent retain couple cache and lead into cooling',async()=>{
|
||||
|
||||
@@ -25,7 +25,7 @@ test('purchase converts packs to pieces and cents; invalid quantities never subm
|
||||
e.component.setData({quantity:'1.5'});await e.component.submit();assert.equal(e.calls.length,1);
|
||||
});
|
||||
test('undo includes the original record and fills a seven-day chart with zero days',async()=>{
|
||||
const e=setup(async()=>result());e.component.undo({currentTarget:{dataset:{id:17}}});await Promise.resolve();await Promise.resolve();assert.equal(e.calls[0].kind,'undo');assert.equal(e.calls[0].original_id,17);assert.equal(e.component.data.daily.length,7);
|
||||
const e=setup(async()=>result());e.component.undo({currentTarget:{dataset:{id:'6083e119-6b26-441b-8b70-44d224366238'}}});await Promise.resolve();await Promise.resolve();assert.equal(e.calls[0].kind,'undo');assert.equal(e.calls[0].original_id,'6083e119-6b26-441b-8b70-44d224366238');assert.equal(e.component.data.daily.length,7);
|
||||
});
|
||||
|
||||
test('string zero is active while one is undone, and ledger identity follows the selected account',async()=>{
|
||||
|
||||
Reference in New Issue
Block a user