Improve ISAC libraries, roles, and public profiles

This commit is contained in:
JB
2026-07-27 18:54:46 -04:00
parent fe4acec191
commit 6e12b2f3b7
40 changed files with 1196 additions and 271 deletions
+76
View File
@@ -9,6 +9,20 @@ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Arra
const hasExactKeys = (value, keys) =>
isRecord(value) && Object.keys(value).length === keys.length && keys.every((key) => key in value);
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
const displayNameKey = (displayName) => String(displayName ?? '').normalize('NFKC').toLocaleLowerCase('en-US');
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const publicProfileSlugBase = (displayName, userId) => {
const normalized = String(displayName ?? '')
.normalize('NFKC')
.toLocaleLowerCase('en-US')
.replace(/[^\p{L}\p{N}\p{M}]+/gu, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 64)
.replace(/-+$/g, '');
const fallback = `agent-${String(userId).replaceAll('-', '').slice(0, 8).toLowerCase()}`;
if (!/[\p{L}\p{N}]/u.test(normalized)) return fallback;
return uuidPattern.test(normalized) ? `agent-${normalized}` : normalized;
};
const isRoll = (value) =>
hasExactKeys(value, ['statId', 'value']) && typeof value.statId === 'string' && isFiniteNumber(value.value);
const isRollPair = (value) => Array.isArray(value) && value.length === 2 && value.every(isRoll);
@@ -147,6 +161,14 @@ if (userProfilesTable) {
if (!profileColumns.some((column) => column.name === 'bio_updated_at')) {
await database.exec('ALTER TABLE user_profiles ADD COLUMN bio_updated_at TEXT');
}
if (!profileColumns.some((column) => column.name === 'public_slug')) {
console.log('Adding readable public profile slugs.');
await database.exec('ALTER TABLE user_profiles ADD COLUMN public_slug TEXT');
}
if (!profileColumns.some((column) => column.name === 'display_name_key')) {
console.log('Adding normalized display-name identity keys.');
await database.exec('ALTER TABLE user_profiles ADD COLUMN display_name_key TEXT');
}
}
const commentsDefinition = await (await database.prepare(
@@ -359,9 +381,63 @@ for (const statement of statements) {
if (statement.startsWith('CREATE INDEX IF NOT EXISTS skill_chip_sets_ability_idx')) continue;
if (statement.startsWith('CREATE UNIQUE INDEX IF NOT EXISTS managed_images_source_entity_idx')) continue;
if (statement.startsWith('CREATE INDEX IF NOT EXISTS conditional_effect_clauses_event_idx')) continue;
if (statement.startsWith('CREATE UNIQUE INDEX IF NOT EXISTS user_profiles_public_slug_idx')) continue;
if (statement.startsWith('CREATE UNIQUE INDEX IF NOT EXISTS user_profiles_display_name_key_idx')) continue;
await database.exec(statement);
}
const deletedRoleTable = await (await database.prepare(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='deleted_roles'"
)).get();
if (deletedRoleTable) {
const deletedRoleRows = await (await database.prepare('SELECT role_id FROM deleted_roles')).all();
for (const deletedRole of deletedRoleRows) {
const roleId = String(deletedRole.role_id);
await (await database.prepare('DELETE FROM user_roles WHERE role_id=?')).run(roleId);
await (await database.prepare('DELETE FROM role_permissions WHERE role_id=?')).run(roleId);
await (await database.prepare('DELETE FROM roles WHERE id=?')).run(roleId);
}
}
const profilesForIdentityMigration = await (await database.prepare(`SELECT user_id,display_name,display_name_key,public_slug
FROM user_profiles ORDER BY user_id`)).all();
const claimedProfileSlugs = new Set(
profilesForIdentityMigration
.filter((profile) => profile.public_slug)
.map((profile) => String(profile.public_slug))
);
const claimedDisplayNameKeys = new Set();
for (const profile of profilesForIdentityMigration) {
const userId = String(profile.user_id);
let displayName = String(profile.display_name);
let key = displayNameKey(displayName);
if (claimedDisplayNameKeys.has(key)) {
for (let suffixLength = 5; suffixLength <= 12; suffixLength += 1) {
const candidate = `${displayName} ${userId.replaceAll('-', '').slice(0, suffixLength).toLowerCase()}`;
const candidateKey = displayNameKey(candidate);
if (!claimedDisplayNameKeys.has(candidateKey)) {
displayName = candidate;
key = candidateKey;
break;
}
}
}
claimedDisplayNameKeys.add(key);
let slug = profile.public_slug ? String(profile.public_slug) : '';
if (!slug || displayName !== String(profile.display_name)) {
const base = publicProfileSlugBase(displayName, userId);
slug = claimedProfileSlugs.has(base)
? `${base.slice(0, 55).replace(/-+$/g, '')}~${userId.replaceAll('-', '').slice(0, 8).toLowerCase()}`
: base;
}
claimedProfileSlugs.add(slug);
await (await database.prepare(`UPDATE user_profiles
SET display_name=?,display_name_key=?,public_slug=? WHERE user_id=?`))
.run(displayName, key, slug, userId);
}
await database.exec('CREATE UNIQUE INDEX IF NOT EXISTS user_profiles_public_slug_idx ON user_profiles(public_slug)');
await database.exec('CREATE UNIQUE INDEX IF NOT EXISTS user_profiles_display_name_key_idx ON user_profiles(display_name_key)');
const skillProfileDefinition = await (await database.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='skill_attribute_calculation_profiles'"
)).get();