Improve ISAC libraries, roles, and public profiles
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user