Integrate agents: header user menu, session bridge, migration 0001, bundler resolution
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7 changed files +8,570 −10
modified
apps/web/src/components/layout/site-header.tsx
+2 −3
@@ -3,6 +3,7 @@ import { NAV, NAV_GROUPS, PRIMARY_NAV } from '@/config/nav'; | ||
| 3 | 3 | import { ThemeToggle } from './theme-toggle'; |
| 4 | 4 | import { MobileNav } from './mobile-nav'; |
| 5 | 5 | import { HeaderSearch } from './header-search'; |
| 6 | +import { UserMenu } from '@/components/account/user-menu'; | |
| 6 | 7 | |
| 7 | 8 | export function Logo({ className }: { className?: string }) { |
| 8 | 9 | return ( |
@@ -50,9 +51,7 @@ export function SiteHeader() { | ||
| 50 | 51 | <div className="ml-auto flex items-center gap-1.5"> |
| 51 | 52 | <HeaderSearch /> |
| 52 | 53 | <ThemeToggle /> |
| 53 | − <Link href="/account" className="hidden rounded-md border border-border px-2.5 py-1.5 text-[13px] font-medium text-fg hover:bg-inset sm:inline-flex"> | |
| 54 | − Sign in | |
| 55 | − </Link> | |
| 54 | + <UserMenu /> | |
| 56 | 55 | </div> |
| 57 | 56 | </div> |
| 58 | 57 | </header> |
modified
apps/web/src/lib/ai/request.ts
+4 −5
@@ -1,6 +1,7 @@ | ||
| 1 | 1 | import 'server-only'; |
| 2 | 2 | import { createHmac, randomUUID } from 'node:crypto'; |
| 3 | 3 | import { cookies, headers } from 'next/headers'; |
| 4 | +import { getCurrentUser } from '@/lib/auth/session'; | |
| 4 | 5 | import { getDb, aiQuotas, sql } from '@rareindex/database'; |
| 5 | 6 | |
| 6 | 7 | const SECRET = () => process.env.SESSION_SECRET ?? 'dev-only'; |
@@ -37,11 +38,9 @@ export async function getAnonId(create = true): Promise<string | null> { | ||
| 37 | 38 | */ |
| 38 | 39 | export async function currentUserId(): Promise<string | null> { |
| 39 | 40 | try { |
| 40 | − const jar = await cookies(); | |
| 41 | − const token = jar.get('ri_session')?.value ?? jar.get('session')?.value; | |
| 42 | − if (!token || token.length > 200) return null; | |
| 43 | − const [row] = await getDb().execute(sql`select user_id from sessions where id = ${token} and expires_at > now() limit 1`); | |
| 44 | − return ((row as { user_id?: string } | undefined)?.user_id) ?? null; | |
| 41 | + // Delegates to the account module (hashed session id, revocation, expiry) so both agree. | |
| 42 | + const user = await getCurrentUser(); | |
| 43 | + return user?.id ?? null; | |
| 45 | 44 | } catch { |
| 46 | 45 | return null; |
| 47 | 46 | } |
modified
apps/web/tsconfig.json
+2 −2
@@ -8,8 +8,8 @@ | ||
| 8 | 8 | "noUncheckedIndexedAccess": true, |
| 9 | 9 | "noEmit": true, |
| 10 | 10 | "esModuleInterop": true, |
| 11 | − "module": "nodenext", | |
| 12 | − "moduleResolution": "nodenext", | |
| 11 | + "module": "esnext", | |
| 12 | + "moduleResolution": "bundler", | |
| 13 | 13 | "resolveJsonModule": true, |
| 14 | 14 | "isolatedModules": true, |
| 15 | 15 | "jsx": "react-jsx", |
added
packages/database/migrations/0001_living_prima.sql
+215 −0
@@ -0,0 +1,215 @@ | ||
| 1 | +CREATE TABLE "ai_quotas" ( | |
| 2 | + "key" text NOT NULL, | |
| 3 | + "feature" text NOT NULL, | |
| 4 | + "date" text NOT NULL, | |
| 5 | + "count" integer DEFAULT 0 NOT NULL | |
| 6 | +); | |
| 7 | +--> statement-breakpoint | |
| 8 | +CREATE TABLE "research_messages" ( | |
| 9 | + "id" text PRIMARY KEY NOT NULL, | |
| 10 | + "session_id" text NOT NULL, | |
| 11 | + "role" text NOT NULL, | |
| 12 | + "content" text NOT NULL, | |
| 13 | + "tool_calls" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 14 | + "usage" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 15 | + "usd_est" real DEFAULT 0 NOT NULL, | |
| 16 | + "model" text, | |
| 17 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 18 | +); | |
| 19 | +--> statement-breakpoint | |
| 20 | +CREATE TABLE "research_sessions" ( | |
| 21 | + "id" text PRIMARY KEY NOT NULL, | |
| 22 | + "user_id" text, | |
| 23 | + "anon_id" text, | |
| 24 | + "title" text, | |
| 25 | + "model" text, | |
| 26 | + "message_count" integer DEFAULT 0 NOT NULL, | |
| 27 | + "usd_est" real DEFAULT 0 NOT NULL, | |
| 28 | + "archived" boolean DEFAULT false NOT NULL, | |
| 29 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 30 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 31 | +); | |
| 32 | +--> statement-breakpoint | |
| 33 | +CREATE TABLE "scanner_sessions" ( | |
| 34 | + "id" text PRIMARY KEY NOT NULL, | |
| 35 | + "user_id" text, | |
| 36 | + "anon_id" text, | |
| 37 | + "ip_hash" text, | |
| 38 | + "mode" text NOT NULL, | |
| 39 | + "input_url" text, | |
| 40 | + "input_text" text, | |
| 41 | + "image_count" integer DEFAULT 0 NOT NULL, | |
| 42 | + "thumbnails" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 43 | + "guess" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 44 | + "guess_confidence" real, | |
| 45 | + "candidates" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 46 | + "chosen_asset_id" text, | |
| 47 | + "listing" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 48 | + "model" text, | |
| 49 | + "usd_est" real DEFAULT 0 NOT NULL, | |
| 50 | + "duration_ms" integer, | |
| 51 | + "error" text, | |
| 52 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 53 | + "chosen_at" timestamp with time zone | |
| 54 | +); | |
| 55 | +--> statement-breakpoint | |
| 56 | +CREATE TABLE "auth_codes" ( | |
| 57 | + "id" text PRIMARY KEY NOT NULL, | |
| 58 | + "user_id" text, | |
| 59 | + "email" text NOT NULL, | |
| 60 | + "purpose" text NOT NULL, | |
| 61 | + "code_hash" text NOT NULL, | |
| 62 | + "expires_at" timestamp with time zone NOT NULL, | |
| 63 | + "attempts" integer DEFAULT 0 NOT NULL, | |
| 64 | + "consumed_at" timestamp with time zone, | |
| 65 | + "payload" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 66 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 67 | +); | |
| 68 | +--> statement-breakpoint | |
| 69 | +CREATE TABLE "login_events" ( | |
| 70 | + "id" text PRIMARY KEY NOT NULL, | |
| 71 | + "user_id" text, | |
| 72 | + "email" text, | |
| 73 | + "ip" text, | |
| 74 | + "user_agent" text, | |
| 75 | + "outcome" text NOT NULL, | |
| 76 | + "method" text, | |
| 77 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 78 | +); | |
| 79 | +--> statement-breakpoint | |
| 80 | +CREATE TABLE "notifications" ( | |
| 81 | + "id" text PRIMARY KEY NOT NULL, | |
| 82 | + "user_id" text NOT NULL, | |
| 83 | + "kind" text NOT NULL, | |
| 84 | + "title" text NOT NULL, | |
| 85 | + "body" text, | |
| 86 | + "href" text, | |
| 87 | + "payload" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 88 | + "read_at" timestamp with time zone, | |
| 89 | + "emailed_at" timestamp with time zone, | |
| 90 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 91 | +); | |
| 92 | +--> statement-breakpoint | |
| 93 | +CREATE TABLE "price_targets" ( | |
| 94 | + "id" text PRIMARY KEY NOT NULL, | |
| 95 | + "user_id" text NOT NULL, | |
| 96 | + "asset_id" text NOT NULL, | |
| 97 | + "variant_id" text, | |
| 98 | + "direction" text DEFAULT 'above' NOT NULL, | |
| 99 | + "target_usd" numeric(18, 4) NOT NULL, | |
| 100 | + "baseline_usd" numeric(18, 4), | |
| 101 | + "note" text, | |
| 102 | + "hit_at" timestamp with time zone, | |
| 103 | + "notified_at" timestamp with time zone, | |
| 104 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 105 | +); | |
| 106 | +--> statement-breakpoint | |
| 107 | +CREATE TABLE "rate_limits" ( | |
| 108 | + "key" text PRIMARY KEY NOT NULL, | |
| 109 | + "count" integer DEFAULT 0 NOT NULL, | |
| 110 | + "reset_at" timestamp with time zone NOT NULL | |
| 111 | +); | |
| 112 | +--> statement-breakpoint | |
| 113 | +CREATE TABLE "recovery_codes" ( | |
| 114 | + "id" text PRIMARY KEY NOT NULL, | |
| 115 | + "user_id" text NOT NULL, | |
| 116 | + "code_hash" text NOT NULL, | |
| 117 | + "used_at" timestamp with time zone, | |
| 118 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 119 | +); | |
| 120 | +--> statement-breakpoint | |
| 121 | +CREATE TABLE "saved_searches" ( | |
| 122 | + "id" text PRIMARY KEY NOT NULL, | |
| 123 | + "user_id" text NOT NULL, | |
| 124 | + "name" text NOT NULL, | |
| 125 | + "url" text NOT NULL, | |
| 126 | + "params" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 127 | + "notify" boolean DEFAULT false NOT NULL, | |
| 128 | + "last_run_at" timestamp with time zone, | |
| 129 | + "last_count" integer, | |
| 130 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 131 | +); | |
| 132 | +--> statement-breakpoint | |
| 133 | +CREATE TABLE "trusted_devices" ( | |
| 134 | + "id" text PRIMARY KEY NOT NULL, | |
| 135 | + "user_id" text NOT NULL, | |
| 136 | + "token_hash" text NOT NULL, | |
| 137 | + "label" text, | |
| 138 | + "user_agent" text, | |
| 139 | + "ip" text, | |
| 140 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 141 | + "last_used_at" timestamp with time zone, | |
| 142 | + "expires_at" timestamp with time zone NOT NULL, | |
| 143 | + "revoked_at" timestamp with time zone | |
| 144 | +); | |
| 145 | +--> statement-breakpoint | |
| 146 | +CREATE TABLE "uploads" ( | |
| 147 | + "id" text PRIMARY KEY NOT NULL, | |
| 148 | + "user_id" text NOT NULL, | |
| 149 | + "kind" text NOT NULL, | |
| 150 | + "path" text NOT NULL, | |
| 151 | + "mime" text NOT NULL, | |
| 152 | + "bytes" integer NOT NULL, | |
| 153 | + "width" integer, | |
| 154 | + "height" integer, | |
| 155 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 156 | +); | |
| 157 | +--> statement-breakpoint | |
| 158 | +CREATE TABLE "user_badges" ( | |
| 159 | + "user_id" text NOT NULL, | |
| 160 | + "badge" text NOT NULL, | |
| 161 | + "evidence" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 162 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 163 | +); | |
| 164 | +--> statement-breakpoint | |
| 165 | +DROP INDEX "normalized_records_raw_uq";--> statement-breakpoint | |
| 166 | +ALTER TABLE "normalized_records" ADD COLUMN "seq" integer DEFAULT 0 NOT NULL;--> statement-breakpoint | |
| 167 | +ALTER TABLE "alerts" ADD COLUMN "name" text;--> statement-breakpoint | |
| 168 | +ALTER TABLE "alerts" ADD COLUMN "cooldown_minutes" integer DEFAULT 1440 NOT NULL;--> statement-breakpoint | |
| 169 | +ALTER TABLE "alerts" ADD COLUMN "trigger_count" integer DEFAULT 0 NOT NULL;--> statement-breakpoint | |
| 170 | +ALTER TABLE "collection_items" ADD COLUMN "tags" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint | |
| 171 | +ALTER TABLE "collection_items" ADD COLUMN "condition" text;--> statement-breakpoint | |
| 172 | +ALTER TABLE "collection_items" ADD COLUMN "manual_value_usd" numeric(18, 4);--> statement-breakpoint | |
| 173 | +ALTER TABLE "collection_items" ADD COLUMN "sold_at" date;--> statement-breakpoint | |
| 174 | +ALTER TABLE "collection_items" ADD COLUMN "sold_price_usd" numeric(18, 4);--> statement-breakpoint | |
| 175 | +ALTER TABLE "collections" ADD COLUMN "kind" text DEFAULT 'collection' NOT NULL;--> statement-breakpoint | |
| 176 | +ALTER TABLE "collections" ADD COLUMN "budget_usd" numeric(18, 4);--> statement-breakpoint | |
| 177 | +ALTER TABLE "collections" ADD COLUMN "color" text;--> statement-breakpoint | |
| 178 | +ALTER TABLE "sessions" ADD COLUMN "ip" text;--> statement-breakpoint | |
| 179 | +ALTER TABLE "sessions" ADD COLUMN "last_seen_at" timestamp with time zone;--> statement-breakpoint | |
| 180 | +ALTER TABLE "sessions" ADD COLUMN "revoked_at" timestamp with time zone;--> statement-breakpoint | |
| 181 | +ALTER TABLE "users" ADD COLUMN "handle" text;--> statement-breakpoint | |
| 182 | +ALTER TABLE "users" ADD COLUMN "avatar_url" text;--> statement-breakpoint | |
| 183 | +ALTER TABLE "users" ADD COLUMN "bio" text;--> statement-breakpoint | |
| 184 | +ALTER TABLE "users" ADD COLUMN "mfa_enabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint | |
| 185 | +ALTER TABLE "users" ADD COLUMN "totp_secret_enc" text;--> statement-breakpoint | |
| 186 | +ALTER TABLE "users" ADD COLUMN "always_ask_code" boolean DEFAULT true NOT NULL;--> statement-breakpoint | |
| 187 | +ALTER TABLE "users" ADD COLUMN "password_changed_at" timestamp with time zone;--> statement-breakpoint | |
| 188 | +ALTER TABLE "users" ADD COLUMN "pending_email" text;--> statement-breakpoint | |
| 189 | +ALTER TABLE "users" ADD COLUMN "deleted_at" timestamp with time zone;--> statement-breakpoint | |
| 190 | +ALTER TABLE "users" ADD COLUMN "purge_after" timestamp with time zone;--> statement-breakpoint | |
| 191 | +ALTER TABLE "watchlist_items" ADD COLUMN "label" text;--> statement-breakpoint | |
| 192 | +ALTER TABLE "watchlist_items" ADD COLUMN "note" text;--> statement-breakpoint | |
| 193 | +ALTER TABLE "watchlist_items" ADD COLUMN "target_price_usd" numeric(18, 4);--> statement-breakpoint | |
| 194 | +ALTER TABLE "watchlist_items" ADD COLUMN "baseline_usd" numeric(18, 4);--> statement-breakpoint | |
| 195 | +CREATE INDEX "ai_quotas_uq" ON "ai_quotas" USING btree ("key","feature","date");--> statement-breakpoint | |
| 196 | +CREATE INDEX "research_messages_session_idx" ON "research_messages" USING btree ("session_id","created_at");--> statement-breakpoint | |
| 197 | +CREATE INDEX "research_sessions_anon_idx" ON "research_sessions" USING btree ("anon_id","updated_at");--> statement-breakpoint | |
| 198 | +CREATE INDEX "research_sessions_user_idx" ON "research_sessions" USING btree ("user_id","updated_at");--> statement-breakpoint | |
| 199 | +CREATE INDEX "scanner_sessions_created_idx" ON "scanner_sessions" USING btree ("created_at");--> statement-breakpoint | |
| 200 | +CREATE INDEX "scanner_sessions_ip_idx" ON "scanner_sessions" USING btree ("ip_hash","created_at");--> statement-breakpoint | |
| 201 | +CREATE INDEX "scanner_sessions_user_idx" ON "scanner_sessions" USING btree ("user_id");--> statement-breakpoint | |
| 202 | +CREATE INDEX "auth_codes_lookup_idx" ON "auth_codes" USING btree ("email","purpose","created_at");--> statement-breakpoint | |
| 203 | +CREATE INDEX "login_events_user_idx" ON "login_events" USING btree ("user_id","created_at");--> statement-breakpoint | |
| 204 | +CREATE INDEX "notifications_user_idx" ON "notifications" USING btree ("user_id","created_at");--> statement-breakpoint | |
| 205 | +CREATE INDEX "notifications_unread_idx" ON "notifications" USING btree ("user_id","read_at");--> statement-breakpoint | |
| 206 | +CREATE INDEX "price_targets_user_idx" ON "price_targets" USING btree ("user_id");--> statement-breakpoint | |
| 207 | +CREATE INDEX "price_targets_asset_idx" ON "price_targets" USING btree ("asset_id","hit_at");--> statement-breakpoint | |
| 208 | +CREATE INDEX "recovery_codes_user_idx" ON "recovery_codes" USING btree ("user_id");--> statement-breakpoint | |
| 209 | +CREATE INDEX "saved_searches_user_idx" ON "saved_searches" USING btree ("user_id");--> statement-breakpoint | |
| 210 | +CREATE INDEX "trusted_devices_user_idx" ON "trusted_devices" USING btree ("user_id");--> statement-breakpoint | |
| 211 | +CREATE UNIQUE INDEX "trusted_devices_token_uq" ON "trusted_devices" USING btree ("token_hash");--> statement-breakpoint | |
| 212 | +CREATE INDEX "uploads_user_idx" ON "uploads" USING btree ("user_id");--> statement-breakpoint | |
| 213 | +CREATE UNIQUE INDEX "user_badges_uq" ON "user_badges" USING btree ("user_id","badge");--> statement-breakpoint | |
| 214 | +CREATE UNIQUE INDEX "normalized_records_raw_uq" ON "normalized_records" USING btree ("raw_record_id","kind","seq");--> statement-breakpoint | |
| 215 | +ALTER TABLE "users" ADD CONSTRAINT "users_handle_unique" UNIQUE("handle"); | |
| \ No newline at end of file | ||
added
packages/database/migrations/meta/0001_snapshot.json
+8103 −0
Large diff (8,104 lines) is not loaded by default to keep the page fast.Load diff
modified
packages/database/migrations/meta/_journal.json
+7 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
modified
pnpm-lock.yaml
+237 −0
Large diff (566 lines) is not loaded by default to keep the page fast.Load diff