162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
'use server';
|
|
|
|
import { ValidatorMap } from '@/types/validator-map';
|
|
import { db } from '@/db';
|
|
import { ArcadeCab, countryValidator, getArcadeCabs, getArcadePermissions } from '@/data/arcade';
|
|
import { requireUser } from '@/actions/auth';
|
|
import { requireArcadePermission } from '@/helpers/permissions';
|
|
import { ArcadePermissions } from '@/types/permissions';
|
|
import type { Entries } from 'type-fest';
|
|
import { revalidatePath } from 'next/cache';
|
|
|
|
export type CabUpdate = Omit<ArcadeCab, 'id' | 'arcade'>;
|
|
|
|
const CAB_VALIDATORS: ValidatorMap<CabUpdate, number | null> = new Map();
|
|
CAB_VALIDATORS.set('country', countryValidator);
|
|
|
|
|
|
const booleanValidator = (val: number | null | undefined) => {
|
|
val = +(val as any);
|
|
if (val !== 0 && val !== 1)
|
|
throw new Error('Invalid boolean value');
|
|
return val;
|
|
}
|
|
|
|
CAB_VALIDATORS.set('board', async (val, cab) => {
|
|
val = val!.toUpperCase().replace(/-/g, '').trim();
|
|
|
|
if (!/^[A-Z\d]{15}$/.test(val!))
|
|
throw new Error('Invalid board number, must be 15 alphanumeric characters');
|
|
|
|
const existing = await db.selectFrom('machine')
|
|
.where(({ eb, and }) => and([
|
|
eb('board', '=', val!),
|
|
...(cab === null ? [] : [
|
|
eb('id', '!=', cab)
|
|
])
|
|
]))
|
|
.select('board')
|
|
.executeTakeFirst();
|
|
|
|
if (existing)
|
|
throw new Error('That board is already in use');
|
|
|
|
return val;
|
|
});
|
|
|
|
const getMachineBySerial = async (serial: string, excludeCab: number | null = null) => {
|
|
return db.selectFrom('machine')
|
|
.where(({ eb, and }) => and([
|
|
eb('serial', 'like', `${serial.slice(0, 11)}%`),
|
|
...(excludeCab === null ? [] : [
|
|
eb('id', '!=', excludeCab)
|
|
])
|
|
]))
|
|
.select('serial')
|
|
.executeTakeFirst();
|
|
}
|
|
|
|
CAB_VALIDATORS.set('serial', async (val, cab) => {
|
|
val = val!.toUpperCase().replace(/-/g, '').trim();
|
|
|
|
// https://gitea.tendokyu.moe/Hay1tsme/artemis/src/commit/87c7c91e3a7158aabfd2b8dbf69d6a5ca0249da1/core/adb_handlers/base.py#L123
|
|
if (!/^A\d{2}[EX]\d{2}[A-HJ-NP-Z]\d{8}$/.test(val))
|
|
throw new Error('Invalid keychip format');
|
|
|
|
if (await getMachineBySerial(val, cab))
|
|
throw new Error('That serial is already in use');
|
|
|
|
return val;
|
|
});
|
|
|
|
CAB_VALIDATORS.set('is_cab', booleanValidator);
|
|
CAB_VALIDATORS.set('ota_enable', booleanValidator);
|
|
CAB_VALIDATORS.set('game', val => {
|
|
val = val!.trim().toUpperCase();
|
|
// https://gitea.tendokyu.moe/Hay1tsme/artemis/src/commit/87c7c91e3a7158aabfd2b8dbf69d6a5ca0249da1/core/adb_handlers/base.py#L117
|
|
if (!/^S[A-Z\d]{3}P?$/.test(val))
|
|
throw new Error('Game must be in the format Sxxx or SxxxP');
|
|
});
|
|
CAB_VALIDATORS.set('timezone', () => {});
|
|
CAB_VALIDATORS.set('memo', () => {});
|
|
|
|
|
|
|
|
export const deleteMachine = async (arcade: number, machine: number) => {
|
|
const user = await requireUser();
|
|
const arcadePermissions = await getArcadePermissions(user, arcade);
|
|
|
|
requireArcadePermission(arcadePermissions, user.permissions, ArcadePermissions.REGISTRAR);
|
|
|
|
await db.deleteFrom('machine')
|
|
.where('arcade', '=', arcade)
|
|
.where('id', '=', machine)
|
|
.executeTakeFirst();
|
|
|
|
revalidatePath('/arcade', 'page');
|
|
revalidatePath('/arcade/[arcadeId]', 'page');
|
|
};
|
|
|
|
const validateUpdate = async (update: CabUpdate, cab: number | null) => {
|
|
if (!('serial' in update))
|
|
return { error: true, message: 'Keychip is required' };
|
|
|
|
for (let [key, val] of (Object.entries(update) as Entries<CabUpdate>)) {
|
|
if (!CAB_VALIDATORS.has(key))
|
|
return { error: true, message: `Unknown key ${key}` };
|
|
if (key === 'serial' && !val)
|
|
return { error: true, message: 'Keychip is required' };
|
|
|
|
try {
|
|
if (val === undefined) val = null;
|
|
if (val !== null)
|
|
(update as any)[key] = ((await CAB_VALIDATORS.get(key)!(val, cab)) ?? val) as any;
|
|
else
|
|
(update as any)[key] = null;
|
|
} catch (e: any) {
|
|
return { error: true, message: e?.message ?? 'Unknown error' };
|
|
}
|
|
}
|
|
}
|
|
|
|
export const updateMachine = async ({ arcade, machine, update }: { arcade: number, machine: number, update: CabUpdate }) => {
|
|
const user = await requireUser();
|
|
const arcadePermissions = await getArcadePermissions(user, arcade);
|
|
|
|
requireArcadePermission(arcadePermissions, user.permissions, ArcadePermissions.REGISTRAR);
|
|
|
|
const res = await validateUpdate(update, machine);
|
|
if (res?.error) return res;
|
|
|
|
await db.updateTable('machine')
|
|
.where('machine.id', '=', machine)
|
|
.where('machine.arcade', '=', arcade)
|
|
.set(update)
|
|
.executeTakeFirst();
|
|
|
|
revalidatePath('/arcade', 'page');
|
|
revalidatePath('/arcade/[arcadeId]', 'page');
|
|
};
|
|
|
|
export const createMachine = async ({ arcade, update }: { arcade: number, update: CabUpdate }) => {
|
|
const user = await requireUser();
|
|
const arcadePermissions = await getArcadePermissions(user, arcade);
|
|
|
|
requireArcadePermission(arcadePermissions, user.permissions, ArcadePermissions.REGISTRAR);
|
|
|
|
const res = await validateUpdate(update, null);
|
|
if (res?.error) return { ...res, data: [] };
|
|
|
|
await db.insertInto('machine')
|
|
.values({
|
|
...update,
|
|
arcade
|
|
})
|
|
.executeTakeFirst();
|
|
|
|
revalidatePath('/arcade', 'page');
|
|
revalidatePath('/arcade/[arcadeId]', 'page');
|
|
|
|
return { error: false, message: '', data: structuredClone(await getArcadeCabs({ arcade, user, permissions: arcadePermissions })) };
|
|
}
|