Skip to main content

Items Schema Deep Dive

Overview

In the Ever Works Template, items are stored in a Git-based CMS (.content/ directory), not in a traditional database table. However, multiple database tables support item-related operations such as tracking views, auditing changes, indexing locations, managing favorites, featuring items, and linking items to companies.

This page documents every database table that references or supports items.

Source file: template/lib/db/schema.ts


Item-Supporting Tables

TablePurpose
favoritesUser-saved favorite items
featured_itemsAdmin-curated featured items
item_viewsPer-day unique view tracking
item_audit_logsComplete change history for admin panel
item_location_indexGeospatial index for "Near Me" filtering
items_companiesLinks items to company records
location_index_metaSingleton metadata for location index

Table: favorites

Stores user bookmark/favorite relationships to items, identified by slug.

Columns

ColumnDB NameTypeNullableDefaultConstraints
ididtextNocrypto.randomUUID()Primary Key
userIduserIdtextNo-FK -> users.id (CASCADE)
itemSlugitem_slugtextNo--
itemNameitem_nametextNo--
itemIconUrlitem_icon_urltextYes--
itemCategoryitem_categorytextYes--
createdAtcreated_attimestampNonow()-
updatedAtupdated_attimestampNonow()-

Indexes

NameColumnsType
user_item_favorite_unique_idx(userId, itemSlug)Unique
favorites_user_id_idxuserIdB-tree
favorites_item_slug_idxitemSlugB-tree
favorites_created_at_idxcreatedAtB-tree

TypeScript Types

export type Favorite = typeof favorites.$inferSelect;
export type NewFavorite = typeof favorites.$inferInsert;
export type FavoriteWithUser = Favorite & {
user: typeof users.$inferSelect;
};

Admin-curated list of items to highlight on the site. Supports ordering and optional expiration.

Columns

ColumnDB NameTypeNullableDefaultConstraints
ididtextNocrypto.randomUUID()Primary Key
itemSlugitem_slugtextNo--
itemNameitem_nametextNo--
itemIconUrlitem_icon_urltextYes--
itemCategoryitem_categorytextYes--
itemDescriptionitem_descriptiontextYes--
featuredOrderfeatured_orderintegerNo0Display ordering
featuredUntilfeatured_untiltimestampYes-Optional expiration
isActiveis_activebooleanNotrue-
featuredByfeatured_bytextNo-Admin user ID
featuredAtfeatured_attimestampNonow()-
createdAtcreated_attimestampNonow()-
updatedAtupdated_attimestampNonow()-

Indexes

NameColumnsType
featured_items_item_slug_idxitemSlugB-tree
featured_items_featured_order_idxfeaturedOrderB-tree
featured_items_is_active_idxisActiveB-tree
featured_items_featured_at_idxfeaturedAtB-tree
featured_items_featured_until_idxfeaturedUntilB-tree

TypeScript Types

export type FeaturedItem = typeof featuredItems.$inferSelect;
export type NewFeaturedItem = typeof featuredItems.$inferInsert;

Table: item_views

Tracks unique daily views per item. Uses cookie-based anonymous viewer identification and UTC date deduplication. Does not store IP addresses for privacy.

Columns

ColumnDB NameTypeNullableDefaultConstraints
ididtextNocrypto.randomUUID()Primary Key
itemIditem_idtextNo-Item slug
viewerIdviewer_idtextNo-Anonymous cookie ID
viewedDateUtcviewed_date_utctextNo-YYYY-MM-DD format
viewedAtviewed_attimestamp (tz)Nonow()Precise view time

Indexes

NameColumnsType
item_views_unique_daily_idx(itemId, viewerId, viewedDateUtc)Unique
item_views_item_date_idx(itemId, viewedDateUtc)Composite B-tree

TypeScript Types

export type ItemView = typeof itemViews.$inferSelect;
export type NewItemView = typeof itemViews.$inferInsert;

Table: item_audit_logs

Stores the complete change history for items managed through the admin panel. Since items live in Git, itemId is the slug (not a foreign key).

Columns

ColumnDB NameTypeNullableDefaultConstraints
ididtextNocrypto.randomUUID()Primary Key
itemIditem_idtextNo-Item slug
itemNameitem_nametextNo-Denormalized
actionactiontext (enum)No-See enum values below
previousStatusprevious_statustextYes-For status changes
newStatusnew_statustextYes-For status changes
changeschangesjsonbYes-{ field: { old, new } }
performedByperformed_bytextYes-FK -> users.id (SET NULL)
performedByNameperformed_by_nametextYes-Denormalized
notesnotestextYes-Review notes
metadatametadatajsonbYes-IP, user agent, etc.
createdAtcreated_attimestamp (tz)Nonow()-

Action Enum Values

export const ItemAuditAction = {
CREATED: 'created',
UPDATED: 'updated',
STATUS_CHANGED: 'status_changed',
REVIEWED: 'reviewed',
DELETED: 'deleted',
RESTORED: 'restored'
} as const;

Indexes

