u
This commit is contained in:
@@ -41,7 +41,7 @@ export default function createUnderParent(provider) {
|
||||
* const article = await store.article.createUnderParent({ googleAuthId: 42, parentId: 5, name: 'Cola' });
|
||||
* // => { id, parentId, name, sku, track_stock }
|
||||
*/
|
||||
return async ({ googleAuthId, parentId, name, track_stock, trackStock }, tx) => {
|
||||
return async ({ googleAuthId, parentId, name, track_stock, trackStock, sku: inputSku }, tx) => {
|
||||
const rawTrack = track_stock ?? trackStock;
|
||||
const finalTrackStock = rawTrack == null ? 1 : (rawTrack ? 1 : 0);
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function createUnderParent(provider) {
|
||||
);
|
||||
|
||||
const unitId = await ensureUnit(runner, languageId, companyId);
|
||||
const sku = await allocateSku(companyId, runner);
|
||||
const sku = (typeof inputSku === 'string' && inputSku.trim()) ? inputSku.trim() : await allocateSku(companyId, runner);
|
||||
const slug = await resolveSlug(runner, { companyId, languageId, name });
|
||||
|
||||
let article;
|
||||
|
||||
42
src/store/modelconversation/create.js
Normal file
42
src/store/modelconversation/create.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { CREATE as MSSQL_CREATE } from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { CREATE as SQLITE_CREATE } from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function create(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_CREATE : MSSQL_CREATE;
|
||||
|
||||
/**
|
||||
* Creates a new (idle) model conversation for the user in the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input - Conversation input.
|
||||
* @param {string} input.provider - LLM provider name.
|
||||
* @param {string} input.model - Model identifier.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, companyuser_id: number, provider: string, model: string, created_at: string, updated_at: string, state: string}>}
|
||||
* @throws {Error} 'Provider required' or 'Model required'.
|
||||
*
|
||||
* @example
|
||||
* const c = await store.modelconversation.create(42, { provider: 'openrouter', model: 'openai/gpt-5' });
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const providerName =
|
||||
typeof input.provider === 'string' ? input.provider.trim() : '';
|
||||
if (!providerName) throw new Error('Provider required');
|
||||
const model = typeof input.model === 'string' ? input.model.trim() : '';
|
||||
if (!model) throw new Error('Model required');
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, {
|
||||
companyuserId,
|
||||
provider: providerName,
|
||||
model,
|
||||
});
|
||||
return shape(result.rows[0]);
|
||||
};
|
||||
}
|
||||
34
src/store/modelconversation/delete.js
Normal file
34
src/store/modelconversation/delete.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DELETE_BY_COMPANYUSER_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { DELETE_BY_COMPANYUSER_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function del(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql =
|
||||
provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
||||
|
||||
/**
|
||||
* Deletes a model conversation by ID for the user in the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} conversationId - The conversation id.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} 'Conversation not found'.
|
||||
*/
|
||||
return async (googleAuthId, conversationId, tx) => {
|
||||
const id = Number(conversationId);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { id, companyuserId });
|
||||
if (!result.rowsAffected) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
33
src/store/modelconversation/get.js
Normal file
33
src/store/modelconversation/get.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { GET_BY_COMPANYUSER_AND_ID as MSSQL_GET } from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { GET_BY_COMPANYUSER_AND_ID as SQLITE_GET } from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function get(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
|
||||
/**
|
||||
* Gets a single model conversation by ID for the user in the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} conversationId - The conversation id.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, companyuser_id: number, provider: string, model: string, created_at: string, updated_at: string, state: string}>}
|
||||
* @throws {Error} 'Conversation not found'.
|
||||
*/
|
||||
return async (googleAuthId, conversationId, tx) => {
|
||||
const id = Number(conversationId);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { id, companyuserId });
|
||||
if (!result.rows[0]) throw new Error('Conversation not found');
|
||||
return shape(result.rows[0]);
|
||||
};
|
||||
}
|
||||
27
src/store/modelconversation/list.js
Normal file
27
src/store/modelconversation/list.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { LIST_BY_COMPANYUSER as MSSQL_LIST } from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { LIST_BY_COMPANYUSER as SQLITE_LIST } from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function list(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_LIST : MSSQL_LIST;
|
||||
|
||||
/**
|
||||
* Lists the user's own model conversations in the selected company, newest activity first.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<Array<{id: number, companyuser_id: number, provider: string, model: string, created_at: string, updated_at: string, state: string}>>}
|
||||
*
|
||||
* @example
|
||||
* const conversations = await store.modelconversation.list(42);
|
||||
*/
|
||||
return async (googleAuthId, tx) => {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
const result = await runner.query(sql, { companyuserId });
|
||||
return result.rows.map(shape);
|
||||
};
|
||||
}
|
||||
37
src/store/modelconversation/setState.js
Normal file
37
src/store/modelconversation/setState.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { SET_STATE as MSSQL_SET_STATE } from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { SET_STATE as SQLITE_SET_STATE } from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function setState(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SET_STATE : MSSQL_SET_STATE;
|
||||
|
||||
/**
|
||||
* Sets the state of a model conversation ('idle' | 'busy').
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} conversationId - The conversation id.
|
||||
* @param {string} state - The new state ('idle' | 'busy').
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, companyuser_id: number, provider: string, model: string, created_at: string, updated_at: string, state: string}>}
|
||||
* @throws {Error} 'Invalid state' or 'Conversation not found'.
|
||||
*/
|
||||
return async (googleAuthId, conversationId, state, tx) => {
|
||||
const id = Number(conversationId);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
if (state !== 'idle' && state !== 'busy') {
|
||||
throw new Error("Invalid state: must be 'idle' or 'busy'");
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { id, companyuserId, state });
|
||||
if (!result.rows[0]) throw new Error('Conversation not found');
|
||||
return shape(result.rows[0]);
|
||||
};
|
||||
}
|
||||
11
src/store/modelconversation/shape.js
Normal file
11
src/store/modelconversation/shape.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
companyuser_id: row.companyuser_id ?? row.companyuserId,
|
||||
provider: row.provider,
|
||||
model: row.model,
|
||||
created_at: row.created_at ?? row.createdAt,
|
||||
updated_at: row.updated_at ?? row.updatedAt,
|
||||
state: row.state,
|
||||
};
|
||||
}
|
||||
85
src/store/modelmessage/add.js
Normal file
85
src/store/modelmessage/add.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
GET_BY_COMPANYUSER_AND_ID as MSSQL_GET_CONV,
|
||||
TOUCH as MSSQL_TOUCH_UNCONDITIONAL,
|
||||
TOUCH_IF_IDLE as MSSQL_TOUCH,
|
||||
} from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { CREATE as MSSQL_CREATE } from '../../providers/mssql/queries/modelmessage.js';
|
||||
import {
|
||||
GET_BY_COMPANYUSER_AND_ID as SQLITE_GET_CONV,
|
||||
TOUCH as SQLITE_TOUCH_UNCONDITIONAL,
|
||||
TOUCH_IF_IDLE as SQLITE_TOUCH,
|
||||
} from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import { CREATE as SQLITE_CREATE } from '../../providers/sqlite3/queries/modelmessage.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function add(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const getConvSql = isSqlite ? SQLITE_GET_CONV : MSSQL_GET_CONV;
|
||||
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||
const touchUnconditionalSql = isSqlite ? SQLITE_TOUCH_UNCONDITIONAL : MSSQL_TOUCH_UNCONDITIONAL;
|
||||
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
|
||||
|
||||
/**
|
||||
* Appends a message to one of the user's own conversations.
|
||||
* `user` messages are refused unless the conversation state is `idle`;
|
||||
* other roles ('assistant', 'system', 'tool', 'reasoning') touch `updated_at` unconditionally.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} conversationId - The conversation id.
|
||||
* @param {object} input - Message input.
|
||||
* @param {string} input.message - Message text.
|
||||
* @param {'user'|'assistant'|'system'|'tool'|'reasoning'} [input.role='user'] - Message role.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, conversation_id: number, created_at: string, role: string, message: string}>}
|
||||
* @throws {Error} 'Conversation not found', 'Conversation is not idle', 'Message required', 'Invalid role'.
|
||||
*/
|
||||
return async (googleAuthId, conversationId, input = {}, tx) => {
|
||||
const id = Number(conversationId);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
const role = input.role || 'user';
|
||||
if (!['user', 'assistant', 'system', 'tool', 'reasoning'].includes(role)) {
|
||||
throw new Error('Invalid role');
|
||||
}
|
||||
const message = input.message != null ? String(input.message) : '';
|
||||
|
||||
if (!message.trim()) {
|
||||
throw new Error('Message required');
|
||||
}
|
||||
|
||||
const run = async (runner) => {
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
|
||||
if (role === 'user') {
|
||||
const touched = await runner.query(touchSql, { id, companyuserId });
|
||||
if (!touched.rowsAffected) {
|
||||
const conv = await runner.query(getConvSql, { id, companyuserId });
|
||||
if (!conv.rows[0]) throw new Error('Conversation not found');
|
||||
throw new Error('Conversation is not idle');
|
||||
}
|
||||
} else {
|
||||
const touched = await runner.query(touchUnconditionalSql, { id, companyuserId });
|
||||
if (!touched.rowsAffected) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
}
|
||||
|
||||
const result = await runner.query(createSql, {
|
||||
conversationId: id,
|
||||
role,
|
||||
message,
|
||||
});
|
||||
return shape(result.rows[0]);
|
||||
};
|
||||
|
||||
if (tx) return run(makeRunner(provider, tx));
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
42
src/store/modelmessage/list.js
Normal file
42
src/store/modelmessage/list.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { GET_BY_COMPANYUSER_AND_ID as MSSQL_GET_CONV } from '../../providers/mssql/queries/modelconversation.js';
|
||||
import { LIST_BY_CONVERSATION as MSSQL_LIST } from '../../providers/mssql/queries/modelmessage.js';
|
||||
import { GET_BY_COMPANYUSER_AND_ID as SQLITE_GET_CONV } from '../../providers/sqlite3/queries/modelconversation.js';
|
||||
import { LIST_BY_CONVERSATION as SQLITE_LIST } from '../../providers/sqlite3/queries/modelmessage.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function list(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const getConvSql = isSqlite ? SQLITE_GET_CONV : MSSQL_GET_CONV;
|
||||
const listSql = isSqlite ? SQLITE_LIST : MSSQL_LIST;
|
||||
|
||||
/**
|
||||
* Lists messages of one of the user's own conversations, oldest first.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} conversationId - The conversation id.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<Array<{id: number, conversation_id: number, created_at: string, role: string, message: string}>>}
|
||||
* @throws {Error} 'Conversation not found' if missing or owned by another user.
|
||||
*
|
||||
* @example
|
||||
* const messages = await store.modelmessage.list(42, 7);
|
||||
*/
|
||||
return async (googleAuthId, conversationId, tx) => {
|
||||
const id = Number(conversationId);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Conversation not found');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyuserId } = await access(googleAuthId, runner);
|
||||
|
||||
const conv = await runner.query(getConvSql, { id, companyuserId });
|
||||
if (!conv.rows[0]) throw new Error('Conversation not found');
|
||||
|
||||
const result = await runner.query(listSql, { conversationId: id });
|
||||
return result.rows.map(shape);
|
||||
};
|
||||
}
|
||||
9
src/store/modelmessage/shape.js
Normal file
9
src/store/modelmessage/shape.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
conversation_id: row.conversation_id ?? row.conversationId,
|
||||
created_at: row.created_at ?? row.createdAt,
|
||||
role: row.role,
|
||||
message: row.message,
|
||||
};
|
||||
}
|
||||
37
src/store/modelvfs/delete.js
Normal file
37
src/store/modelvfs/delete.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { DELETE_BY_COMPANY_AND_NAME as MSSQL_DELETE } from '../../providers/mssql/queries/modelvfs.js';
|
||||
import { DELETE_BY_COMPANY_AND_NAME as SQLITE_DELETE } from '../../providers/sqlite3/queries/modelvfs.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function deleteFile(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
||||
|
||||
/**
|
||||
* Deletes a virtual file by name for the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} name - File name.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{deleted: boolean}>}
|
||||
*/
|
||||
return async (googleAuthId, name, tx) => {
|
||||
const filename = typeof name === 'string' ? name.trim() : '';
|
||||
if (!filename) throw new Error('File name is required');
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { companyId, name: filename });
|
||||
const rows = result.rows || [];
|
||||
|
||||
if (provider?.dialect === 'sqlite3') {
|
||||
for (const row of rows) {
|
||||
if (runner.deleteVfsContent) await runner.deleteVfsContent(row.id);
|
||||
else if (provider.deleteVfsContent) await provider.deleteVfsContent(row.id);
|
||||
}
|
||||
}
|
||||
|
||||
return { deleted: rows.length > 0 };
|
||||
};
|
||||
}
|
||||
25
src/store/modelvfs/list.js
Normal file
25
src/store/modelvfs/list.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { LIST_BY_COMPANY as MSSQL_LIST } from '../../providers/mssql/queries/modelvfs.js';
|
||||
import { LIST_BY_COMPANY as SQLITE_LIST } from '../../providers/sqlite3/queries/modelvfs.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function list(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_LIST : MSSQL_LIST;
|
||||
|
||||
/**
|
||||
* Lists virtual files for the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<Array<{id: number, company_id: number, name: string, mime: string, meta: any, created_at: string, updated_at: string}>>}
|
||||
*/
|
||||
return async (googleAuthId, tx) => {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { companyId });
|
||||
return result.rows.map(shape);
|
||||
};
|
||||
}
|
||||
49
src/store/modelvfs/read.js
Normal file
49
src/store/modelvfs/read.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { GET_BY_COMPANY_AND_NAME as MSSQL_GET } from '../../providers/mssql/queries/modelvfs.js';
|
||||
import { GET_BY_COMPANY_AND_NAME as SQLITE_GET } from '../../providers/sqlite3/queries/modelvfs.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function read(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
|
||||
/**
|
||||
* Reads a virtual file by name for the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} name - File name.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{id: number, company_id: number, name: string, mime: string, content: Buffer, meta: any, created_at: string, updated_at: string}>}
|
||||
* @throws {Error} 'File not found'
|
||||
*/
|
||||
return async (googleAuthId, name, tx) => {
|
||||
const filename = typeof name === 'string' ? name.trim() : '';
|
||||
if (!filename) throw new Error('File name is required');
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { companyId, name: filename });
|
||||
const row = result.rows[0];
|
||||
if (!row) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
|
||||
let content;
|
||||
if (provider?.dialect === 'sqlite3') {
|
||||
content = runner.readVfsContent
|
||||
? await runner.readVfsContent(row.id)
|
||||
: await provider.readVfsContent?.(row.id);
|
||||
if (!content) content = Buffer.alloc(0);
|
||||
} else {
|
||||
content = row.content;
|
||||
}
|
||||
|
||||
return {
|
||||
...shape(row),
|
||||
content,
|
||||
size: content?.length || 0,
|
||||
};
|
||||
};
|
||||
}
|
||||
33
src/store/modelvfs/reset.js
Normal file
33
src/store/modelvfs/reset.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { DELETE_ALL_BY_COMPANY as MSSQL_RESET } from '../../providers/mssql/queries/modelvfs.js';
|
||||
import { DELETE_ALL_BY_COMPANY as SQLITE_RESET } from '../../providers/sqlite3/queries/modelvfs.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function reset(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_RESET : MSSQL_RESET;
|
||||
|
||||
/**
|
||||
* Deletes all virtual files for the selected company (reset VFS).
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{deletedCount: number}>}
|
||||
*/
|
||||
return async (googleAuthId, tx) => {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { companyId });
|
||||
const rows = result.rows || [];
|
||||
|
||||
if (provider?.dialect === 'sqlite3') {
|
||||
for (const row of rows) {
|
||||
if (runner.deleteVfsContent) await runner.deleteVfsContent(row.id);
|
||||
else if (provider.deleteVfsContent) await provider.deleteVfsContent(row.id);
|
||||
}
|
||||
}
|
||||
|
||||
return { deletedCount: rows.length };
|
||||
};
|
||||
}
|
||||
21
src/store/modelvfs/shape.js
Normal file
21
src/store/modelvfs/shape.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export default function shape(row) {
|
||||
if (!row) return null;
|
||||
let parsedMeta = null;
|
||||
if (row.meta) {
|
||||
try {
|
||||
parsedMeta = typeof row.meta === 'string' ? JSON.parse(row.meta) : row.meta;
|
||||
} catch {
|
||||
parsedMeta = row.meta;
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: row.id,
|
||||
company_id: row.company_id,
|
||||
name: row.name,
|
||||
mime: row.mime,
|
||||
meta: parsedMeta,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
...(row.content !== undefined ? { content: row.content } : {}),
|
||||
};
|
||||
}
|
||||
61
src/store/modelvfs/write.js
Normal file
61
src/store/modelvfs/write.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { UPSERT as MSSQL_UPSERT } from '../../providers/mssql/queries/modelvfs.js';
|
||||
import { UPSERT as SQLITE_UPSERT } from '../../providers/sqlite3/queries/modelvfs.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shape from './shape.js';
|
||||
|
||||
export default function write(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_UPSERT : MSSQL_UPSERT;
|
||||
|
||||
/**
|
||||
* Writes (inserts or overwrites) a virtual file for the selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input
|
||||
* @param {string} input.name - File path/name (e.g. 'candidate.avif', 'report.json').
|
||||
* @param {string} [input.mime='application/octet-stream'] - Mime type.
|
||||
* @param {Buffer|string} input.content - File content buffer or string.
|
||||
* @param {object|string} [input.meta] - Optional metadata object or JSON string.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The stored file record metadata.
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const name = typeof input.name === 'string' ? input.name.trim() : '';
|
||||
if (!name) throw new Error('File name is required');
|
||||
const mime = typeof input.mime === 'string' && input.mime.trim() ? input.mime.trim() : 'application/octet-stream';
|
||||
const contentBuffer = Buffer.isBuffer(input.content)
|
||||
? input.content
|
||||
: Buffer.from(input.content != null ? String(input.content) : '');
|
||||
const metaStr = input.meta != null
|
||||
? (typeof input.meta === 'object' ? JSON.stringify(input.meta) : String(input.meta))
|
||||
: null;
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const params = {
|
||||
companyId,
|
||||
name,
|
||||
mime,
|
||||
meta: metaStr,
|
||||
...(provider?.dialect !== 'sqlite3' ? { content: contentBuffer } : {}),
|
||||
};
|
||||
|
||||
const result = await runner.query(sql, params);
|
||||
const row = result.rows[0];
|
||||
|
||||
if (provider?.dialect === 'sqlite3' && row?.id) {
|
||||
if (runner.writeVfsContent) {
|
||||
await runner.writeVfsContent(row.id, contentBuffer);
|
||||
} else if (provider.writeVfsContent) {
|
||||
await provider.writeVfsContent(row.id, contentBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...shape(row),
|
||||
size: contentBuffer.length,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -71,5 +71,8 @@ function createCallableRunner(base) {
|
||||
run.readPicThumb = base.readPicThumb?.bind(base);
|
||||
run.deleteFile = base.deleteFile?.bind(base);
|
||||
run.deletePic = base.deletePic?.bind(base);
|
||||
run.writeVfsContent = base.writeVfsContent?.bind(base);
|
||||
run.readVfsContent = base.readVfsContent?.bind(base);
|
||||
run.deleteVfsContent = base.deleteVfsContent?.bind(base);
|
||||
return run;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user