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 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
Menu

Theme