SPB Git

spb/rareindex

Public

Integrate agents: header user menu, session bridge, migration 0001, bundler resolution

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Sep 7, 2026) parent 34af8bd Browse files

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 import { ThemeToggle } from './theme-toggle'; 3 import { ThemeToggle } from './theme-toggle';
4 import { MobileNav } from './mobile-nav'; 4 import { MobileNav } from './mobile-nav';
5 import { HeaderSearch } from './header-search'; 5 import { HeaderSearch } from './header-search';
6+import { UserMenu } from '@/components/account/user-menu';
6 7
7 export function Logo({ className }: { className?: string }) { 8 export function Logo({ className }: { className?: string }) {
8 return ( 9 return (
@@ -50,9 +51,7 @@ export function SiteHeader() {
50 <div className="ml-auto flex items-center gap-1.5"> 51 <div className="ml-auto flex items-center gap-1.5">
51 <HeaderSearch /> 52 <HeaderSearch />
52 <ThemeToggle /> 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+ <UserMenu />
54− Sign in
55− </Link>
56 </div> 55 </div>
57 </div> 56 </div>
58 </header> 57 </header>
modified apps/web/src/lib/ai/request.ts +4 −5
@@ -1,6 +1,7 @@
1 import 'server-only'; 1 import 'server-only';
2 import { createHmac, randomUUID } from 'node:crypto'; 2 import { createHmac, randomUUID } from 'node:crypto';
3 import { cookies, headers } from 'next/headers'; 3 import { cookies, headers } from 'next/headers';
4+import { getCurrentUser } from '@/lib/auth/session';
4 import { getDb, aiQuotas, sql } from '@rareindex/database'; 5 import { getDb, aiQuotas, sql } from '@rareindex/database';
5 6
6 const SECRET = () => process.env.SESSION_SECRET ?? 'dev-only'; 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 export async function currentUserId(): Promise<string | null> { 39 export async function currentUserId(): Promise<string | null> {
39 try { 40 try {
40− const jar = await cookies(); 41+ // Delegates to the account module (hashed session id, revocation, expiry) so both agree.
41− const token = jar.get('ri_session')?.value ?? jar.get('session')?.value; 42+ const user = await getCurrentUser();
42− if (!token || token.length > 200) return null; 43+ return user?.id ?? 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;
45 } catch { 44 } catch {
46 return null; 45 return null;
47 } 46 }
modified apps/web/tsconfig.json +2 −2
@@ -8,8 +8,8 @@
8 "noUncheckedIndexedAccess": true, 8 "noUncheckedIndexedAccess": true,
9 "noEmit": true, 9 "noEmit": true,
10 "esModuleInterop": true, 10 "esModuleInterop": true,
11− "module": "nodenext", 11+ "module": "esnext",
12− "moduleResolution": "nodenext", 12+ "moduleResolution": "bundler",
13 "resolveJsonModule": true, 13 "resolveJsonModule": true,
14 "isolatedModules": true, 14 "isolatedModules": true,
15 "jsx": "react-jsx", 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
Menu

Theme