All checks were successful
Publish FaceAI Container / publish (push) Successful in 3m22s
- Added optional live FaceAI checks in README.md - Implemented relative storage segment parsing in race-storage.js - Updated server.js to include relative directory in race storage - Refactored legacyAssets.js to resolve asset base URL dynamically - Expanded live race tests to validate FaceAI app launch and metadata - Introduced portrait image handling for live upload flow - Updated faceai_handoff.php to process race storage relative directory
151 lines
No EOL
4 KiB
JavaScript
151 lines
No EOL
4 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
|
|
const ITALIAN_MONTH_NAMES = [
|
|
'GENNAIO',
|
|
'FEBBRAIO',
|
|
'MARZO',
|
|
'APRILE',
|
|
'MAGGIO',
|
|
'GIUGNO',
|
|
'LUGLIO',
|
|
'AGOSTO',
|
|
'SETTEMBRE',
|
|
'OTTOBRE',
|
|
'NOVEMBRE',
|
|
'DICEMBRE'
|
|
];
|
|
|
|
function sanitizePathSegment(value) {
|
|
const normalized = String(value || '').trim();
|
|
|
|
if (!normalized) {
|
|
return '';
|
|
}
|
|
|
|
if (normalized === '.' || normalized === '..' || normalized.includes('..') || /[\\/]/.test(normalized)) {
|
|
throw new Error('Invalid race storage path segment');
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
function parseRelativeStorageSegments(value) {
|
|
const normalized = String(value || '').trim().replace(/\\/g, '/');
|
|
|
|
if (!normalized) {
|
|
return [];
|
|
}
|
|
|
|
return normalized
|
|
.split('/')
|
|
.map((segment) => segment.trim())
|
|
.filter(Boolean)
|
|
.map((segment) => sanitizePathSegment(segment));
|
|
}
|
|
|
|
export function normalizeRaceFolderName(value) {
|
|
return String(value || '')
|
|
.trim()
|
|
.replace(/[<>:"/\\|?*]/g, ' ')
|
|
.replace(/\s+/g, ' ')
|
|
.toUpperCase();
|
|
}
|
|
|
|
export function buildMonthFolder(year, monthIndex) {
|
|
const safeYear = sanitizePathSegment(year);
|
|
const normalizedMonthIndex = Number(monthIndex);
|
|
if (!safeYear || Number.isNaN(normalizedMonthIndex) || normalizedMonthIndex < 1 || normalizedMonthIndex > 12) {
|
|
return '';
|
|
}
|
|
|
|
return `${String(normalizedMonthIndex).padStart(2, '0')}.${ITALIAN_MONTH_NAMES[normalizedMonthIndex - 1]}`;
|
|
}
|
|
|
|
export function buildRaceStorage(storageInput = {}) {
|
|
const relativeSegments = parseRelativeStorageSegments(storageInput.relativeDir);
|
|
const relativeYear = relativeSegments[0] || '';
|
|
const relativeMonthFolder = relativeSegments.length >= 3 ? relativeSegments[1] : '';
|
|
const relativeRaceFolder = relativeSegments.length >= 3
|
|
? relativeSegments[2]
|
|
: (relativeSegments.length >= 2 ? relativeSegments[1] : '');
|
|
|
|
const year = sanitizePathSegment(storageInput.year || relativeYear);
|
|
const monthFolder = sanitizePathSegment(storageInput.monthFolder || relativeMonthFolder);
|
|
const raceFolder = sanitizePathSegment(normalizeRaceFolderName(storageInput.raceFolder || relativeRaceFolder));
|
|
|
|
if (!year || !monthFolder || !raceFolder) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
year,
|
|
monthFolder,
|
|
raceFolder,
|
|
relativeDir: path.posix.join(year, monthFolder, raceFolder)
|
|
};
|
|
}
|
|
|
|
export async function resolveRacePklAvailability({ pklRoot, race }) {
|
|
if (!pklRoot) {
|
|
return {
|
|
available: false,
|
|
reasonCode: 'PKL_ROOT_NOT_CONFIGURED',
|
|
message: 'The PKL root is not configured for this FaceAI environment.',
|
|
storage: null
|
|
};
|
|
}
|
|
|
|
const storage = buildRaceStorage(race?.storage || race);
|
|
if (!storage) {
|
|
return {
|
|
available: false,
|
|
reasonCode: 'MISSING_RACE_STORAGE',
|
|
message: 'The legacy handoff did not provide the folder metadata required to resolve FaceAI data for this race.',
|
|
storage: null
|
|
};
|
|
}
|
|
|
|
const raceDir = path.join(pklRoot, storage.year, storage.monthFolder, storage.raceFolder);
|
|
|
|
let entries;
|
|
try {
|
|
entries = await fs.readdir(raceDir, { withFileTypes: true });
|
|
} catch (error) {
|
|
if (error?.code === 'ENOENT') {
|
|
return {
|
|
available: false,
|
|
reasonCode: 'RACE_DIRECTORY_NOT_FOUND',
|
|
message: `No FaceAI dataset directory exists for ${storage.relativeDir}.`,
|
|
storage,
|
|
raceDir
|
|
};
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
|
|
const pklEntry = entries
|
|
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.pkl'))
|
|
.sort((left, right) => left.name.localeCompare(right.name, 'en'))[0];
|
|
|
|
if (!pklEntry) {
|
|
return {
|
|
available: false,
|
|
reasonCode: 'PKL_FILE_NOT_FOUND',
|
|
message: `The race directory ${storage.relativeDir} exists, but it does not contain any .pkl file.`,
|
|
storage,
|
|
raceDir
|
|
};
|
|
}
|
|
|
|
return {
|
|
available: true,
|
|
reasonCode: null,
|
|
message: `Using ${storage.relativeDir}/${pklEntry.name}`,
|
|
storage,
|
|
raceDir,
|
|
pklPath: path.join(raceDir, pklEntry.name),
|
|
pklFileName: pklEntry.name
|
|
};
|
|
} |