This commit is contained in:
seb
2026-10-09 17:55:47 +02:00
parent f3674edcb7
commit 4ae3439289
6 changed files with 295 additions and 58 deletions

78
src/store/brand/create.js Normal file
View File

@@ -0,0 +1,78 @@
import {
CREATE as MSSQL_CREATE,
GET_BY_COMPANY_AND_NAME as MSSQL_GET_BY_COMPANY_AND_NAME,
} from '../../providers/mssql/queries/brand.js';
import {
CREATE as SQLITE_CREATE,
GET_BY_COMPANY_AND_NAME as SQLITE_GET_BY_COMPANY_AND_NAME,
} from '../../providers/sqlite3/queries/brand.js';
import { isUniqueConstraintError } from '../../providers/provider.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
export default function create(provider) {
const access = resolveSelectedCompanyAccess(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
const getSql = isSqlite
? SQLITE_GET_BY_COMPANY_AND_NAME
: MSSQL_GET_BY_COMPANY_AND_NAME;
/**
* Creates a brand for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The brand input.
* @param {string} input.name - The brand name.
* @param {string} [input.gsprpinfo] - Optional GSPRP info.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>} The created brand.
* @throws {Error} If the brand is invalid or already exists.
*
* @example
* const brand = await store.brand.create(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
* // => { id, name, gsprpinfo, pics: [] }
*/
return async (googleAuthId, input = {}, tx) => {
const name = input.name?.trim();
if (!name) {
throw new Error('Invalid brand');
}
const gsprpinfo = input.gsprpinfo ?? null;
const runner = makeRunner(provider, tx);
const { companyId } = await access(googleAuthId, tx);
try {
const created = await runner.query(createSql, {
companyId,
name,
gsprpinfo,
});
const id = created.rows[0]?.id ?? created.insertId;
if (id == null) {
const fetched = await runner.query(getSql, { companyId, name });
const row = fetched.rows[0];
return {
id: row.id,
name,
gsprpinfo,
pics: [],
};
}
return {
id,
name,
gsprpinfo,
pics: [],
};
} catch (err) {
if (isUniqueConstraintError(err)) {
throw new Error('Brand already exists');
}
throw err;
}
};
}

90
src/store/brand/update.js Normal file
View File

@@ -0,0 +1,90 @@
import {
GET_BY_ID_AND_COMPANY as MSSQL_GET,
UPDATE_BY_ID as MSSQL_UPDATE,
} from '../../providers/mssql/queries/brand.js';
import {
GET_BY_ID_AND_COMPANY as SQLITE_GET,
UPDATE_BY_ID as SQLITE_UPDATE,
} from '../../providers/sqlite3/queries/brand.js';
import { isUniqueConstraintError } from '../../providers/provider.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
export default function update(provider) {
const access = resolveSelectedCompanyAccess(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const getSql = isSqlite ? SQLITE_GET : MSSQL_GET;
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
/**
* Renames/updates a brand for the user's selected company by id.
* Touching `article.updated_at` for all articles linked via `brand_id`
* when the name actually changes, so list caches invalidate.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the brand to update.
* @param {object} input - The update input.
* @param {string} [input.name] - The new brand name.
* @param {string} [input.gsprpinfo] - The new GSPRP info.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null, touchedArticleCount: number}>} The updated brand plus the number of linked articles whose `updated_at` was bumped.
* @throws {Error} If the brand is not found, invalid, or the name is already taken.
*
* @example
* const brand = await store.brand.update(42, 3, { name: 'Coca-Cola GmbH' });
* // => { id: 3, name, gsprpinfo, touchedArticleCount: 2 }
*/
return async (googleAuthId, id, input = {}, tx) => {
const numericId = Number(id);
if (!Number.isInteger(numericId) || numericId <= 0) {
throw new Error('Brand not found');
}
const runner = makeRunner(provider, tx);
const { companyId } = await access(googleAuthId, tx);
const existing = await runner.query(getSql, { id: numericId, companyId });
const current = existing.rows[0];
if (!current) {
throw new Error('Brand not found');
}
const name = input.name !== undefined ? input.name?.trim() : current.name;
if (!name) {
throw new Error('Invalid brand');
}
const gsprpinfo = input.gsprpinfo !== undefined ? input.gsprpinfo : current.gsprpinfo;
try {
await runner.query(updateSql, {
id: numericId,
companyId,
name,
gsprpinfo,
});
} catch (err) {
if (isUniqueConstraintError(err)) {
throw new Error('Brand already exists');
}
throw err;
}
let touchedArticleCount = 0;
if (name !== current.name) {
// Brand name is denormalized into article list views — bump
// updated_at so caches/incremental syncs pick up the rename.
const touchSql = isSqlite
? "UPDATE article SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE brand_id = @brandId AND company_id = @companyId"
: 'UPDATE article SET updated_at = SYSUTCDATETIME() WHERE brand_id = @brandId AND company_id = @companyId';
const touched = await runner.query(touchSql, { brandId: numericId, companyId });
touchedArticleCount = touched.rowsAffected ?? 0;
}
return {
id: numericId,
name,
gsprpinfo,
touchedArticleCount,
};
};
}