NameColumnsType
item_audit_logs_item_id_idxitemIdB-tree
item_audit_logs_action_idxactionB-tree
item_audit_logs_performed_by_idxperformedByB-tree
item_audit_logs_created_at_idxcreatedAtB-tree
item_audit_logs_item_id_action_idx(itemId, action)Composite B-tree

TypeScript Types

export type ItemAuditLog = typeof itemAuditLogs.$inferSelect;
export type NewItemAuditLog = typeof itemAuditLogs.$inferInsert;
export type ItemAuditChanges = Record<string, { old: unknown; new: unknown }>;

Table: item_location_index

Geospatial index for items, enabling "Near Me" filtering and distance-based sorting. This is an index-only table -- the source of truth remains in the Git CMS.

Columns

ColumnDB NameTypeNullableDefaultConstraints
itemSlugitem_slugtextNo-Primary Key
latitudelatitudedoublePrecisionNo--
longitudelongitudedoublePrecisionNo--
addressaddresstextYes--
citycitytextYes--
statestatetextYes--
countrycountrytextYes--
cityNormalizedcity_normalizedtextYes-Lowercase, trimmed
countryNormalizedcountry_normalizedtextYes-Lowercase, trimmed
postalCodepostal_codetextYes--
serviceAreaservice_areatextYes--
isRemoteis_remotebooleanNofalse-
indexedAtindexed_attimestamp (tz)Nonow()-

Indexes

NameColumnsType
item_location_index_latitude_idxlatitudeB-tree
item_location_index_longitude_idxlongitudeB-tree
item_location_index_city_idxcityB-tree
item_location_index_country_idxcountryB-tree
item_location_index_city_normalized_idxcityNormalizedB-tree
item_location_index_country_normalized_idxcountryNormalizedB-tree
item_location_index_is_remote_idxisRemoteB-tree
item_location_index_indexed_at_idxindexedAtB-tree
item_location_index_lat_long_idx(latitude, longitude)Composite B-tree

TypeScript Types

export type ItemLocationIndex = typeof itemLocationIndex.$inferSelect;
export type NewItemLocationIndex = typeof itemLocationIndex.$inferInsert;

Table: items_companies

Links item slugs to company database records.

Columns

ColumnDB NameTypeNullableDefaultConstraints
itemSlugitem_slugtextNo-Unique
companyIdcompany_idtextNo-FK -> companies.id (CASCADE)
createdAtcreated_attimestamp (tz)Nonow()-
updatedAtupdated_attimestamp (tz)Nonow()-

Indexes

NameColumnsType
items_companies_company_id_idxcompanyIdB-tree

Table: location_index_meta

Singleton table tracking location index rebuild metadata across deployments.

Columns

ColumnDB NameTypeNullableDefaultConstraints
ididtextNo'singleton'Primary Key
lastRebuildAtlast_rebuild_attimestamp (tz)Yes--
lastRebuildDurationMslast_rebuild_duration_msintegerYes--
lastRebuildItemCountlast_rebuild_item_countintegerYes--
updatedAtupdated_attimestamp (tz)Nonow()-

Indexes

NameColumnsType
location_index_meta_singleton_idxidUnique

Relations Diagram


Query Examples

Fetch user favorites

import { db } from '@/lib/db/drizzle';
import { favorites } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';

const userFavorites = await db
.select()
.from(favorites)
.where(eq(favorites.userId, userId));

Record an item view

import { itemViews } from '@/lib/db/schema';

await db.insert(itemViews).values({
itemId: 'my-item-slug',
viewerId: cookieViewerId,
viewedDateUtc: '2025-01-15',
}).onConflictDoNothing();
import { featuredItems } from '@/lib/db/schema';
import { eq, asc, or, isNull, gte } from 'drizzle-orm';

const featured = await db
.select()
.from(featuredItems)
.where(eq(featuredItems.isActive, true))
.orderBy(asc(featuredItems.featuredOrder));

Find items near a location (bounding box)

import { itemLocationIndex } from '@/lib/db/schema';
import { and, between } from 'drizzle-orm';

const nearby = await db
.select()
.from(itemLocationIndex)
.where(
and(
between(itemLocationIndex.latitude, minLat, maxLat),
between(itemLocationIndex.longitude, minLng, maxLng)
)
);

Get audit history for an item

import { itemAuditLogs } from '@/lib/db/schema';
import { eq, desc } from 'drizzle-orm';

const history = await db
.select()
.from(itemAuditLogs)
.where(eq(itemAuditLogs.itemId, 'my-item-slug'))
.orderBy(desc(itemAuditLogs.createdAt));

Design Notes

  • Items are NOT in the database. They live in a Git-based CMS cloned into .content/. The database only stores metadata, indexes, and relationships.
  • Item identification is by slug. All item-supporting tables reference items via item_slug or item_id (which IS the slug), not via foreign keys.
  • Denormalization is intentional. Tables like favorites and featured_items store item_name and item_icon_url to avoid cross-system lookups at read time.
  • Privacy-first views. The item_views table uses anonymous cookie IDs and does not store IP addresses.