Add companyLinks schema, favicon helper with SSRF guard, URL validator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 11:45:08 +07:00
parent 5d9c0f0249
commit 84a98efd6e
4 changed files with 323 additions and 1 deletions
+76 -1
View File
@@ -735,6 +735,78 @@ export const companyDocumentVersions = pgTable(
]
);
// ── Company Links (internal tool & bookmark hub) ──────
export const companyLinkCategoryEnum = pgEnum('company_link_category', [
'internal_tool',
'communication',
'social_media',
'analytics',
'banking',
'government',
'storage',
'marketing',
'development',
'website',
'other'
]);
export const companyLinks = pgTable(
'company_links',
{
id: uuid('id').primaryKey().defaultRandom(),
companyId: uuid('company_id')
.notNull()
.references(() => companies.id, { onDelete: 'cascade' }),
category: companyLinkCategoryEnum('category').notNull(),
customLabel: text('custom_label'),
title: text('title').notNull(),
url: text('url').notNull(),
description: text('description'),
faviconUrl: text('favicon_url'),
faviconFetchedAt: timestamp('favicon_fetched_at', { withTimezone: true }),
sortOrder: integer('sort_order').notNull().default(0),
createdBy: text('created_by').references(() => users.id, { onDelete: 'set null' }),
deletedAt: timestamp('deleted_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
},
(table) => [
index('company_links_company_cat_sort_idx').on(table.companyId, table.category, table.sortOrder)
]
);
export const userCompanyLinks = pgTable(
'user_company_links',
{
id: uuid('id').primaryKey().defaultRandom(),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
companyId: uuid('company_id')
.notNull()
.references(() => companies.id, { onDelete: 'cascade' }),
category: companyLinkCategoryEnum('category').notNull(),
customLabel: text('custom_label'),
title: text('title').notNull(),
url: text('url').notNull(),
description: text('description'),
faviconUrl: text('favicon_url'),
faviconFetchedAt: timestamp('favicon_fetched_at', { withTimezone: true }),
sortOrder: integer('sort_order').notNull().default(0),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
},
(table) => [
index('user_company_links_user_company_idx').on(
table.userId,
table.companyId,
table.category,
table.sortOrder
)
]
);
// ── Company Profile (bank accounts, cards, addresses) ──
export const companyAddressTypeEnum = pgEnum('company_address_type', [
@@ -885,7 +957,10 @@ export const companyLogEventEnum = pgEnum('company_log_event', [
'document_uploaded',
'document_version_added',
'document_metadata_updated',
'document_deleted'
'document_deleted',
'link_added',
'link_updated',
'link_deleted'
]);
export const companyLog = pgTable(