fix: brancher Tailwind v4 sur Turbopack (classes utilitaires manquantes en prod)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
16 changed files +4,062 −5
added
drizzle.config.ts
+2 −0
@@ -0,0 +1,2 @@ | ||
| 1 | +import { defineConfig } from "drizzle-kit"; | |
| 2 | +export default defineConfig({ schema: "./src/db/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL ?? "postgresql://toveli_app:toveli_dev@127.0.0.1:5432/toveli" }, strict: true, verbose: false }); | |
added
drizzle/0000_daily_dexter_bennett.sql
+318 −0
@@ -0,0 +1,318 @@ | ||
| 1 | +CREATE TABLE "audit_events" ( | |
| 2 | + "id" bigserial PRIMARY KEY NOT NULL, | |
| 3 | + "actor_id" uuid, | |
| 4 | + "action" text NOT NULL, | |
| 5 | + "object_type" text, | |
| 6 | + "object_id" text, | |
| 7 | + "ip_hash" text, | |
| 8 | + "metadata_redacted" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 9 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 10 | +); | |
| 11 | +--> statement-breakpoint | |
| 12 | +CREATE TABLE "email_deliveries" ( | |
| 13 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 14 | + "to_hash" text NOT NULL, | |
| 15 | + "template" text NOT NULL, | |
| 16 | + "provider_id" text, | |
| 17 | + "status" text NOT NULL, | |
| 18 | + "error" text, | |
| 19 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 20 | +); | |
| 21 | +--> statement-breakpoint | |
| 22 | +CREATE TABLE "favorites" ( | |
| 23 | + "user_id" uuid NOT NULL, | |
| 24 | + "listing_uid" text NOT NULL, | |
| 25 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 26 | + CONSTRAINT "favorites_user_id_listing_uid_pk" PRIMARY KEY("user_id","listing_uid") | |
| 27 | +); | |
| 28 | +--> statement-breakpoint | |
| 29 | +CREATE TABLE "inquiries" ( | |
| 30 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 31 | + "listing_id" uuid NOT NULL, | |
| 32 | + "sender_user_id" uuid NOT NULL, | |
| 33 | + "recipient_user_id" uuid NOT NULL, | |
| 34 | + "category" text DEFAULT 'INFO' NOT NULL, | |
| 35 | + "status" text DEFAULT 'OPEN' NOT NULL, | |
| 36 | + "last_message_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 37 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 38 | +); | |
| 39 | +--> statement-breakpoint | |
| 40 | +CREATE TABLE "listing_claims" ( | |
| 41 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 42 | + "aggregated_uid" text NOT NULL, | |
| 43 | + "requester_id" uuid NOT NULL, | |
| 44 | + "status" text DEFAULT 'PENDING' NOT NULL, | |
| 45 | + "message" text, | |
| 46 | + "proof_keys" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 47 | + "reviewed_by" uuid, | |
| 48 | + "reviewed_at" timestamp with time zone, | |
| 49 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 50 | +); | |
| 51 | +--> statement-breakpoint | |
| 52 | +CREATE TABLE "listing_media" ( | |
| 53 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 54 | + "listing_id" uuid NOT NULL, | |
| 55 | + "storage_key" text NOT NULL, | |
| 56 | + "media_type" text DEFAULT 'IMAGE' NOT NULL, | |
| 57 | + "mime_type" text, | |
| 58 | + "position" integer DEFAULT 0 NOT NULL, | |
| 59 | + "alt_text" text, | |
| 60 | + "license_status" text DEFAULT 'OWNER_DECLARED' NOT NULL, | |
| 61 | + "scan_status" text DEFAULT 'PENDING' NOT NULL, | |
| 62 | + "width" integer, | |
| 63 | + "height" integer, | |
| 64 | + "bytes" integer, | |
| 65 | + "status" text DEFAULT 'UPLOADING' NOT NULL, | |
| 66 | + "upload_token" text, | |
| 67 | + "uploaded_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 68 | +); | |
| 69 | +--> statement-breakpoint | |
| 70 | +CREATE TABLE "listing_versions" ( | |
| 71 | + "id" bigserial PRIMARY KEY NOT NULL, | |
| 72 | + "listing_id" uuid NOT NULL, | |
| 73 | + "version" integer NOT NULL, | |
| 74 | + "actor_id" uuid, | |
| 75 | + "snapshot" jsonb NOT NULL, | |
| 76 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 77 | +); | |
| 78 | +--> statement-breakpoint | |
| 79 | +CREATE TABLE "listings" ( | |
| 80 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 81 | + "slug" text NOT NULL, | |
| 82 | + "property_id" uuid NOT NULL, | |
| 83 | + "owner_user_id" uuid, | |
| 84 | + "listing_type" text NOT NULL, | |
| 85 | + "source_type" text DEFAULT 'DIRECT_OWNER' NOT NULL, | |
| 86 | + "status" text DEFAULT 'DRAFT' NOT NULL, | |
| 87 | + "title" text, | |
| 88 | + "description" text, | |
| 89 | + "price_amount" numeric(12, 2), | |
| 90 | + "currency" text DEFAULT 'CAD' NOT NULL, | |
| 91 | + "rental_period" text, | |
| 92 | + "availability_date" text, | |
| 93 | + "furnished" boolean, | |
| 94 | + "pets_policy" text, | |
| 95 | + "smoking_policy" text, | |
| 96 | + "parking" text, | |
| 97 | + "lease_terms" text, | |
| 98 | + "inclusions" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 99 | + "features" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 100 | + "publication_at" timestamp with time zone, | |
| 101 | + "expires_at" timestamp with time zone, | |
| 102 | + "moderation_state" text DEFAULT 'NONE' NOT NULL, | |
| 103 | + "moderation_note" text, | |
| 104 | + "external_url" text, | |
| 105 | + "upstream_source" text, | |
| 106 | + "upstream_id" text, | |
| 107 | + "upstream_sync_at" timestamp with time zone, | |
| 108 | + "wizard_step" integer DEFAULT 0 NOT NULL, | |
| 109 | + "attested_at" timestamp with time zone, | |
| 110 | + "version" integer DEFAULT 1 NOT NULL, | |
| 111 | + "views_count" integer DEFAULT 0 NOT NULL, | |
| 112 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 113 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 114 | + CONSTRAINT "listings_type_ck" CHECK ("listings"."listing_type" in ('SALE','RENT')), | |
| 115 | + CONSTRAINT "listings_source_ck" CHECK ("listings"."source_type" in ('DIRECT_OWNER','DIRECT_AGENT','AGGREGATED_PARTNER')), | |
| 116 | + CONSTRAINT "listings_status_ck" CHECK ("listings"."status" in ('DRAFT','PENDING_REVIEW','PUBLISHED','PAUSED','REJECTED','ARCHIVED','SOLD','RENTED')), | |
| 117 | + CONSTRAINT "listings_price_ck" CHECK ("listings"."price_amount" is null or "listings"."price_amount" >= 0), | |
| 118 | + CONSTRAINT "listings_owner_direct_ck" CHECK ("listings"."source_type" = 'AGGREGATED_PARTNER' or "listings"."owner_user_id" is not null), | |
| 119 | + CONSTRAINT "listings_rent_period_ck" CHECK ("listings"."listing_type" <> 'RENT' or "listings"."rental_period" is null or "listings"."rental_period" in ('MONTH','WEEK')) | |
| 120 | +); | |
| 121 | +--> statement-breakpoint | |
| 122 | +CREATE TABLE "messages" ( | |
| 123 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 124 | + "inquiry_id" uuid NOT NULL, | |
| 125 | + "sender_id" uuid NOT NULL, | |
| 126 | + "body" text NOT NULL, | |
| 127 | + "idempotency_key" text, | |
| 128 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 129 | + "read_at" timestamp with time zone | |
| 130 | +); | |
| 131 | +--> statement-breakpoint | |
| 132 | +CREATE TABLE "moderation_actions" ( | |
| 133 | + "id" bigserial PRIMARY KEY NOT NULL, | |
| 134 | + "actor_id" uuid NOT NULL, | |
| 135 | + "target_type" text NOT NULL, | |
| 136 | + "target_id" text NOT NULL, | |
| 137 | + "action" text NOT NULL, | |
| 138 | + "note" text, | |
| 139 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 140 | +); | |
| 141 | +--> statement-breakpoint | |
| 142 | +CREATE TABLE "otp_challenges" ( | |
| 143 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 144 | + "email_normalized" text NOT NULL, | |
| 145 | + "purpose" text DEFAULT 'LOGIN' NOT NULL, | |
| 146 | + "secret_digest" text NOT NULL, | |
| 147 | + "expires_at" timestamp with time zone NOT NULL, | |
| 148 | + "attempts" integer DEFAULT 0 NOT NULL, | |
| 149 | + "consumed_at" timestamp with time zone, | |
| 150 | + "requested_ip_hash" text, | |
| 151 | + "meta" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 152 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 153 | +); | |
| 154 | +--> statement-breakpoint | |
| 155 | +CREATE TABLE "properties" ( | |
| 156 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 157 | + "canonical_address" text, | |
| 158 | + "unit" text, | |
| 159 | + "city" text, | |
| 160 | + "region" text, | |
| 161 | + "postal_code" text, | |
| 162 | + "lat" double precision, | |
| 163 | + "lng" double precision, | |
| 164 | + "geographic_precision" text DEFAULT 'EXACT' NOT NULL, | |
| 165 | + "geo_source" text, | |
| 166 | + "role_unit_id" text, | |
| 167 | + "property_type" text NOT NULL, | |
| 168 | + "bedrooms" numeric(4, 1), | |
| 169 | + "bathrooms" numeric(4, 1), | |
| 170 | + "area_sqft" integer, | |
| 171 | + "lot_sqft" integer, | |
| 172 | + "year_built" integer, | |
| 173 | + "privacy_level" text DEFAULT 'EXACT' NOT NULL, | |
| 174 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 175 | +); | |
| 176 | +--> statement-breakpoint | |
| 177 | +CREATE TABLE "rate_limits" ( | |
| 178 | + "key" text NOT NULL, | |
| 179 | + "window_start" timestamp with time zone NOT NULL, | |
| 180 | + "count" integer DEFAULT 0 NOT NULL, | |
| 181 | + CONSTRAINT "rate_limits_key_window_start_pk" PRIMARY KEY("key","window_start") | |
| 182 | +); | |
| 183 | +--> statement-breakpoint | |
| 184 | +CREATE TABLE "reports" ( | |
| 185 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 186 | + "target_type" text NOT NULL, | |
| 187 | + "target_id" text NOT NULL, | |
| 188 | + "reporter_id" uuid, | |
| 189 | + "reason" text NOT NULL, | |
| 190 | + "details" text, | |
| 191 | + "status" text DEFAULT 'OPEN' NOT NULL, | |
| 192 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 193 | +); | |
| 194 | +--> statement-breakpoint | |
| 195 | +CREATE TABLE "saved_searches" ( | |
| 196 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 197 | + "user_id" uuid NOT NULL, | |
| 198 | + "label" text NOT NULL, | |
| 199 | + "query" jsonb NOT NULL, | |
| 200 | + "alert_enabled" boolean DEFAULT false NOT NULL, | |
| 201 | + "alert_frequency" text DEFAULT 'DAILY' NOT NULL, | |
| 202 | + "last_alert_at" timestamp with time zone, | |
| 203 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 204 | +); | |
| 205 | +--> statement-breakpoint | |
| 206 | +CREATE TABLE "sessions" ( | |
| 207 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 208 | + "user_id" uuid NOT NULL, | |
| 209 | + "token_digest" text NOT NULL, | |
| 210 | + "issued_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 211 | + "last_seen_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 212 | + "expires_at" timestamp with time zone NOT NULL, | |
| 213 | + "revoked_at" timestamp with time zone, | |
| 214 | + "device_label" text, | |
| 215 | + "ip_hash" text, | |
| 216 | + "mfa_at" timestamp with time zone | |
| 217 | +); | |
| 218 | +--> statement-breakpoint | |
| 219 | +CREATE TABLE "user_profiles" ( | |
| 220 | + "user_id" uuid PRIMARY KEY NOT NULL, | |
| 221 | + "public_name" text, | |
| 222 | + "bio" text, | |
| 223 | + "avatar_key" text, | |
| 224 | + "seller_type" text DEFAULT 'OWNER' NOT NULL, | |
| 225 | + "verification_status" text DEFAULT 'NONE' NOT NULL, | |
| 226 | + "contact_preferences" jsonb DEFAULT '{"email":true,"phone":false}'::jsonb NOT NULL, | |
| 227 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| 228 | +); | |
| 229 | +--> statement-breakpoint | |
| 230 | +CREATE TABLE "user_roles" ( | |
| 231 | + "user_id" uuid NOT NULL, | |
| 232 | + "role" text NOT NULL, | |
| 233 | + "granted_by" uuid, | |
| 234 | + "granted_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 235 | + "note" text, | |
| 236 | + CONSTRAINT "user_roles_user_id_role_pk" PRIMARY KEY("user_id","role"), | |
| 237 | + CONSTRAINT "user_roles_role_ck" CHECK ("user_roles"."role" in ('ADMIN','MODERATOR','AGENT_VERIFIED')) | |
| 238 | +); | |
| 239 | +--> statement-breakpoint | |
| 240 | +CREATE TABLE "users" ( | |
| 241 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 242 | + "email" text NOT NULL, | |
| 243 | + "email_verified_at" timestamp with time zone, | |
| 244 | + "display_name" text, | |
| 245 | + "phone_encrypted" text, | |
| 246 | + "locale" text DEFAULT 'fr-CA' NOT NULL, | |
| 247 | + "status" text DEFAULT 'ACTIVE' NOT NULL, | |
| 248 | + "prefs" jsonb DEFAULT '{}'::jsonb NOT NULL, | |
| 249 | + "totp_secret_encrypted" text, | |
| 250 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 251 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 252 | + "deleted_at" timestamp with time zone, | |
| 253 | + CONSTRAINT "users_status_ck" CHECK ("users"."status" in ('ACTIVE','SUSPENDED','DELETED')) | |
| 254 | +); | |
| 255 | +--> statement-breakpoint | |
| 256 | +CREATE TABLE "viewing_requests" ( | |
| 257 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 258 | + "listing_id" uuid NOT NULL, | |
| 259 | + "inquiry_id" uuid, | |
| 260 | + "requester_id" uuid NOT NULL, | |
| 261 | + "owner_id" uuid NOT NULL, | |
| 262 | + "proposed_slots" jsonb NOT NULL, | |
| 263 | + "confirmed_slot" timestamp with time zone, | |
| 264 | + "status" text DEFAULT 'REQUESTED' NOT NULL, | |
| 265 | + "note" text, | |
| 266 | + "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 267 | + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 268 | + CONSTRAINT "viewings_status_ck" CHECK ("viewing_requests"."status" in ('REQUESTED','CONFIRMED','CANCELLED','DONE')) | |
| 269 | +); | |
| 270 | +--> statement-breakpoint | |
| 271 | +ALTER TABLE "favorites" ADD CONSTRAINT "favorites_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 272 | +ALTER TABLE "inquiries" ADD CONSTRAINT "inquiries_listing_id_listings_id_fk" FOREIGN KEY ("listing_id") REFERENCES "public"."listings"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 273 | +ALTER TABLE "inquiries" ADD CONSTRAINT "inquiries_sender_user_id_users_id_fk" FOREIGN KEY ("sender_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 274 | +ALTER TABLE "inquiries" ADD CONSTRAINT "inquiries_recipient_user_id_users_id_fk" FOREIGN KEY ("recipient_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 275 | +ALTER TABLE "listing_claims" ADD CONSTRAINT "listing_claims_requester_id_users_id_fk" FOREIGN KEY ("requester_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 276 | +ALTER TABLE "listing_media" ADD CONSTRAINT "listing_media_listing_id_listings_id_fk" FOREIGN KEY ("listing_id") REFERENCES "public"."listings"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 277 | +ALTER TABLE "listing_versions" ADD CONSTRAINT "listing_versions_listing_id_listings_id_fk" FOREIGN KEY ("listing_id") REFERENCES "public"."listings"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 278 | +ALTER TABLE "listings" ADD CONSTRAINT "listings_property_id_properties_id_fk" FOREIGN KEY ("property_id") REFERENCES "public"."properties"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint | |
| 279 | +ALTER TABLE "listings" ADD CONSTRAINT "listings_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint | |
| 280 | +ALTER TABLE "messages" ADD CONSTRAINT "messages_inquiry_id_inquiries_id_fk" FOREIGN KEY ("inquiry_id") REFERENCES "public"."inquiries"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 281 | +ALTER TABLE "messages" ADD CONSTRAINT "messages_sender_id_users_id_fk" FOREIGN KEY ("sender_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 282 | +ALTER TABLE "reports" ADD CONSTRAINT "reports_reporter_id_users_id_fk" FOREIGN KEY ("reporter_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint | |
| 283 | +ALTER TABLE "saved_searches" ADD CONSTRAINT "saved_searches_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 284 | +ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 285 | +ALTER TABLE "user_profiles" ADD CONSTRAINT "user_profiles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 286 | +ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 287 | +ALTER TABLE "viewing_requests" ADD CONSTRAINT "viewing_requests_listing_id_listings_id_fk" FOREIGN KEY ("listing_id") REFERENCES "public"."listings"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 288 | +ALTER TABLE "viewing_requests" ADD CONSTRAINT "viewing_requests_inquiry_id_inquiries_id_fk" FOREIGN KEY ("inquiry_id") REFERENCES "public"."inquiries"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint | |
| 289 | +ALTER TABLE "viewing_requests" ADD CONSTRAINT "viewing_requests_requester_id_users_id_fk" FOREIGN KEY ("requester_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 290 | +ALTER TABLE "viewing_requests" ADD CONSTRAINT "viewing_requests_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 291 | +CREATE INDEX "audit_created_idx" ON "audit_events" USING btree ("created_at");--> statement-breakpoint | |
| 292 | +CREATE INDEX "audit_actor_idx" ON "audit_events" USING btree ("actor_id");--> statement-breakpoint | |
| 293 | +CREATE INDEX "email_deliveries_created_idx" ON "email_deliveries" USING btree ("created_at");--> statement-breakpoint | |
| 294 | +CREATE INDEX "inquiries_listing_idx" ON "inquiries" USING btree ("listing_id");--> statement-breakpoint | |
| 295 | +CREATE INDEX "inquiries_sender_idx" ON "inquiries" USING btree ("sender_user_id");--> statement-breakpoint | |
| 296 | +CREATE INDEX "inquiries_recipient_idx" ON "inquiries" USING btree ("recipient_user_id");--> statement-breakpoint | |
| 297 | +CREATE UNIQUE INDEX "inquiries_unique_thread" ON "inquiries" USING btree ("listing_id","sender_user_id");--> statement-breakpoint | |
| 298 | +CREATE INDEX "listing_claims_uid_idx" ON "listing_claims" USING btree ("aggregated_uid");--> statement-breakpoint | |
| 299 | +CREATE INDEX "listing_media_listing_idx" ON "listing_media" USING btree ("listing_id","position");--> statement-breakpoint | |
| 300 | +CREATE INDEX "listing_versions_listing_idx" ON "listing_versions" USING btree ("listing_id","version");--> statement-breakpoint | |
| 301 | +CREATE UNIQUE INDEX "listings_slug_uq" ON "listings" USING btree ("slug");--> statement-breakpoint | |
| 302 | +CREATE INDEX "listings_status_idx" ON "listings" USING btree ("status","listing_type","publication_at");--> statement-breakpoint | |
| 303 | +CREATE INDEX "listings_owner_idx" ON "listings" USING btree ("owner_user_id");--> statement-breakpoint | |
| 304 | +CREATE UNIQUE INDEX "listings_upstream_uq" ON "listings" USING btree ("upstream_source","upstream_id");--> statement-breakpoint | |
| 305 | +CREATE INDEX "messages_inquiry_idx" ON "messages" USING btree ("inquiry_id","created_at");--> statement-breakpoint | |
| 306 | +CREATE UNIQUE INDEX "messages_idem_uq" ON "messages" USING btree ("inquiry_id","idempotency_key");--> statement-breakpoint | |
| 307 | +CREATE INDEX "otp_email_idx" ON "otp_challenges" USING btree ("email_normalized","created_at");--> statement-breakpoint | |
| 308 | +CREATE INDEX "otp_expires_idx" ON "otp_challenges" USING btree ("expires_at");--> statement-breakpoint | |
| 309 | +CREATE INDEX "properties_geo_idx" ON "properties" USING btree ("lat","lng");--> statement-breakpoint | |
| 310 | +CREATE INDEX "properties_city_idx" ON "properties" USING btree ("city");--> statement-breakpoint | |
| 311 | +CREATE INDEX "reports_status_idx" ON "reports" USING btree ("status","created_at");--> statement-breakpoint | |
| 312 | +CREATE INDEX "saved_searches_user_idx" ON "saved_searches" USING btree ("user_id");--> statement-breakpoint | |
| 313 | +CREATE UNIQUE INDEX "sessions_token_uq" ON "sessions" USING btree ("token_digest");--> statement-breakpoint | |
| 314 | +CREATE INDEX "sessions_user_idx" ON "sessions" USING btree ("user_id");--> statement-breakpoint | |
| 315 | +CREATE UNIQUE INDEX "users_email_uq" ON "users" USING btree ("email");--> statement-breakpoint | |
| 316 | +CREATE INDEX "viewings_listing_idx" ON "viewing_requests" USING btree ("listing_id");--> statement-breakpoint | |
| 317 | +CREATE INDEX "viewings_requester_idx" ON "viewing_requests" USING btree ("requester_id");--> statement-breakpoint | |
| 318 | +CREATE INDEX "viewings_owner_idx" ON "viewing_requests" USING btree ("owner_id"); | |
| \ No newline at end of file | ||
added
drizzle/meta/0000_snapshot.json
+2377 −0
@@ -0,0 +1,2377 @@ | ||
| 1 | +{ | |
| 2 | + "id": "b60423f1-a506-4cde-9fb2-100a0462ff66", | |
| 3 | + "prevId": "00000000-0000-0000-0000-000000000000", | |
| 4 | + "version": "7", | |
| 5 | + "dialect": "postgresql", | |
| 6 | + "tables": { | |
| 7 | + "public.audit_events": { | |
| 8 | + "name": "audit_events", | |
| 9 | + "schema": "", | |
| 10 | + "columns": { | |
| 11 | + "id": { | |
| 12 | + "name": "id", | |
| 13 | + "type": "bigserial", | |
| 14 | + "primaryKey": true, | |
| 15 | + "notNull": true | |
| 16 | + }, | |
| 17 | + "actor_id": { | |
| 18 | + "name": "actor_id", | |
| 19 | + "type": "uuid", | |
| 20 | + "primaryKey": false, | |
| 21 | + "notNull": false | |
| 22 | + }, | |
| 23 | + "action": { | |
| 24 | + "name": "action", | |
| 25 | + "type": "text", | |
| 26 | + "primaryKey": false, | |
| 27 | + "notNull": true | |
| 28 | + }, | |
| 29 | + "object_type": { | |
| 30 | + "name": "object_type", | |
| 31 | + "type": "text", | |
| 32 | + "primaryKey": false, | |
| 33 | + "notNull": false | |
| 34 | + }, | |
| 35 | + "object_id": { | |
| 36 | + "name": "object_id", | |
| 37 | + "type": "text", | |
| 38 | + "primaryKey": false, | |
| 39 | + "notNull": false | |
| 40 | + }, | |
| 41 | + "ip_hash": { | |
| 42 | + "name": "ip_hash", | |
| 43 | + "type": "text", | |
| 44 | + "primaryKey": false, | |
| 45 | + "notNull": false | |
| 46 | + }, | |
| 47 | + "metadata_redacted": { | |
| 48 | + "name": "metadata_redacted", | |
| 49 | + "type": "jsonb", | |
| 50 | + "primaryKey": false, | |
| 51 | + "notNull": true, | |
| 52 | + "default": "'{}'::jsonb" | |
| 53 | + }, | |
| 54 | + "created_at": { | |
| 55 | + "name": "created_at", | |
| 56 | + "type": "timestamp with time zone", | |
| 57 | + "primaryKey": false, | |
| 58 | + "notNull": true, | |
| 59 | + "default": "now()" | |
| 60 | + } | |
| 61 | + }, | |
| 62 | + "indexes": { | |
| 63 | + "audit_created_idx": { | |
| 64 | + "name": "audit_created_idx", | |
| 65 | + "columns": [ | |
| 66 | + { | |
| 67 | + "expression": "created_at", | |
| 68 | + "isExpression": false, | |
| 69 | + "asc": true, | |
| 70 | + "nulls": "last" | |
| 71 | + } | |
| 72 | + ], | |
| 73 | + "isUnique": false, | |
| 74 | + "concurrently": false, | |
| 75 | + "method": "btree", | |
| 76 | + "with": {} | |
| 77 | + }, | |
| 78 | + "audit_actor_idx": { | |
| 79 | + "name": "audit_actor_idx", | |
| 80 | + "columns": [ | |
| 81 | + { | |
| 82 | + "expression": "actor_id", | |
| 83 | + "isExpression": false, | |
| 84 | + "asc": true, | |
| 85 | + "nulls": "last" | |
| 86 | + } | |
| 87 | + ], | |
| 88 | + "isUnique": false, | |
| 89 | + "concurrently": false, | |
| 90 | + "method": "btree", | |
| 91 | + "with": {} | |
| 92 | + } | |
| 93 | + }, | |
| 94 | + "foreignKeys": {}, | |
| 95 | + "compositePrimaryKeys": {}, | |
| 96 | + "uniqueConstraints": {}, | |
| 97 | + "policies": {}, | |
| 98 | + "checkConstraints": {}, | |
| 99 | + "isRLSEnabled": false | |
| 100 | + }, | |
| 101 | + "public.email_deliveries": { | |
| 102 | + "name": "email_deliveries", | |
| 103 | + "schema": "", | |
| 104 | + "columns": { | |
| 105 | + "id": { | |
| 106 | + "name": "id", | |
| 107 | + "type": "uuid", | |
| 108 | + "primaryKey": true, | |
| 109 | + "notNull": true, | |
| 110 | + "default": "gen_random_uuid()" | |
| 111 | + }, | |
| 112 | + "to_hash": { | |
| 113 | + "name": "to_hash", | |
| 114 | + "type": "text", | |
| 115 | + "primaryKey": false, | |
| 116 | + "notNull": true | |
| 117 | + }, | |
| 118 | + "template": { | |
| 119 | + "name": "template", | |
| 120 | + "type": "text", | |
| 121 | + "primaryKey": false, | |
| 122 | + "notNull": true | |
| 123 | + }, | |
| 124 | + "provider_id": { | |
| 125 | + "name": "provider_id", | |
| 126 | + "type": "text", | |
| 127 | + "primaryKey": false, | |
| 128 | + "notNull": false | |
| 129 | + }, | |
| 130 | + "status": { | |
| 131 | + "name": "status", | |
| 132 | + "type": "text", | |
| 133 | + "primaryKey": false, | |
| 134 | + "notNull": true | |
| 135 | + }, | |
| 136 | + "error": { | |
| 137 | + "name": "error", | |
| 138 | + "type": "text", | |
| 139 | + "primaryKey": false, | |
| 140 | + "notNull": false | |
| 141 | + }, | |
| 142 | + "created_at": { | |
| 143 | + "name": "created_at", | |
| 144 | + "type": "timestamp with time zone", | |
| 145 | + "primaryKey": false, | |
| 146 | + "notNull": true, | |
| 147 | + "default": "now()" | |
| 148 | + } | |
| 149 | + }, | |
| 150 | + "indexes": { | |
| 151 | + "email_deliveries_created_idx": { | |
| 152 | + "name": "email_deliveries_created_idx", | |
| 153 | + "columns": [ | |
| 154 | + { | |
| 155 | + "expression": "created_at", | |
| 156 | + "isExpression": false, | |
| 157 | + "asc": true, | |
| 158 | + "nulls": "last" | |
| 159 | + } | |
| 160 | + ], | |
| 161 | + "isUnique": false, | |
| 162 | + "concurrently": false, | |
| 163 | + "method": "btree", | |
| 164 | + "with": {} | |
| 165 | + } | |
| 166 | + }, | |
| 167 | + "foreignKeys": {}, | |
| 168 | + "compositePrimaryKeys": {}, | |
| 169 | + "uniqueConstraints": {}, | |
| 170 | + "policies": {}, | |
| 171 | + "checkConstraints": {}, | |
| 172 | + "isRLSEnabled": false | |
| 173 | + }, | |
| 174 | + "public.favorites": { | |
| 175 | + "name": "favorites", | |
| 176 | + "schema": "", | |
| 177 | + "columns": { | |
| 178 | + "user_id": { | |
| 179 | + "name": "user_id", | |
| 180 | + "type": "uuid", | |
| 181 | + "primaryKey": false, | |
| 182 | + "notNull": true | |
| 183 | + }, | |
| 184 | + "listing_uid": { | |
| 185 | + "name": "listing_uid", | |
| 186 | + "type": "text", | |
| 187 | + "primaryKey": false, | |
| 188 | + "notNull": true | |
| 189 | + }, | |
| 190 | + "created_at": { | |
| 191 | + "name": "created_at", | |
| 192 | + "type": "timestamp with time zone", | |
| 193 | + "primaryKey": false, | |
| 194 | + "notNull": true, | |
| 195 | + "default": "now()" | |
| 196 | + } | |
| 197 | + }, | |
| 198 | + "indexes": {}, | |
| 199 | + "foreignKeys": { | |
| 200 | + "favorites_user_id_users_id_fk": { | |
| 201 | + "name": "favorites_user_id_users_id_fk", | |
| 202 | + "tableFrom": "favorites", | |
| 203 | + "tableTo": "users", | |
| 204 | + "columnsFrom": [ | |
| 205 | + "user_id" | |
| 206 | + ], | |
| 207 | + "columnsTo": [ | |
| 208 | + "id" | |
| 209 | + ], | |
| 210 | + "onDelete": "cascade", | |
| 211 | + "onUpdate": "no action" | |
| 212 | + } | |
| 213 | + }, | |
| 214 | + "compositePrimaryKeys": { | |
| 215 | + "favorites_user_id_listing_uid_pk": { | |
| 216 | + "name": "favorites_user_id_listing_uid_pk", | |
| 217 | + "columns": [ | |
| 218 | + "user_id", | |
| 219 | + "listing_uid" | |
| 220 | + ] | |
| 221 | + } | |
| 222 | + }, | |
| 223 | + "uniqueConstraints": {}, | |
| 224 | + "policies": {}, | |
| 225 | + "checkConstraints": {}, | |
| 226 | + "isRLSEnabled": false | |
| 227 | + }, | |
| 228 | + "public.inquiries": { | |
| 229 | + "name": "inquiries", | |
| 230 | + "schema": "", | |
| 231 | + "columns": { | |
| 232 | + "id": { | |
| 233 | + "name": "id", | |
| 234 | + "type": "uuid", | |
| 235 | + "primaryKey": true, | |
| 236 | + "notNull": true, | |
| 237 | + "default": "gen_random_uuid()" | |
| 238 | + }, | |
| 239 | + "listing_id": { | |
| 240 | + "name": "listing_id", | |
| 241 | + "type": "uuid", | |
| 242 | + "primaryKey": false, | |
| 243 | + "notNull": true | |
| 244 | + }, | |
| 245 | + "sender_user_id": { | |
| 246 | + "name": "sender_user_id", | |
| 247 | + "type": "uuid", | |
| 248 | + "primaryKey": false, | |
| 249 | + "notNull": true | |
| 250 | + }, | |
| 251 | + "recipient_user_id": { | |
| 252 | + "name": "recipient_user_id", | |
| 253 | + "type": "uuid", | |
| 254 | + "primaryKey": false, | |
| 255 | + "notNull": true | |
| 256 | + }, | |
| 257 | + "category": { | |
| 258 | + "name": "category", | |
| 259 | + "type": "text", | |
| 260 | + "primaryKey": false, | |
| 261 | + "notNull": true, | |
| 262 | + "default": "'INFO'" | |
| 263 | + }, | |
| 264 | + "status": { | |
| 265 | + "name": "status", | |
| 266 | + "type": "text", | |
| 267 | + "primaryKey": false, | |
| 268 | + "notNull": true, | |
| 269 | + "default": "'OPEN'" | |
| 270 | + }, | |
| 271 | + "last_message_at": { | |
| 272 | + "name": "last_message_at", | |
| 273 | + "type": "timestamp with time zone", | |
| 274 | + "primaryKey": false, | |
| 275 | + "notNull": true, | |
| 276 | + "default": "now()" | |
| 277 | + }, | |
| 278 | + "created_at": { | |
| 279 | + "name": "created_at", | |
| 280 | + "type": "timestamp with time zone", | |
| 281 | + "primaryKey": false, | |
| 282 | + "notNull": true, | |
| 283 | + "default": "now()" | |
| 284 | + } | |
| 285 | + }, | |
| 286 | + "indexes": { | |
| 287 | + "inquiries_listing_idx": { | |
| 288 | + "name": "inquiries_listing_idx", | |
| 289 | + "columns": [ | |
| 290 | + { | |
| 291 | + "expression": "listing_id", | |
| 292 | + "isExpression": false, | |
| 293 | + "asc": true, | |
| 294 | + "nulls": "last" | |
| 295 | + } | |
| 296 | + ], | |
| 297 | + "isUnique": false, | |
| 298 | + "concurrently": false, | |
| 299 | + "method": "btree", | |
| 300 | + "with": {} | |
| 301 | + }, | |
| 302 | + "inquiries_sender_idx": { | |
| 303 | + "name": "inquiries_sender_idx", | |
| 304 | + "columns": [ | |
| 305 | + { | |
| 306 | + "expression": "sender_user_id", | |
| 307 | + "isExpression": false, | |
| 308 | + "asc": true, | |
| 309 | + "nulls": "last" | |
| 310 | + } | |
| 311 | + ], | |
| 312 | + "isUnique": false, | |
| 313 | + "concurrently": false, | |
| 314 | + "method": "btree", | |
| 315 | + "with": {} | |
| 316 | + }, | |
| 317 | + "inquiries_recipient_idx": { | |
| 318 | + "name": "inquiries_recipient_idx", | |
| 319 | + "columns": [ | |
| 320 | + { | |
| 321 | + "expression": "recipient_user_id", | |
| 322 | + "isExpression": false, | |
| 323 | + "asc": true, | |
| 324 | + "nulls": "last" | |
| 325 | + } | |
| 326 | + ], | |
| 327 | + "isUnique": false, | |
| 328 | + "concurrently": false, | |
| 329 | + "method": "btree", | |
| 330 | + "with": {} | |
| 331 | + }, | |
| 332 | + "inquiries_unique_thread": { | |
| 333 | + "name": "inquiries_unique_thread", | |
| 334 | + "columns": [ | |
| 335 | + { | |
| 336 | + "expression": "listing_id", | |
| 337 | + "isExpression": false, | |
| 338 | + "asc": true, | |
| 339 | + "nulls": "last" | |
| 340 | + }, | |
| 341 | + { | |
| 342 | + "expression": "sender_user_id", | |
| 343 | + "isExpression": false, | |
| 344 | + "asc": true, | |
| 345 | + "nulls": "last" | |
| 346 | + } | |
| 347 | + ], | |
| 348 | + "isUnique": true, | |
| 349 | + "concurrently": false, | |
| 350 | + "method": "btree", | |
| 351 | + "with": {} | |
| 352 | + } | |
| 353 | + }, | |
| 354 | + "foreignKeys": { | |
| 355 | + "inquiries_listing_id_listings_id_fk": { | |
| 356 | + "name": "inquiries_listing_id_listings_id_fk", | |
| 357 | + "tableFrom": "inquiries", | |
| 358 | + "tableTo": "listings", | |
| 359 | + "columnsFrom": [ | |
| 360 | + "listing_id" | |
| 361 | + ], | |
| 362 | + "columnsTo": [ | |
| 363 | + "id" | |
| 364 | + ], | |
| 365 | + "onDelete": "cascade", | |
| 366 | + "onUpdate": "no action" | |
| 367 | + }, | |
| 368 | + "inquiries_sender_user_id_users_id_fk": { | |
| 369 | + "name": "inquiries_sender_user_id_users_id_fk", | |
| 370 | + "tableFrom": "inquiries", | |
| 371 | + "tableTo": "users", | |
| 372 | + "columnsFrom": [ | |
| 373 | + "sender_user_id" | |
| 374 | + ], | |
| 375 | + "columnsTo": [ | |
| 376 | + "id" | |
| 377 | + ], | |
| 378 | + "onDelete": "cascade", | |
| 379 | + "onUpdate": "no action" | |
| 380 | + }, | |
| 381 | + "inquiries_recipient_user_id_users_id_fk": { | |
| 382 | + "name": "inquiries_recipient_user_id_users_id_fk", | |
| 383 | + "tableFrom": "inquiries", | |
| 384 | + "tableTo": "users", | |
| 385 | + "columnsFrom": [ | |
| 386 | + "recipient_user_id" | |
| 387 | + ], | |
| 388 | + "columnsTo": [ | |
| 389 | + "id" | |
| 390 | + ], | |
| 391 | + "onDelete": "cascade", | |
| 392 | + "onUpdate": "no action" | |
| 393 | + } | |
| 394 | + }, | |
| 395 | + "compositePrimaryKeys": {}, | |
| 396 | + "uniqueConstraints": {}, | |
| 397 | + "policies": {}, | |
| 398 | + "checkConstraints": {}, | |
| 399 | + "isRLSEnabled": false | |
| 400 | + }, | |
| 401 | + "public.listing_claims": { | |
| 402 | + "name": "listing_claims", | |
| 403 | + "schema": "", | |
| 404 | + "columns": { | |
| 405 | + "id": { | |
| 406 | + "name": "id", | |
| 407 | + "type": "uuid", | |
| 408 | + "primaryKey": true, | |
| 409 | + "notNull": true, | |
| 410 | + "default": "gen_random_uuid()" | |
| 411 | + }, | |
| 412 | + "aggregated_uid": { | |
| 413 | + "name": "aggregated_uid", | |
| 414 | + "type": "text", | |
| 415 | + "primaryKey": false, | |
| 416 | + "notNull": true | |
| 417 | + }, | |
| 418 | + "requester_id": { | |
| 419 | + "name": "requester_id", | |
| 420 | + "type": "uuid", | |
| 421 | + "primaryKey": false, | |
| 422 | + "notNull": true | |
| 423 | + }, | |
| 424 | + "status": { | |
| 425 | + "name": "status", | |
| 426 | + "type": "text", | |
| 427 | + "primaryKey": false, | |
| 428 | + "notNull": true, | |
| 429 | + "default": "'PENDING'" | |
| 430 | + }, | |
| 431 | + "message": { | |
| 432 | + "name": "message", | |
| 433 | + "type": "text", | |
| 434 | + "primaryKey": false, | |
| 435 | + "notNull": false | |
| 436 | + }, | |
| 437 | + "proof_keys": { | |
| 438 | + "name": "proof_keys", | |
| 439 | + "type": "jsonb", | |
| 440 | + "primaryKey": false, | |
| 441 | + "notNull": true, | |
| 442 | + "default": "'[]'::jsonb" | |
| 443 | + }, | |
| 444 | + "reviewed_by": { | |
| 445 | + "name": "reviewed_by", | |
| 446 | + "type": "uuid", | |
| 447 | + "primaryKey": false, | |
| 448 | + "notNull": false | |
| 449 | + }, | |
| 450 | + "reviewed_at": { | |
| 451 | + "name": "reviewed_at", | |
| 452 | + "type": "timestamp with time zone", | |
| 453 | + "primaryKey": false, | |
| 454 | + "notNull": false | |
| 455 | + }, | |
| 456 | + "created_at": { | |
| 457 | + "name": "created_at", | |
| 458 | + "type": "timestamp with time zone", | |
| 459 | + "primaryKey": false, | |
| 460 | + "notNull": true, | |
| 461 | + "default": "now()" | |
| 462 | + } | |
| 463 | + }, | |
| 464 | + "indexes": { | |
| 465 | + "listing_claims_uid_idx": { | |
| 466 | + "name": "listing_claims_uid_idx", | |
| 467 | + "columns": [ | |
| 468 | + { | |
| 469 | + "expression": "aggregated_uid", | |
| 470 | + "isExpression": false, | |
| 471 | + "asc": true, | |
| 472 | + "nulls": "last" | |
| 473 | + } | |
| 474 | + ], | |
| 475 | + "isUnique": false, | |
| 476 | + "concurrently": false, | |
| 477 | + "method": "btree", | |
| 478 | + "with": {} | |
| 479 | + } | |
| 480 | + }, | |
| 481 | + "foreignKeys": { | |
| 482 | + "listing_claims_requester_id_users_id_fk": { | |
| 483 | + "name": "listing_claims_requester_id_users_id_fk", | |
| 484 | + "tableFrom": "listing_claims", | |
| 485 | + "tableTo": "users", | |
| 486 | + "columnsFrom": [ | |
| 487 | + "requester_id" | |
| 488 | + ], | |
| 489 | + "columnsTo": [ | |
| 490 | + "id" | |
| 491 | + ], | |
| 492 | + "onDelete": "cascade", | |
| 493 | + "onUpdate": "no action" | |
| 494 | + } | |
| 495 | + }, | |
| 496 | + "compositePrimaryKeys": {}, | |
| 497 | + "uniqueConstraints": {}, | |
| 498 | + "policies": {}, | |
| 499 | + "checkConstraints": {}, | |
| 500 | + "isRLSEnabled": false | |
| 501 | + }, | |
| 502 | + "public.listing_media": { | |
| 503 | + "name": "listing_media", | |
| 504 | + "schema": "", | |
| 505 | + "columns": { | |
| 506 | + "id": { | |
| 507 | + "name": "id", | |
| 508 | + "type": "uuid", | |
| 509 | + "primaryKey": true, | |
| 510 | + "notNull": true, | |
| 511 | + "default": "gen_random_uuid()" | |
| 512 | + }, | |
| 513 | + "listing_id": { | |
| 514 | + "name": "listing_id", | |
| 515 | + "type": "uuid", | |
| 516 | + "primaryKey": false, | |
| 517 | + "notNull": true | |
| 518 | + }, | |
| 519 | + "storage_key": { | |
| 520 | + "name": "storage_key", | |
| 521 | + "type": "text", | |
| 522 | + "primaryKey": false, | |
| 523 | + "notNull": true | |
| 524 | + }, | |
| 525 | + "media_type": { | |
| 526 | + "name": "media_type", | |
| 527 | + "type": "text", | |
| 528 | + "primaryKey": false, | |
| 529 | + "notNull": true, | |
| 530 | + "default": "'IMAGE'" | |
| 531 | + }, | |
| 532 | + "mime_type": { | |
| 533 | + "name": "mime_type", | |
| 534 | + "type": "text", | |
| 535 | + "primaryKey": false, | |
| 536 | + "notNull": false | |
| 537 | + }, | |
| 538 | + "position": { | |
| 539 | + "name": "position", | |
| 540 | + "type": "integer", | |
| 541 | + "primaryKey": false, | |
| 542 | + "notNull": true, | |
| 543 | + "default": 0 | |
| 544 | + }, | |
| 545 | + "alt_text": { | |
| 546 | + "name": "alt_text", | |
| 547 | + "type": "text", | |
| 548 | + "primaryKey": false, | |
| 549 | + "notNull": false | |
| 550 | + }, | |
| 551 | + "license_status": { | |
| 552 | + "name": "license_status", | |
| 553 | + "type": "text", | |
| 554 | + "primaryKey": false, | |
| 555 | + "notNull": true, | |
| 556 | + "default": "'OWNER_DECLARED'" | |
| 557 | + }, | |
| 558 | + "scan_status": { | |
| 559 | + "name": "scan_status", | |
| 560 | + "type": "text", | |
| 561 | + "primaryKey": false, | |
| 562 | + "notNull": true, | |
| 563 | + "default": "'PENDING'" | |
| 564 | + }, | |
| 565 | + "width": { | |
| 566 | + "name": "width", | |
| 567 | + "type": "integer", | |
| 568 | + "primaryKey": false, | |
| 569 | + "notNull": false | |
| 570 | + }, | |
| 571 | + "height": { | |
| 572 | + "name": "height", | |
| 573 | + "type": "integer", | |
| 574 | + "primaryKey": false, | |
| 575 | + "notNull": false | |
| 576 | + }, | |
| 577 | + "bytes": { | |
| 578 | + "name": "bytes", | |
| 579 | + "type": "integer", | |
| 580 | + "primaryKey": false, | |
| 581 | + "notNull": false | |
| 582 | + }, | |
| 583 | + "status": { | |
| 584 | + "name": "status", | |
| 585 | + "type": "text", | |
| 586 | + "primaryKey": false, | |
| 587 | + "notNull": true, | |
| 588 | + "default": "'UPLOADING'" | |
| 589 | + }, | |
| 590 | + "upload_token": { | |
| 591 | + "name": "upload_token", | |
| 592 | + "type": "text", | |
| 593 | + "primaryKey": false, | |
| 594 | + "notNull": false | |
| 595 | + }, | |
| 596 | + "uploaded_at": { | |
| 597 | + "name": "uploaded_at", | |
| 598 | + "type": "timestamp with time zone", | |
| 599 | + "primaryKey": false, | |
| 600 | + "notNull": true, | |
| 601 | + "default": "now()" | |
| 602 | + } | |
| 603 | + }, | |
| 604 | + "indexes": { | |
| 605 | + "listing_media_listing_idx": { | |
| 606 | + "name": "listing_media_listing_idx", | |
| 607 | + "columns": [ | |
| 608 | + { | |
| 609 | + "expression": "listing_id", | |
| 610 | + "isExpression": false, | |
| 611 | + "asc": true, | |
| 612 | + "nulls": "last" | |
| 613 | + }, | |
| 614 | + { | |
| 615 | + "expression": "position", | |
| 616 | + "isExpression": false, | |
| 617 | + "asc": true, | |
| 618 | + "nulls": "last" | |
| 619 | + } | |
| 620 | + ], | |
| 621 | + "isUnique": false, | |
| 622 | + "concurrently": false, | |
| 623 | + "method": "btree", | |
| 624 | + "with": {} | |
| 625 | + } | |
| 626 | + }, | |
| 627 | + "foreignKeys": { | |
| 628 | + "listing_media_listing_id_listings_id_fk": { | |
| 629 | + "name": "listing_media_listing_id_listings_id_fk", | |
| 630 | + "tableFrom": "listing_media", | |
| 631 | + "tableTo": "listings", | |
| 632 | + "columnsFrom": [ | |
| 633 | + "listing_id" | |
| 634 | + ], | |
| 635 | + "columnsTo": [ | |
| 636 | + "id" | |
| 637 | + ], | |
| 638 | + "onDelete": "cascade", | |
| 639 | + "onUpdate": "no action" | |
| 640 | + } | |
| 641 | + }, | |
| 642 | + "compositePrimaryKeys": {}, | |
| 643 | + "uniqueConstraints": {}, | |
| 644 | + "policies": {}, | |
| 645 | + "checkConstraints": {}, | |
| 646 | + "isRLSEnabled": false | |
| 647 | + }, | |
| 648 | + "public.listing_versions": { | |
| 649 | + "name": "listing_versions", | |
| 650 | + "schema": "", | |
| 651 | + "columns": { | |
| 652 | + "id": { | |
| 653 | + "name": "id", | |
| 654 | + "type": "bigserial", | |
| 655 | + "primaryKey": true, | |
| 656 | + "notNull": true | |
| 657 | + }, | |
| 658 | + "listing_id": { | |
| 659 | + "name": "listing_id", | |
| 660 | + "type": "uuid", | |
| 661 | + "primaryKey": false, | |
| 662 | + "notNull": true | |
| 663 | + }, | |
| 664 | + "version": { | |
| 665 | + "name": "version", | |
| 666 | + "type": "integer", | |
| 667 | + "primaryKey": false, | |
| 668 | + "notNull": true | |
| 669 | + }, | |
| 670 | + "actor_id": { | |
| 671 | + "name": "actor_id", | |
| 672 | + "type": "uuid", | |
| 673 | + "primaryKey": false, | |
| 674 | + "notNull": false | |
| 675 | + }, | |
| 676 | + "snapshot": { | |
| 677 | + "name": "snapshot", | |
| 678 | + "type": "jsonb", | |
| 679 | + "primaryKey": false, | |
| 680 | + "notNull": true | |
| 681 | + }, | |
| 682 | + "created_at": { | |
| 683 | + "name": "created_at", | |
| 684 | + "type": "timestamp with time zone", | |
| 685 | + "primaryKey": false, | |
| 686 | + "notNull": true, | |
| 687 | + "default": "now()" | |
| 688 | + } | |
| 689 | + }, | |
| 690 | + "indexes": { | |
| 691 | + "listing_versions_listing_idx": { | |
| 692 | + "name": "listing_versions_listing_idx", | |
| 693 | + "columns": [ | |
| 694 | + { | |
| 695 | + "expression": "listing_id", | |
| 696 | + "isExpression": false, | |
| 697 | + "asc": true, | |
| 698 | + "nulls": "last" | |
| 699 | + }, | |
| 700 | + { | |
| 701 | + "expression": "version", | |
| 702 | + "isExpression": false, | |
| 703 | + "asc": true, | |
| 704 | + "nulls": "last" | |
| 705 | + } | |
| 706 | + ], | |
| 707 | + "isUnique": false, | |
| 708 | + "concurrently": false, | |
| 709 | + "method": "btree", | |
| 710 | + "with": {} | |
| 711 | + } | |
| 712 | + }, | |
| 713 | + "foreignKeys": { | |
| 714 | + "listing_versions_listing_id_listings_id_fk": { | |
| 715 | + "name": "listing_versions_listing_id_listings_id_fk", | |
| 716 | + "tableFrom": "listing_versions", | |
| 717 | + "tableTo": "listings", | |
| 718 | + "columnsFrom": [ | |
| 719 | + "listing_id" | |
| 720 | + ], | |
| 721 | + "columnsTo": [ | |
| 722 | + "id" | |
| 723 | + ], | |
| 724 | + "onDelete": "cascade", | |
| 725 | + "onUpdate": "no action" | |
| 726 | + } | |
| 727 | + }, | |
| 728 | + "compositePrimaryKeys": {}, | |
| 729 | + "uniqueConstraints": {}, | |
| 730 | + "policies": {}, | |
| 731 | + "checkConstraints": {}, | |
| 732 | + "isRLSEnabled": false | |
| 733 | + }, | |
| 734 | + "public.listings": { | |
| 735 | + "name": "listings", | |
| 736 | + "schema": "", | |
| 737 | + "columns": { | |
| 738 | + "id": { | |
| 739 | + "name": "id", | |
| 740 | + "type": "uuid", | |
| 741 | + "primaryKey": true, | |
| 742 | + "notNull": true, | |
| 743 | + "default": "gen_random_uuid()" | |
| 744 | + }, | |
| 745 | + "slug": { | |
| 746 | + "name": "slug", | |
| 747 | + "type": "text", | |
| 748 | + "primaryKey": false, | |
| 749 | + "notNull": true | |
| 750 | + }, | |
| 751 | + "property_id": { | |
| 752 | + "name": "property_id", | |
| 753 | + "type": "uuid", | |
| 754 | + "primaryKey": false, | |
| 755 | + "notNull": true | |
| 756 | + }, | |
| 757 | + "owner_user_id": { | |
| 758 | + "name": "owner_user_id", | |
| 759 | + "type": "uuid", | |
| 760 | + "primaryKey": false, | |
| 761 | + "notNull": false | |
| 762 | + }, | |
| 763 | + "listing_type": { | |
| 764 | + "name": "listing_type", | |
| 765 | + "type": "text", | |
| 766 | + "primaryKey": false, | |
| 767 | + "notNull": true | |
| 768 | + }, | |
| 769 | + "source_type": { | |
| 770 | + "name": "source_type", | |
| 771 | + "type": "text", | |
| 772 | + "primaryKey": false, | |
| 773 | + "notNull": true, | |
| 774 | + "default": "'DIRECT_OWNER'" | |
| 775 | + }, | |
| 776 | + "status": { | |
| 777 | + "name": "status", | |
| 778 | + "type": "text", | |
| 779 | + "primaryKey": false, | |
| 780 | + "notNull": true, | |
| 781 | + "default": "'DRAFT'" | |
| 782 | + }, | |
| 783 | + "title": { | |
| 784 | + "name": "title", | |
| 785 | + "type": "text", | |
| 786 | + "primaryKey": false, | |
| 787 | + "notNull": false | |
| 788 | + }, | |
| 789 | + "description": { | |
| 790 | + "name": "description", | |
| 791 | + "type": "text", | |
| 792 | + "primaryKey": false, | |
| 793 | + "notNull": false | |
| 794 | + }, | |
| 795 | + "price_amount": { | |
| 796 | + "name": "price_amount", | |
| 797 | + "type": "numeric(12, 2)", | |
| 798 | + "primaryKey": false, | |
| 799 | + "notNull": false | |
| 800 | + }, | |
| 801 | + "currency": { | |
| 802 | + "name": "currency", | |
| 803 | + "type": "text", | |
| 804 | + "primaryKey": false, | |
| 805 | + "notNull": true, | |
| 806 | + "default": "'CAD'" | |
| 807 | + }, | |
| 808 | + "rental_period": { | |
| 809 | + "name": "rental_period", | |
| 810 | + "type": "text", | |
| 811 | + "primaryKey": false, | |
| 812 | + "notNull": false | |
| 813 | + }, | |
| 814 | + "availability_date": { | |
| 815 | + "name": "availability_date", | |
| 816 | + "type": "text", | |
| 817 | + "primaryKey": false, | |
| 818 | + "notNull": false | |
| 819 | + }, | |
| 820 | + "furnished": { | |
| 821 | + "name": "furnished", | |
| 822 | + "type": "boolean", | |
| 823 | + "primaryKey": false, | |
| 824 | + "notNull": false | |
| 825 | + }, | |
| 826 | + "pets_policy": { | |
| 827 | + "name": "pets_policy", | |
| 828 | + "type": "text", | |
| 829 | + "primaryKey": false, | |
| 830 | + "notNull": false | |
| 831 | + }, | |
| 832 | + "smoking_policy": { | |
| 833 | + "name": "smoking_policy", | |
| 834 | + "type": "text", | |
| 835 | + "primaryKey": false, | |
| 836 | + "notNull": false | |
| 837 | + }, | |
| 838 | + "parking": { | |
| 839 | + "name": "parking", | |
| 840 | + "type": "text", | |
| 841 | + "primaryKey": false, | |
| 842 | + "notNull": false | |
| 843 | + }, | |
| 844 | + "lease_terms": { | |
| 845 | + "name": "lease_terms", | |
| 846 | + "type": "text", | |
| 847 | + "primaryKey": false, | |
| 848 | + "notNull": false | |
| 849 | + }, | |
| 850 | + "inclusions": { | |
| 851 | + "name": "inclusions", | |
| 852 | + "type": "jsonb", | |
| 853 | + "primaryKey": false, | |
| 854 | + "notNull": true, | |
| 855 | + "default": "'[]'::jsonb" | |
| 856 | + }, | |
| 857 | + "features": { | |
| 858 | + "name": "features", | |
| 859 | + "type": "jsonb", | |
| 860 | + "primaryKey": false, | |
| 861 | + "notNull": true, | |
| 862 | + "default": "'{}'::jsonb" | |
| 863 | + }, | |
| 864 | + "publication_at": { | |
| 865 | + "name": "publication_at", | |
| 866 | + "type": "timestamp with time zone", | |
| 867 | + "primaryKey": false, | |
| 868 | + "notNull": false | |
| 869 | + }, | |
| 870 | + "expires_at": { | |
| 871 | + "name": "expires_at", | |
| 872 | + "type": "timestamp with time zone", | |
| 873 | + "primaryKey": false, | |
| 874 | + "notNull": false | |
| 875 | + }, | |
| 876 | + "moderation_state": { | |
| 877 | + "name": "moderation_state", | |
| 878 | + "type": "text", | |
| 879 | + "primaryKey": false, | |
| 880 | + "notNull": true, | |
| 881 | + "default": "'NONE'" | |
| 882 | + }, | |
| 883 | + "moderation_note": { | |
| 884 | + "name": "moderation_note", | |
| 885 | + "type": "text", | |
| 886 | + "primaryKey": false, | |
| 887 | + "notNull": false | |
| 888 | + }, | |
| 889 | + "external_url": { | |
| 890 | + "name": "external_url", | |
| 891 | + "type": "text", | |
| 892 | + "primaryKey": false, | |
| 893 | + "notNull": false | |
| 894 | + }, | |
| 895 | + "upstream_source": { | |
| 896 | + "name": "upstream_source", | |
| 897 | + "type": "text", | |
| 898 | + "primaryKey": false, | |
| 899 | + "notNull": false | |
| 900 | + }, | |
| 901 | + "upstream_id": { | |
| 902 | + "name": "upstream_id", | |
| 903 | + "type": "text", | |
| 904 | + "primaryKey": false, | |
| 905 | + "notNull": false | |
| 906 | + }, | |
| 907 | + "upstream_sync_at": { | |
| 908 | + "name": "upstream_sync_at", | |
| 909 | + "type": "timestamp with time zone", | |
| 910 | + "primaryKey": false, | |
| 911 | + "notNull": false | |
| 912 | + }, | |
| 913 | + "wizard_step": { | |
| 914 | + "name": "wizard_step", | |
| 915 | + "type": "integer", | |
| 916 | + "primaryKey": false, | |
| 917 | + "notNull": true, | |
| 918 | + "default": 0 | |
| 919 | + }, | |
| 920 | + "attested_at": { | |
| 921 | + "name": "attested_at", | |
| 922 | + "type": "timestamp with time zone", | |
| 923 | + "primaryKey": false, | |
| 924 | + "notNull": false | |
| 925 | + }, | |
| 926 | + "version": { | |
| 927 | + "name": "version", | |
| 928 | + "type": "integer", | |
| 929 | + "primaryKey": false, | |
| 930 | + "notNull": true, | |
| 931 | + "default": 1 | |
| 932 | + }, | |
| 933 | + "views_count": { | |
| 934 | + "name": "views_count", | |
| 935 | + "type": "integer", | |
| 936 | + "primaryKey": false, | |
| 937 | + "notNull": true, | |
| 938 | + "default": 0 | |
| 939 | + }, | |
| 940 | + "created_at": { | |
| 941 | + "name": "created_at", | |
| 942 | + "type": "timestamp with time zone", | |
| 943 | + "primaryKey": false, | |
| 944 | + "notNull": true, | |
| 945 | + "default": "now()" | |
| 946 | + }, | |
| 947 | + "updated_at": { | |
| 948 | + "name": "updated_at", | |
| 949 | + "type": "timestamp with time zone", | |
| 950 | + "primaryKey": false, | |
| 951 | + "notNull": true, | |
| 952 | + "default": "now()" | |
| 953 | + } | |
| 954 | + }, | |
| 955 | + "indexes": { | |
| 956 | + "listings_slug_uq": { | |
| 957 | + "name": "listings_slug_uq", | |
| 958 | + "columns": [ | |
| 959 | + { | |
| 960 | + "expression": "slug", | |
| 961 | + "isExpression": false, | |
| 962 | + "asc": true, | |
| 963 | + "nulls": "last" | |
| 964 | + } | |
| 965 | + ], | |
| 966 | + "isUnique": true, | |
| 967 | + "concurrently": false, | |
| 968 | + "method": "btree", | |
| 969 | + "with": {} | |
| 970 | + }, | |
| 971 | + "listings_status_idx": { | |
| 972 | + "name": "listings_status_idx", | |
| 973 | + "columns": [ | |
| 974 | + { | |
| 975 | + "expression": "status", | |
| 976 | + "isExpression": false, | |
| 977 | + "asc": true, | |
| 978 | + "nulls": "last" | |
| 979 | + }, | |
| 980 | + { | |
| 981 | + "expression": "listing_type", | |
| 982 | + "isExpression": false, | |
| 983 | + "asc": true, | |
| 984 | + "nulls": "last" | |
| 985 | + }, | |
| 986 | + { | |
| 987 | + "expression": "publication_at", | |
| 988 | + "isExpression": false, | |
| 989 | + "asc": true, | |
| 990 | + "nulls": "last" | |
| 991 | + } | |
| 992 | + ], | |
| 993 | + "isUnique": false, | |
| 994 | + "concurrently": false, | |
| 995 | + "method": "btree", | |
| 996 | + "with": {} | |
| 997 | + }, | |
| 998 | + "listings_owner_idx": { | |
| 999 | + "name": "listings_owner_idx", | |
| 1000 | + "columns": [ | |
| 1001 | + { | |
| 1002 | + "expression": "owner_user_id", | |
| 1003 | + "isExpression": false, | |
| 1004 | + "asc": true, | |
| 1005 | + "nulls": "last" | |
| 1006 | + } | |
| 1007 | + ], | |
| 1008 | + "isUnique": false, | |
| 1009 | + "concurrently": false, | |
| 1010 | + "method": "btree", | |
| 1011 | + "with": {} | |
| 1012 | + }, | |
| 1013 | + "listings_upstream_uq": { | |
| 1014 | + "name": "listings_upstream_uq", | |
| 1015 | + "columns": [ | |
| 1016 | + { | |
| 1017 | + "expression": "upstream_source", | |
| 1018 | + "isExpression": false, | |
| 1019 | + "asc": true, | |
| 1020 | + "nulls": "last" | |
| 1021 | + }, | |
| 1022 | + { | |
| 1023 | + "expression": "upstream_id", | |
| 1024 | + "isExpression": false, | |
| 1025 | + "asc": true, | |
| 1026 | + "nulls": "last" | |
| 1027 | + } | |
| 1028 | + ], | |
| 1029 | + "isUnique": true, | |
| 1030 | + "concurrently": false, | |
| 1031 | + "method": "btree", | |
| 1032 | + "with": {} | |
| 1033 | + } | |
| 1034 | + }, | |
| 1035 | + "foreignKeys": { | |
| 1036 | + "listings_property_id_properties_id_fk": { | |
| 1037 | + "name": "listings_property_id_properties_id_fk", | |
| 1038 | + "tableFrom": "listings", | |
| 1039 | + "tableTo": "properties", | |
| 1040 | + "columnsFrom": [ | |
| 1041 | + "property_id" | |
| 1042 | + ], | |
| 1043 | + "columnsTo": [ | |
| 1044 | + "id" | |
| 1045 | + ], | |
| 1046 | + "onDelete": "restrict", | |
| 1047 | + "onUpdate": "no action" | |
| 1048 | + }, | |
| 1049 | + "listings_owner_user_id_users_id_fk": { | |
| 1050 | + "name": "listings_owner_user_id_users_id_fk", | |
| 1051 | + "tableFrom": "listings", | |
| 1052 | + "tableTo": "users", | |
| 1053 | + "columnsFrom": [ | |
| 1054 | + "owner_user_id" | |
| 1055 | + ], | |
| 1056 | + "columnsTo": [ | |
| 1057 | + "id" | |
| 1058 | + ], | |
| 1059 | + "onDelete": "set null", | |
| 1060 | + "onUpdate": "no action" | |
| 1061 | + } | |
| 1062 | + }, | |
| 1063 | + "compositePrimaryKeys": {}, | |
| 1064 | + "uniqueConstraints": {}, | |
| 1065 | + "policies": {}, | |
| 1066 | + "checkConstraints": { | |
| 1067 | + "listings_type_ck": { | |
| 1068 | + "name": "listings_type_ck", | |
| 1069 | + "value": "\"listings\".\"listing_type\" in ('SALE','RENT')" | |
| 1070 | + }, | |
| 1071 | + "listings_source_ck": { | |
| 1072 | + "name": "listings_source_ck", | |
| 1073 | + "value": "\"listings\".\"source_type\" in ('DIRECT_OWNER','DIRECT_AGENT','AGGREGATED_PARTNER')" | |
| 1074 | + }, | |
| 1075 | + "listings_status_ck": { | |
| 1076 | + "name": "listings_status_ck", | |
| 1077 | + "value": "\"listings\".\"status\" in ('DRAFT','PENDING_REVIEW','PUBLISHED','PAUSED','REJECTED','ARCHIVED','SOLD','RENTED')" | |
| 1078 | + }, | |
| 1079 | + "listings_price_ck": { | |
| 1080 | + "name": "listings_price_ck", | |
| 1081 | + "value": "\"listings\".\"price_amount\" is null or \"listings\".\"price_amount\" >= 0" | |
| 1082 | + }, | |
| 1083 | + "listings_owner_direct_ck": { | |
| 1084 | + "name": "listings_owner_direct_ck", | |
| 1085 | + "value": "\"listings\".\"source_type\" = 'AGGREGATED_PARTNER' or \"listings\".\"owner_user_id\" is not null" | |
| 1086 | + }, | |
| 1087 | + "listings_rent_period_ck": { | |
| 1088 | + "name": "listings_rent_period_ck", | |
| 1089 | + "value": "\"listings\".\"listing_type\" <> 'RENT' or \"listings\".\"rental_period\" is null or \"listings\".\"rental_period\" in ('MONTH','WEEK')" | |
| 1090 | + } | |
| 1091 | + }, | |
| 1092 | + "isRLSEnabled": false | |
| 1093 | + }, | |
| 1094 | + "public.messages": { | |
| 1095 | + "name": "messages", | |
| 1096 | + "schema": "", | |
| 1097 | + "columns": { | |
| 1098 | + "id": { | |
| 1099 | + "name": "id", | |
| 1100 | + "type": "uuid", | |
| 1101 | + "primaryKey": true, | |
| 1102 | + "notNull": true, | |
| 1103 | + "default": "gen_random_uuid()" | |
| 1104 | + }, | |
| 1105 | + "inquiry_id": { | |
| 1106 | + "name": "inquiry_id", | |
| 1107 | + "type": "uuid", | |
| 1108 | + "primaryKey": false, | |
| 1109 | + "notNull": true | |
| 1110 | + }, | |
| 1111 | + "sender_id": { | |
| 1112 | + "name": "sender_id", | |
| 1113 | + "type": "uuid", | |
| 1114 | + "primaryKey": false, | |
| 1115 | + "notNull": true | |
| 1116 | + }, | |
| 1117 | + "body": { | |
| 1118 | + "name": "body", | |
| 1119 | + "type": "text", | |
| 1120 | + "primaryKey": false, | |
| 1121 | + "notNull": true | |
| 1122 | + }, | |
| 1123 | + "idempotency_key": { | |
| 1124 | + "name": "idempotency_key", | |
| 1125 | + "type": "text", | |
| 1126 | + "primaryKey": false, | |
| 1127 | + "notNull": false | |
| 1128 | + }, | |
| 1129 | + "created_at": { | |
| 1130 | + "name": "created_at", | |
| 1131 | + "type": "timestamp with time zone", | |
| 1132 | + "primaryKey": false, | |
| 1133 | + "notNull": true, | |
| 1134 | + "default": "now()" | |
| 1135 | + }, | |
| 1136 | + "read_at": { | |
| 1137 | + "name": "read_at", | |
| 1138 | + "type": "timestamp with time zone", | |
| 1139 | + "primaryKey": false, | |
| 1140 | + "notNull": false | |
| 1141 | + } | |
| 1142 | + }, | |
| 1143 | + "indexes": { | |
| 1144 | + "messages_inquiry_idx": { | |
| 1145 | + "name": "messages_inquiry_idx", | |
| 1146 | + "columns": [ | |
| 1147 | + { | |
| 1148 | + "expression": "inquiry_id", | |
| 1149 | + "isExpression": false, | |
| 1150 | + "asc": true, | |
| 1151 | + "nulls": "last" | |
| 1152 | + }, | |
| 1153 | + { | |
| 1154 | + "expression": "created_at", | |
| 1155 | + "isExpression": false, | |
| 1156 | + "asc": true, | |
| 1157 | + "nulls": "last" | |
| 1158 | + } | |
| 1159 | + ], | |
| 1160 | + "isUnique": false, | |
| 1161 | + "concurrently": false, | |
| 1162 | + "method": "btree", | |
| 1163 | + "with": {} | |
| 1164 | + }, | |
| 1165 | + "messages_idem_uq": { | |
| 1166 | + "name": "messages_idem_uq", | |
| 1167 | + "columns": [ | |
| 1168 | + { | |
| 1169 | + "expression": "inquiry_id", | |
| 1170 | + "isExpression": false, | |
| 1171 | + "asc": true, | |
| 1172 | + "nulls": "last" | |
| 1173 | + }, | |
| 1174 | + { | |
| 1175 | + "expression": "idempotency_key", | |
| 1176 | + "isExpression": false, | |
| 1177 | + "asc": true, | |
| 1178 | + "nulls": "last" | |
| 1179 | + } | |
| 1180 | + ], | |
| 1181 | + "isUnique": true, | |
| 1182 | + "concurrently": false, | |
| 1183 | + "method": "btree", | |
| 1184 | + "with": {} | |
| 1185 | + } | |
| 1186 | + }, | |
| 1187 | + "foreignKeys": { | |
| 1188 | + "messages_inquiry_id_inquiries_id_fk": { | |
| 1189 | + "name": "messages_inquiry_id_inquiries_id_fk", | |
| 1190 | + "tableFrom": "messages", | |
| 1191 | + "tableTo": "inquiries", | |
| 1192 | + "columnsFrom": [ | |
| 1193 | + "inquiry_id" | |
| 1194 | + ], | |
| 1195 | + "columnsTo": [ | |
| 1196 | + "id" | |
| 1197 | + ], | |
| 1198 | + "onDelete": "cascade", | |
| 1199 | + "onUpdate": "no action" | |
| 1200 | + }, | |
| 1201 | + "messages_sender_id_users_id_fk": { | |
| 1202 | + "name": "messages_sender_id_users_id_fk", | |
| 1203 | + "tableFrom": "messages", | |
| 1204 | + "tableTo": "users", | |
| 1205 | + "columnsFrom": [ | |
| 1206 | + "sender_id" | |
| 1207 | + ], | |
| 1208 | + "columnsTo": [ | |
| 1209 | + "id" | |
| 1210 | + ], | |
| 1211 | + "onDelete": "cascade", | |
| 1212 | + "onUpdate": "no action" | |
| 1213 | + } | |
| 1214 | + }, | |
| 1215 | + "compositePrimaryKeys": {}, | |
| 1216 | + "uniqueConstraints": {}, | |
| 1217 | + "policies": {}, | |
| 1218 | + "checkConstraints": {}, | |
| 1219 | + "isRLSEnabled": false | |
| 1220 | + }, | |
| 1221 | + "public.moderation_actions": { | |
| 1222 | + "name": "moderation_actions", | |
| 1223 | + "schema": "", | |
| 1224 | + "columns": { | |
| 1225 | + "id": { | |
| 1226 | + "name": "id", | |
| 1227 | + "type": "bigserial", | |
| 1228 | + "primaryKey": true, | |
| 1229 | + "notNull": true | |
| 1230 | + }, | |
| 1231 | + "actor_id": { | |
| 1232 | + "name": "actor_id", | |
| 1233 | + "type": "uuid", | |
| 1234 | + "primaryKey": false, | |
| 1235 | + "notNull": true | |
| 1236 | + }, | |
| 1237 | + "target_type": { | |
| 1238 | + "name": "target_type", | |
| 1239 | + "type": "text", | |
| 1240 | + "primaryKey": false, | |
| 1241 | + "notNull": true | |
| 1242 | + }, | |
| 1243 | + "target_id": { | |
| 1244 | + "name": "target_id", | |
| 1245 | + "type": "text", | |
| 1246 | + "primaryKey": false, | |
| 1247 | + "notNull": true | |
| 1248 | + }, | |
| 1249 | + "action": { | |
| 1250 | + "name": "action", | |
| 1251 | + "type": "text", | |
| 1252 | + "primaryKey": false, | |
| 1253 | + "notNull": true | |
| 1254 | + }, | |
| 1255 | + "note": { | |
| 1256 | + "name": "note", | |
| 1257 | + "type": "text", | |
| 1258 | + "primaryKey": false, | |
| 1259 | + "notNull": false | |
| 1260 | + }, | |
| 1261 | + "created_at": { | |
| 1262 | + "name": "created_at", | |
| 1263 | + "type": "timestamp with time zone", | |
| 1264 | + "primaryKey": false, | |
| 1265 | + "notNull": true, | |
| 1266 | + "default": "now()" | |
| 1267 | + } | |
| 1268 | + }, | |
| 1269 | + "indexes": {}, | |
| 1270 | + "foreignKeys": {}, | |
| 1271 | + "compositePrimaryKeys": {}, | |
| 1272 | + "uniqueConstraints": {}, | |
| 1273 | + "policies": {}, | |
| 1274 | + "checkConstraints": {}, | |
| 1275 | + "isRLSEnabled": false | |
| 1276 | + }, | |
| 1277 | + "public.otp_challenges": { | |
| 1278 | + "name": "otp_challenges", | |
| 1279 | + "schema": "", | |
| 1280 | + "columns": { | |
| 1281 | + "id": { | |
| 1282 | + "name": "id", | |
| 1283 | + "type": "uuid", | |
| 1284 | + "primaryKey": true, | |
| 1285 | + "notNull": true, | |
| 1286 | + "default": "gen_random_uuid()" | |
| 1287 | + }, | |
| 1288 | + "email_normalized": { | |
| 1289 | + "name": "email_normalized", | |
| 1290 | + "type": "text", | |
| 1291 | + "primaryKey": false, | |
| 1292 | + "notNull": true | |
| 1293 | + }, | |
| 1294 | + "purpose": { | |
| 1295 | + "name": "purpose", | |
| 1296 | + "type": "text", | |
| 1297 | + "primaryKey": false, | |
| 1298 | + "notNull": true, | |
| 1299 | + "default": "'LOGIN'" | |
| 1300 | + }, | |
| 1301 | + "secret_digest": { | |
| 1302 | + "name": "secret_digest", | |
| 1303 | + "type": "text", | |
| 1304 | + "primaryKey": false, | |
| 1305 | + "notNull": true | |
| 1306 | + }, | |
| 1307 | + "expires_at": { | |
| 1308 | + "name": "expires_at", | |
| 1309 | + "type": "timestamp with time zone", | |
| 1310 | + "primaryKey": false, | |
| 1311 | + "notNull": true | |
| 1312 | + }, | |
| 1313 | + "attempts": { | |
| 1314 | + "name": "attempts", | |
| 1315 | + "type": "integer", | |
| 1316 | + "primaryKey": false, | |
| 1317 | + "notNull": true, | |
| 1318 | + "default": 0 | |
| 1319 | + }, | |
| 1320 | + "consumed_at": { | |
| 1321 | + "name": "consumed_at", | |
| 1322 | + "type": "timestamp with time zone", | |
| 1323 | + "primaryKey": false, | |
| 1324 | + "notNull": false | |
| 1325 | + }, | |
| 1326 | + "requested_ip_hash": { | |
| 1327 | + "name": "requested_ip_hash", | |
| 1328 | + "type": "text", | |
| 1329 | + "primaryKey": false, | |
| 1330 | + "notNull": false | |
| 1331 | + }, | |
| 1332 | + "meta": { | |
| 1333 | + "name": "meta", | |
| 1334 | + "type": "jsonb", | |
| 1335 | + "primaryKey": false, | |
| 1336 | + "notNull": true, | |
| 1337 | + "default": "'{}'::jsonb" | |
| 1338 | + }, | |
| 1339 | + "created_at": { | |
| 1340 | + "name": "created_at", | |
| 1341 | + "type": "timestamp with time zone", | |
| 1342 | + "primaryKey": false, | |
| 1343 | + "notNull": true, | |
| 1344 | + "default": "now()" | |
| 1345 | + } | |
| 1346 | + }, | |
| 1347 | + "indexes": { | |
| 1348 | + "otp_email_idx": { | |
| 1349 | + "name": "otp_email_idx", | |
| 1350 | + "columns": [ | |
| 1351 | + { | |
| 1352 | + "expression": "email_normalized", | |
| 1353 | + "isExpression": false, | |
| 1354 | + "asc": true, | |
| 1355 | + "nulls": "last" | |
| 1356 | + }, | |
| 1357 | + { | |
| 1358 | + "expression": "created_at", | |
| 1359 | + "isExpression": false, | |
| 1360 | + "asc": true, | |
| 1361 | + "nulls": "last" | |
| 1362 | + } | |
| 1363 | + ], | |
| 1364 | + "isUnique": false, | |
| 1365 | + "concurrently": false, | |
| 1366 | + "method": "btree", | |
| 1367 | + "with": {} | |
| 1368 | + }, | |
| 1369 | + "otp_expires_idx": { | |
| 1370 | + "name": "otp_expires_idx", | |
| 1371 | + "columns": [ | |
| 1372 | + { | |
| 1373 | + "expression": "expires_at", | |
| 1374 | + "isExpression": false, | |
| 1375 | + "asc": true, | |
| 1376 | + "nulls": "last" | |
| 1377 | + } | |
| 1378 | + ], | |
| 1379 | + "isUnique": false, | |
| 1380 | + "concurrently": false, | |
| 1381 | + "method": "btree", | |
| 1382 | + "with": {} | |
| 1383 | + } | |
| 1384 | + }, | |
| 1385 | + "foreignKeys": {}, | |
| 1386 | + "compositePrimaryKeys": {}, | |
| 1387 | + "uniqueConstraints": {}, | |
| 1388 | + "policies": {}, | |
| 1389 | + "checkConstraints": {}, | |
| 1390 | + "isRLSEnabled": false | |
| 1391 | + }, | |
| 1392 | + "public.properties": { | |
| 1393 | + "name": "properties", | |
| 1394 | + "schema": "", | |
| 1395 | + "columns": { | |
| 1396 | + "id": { | |
| 1397 | + "name": "id", | |
| 1398 | + "type": "uuid", | |
| 1399 | + "primaryKey": true, | |
| 1400 | + "notNull": true, | |
| 1401 | + "default": "gen_random_uuid()" | |
| 1402 | + }, | |
| 1403 | + "canonical_address": { | |
| 1404 | + "name": "canonical_address", | |
| 1405 | + "type": "text", | |
| 1406 | + "primaryKey": false, | |
| 1407 | + "notNull": false | |
| 1408 | + }, | |
| 1409 | + "unit": { | |
| 1410 | + "name": "unit", | |
| 1411 | + "type": "text", | |
| 1412 | + "primaryKey": false, | |
| 1413 | + "notNull": false | |
| 1414 | + }, | |
| 1415 | + "city": { | |
| 1416 | + "name": "city", | |
| 1417 | + "type": "text", | |
| 1418 | + "primaryKey": false, | |
| 1419 | + "notNull": false | |
| 1420 | + }, | |
| 1421 | + "region": { | |
| 1422 | + "name": "region", | |
| 1423 | + "type": "text", | |
| 1424 | + "primaryKey": false, | |
| 1425 | + "notNull": false | |
| 1426 | + }, | |
| 1427 | + "postal_code": { | |
| 1428 | + "name": "postal_code", | |
| 1429 | + "type": "text", | |
| 1430 | + "primaryKey": false, | |
| 1431 | + "notNull": false | |
| 1432 | + }, | |
| 1433 | + "lat": { | |
| 1434 | + "name": "lat", | |
| 1435 | + "type": "double precision", | |
| 1436 | + "primaryKey": false, | |
| 1437 | + "notNull": false | |
| 1438 | + }, | |
| 1439 | + "lng": { | |
| 1440 | + "name": "lng", | |
| 1441 | + "type": "double precision", | |
| 1442 | + "primaryKey": false, | |
| 1443 | + "notNull": false | |
| 1444 | + }, | |
| 1445 | + "geographic_precision": { | |
| 1446 | + "name": "geographic_precision", | |
| 1447 | + "type": "text", | |
| 1448 | + "primaryKey": false, | |
| 1449 | + "notNull": true, | |
| 1450 | + "default": "'EXACT'" | |
| 1451 | + }, | |
| 1452 | + "geo_source": { | |
| 1453 | + "name": "geo_source", | |
| 1454 | + "type": "text", | |
| 1455 | + "primaryKey": false, | |
| 1456 | + "notNull": false | |
| 1457 | + }, | |
| 1458 | + "role_unit_id": { | |
| 1459 | + "name": "role_unit_id", | |
| 1460 | + "type": "text", | |
| 1461 | + "primaryKey": false, | |
| 1462 | + "notNull": false | |
| 1463 | + }, | |
| 1464 | + "property_type": { | |
| 1465 | + "name": "property_type", | |
| 1466 | + "type": "text", | |
| 1467 | + "primaryKey": false, | |
| 1468 | + "notNull": true | |
| 1469 | + }, | |
| 1470 | + "bedrooms": { | |
| 1471 | + "name": "bedrooms", | |
| 1472 | + "type": "numeric(4, 1)", | |
| 1473 | + "primaryKey": false, | |
| 1474 | + "notNull": false | |
| 1475 | + }, | |
| 1476 | + "bathrooms": { | |
| 1477 | + "name": "bathrooms", | |
| 1478 | + "type": "numeric(4, 1)", | |
| 1479 | + "primaryKey": false, | |
| 1480 | + "notNull": false | |
| 1481 | + }, | |
| 1482 | + "area_sqft": { | |
| 1483 | + "name": "area_sqft", | |
| 1484 | + "type": "integer", | |
| 1485 | + "primaryKey": false, | |
| 1486 | + "notNull": false | |
| 1487 | + }, | |
| 1488 | + "lot_sqft": { | |
| 1489 | + "name": "lot_sqft", | |
| 1490 | + "type": "integer", | |
| 1491 | + "primaryKey": false, | |
| 1492 | + "notNull": false | |
| 1493 | + }, | |
| 1494 | + "year_built": { | |
| 1495 | + "name": "year_built", | |
| 1496 | + "type": "integer", | |
| 1497 | + "primaryKey": false, | |
| 1498 | + "notNull": false | |
| 1499 | + }, | |
| 1500 | + "privacy_level": { | |
| 1501 | + "name": "privacy_level", | |
| 1502 | + "type": "text", | |
| 1503 | + "primaryKey": false, | |
| 1504 | + "notNull": true, | |
| 1505 | + "default": "'EXACT'" | |
| 1506 | + }, | |
| 1507 | + "created_at": { | |
| 1508 | + "name": "created_at", | |
| 1509 | + "type": "timestamp with time zone", | |
| 1510 | + "primaryKey": false, | |
| 1511 | + "notNull": true, | |
| 1512 | + "default": "now()" | |
| 1513 | + } | |
| 1514 | + }, | |
| 1515 | + "indexes": { | |
| 1516 | + "properties_geo_idx": { | |
| 1517 | + "name": "properties_geo_idx", | |
| 1518 | + "columns": [ | |
| 1519 | + { | |
| 1520 | + "expression": "lat", | |
| 1521 | + "isExpression": false, | |
| 1522 | + "asc": true, | |
| 1523 | + "nulls": "last" | |
| 1524 | + }, | |
| 1525 | + { | |
| 1526 | + "expression": "lng", | |
| 1527 | + "isExpression": false, | |
| 1528 | + "asc": true, | |
| 1529 | + "nulls": "last" | |
| 1530 | + } | |
| 1531 | + ], | |
| 1532 | + "isUnique": false, | |
| 1533 | + "concurrently": false, | |
| 1534 | + "method": "btree", | |
| 1535 | + "with": {} | |
| 1536 | + }, | |
| 1537 | + "properties_city_idx": { | |
| 1538 | + "name": "properties_city_idx", | |
| 1539 | + "columns": [ | |
| 1540 | + { | |
| 1541 | + "expression": "city", | |
| 1542 | + "isExpression": false, | |
| 1543 | + "asc": true, | |
| 1544 | + "nulls": "last" | |
| 1545 | + } | |
| 1546 | + ], | |
| 1547 | + "isUnique": false, | |
| 1548 | + "concurrently": false, | |
| 1549 | + "method": "btree", | |
| 1550 | + "with": {} | |
| 1551 | + } | |
| 1552 | + }, | |
| 1553 | + "foreignKeys": {}, | |
| 1554 | + "compositePrimaryKeys": {}, | |
| 1555 | + "uniqueConstraints": {}, | |
| 1556 | + "policies": {}, | |
| 1557 | + "checkConstraints": {}, | |
| 1558 | + "isRLSEnabled": false | |
| 1559 | + }, | |
| 1560 | + "public.rate_limits": { | |
| 1561 | + "name": "rate_limits", | |
| 1562 | + "schema": "", | |
| 1563 | + "columns": { | |
| 1564 | + "key": { | |
| 1565 | + "name": "key", | |
| 1566 | + "type": "text", | |
| 1567 | + "primaryKey": false, | |
| 1568 | + "notNull": true | |
| 1569 | + }, | |
| 1570 | + "window_start": { | |
| 1571 | + "name": "window_start", | |
| 1572 | + "type": "timestamp with time zone", | |
| 1573 | + "primaryKey": false, | |
| 1574 | + "notNull": true | |
| 1575 | + }, | |
| 1576 | + "count": { | |
| 1577 | + "name": "count", | |
| 1578 | + "type": "integer", | |
| 1579 | + "primaryKey": false, | |
| 1580 | + "notNull": true, | |
| 1581 | + "default": 0 | |
| 1582 | + } | |
| 1583 | + }, | |
| 1584 | + "indexes": {}, | |
| 1585 | + "foreignKeys": {}, | |
| 1586 | + "compositePrimaryKeys": { | |
| 1587 | + "rate_limits_key_window_start_pk": { | |
| 1588 | + "name": "rate_limits_key_window_start_pk", | |
| 1589 | + "columns": [ | |
| 1590 | + "key", | |
| 1591 | + "window_start" | |
| 1592 | + ] | |
| 1593 | + } | |
| 1594 | + }, | |
| 1595 | + "uniqueConstraints": {}, | |
| 1596 | + "policies": {}, | |
| 1597 | + "checkConstraints": {}, | |
| 1598 | + "isRLSEnabled": false | |
| 1599 | + }, | |
| 1600 | + "public.reports": { | |
| 1601 | + "name": "reports", | |
| 1602 | + "schema": "", | |
| 1603 | + "columns": { | |
| 1604 | + "id": { | |
| 1605 | + "name": "id", | |
| 1606 | + "type": "uuid", | |
| 1607 | + "primaryKey": true, | |
| 1608 | + "notNull": true, | |
| 1609 | + "default": "gen_random_uuid()" | |
| 1610 | + }, | |
| 1611 | + "target_type": { | |
| 1612 | + "name": "target_type", | |
| 1613 | + "type": "text", | |
| 1614 | + "primaryKey": false, | |
| 1615 | + "notNull": true | |
| 1616 | + }, | |
| 1617 | + "target_id": { | |
| 1618 | + "name": "target_id", | |
| 1619 | + "type": "text", | |
| 1620 | + "primaryKey": false, | |
| 1621 | + "notNull": true | |
| 1622 | + }, | |
| 1623 | + "reporter_id": { | |
| 1624 | + "name": "reporter_id", | |
| 1625 | + "type": "uuid", | |
| 1626 | + "primaryKey": false, | |
| 1627 | + "notNull": false | |
| 1628 | + }, | |
| 1629 | + "reason": { | |
| 1630 | + "name": "reason", | |
| 1631 | + "type": "text", | |
| 1632 | + "primaryKey": false, | |
| 1633 | + "notNull": true | |
| 1634 | + }, | |
| 1635 | + "details": { | |
| 1636 | + "name": "details", | |
| 1637 | + "type": "text", | |
| 1638 | + "primaryKey": false, | |
| 1639 | + "notNull": false | |
| 1640 | + }, | |
| 1641 | + "status": { | |
| 1642 | + "name": "status", | |
| 1643 | + "type": "text", | |
| 1644 | + "primaryKey": false, | |
| 1645 | + "notNull": true, | |
| 1646 | + "default": "'OPEN'" | |
| 1647 | + }, | |
| 1648 | + "created_at": { | |
| 1649 | + "name": "created_at", | |
| 1650 | + "type": "timestamp with time zone", | |
| 1651 | + "primaryKey": false, | |
| 1652 | + "notNull": true, | |
| 1653 | + "default": "now()" | |
| 1654 | + } | |
| 1655 | + }, | |
| 1656 | + "indexes": { | |
| 1657 | + "reports_status_idx": { | |
| 1658 | + "name": "reports_status_idx", | |
| 1659 | + "columns": [ | |
| 1660 | + { | |
| 1661 | + "expression": "status", | |
| 1662 | + "isExpression": false, | |
| 1663 | + "asc": true, | |
| 1664 | + "nulls": "last" | |
| 1665 | + }, | |
| 1666 | + { | |
| 1667 | + "expression": "created_at", | |
| 1668 | + "isExpression": false, | |
| 1669 | + "asc": true, | |
| 1670 | + "nulls": "last" | |
| 1671 | + } | |
| 1672 | + ], | |
| 1673 | + "isUnique": false, | |
| 1674 | + "concurrently": false, | |
| 1675 | + "method": "btree", | |
| 1676 | + "with": {} | |
| 1677 | + } | |
| 1678 | + }, | |
| 1679 | + "foreignKeys": { | |
| 1680 | + "reports_reporter_id_users_id_fk": { | |
| 1681 | + "name": "reports_reporter_id_users_id_fk", | |
| 1682 | + "tableFrom": "reports", | |
| 1683 | + "tableTo": "users", | |
| 1684 | + "columnsFrom": [ | |
| 1685 | + "reporter_id" | |
| 1686 | + ], | |
| 1687 | + "columnsTo": [ | |
| 1688 | + "id" | |
| 1689 | + ], | |
| 1690 | + "onDelete": "set null", | |
| 1691 | + "onUpdate": "no action" | |
| 1692 | + } | |
| 1693 | + }, | |
| 1694 | + "compositePrimaryKeys": {}, | |
| 1695 | + "uniqueConstraints": {}, | |
| 1696 | + "policies": {}, | |
| 1697 | + "checkConstraints": {}, | |
| 1698 | + "isRLSEnabled": false | |
| 1699 | + }, | |
| 1700 | + "public.saved_searches": { | |
| 1701 | + "name": "saved_searches", | |
| 1702 | + "schema": "", | |
| 1703 | + "columns": { | |
| 1704 | + "id": { | |
| 1705 | + "name": "id", | |
| 1706 | + "type": "uuid", | |
| 1707 | + "primaryKey": true, | |
| 1708 | + "notNull": true, | |
| 1709 | + "default": "gen_random_uuid()" | |
| 1710 | + }, | |
| 1711 | + "user_id": { | |
| 1712 | + "name": "user_id", | |
| 1713 | + "type": "uuid", | |
| 1714 | + "primaryKey": false, | |
| 1715 | + "notNull": true | |
| 1716 | + }, | |
| 1717 | + "label": { | |
| 1718 | + "name": "label", | |
| 1719 | + "type": "text", | |
| 1720 | + "primaryKey": false, | |
| 1721 | + "notNull": true | |
| 1722 | + }, | |
| 1723 | + "query": { | |
| 1724 | + "name": "query", | |
| 1725 | + "type": "jsonb", | |
| 1726 | + "primaryKey": false, | |
| 1727 | + "notNull": true | |
| 1728 | + }, | |
| 1729 | + "alert_enabled": { | |
| 1730 | + "name": "alert_enabled", | |
| 1731 | + "type": "boolean", | |
| 1732 | + "primaryKey": false, | |
| 1733 | + "notNull": true, | |
| 1734 | + "default": false | |
| 1735 | + }, | |
| 1736 | + "alert_frequency": { | |
| 1737 | + "name": "alert_frequency", | |
| 1738 | + "type": "text", | |
| 1739 | + "primaryKey": false, | |
| 1740 | + "notNull": true, | |
| 1741 | + "default": "'DAILY'" | |
| 1742 | + }, | |
| 1743 | + "last_alert_at": { | |
| 1744 | + "name": "last_alert_at", | |
| 1745 | + "type": "timestamp with time zone", | |
| 1746 | + "primaryKey": false, | |
| 1747 | + "notNull": false | |
| 1748 | + }, | |
| 1749 | + "created_at": { | |
| 1750 | + "name": "created_at", | |
| 1751 | + "type": "timestamp with time zone", | |
| 1752 | + "primaryKey": false, | |
| 1753 | + "notNull": true, | |
| 1754 | + "default": "now()" | |
| 1755 | + } | |
| 1756 | + }, | |
| 1757 | + "indexes": { | |
| 1758 | + "saved_searches_user_idx": { | |
| 1759 | + "name": "saved_searches_user_idx", | |
| 1760 | + "columns": [ | |
| 1761 | + { | |
| 1762 | + "expression": "user_id", | |
| 1763 | + "isExpression": false, | |
| 1764 | + "asc": true, | |
| 1765 | + "nulls": "last" | |
| 1766 | + } | |
| 1767 | + ], | |
| 1768 | + "isUnique": false, | |
| 1769 | + "concurrently": false, | |
| 1770 | + "method": "btree", | |
| 1771 | + "with": {} | |
| 1772 | + } | |
| 1773 | + }, | |
| 1774 | + "foreignKeys": { | |
| 1775 | + "saved_searches_user_id_users_id_fk": { | |
| 1776 | + "name": "saved_searches_user_id_users_id_fk", | |
| 1777 | + "tableFrom": "saved_searches", | |
| 1778 | + "tableTo": "users", | |
| 1779 | + "columnsFrom": [ | |
| 1780 | + "user_id" | |
| 1781 | + ], | |
| 1782 | + "columnsTo": [ | |
| 1783 | + "id" | |
| 1784 | + ], | |
| 1785 | + "onDelete": "cascade", | |
| 1786 | + "onUpdate": "no action" | |
| 1787 | + } | |
| 1788 | + }, | |
| 1789 | + "compositePrimaryKeys": {}, | |
| 1790 | + "uniqueConstraints": {}, | |
| 1791 | + "policies": {}, | |
| 1792 | + "checkConstraints": {}, | |
| 1793 | + "isRLSEnabled": false | |
| 1794 | + }, | |
| 1795 | + "public.sessions": { | |
| 1796 | + "name": "sessions", | |
| 1797 | + "schema": "", | |
| 1798 | + "columns": { | |
| 1799 | + "id": { | |
| 1800 | + "name": "id", | |
| 1801 | + "type": "uuid", | |
| 1802 | + "primaryKey": true, | |
| 1803 | + "notNull": true, | |
| 1804 | + "default": "gen_random_uuid()" | |
| 1805 | + }, | |
| 1806 | + "user_id": { | |
| 1807 | + "name": "user_id", | |
| 1808 | + "type": "uuid", | |
| 1809 | + "primaryKey": false, | |
| 1810 | + "notNull": true | |
| 1811 | + }, | |
| 1812 | + "token_digest": { | |
| 1813 | + "name": "token_digest", | |
| 1814 | + "type": "text", | |
| 1815 | + "primaryKey": false, | |
| 1816 | + "notNull": true | |
| 1817 | + }, | |
| 1818 | + "issued_at": { | |
| 1819 | + "name": "issued_at", | |
| 1820 | + "type": "timestamp with time zone", | |
| 1821 | + "primaryKey": false, | |
| 1822 | + "notNull": true, | |
| 1823 | + "default": "now()" | |
| 1824 | + }, | |
| 1825 | + "last_seen_at": { | |
| 1826 | + "name": "last_seen_at", | |
| 1827 | + "type": "timestamp with time zone", | |
| 1828 | + "primaryKey": false, | |
| 1829 | + "notNull": true, | |
| 1830 | + "default": "now()" | |
| 1831 | + }, | |
| 1832 | + "expires_at": { | |
| 1833 | + "name": "expires_at", | |
| 1834 | + "type": "timestamp with time zone", | |
| 1835 | + "primaryKey": false, | |
| 1836 | + "notNull": true | |
| 1837 | + }, | |
| 1838 | + "revoked_at": { | |
| 1839 | + "name": "revoked_at", | |
| 1840 | + "type": "timestamp with time zone", | |
| 1841 | + "primaryKey": false, | |
| 1842 | + "notNull": false | |
| 1843 | + }, | |
| 1844 | + "device_label": { | |
| 1845 | + "name": "device_label", | |
| 1846 | + "type": "text", | |
| 1847 | + "primaryKey": false, | |
| 1848 | + "notNull": false | |
| 1849 | + }, | |
| 1850 | + "ip_hash": { | |
| 1851 | + "name": "ip_hash", | |
| 1852 | + "type": "text", | |
| 1853 | + "primaryKey": false, | |
| 1854 | + "notNull": false | |
| 1855 | + }, | |
| 1856 | + "mfa_at": { | |
| 1857 | + "name": "mfa_at", | |
| 1858 | + "type": "timestamp with time zone", | |
| 1859 | + "primaryKey": false, | |
| 1860 | + "notNull": false | |
| 1861 | + } | |
| 1862 | + }, | |
| 1863 | + "indexes": { | |
| 1864 | + "sessions_token_uq": { | |
| 1865 | + "name": "sessions_token_uq", | |
| 1866 | + "columns": [ | |
| 1867 | + { | |
| 1868 | + "expression": "token_digest", | |
| 1869 | + "isExpression": false, | |
| 1870 | + "asc": true, | |
| 1871 | + "nulls": "last" | |
| 1872 | + } | |
| 1873 | + ], | |
| 1874 | + "isUnique": true, | |
| 1875 | + "concurrently": false, | |
| 1876 | + "method": "btree", | |
| 1877 | + "with": {} | |
| 1878 | + }, | |
| 1879 | + "sessions_user_idx": { | |
| 1880 | + "name": "sessions_user_idx", | |
| 1881 | + "columns": [ | |
| 1882 | + { | |
| 1883 | + "expression": "user_id", | |
| 1884 | + "isExpression": false, | |
| 1885 | + "asc": true, | |
| 1886 | + "nulls": "last" | |
| 1887 | + } | |
| 1888 | + ], | |
| 1889 | + "isUnique": false, | |
| 1890 | + "concurrently": false, | |
| 1891 | + "method": "btree", | |
| 1892 | + "with": {} | |
| 1893 | + } | |
| 1894 | + }, | |
| 1895 | + "foreignKeys": { | |
| 1896 | + "sessions_user_id_users_id_fk": { | |
| 1897 | + "name": "sessions_user_id_users_id_fk", | |
| 1898 | + "tableFrom": "sessions", | |
| 1899 | + "tableTo": "users", | |
| 1900 | + "columnsFrom": [ | |
| 1901 | + "user_id" | |
| 1902 | + ], | |
| 1903 | + "columnsTo": [ | |
| 1904 | + "id" | |
| 1905 | + ], | |
| 1906 | + "onDelete": "cascade", | |
| 1907 | + "onUpdate": "no action" | |
| 1908 | + } | |
| 1909 | + }, | |
| 1910 | + "compositePrimaryKeys": {}, | |
| 1911 | + "uniqueConstraints": {}, | |
| 1912 | + "policies": {}, | |
| 1913 | + "checkConstraints": {}, | |
| 1914 | + "isRLSEnabled": false | |
| 1915 | + }, | |
| 1916 | + "public.user_profiles": { | |
| 1917 | + "name": "user_profiles", | |
| 1918 | + "schema": "", | |
| 1919 | + "columns": { | |
| 1920 | + "user_id": { | |
| 1921 | + "name": "user_id", | |
| 1922 | + "type": "uuid", | |
| 1923 | + "primaryKey": true, | |
| 1924 | + "notNull": true | |
| 1925 | + }, | |
| 1926 | + "public_name": { | |
| 1927 | + "name": "public_name", | |
| 1928 | + "type": "text", | |
| 1929 | + "primaryKey": false, | |
| 1930 | + "notNull": false | |
| 1931 | + }, | |
| 1932 | + "bio": { | |
| 1933 | + "name": "bio", | |
| 1934 | + "type": "text", | |
| 1935 | + "primaryKey": false, | |
| 1936 | + "notNull": false | |
| 1937 | + }, | |
| 1938 | + "avatar_key": { | |
| 1939 | + "name": "avatar_key", | |
| 1940 | + "type": "text", | |
| 1941 | + "primaryKey": false, | |
| 1942 | + "notNull": false | |
| 1943 | + }, | |
| 1944 | + "seller_type": { | |
| 1945 | + "name": "seller_type", | |
| 1946 | + "type": "text", | |
| 1947 | + "primaryKey": false, | |
| 1948 | + "notNull": true, | |
| 1949 | + "default": "'OWNER'" | |
| 1950 | + }, | |
| 1951 | + "verification_status": { | |
| 1952 | + "name": "verification_status", | |
| 1953 | + "type": "text", | |
| 1954 | + "primaryKey": false, | |
| 1955 | + "notNull": true, | |
| 1956 | + "default": "'NONE'" | |
| 1957 | + }, | |
| 1958 | + "contact_preferences": { | |
| 1959 | + "name": "contact_preferences", | |
| 1960 | + "type": "jsonb", | |
| 1961 | + "primaryKey": false, | |
| 1962 | + "notNull": true, | |
| 1963 | + "default": "'{\"email\":true,\"phone\":false}'::jsonb" | |
| 1964 | + }, | |
| 1965 | + "updated_at": { | |
| 1966 | + "name": "updated_at", | |
| 1967 | + "type": "timestamp with time zone", | |
| 1968 | + "primaryKey": false, | |
| 1969 | + "notNull": true, | |
| 1970 | + "default": "now()" | |
| 1971 | + } | |
| 1972 | + }, | |
| 1973 | + "indexes": {}, | |
| 1974 | + "foreignKeys": { | |
| 1975 | + "user_profiles_user_id_users_id_fk": { | |
| 1976 | + "name": "user_profiles_user_id_users_id_fk", | |
| 1977 | + "tableFrom": "user_profiles", | |
| 1978 | + "tableTo": "users", | |
| 1979 | + "columnsFrom": [ | |
| 1980 | + "user_id" | |
| 1981 | + ], | |
| 1982 | + "columnsTo": [ | |
| 1983 | + "id" | |
| 1984 | + ], | |
| 1985 | + "onDelete": "cascade", | |
| 1986 | + "onUpdate": "no action" | |
| 1987 | + } | |
| 1988 | + }, | |
| 1989 | + "compositePrimaryKeys": {}, | |
| 1990 | + "uniqueConstraints": {}, | |
| 1991 | + "policies": {}, | |
| 1992 | + "checkConstraints": {}, | |
| 1993 | + "isRLSEnabled": false | |
| 1994 | + }, | |
| 1995 | + "public.user_roles": { | |
| 1996 | + "name": "user_roles", | |
| 1997 | + "schema": "", | |
| 1998 | + "columns": { | |
| 1999 | + "user_id": { | |
| 2000 | + "name": "user_id", | |
| 2001 | + "type": "uuid", | |
| 2002 | + "primaryKey": false, | |
| 2003 | + "notNull": true | |
| 2004 | + }, | |
| 2005 | + "role": { | |
| 2006 | + "name": "role", | |
| 2007 | + "type": "text", | |
| 2008 | + "primaryKey": false, | |
| 2009 | + "notNull": true | |
| 2010 | + }, | |
| 2011 | + "granted_by": { | |
| 2012 | + "name": "granted_by", | |
| 2013 | + "type": "uuid", | |
| 2014 | + "primaryKey": false, | |
| 2015 | + "notNull": false | |
| 2016 | + }, | |
| 2017 | + "granted_at": { | |
| 2018 | + "name": "granted_at", | |
| 2019 | + "type": "timestamp with time zone", | |
| 2020 | + "primaryKey": false, | |
| 2021 | + "notNull": true, | |
| 2022 | + "default": "now()" | |
| 2023 | + }, | |
| 2024 | + "note": { | |
| 2025 | + "name": "note", | |
| 2026 | + "type": "text", | |
| 2027 | + "primaryKey": false, | |
| 2028 | + "notNull": false | |
| 2029 | + } | |
| 2030 | + }, | |
| 2031 | + "indexes": {}, | |
| 2032 | + "foreignKeys": { | |
| 2033 | + "user_roles_user_id_users_id_fk": { | |
| 2034 | + "name": "user_roles_user_id_users_id_fk", | |
| 2035 | + "tableFrom": "user_roles", | |
| 2036 | + "tableTo": "users", | |
| 2037 | + "columnsFrom": [ | |
| 2038 | + "user_id" | |
| 2039 | + ], | |
| 2040 | + "columnsTo": [ | |
| 2041 | + "id" | |
| 2042 | + ], | |
| 2043 | + "onDelete": "cascade", | |
| 2044 | + "onUpdate": "no action" | |
| 2045 | + } | |
| 2046 | + }, | |
| 2047 | + "compositePrimaryKeys": { | |
| 2048 | + "user_roles_user_id_role_pk": { | |
| 2049 | + "name": "user_roles_user_id_role_pk", | |
| 2050 | + "columns": [ | |
| 2051 | + "user_id", | |
| 2052 | + "role" | |
| 2053 | + ] | |
| 2054 | + } | |
| 2055 | + }, | |
| 2056 | + "uniqueConstraints": {}, | |
| 2057 | + "policies": {}, | |
| 2058 | + "checkConstraints": { | |
| 2059 | + "user_roles_role_ck": { | |
| 2060 | + "name": "user_roles_role_ck", | |
| 2061 | + "value": "\"user_roles\".\"role\" in ('ADMIN','MODERATOR','AGENT_VERIFIED')" | |
| 2062 | + } | |
| 2063 | + }, | |
| 2064 | + "isRLSEnabled": false | |
| 2065 | + }, | |
| 2066 | + "public.users": { | |
| 2067 | + "name": "users", | |
| 2068 | + "schema": "", | |
| 2069 | + "columns": { | |
| 2070 | + "id": { | |
| 2071 | + "name": "id", | |
| 2072 | + "type": "uuid", | |
| 2073 | + "primaryKey": true, | |
| 2074 | + "notNull": true, | |
| 2075 | + "default": "gen_random_uuid()" | |
| 2076 | + }, | |
| 2077 | + "email": { | |
| 2078 | + "name": "email", | |
| 2079 | + "type": "text", | |
| 2080 | + "primaryKey": false, | |
| 2081 | + "notNull": true | |
| 2082 | + }, | |
| 2083 | + "email_verified_at": { | |
| 2084 | + "name": "email_verified_at", | |
| 2085 | + "type": "timestamp with time zone", | |
| 2086 | + "primaryKey": false, | |
| 2087 | + "notNull": false | |
| 2088 | + }, | |
| 2089 | + "display_name": { | |
| 2090 | + "name": "display_name", | |
| 2091 | + "type": "text", | |
| 2092 | + "primaryKey": false, | |
| 2093 | + "notNull": false | |
| 2094 | + }, | |
| 2095 | + "phone_encrypted": { | |
| 2096 | + "name": "phone_encrypted", | |
| 2097 | + "type": "text", | |
| 2098 | + "primaryKey": false, | |
| 2099 | + "notNull": false | |
| 2100 | + }, | |
| 2101 | + "locale": { | |
| 2102 | + "name": "locale", | |
| 2103 | + "type": "text", | |
| 2104 | + "primaryKey": false, | |
| 2105 | + "notNull": true, | |
| 2106 | + "default": "'fr-CA'" | |
| 2107 | + }, | |
| 2108 | + "status": { | |
| 2109 | + "name": "status", | |
| 2110 | + "type": "text", | |
| 2111 | + "primaryKey": false, | |
| 2112 | + "notNull": true, | |
| 2113 | + "default": "'ACTIVE'" | |
| 2114 | + }, | |
| 2115 | + "prefs": { | |
| 2116 | + "name": "prefs", | |
| 2117 | + "type": "jsonb", | |
| 2118 | + "primaryKey": false, | |
| 2119 | + "notNull": true, | |
| 2120 | + "default": "'{}'::jsonb" | |
| 2121 | + }, | |
| 2122 | + "totp_secret_encrypted": { | |
| 2123 | + "name": "totp_secret_encrypted", | |
| 2124 | + "type": "text", | |
| 2125 | + "primaryKey": false, | |
| 2126 | + "notNull": false | |
| 2127 | + }, | |
| 2128 | + "created_at": { | |
| 2129 | + "name": "created_at", | |
| 2130 | + "type": "timestamp with time zone", | |
| 2131 | + "primaryKey": false, | |
| 2132 | + "notNull": true, | |
| 2133 | + "default": "now()" | |
| 2134 | + }, | |
| 2135 | + "updated_at": { | |
| 2136 | + "name": "updated_at", | |
| 2137 | + "type": "timestamp with time zone", | |
| 2138 | + "primaryKey": false, | |
| 2139 | + "notNull": true, | |
| 2140 | + "default": "now()" | |
| 2141 | + }, | |
| 2142 | + "deleted_at": { | |
| 2143 | + "name": "deleted_at", | |
| 2144 | + "type": "timestamp with time zone", | |
| 2145 | + "primaryKey": false, | |
| 2146 | + "notNull": false | |
| 2147 | + } | |
| 2148 | + }, | |
| 2149 | + "indexes": { | |
| 2150 | + "users_email_uq": { | |
| 2151 | + "name": "users_email_uq", | |
| 2152 | + "columns": [ | |
| 2153 | + { | |
| 2154 | + "expression": "email", | |
| 2155 | + "isExpression": false, | |
| 2156 | + "asc": true, | |
| 2157 | + "nulls": "last" | |
| 2158 | + } | |
| 2159 | + ], | |
| 2160 | + "isUnique": true, | |
| 2161 | + "concurrently": false, | |
| 2162 | + "method": "btree", | |
| 2163 | + "with": {} | |
| 2164 | + } | |
| 2165 | + }, | |
| 2166 | + "foreignKeys": {}, | |
| 2167 | + "compositePrimaryKeys": {}, | |
| 2168 | + "uniqueConstraints": {}, | |
| 2169 | + "policies": {}, | |
| 2170 | + "checkConstraints": { | |
| 2171 | + "users_status_ck": { | |
| 2172 | + "name": "users_status_ck", | |
| 2173 | + "value": "\"users\".\"status\" in ('ACTIVE','SUSPENDED','DELETED')" | |
| 2174 | + } | |
| 2175 | + }, | |
| 2176 | + "isRLSEnabled": false | |
| 2177 | + }, | |
| 2178 | + "public.viewing_requests": { | |
| 2179 | + "name": "viewing_requests", | |
| 2180 | + "schema": "", | |
| 2181 | + "columns": { | |
| 2182 | + "id": { | |
| 2183 | + "name": "id", | |
| 2184 | + "type": "uuid", | |
| 2185 | + "primaryKey": true, | |
| 2186 | + "notNull": true, | |
| 2187 | + "default": "gen_random_uuid()" | |
| 2188 | + }, | |
| 2189 | + "listing_id": { | |
| 2190 | + "name": "listing_id", | |
| 2191 | + "type": "uuid", | |
| 2192 | + "primaryKey": false, | |
| 2193 | + "notNull": true | |
| 2194 | + }, | |
| 2195 | + "inquiry_id": { | |
| 2196 | + "name": "inquiry_id", | |
| 2197 | + "type": "uuid", | |
| 2198 | + "primaryKey": false, | |
| 2199 | + "notNull": false | |
| 2200 | + }, | |
| 2201 | + "requester_id": { | |
| 2202 | + "name": "requester_id", | |
| 2203 | + "type": "uuid", | |
| 2204 | + "primaryKey": false, | |
| 2205 | + "notNull": true | |
| 2206 | + }, | |
| 2207 | + "owner_id": { | |
| 2208 | + "name": "owner_id", | |
| 2209 | + "type": "uuid", | |
| 2210 | + "primaryKey": false, | |
| 2211 | + "notNull": true | |
| 2212 | + }, | |
| 2213 | + "proposed_slots": { | |
| 2214 | + "name": "proposed_slots", | |
| 2215 | + "type": "jsonb", | |
| 2216 | + "primaryKey": false, | |
| 2217 | + "notNull": true | |
| 2218 | + }, | |
| 2219 | + "confirmed_slot": { | |
| 2220 | + "name": "confirmed_slot", | |
| 2221 | + "type": "timestamp with time zone", | |
| 2222 | + "primaryKey": false, | |
| 2223 | + "notNull": false | |
| 2224 | + }, | |
| 2225 | + "status": { | |
| 2226 | + "name": "status", | |
| 2227 | + "type": "text", | |
| 2228 | + "primaryKey": false, | |
| 2229 | + "notNull": true, | |
| 2230 | + "default": "'REQUESTED'" | |
| 2231 | + }, | |
| 2232 | + "note": { | |
| 2233 | + "name": "note", | |
| 2234 | + "type": "text", | |
| 2235 | + "primaryKey": false, | |
| 2236 | + "notNull": false | |
| 2237 | + }, | |
| 2238 | + "created_at": { | |
| 2239 | + "name": "created_at", | |
| 2240 | + "type": "timestamp with time zone", | |
| 2241 | + "primaryKey": false, | |
| 2242 | + "notNull": true, | |
| 2243 | + "default": "now()" | |
| 2244 | + }, | |
| 2245 | + "updated_at": { | |
| 2246 | + "name": "updated_at", | |
| 2247 | + "type": "timestamp with time zone", | |
| 2248 | + "primaryKey": false, | |
| 2249 | + "notNull": true, | |
| 2250 | + "default": "now()" | |
| 2251 | + } | |
| 2252 | + }, | |
| 2253 | + "indexes": { | |
| 2254 | + "viewings_listing_idx": { | |
| 2255 | + "name": "viewings_listing_idx", | |
| 2256 | + "columns": [ | |
| 2257 | + { | |
| 2258 | + "expression": "listing_id", | |
| 2259 | + "isExpression": false, | |
| 2260 | + "asc": true, | |
| 2261 | + "nulls": "last" | |
| 2262 | + } | |
| 2263 | + ], | |
| 2264 | + "isUnique": false, | |
| 2265 | + "concurrently": false, | |
| 2266 | + "method": "btree", | |
| 2267 | + "with": {} | |
| 2268 | + }, | |
| 2269 | + "viewings_requester_idx": { | |
| 2270 | + "name": "viewings_requester_idx", | |
| 2271 | + "columns": [ | |
| 2272 | + { | |
| 2273 | + "expression": "requester_id", | |
| 2274 | + "isExpression": false, | |
| 2275 | + "asc": true, | |
| 2276 | + "nulls": "last" | |
| 2277 | + } | |
| 2278 | + ], | |
| 2279 | + "isUnique": false, | |
| 2280 | + "concurrently": false, | |
| 2281 | + "method": "btree", | |
| 2282 | + "with": {} | |
| 2283 | + }, | |
| 2284 | + "viewings_owner_idx": { | |
| 2285 | + "name": "viewings_owner_idx", | |
| 2286 | + "columns": [ | |
| 2287 | + { | |
| 2288 | + "expression": "owner_id", | |
| 2289 | + "isExpression": false, | |
| 2290 | + "asc": true, | |
| 2291 | + "nulls": "last" | |
| 2292 | + } | |
| 2293 | + ], | |
| 2294 | + "isUnique": false, | |
| 2295 | + "concurrently": false, | |
| 2296 | + "method": "btree", | |
| 2297 | + "with": {} | |
| 2298 | + } | |
| 2299 | + }, | |
| 2300 | + "foreignKeys": { | |
| 2301 | + "viewing_requests_listing_id_listings_id_fk": { | |
| 2302 | + "name": "viewing_requests_listing_id_listings_id_fk", | |
| 2303 | + "tableFrom": "viewing_requests", | |
| 2304 | + "tableTo": "listings", | |
| 2305 | + "columnsFrom": [ | |
| 2306 | + "listing_id" | |
| 2307 | + ], | |
| 2308 | + "columnsTo": [ | |
| 2309 | + "id" | |
| 2310 | + ], | |
| 2311 | + "onDelete": "cascade", | |
| 2312 | + "onUpdate": "no action" | |
| 2313 | + }, | |
| 2314 | + "viewing_requests_inquiry_id_inquiries_id_fk": { | |
| 2315 | + "name": "viewing_requests_inquiry_id_inquiries_id_fk", | |
| 2316 | + "tableFrom": "viewing_requests", | |
| 2317 | + "tableTo": "inquiries", | |
| 2318 | + "columnsFrom": [ | |
| 2319 | + "inquiry_id" | |
| 2320 | + ], | |
| 2321 | + "columnsTo": [ | |
| 2322 | + "id" | |
| 2323 | + ], | |
| 2324 | + "onDelete": "set null", | |
| 2325 | + "onUpdate": "no action" | |
| 2326 | + }, | |
| 2327 | + "viewing_requests_requester_id_users_id_fk": { | |
| 2328 | + "name": "viewing_requests_requester_id_users_id_fk", | |
| 2329 | + "tableFrom": "viewing_requests", | |
| 2330 | + "tableTo": "users", | |
| 2331 | + "columnsFrom": [ | |
| 2332 | + "requester_id" | |
| 2333 | + ], | |
| 2334 | + "columnsTo": [ | |
| 2335 | + "id" | |
| 2336 | + ], | |
| 2337 | + "onDelete": "cascade", | |
| 2338 | + "onUpdate": "no action" | |
| 2339 | + }, | |
| 2340 | + "viewing_requests_owner_id_users_id_fk": { | |
| 2341 | + "name": "viewing_requests_owner_id_users_id_fk", | |
| 2342 | + "tableFrom": "viewing_requests", | |
| 2343 | + "tableTo": "users", | |
| 2344 | + "columnsFrom": [ | |
| 2345 | + "owner_id" | |
| 2346 | + ], | |
| 2347 | + "columnsTo": [ | |
| 2348 | + "id" | |
| 2349 | + ], | |
| 2350 | + "onDelete": "cascade", | |
| 2351 | + "onUpdate": "no action" | |
| 2352 | + } | |
| 2353 | + }, | |
| 2354 | + "compositePrimaryKeys": {}, | |
| 2355 | + "uniqueConstraints": {}, | |
| 2356 | + "policies": {}, | |
| 2357 | + "checkConstraints": { | |
| 2358 | + "viewings_status_ck": { | |
| 2359 | + "name": "viewings_status_ck", | |
| 2360 | + "value": "\"viewing_requests\".\"status\" in ('REQUESTED','CONFIRMED','CANCELLED','DONE')" | |
| 2361 | + } | |
| 2362 | + }, | |
| 2363 | + "isRLSEnabled": false | |
| 2364 | + } | |
| 2365 | + }, | |
| 2366 | + "enums": {}, | |
| 2367 | + "schemas": {}, | |
| 2368 | + "sequences": {}, | |
| 2369 | + "roles": {}, | |
| 2370 | + "policies": {}, | |
| 2371 | + "views": {}, | |
| 2372 | + "_meta": { | |
| 2373 | + "columns": {}, | |
| 2374 | + "schemas": {}, | |
| 2375 | + "tables": {} | |
| 2376 | + } | |
| 2377 | +} | |
| \ No newline at end of file | ||
added
drizzle/meta/_journal.json
+13 −0
@@ -0,0 +1,13 @@ | ||
| 1 | +{ | |
| 2 | + "version": "7", | |
| 3 | + "dialect": "postgresql", | |
| 4 | + "entries": [ | |
| 5 | + { | |
| 6 | + "idx": 0, | |
| 7 | + "version": "7", | |
| 8 | + "when": 1792259087015, | |
| 9 | + "tag": "0000_daily_dexter_bennett", | |
| 10 | + "breakpoints": true | |
| 11 | + } | |
| 12 | + ] | |
| 13 | +} | |
| \ No newline at end of file | ||
modified
next.config.ts
+2 −1
@@ -1,6 +1,7 @@ | ||
| 1 | 1 | import type { NextConfig } from "next"; |
| 2 | 2 | const nextConfig: NextConfig = { |
| 3 | − serverExternalPackages: ["better-sqlite3"], | |
| 3 | + serverExternalPackages: ["better-sqlite3", "sharp", "heic-convert"], | |
| 4 | + turbopack: { rules: { "*.css": { loaders: ["@tailwindcss/turbopack"], as: "*.css" } } }, | |
| 4 | 5 | poweredByHeader: false, |
| 5 | 6 | async headers() { |
| 6 | 7 | return [{ source: "/(.*)", headers: [ |
modified
package.json
+15 −1
@@ -9,25 +9,39 @@ | ||
| 9 | 9 | "lint": "eslint", |
| 10 | 10 | "sync": "tsx scripts/sync.ts", |
| 11 | 11 | "test": "vitest run", |
| 12 | − "typecheck": "tsc --noEmit" | |
| 12 | + "typecheck": "tsc --noEmit", | |
| 13 | + "db:generate": "drizzle-kit generate", | |
| 14 | + "db:migrate": "tsx scripts/migrate.ts", | |
| 15 | + "db:studio": "drizzle-kit studio", | |
| 16 | + "test:db": "TOVELI_TEST_DB=1 vitest run tests/db" | |
| 13 | 17 | }, |
| 14 | 18 | "dependencies": { |
| 15 | 19 | "@anthropic-ai/sdk": "^0.135.0", |
| 16 | 20 | "better-sqlite3": "^13.0.3", |
| 21 | + "drizzle-orm": "^0.45.4", | |
| 22 | + "heic-convert": "^2.1.0", | |
| 17 | 23 | "maplibre-gl": "^6.14.0", |
| 24 | + "nanoid": "^6.0.2", | |
| 18 | 25 | "next": "16.4.2", |
| 26 | + "otpauth": "^9.5.2", | |
| 27 | + "postgres": "^3.4.9", | |
| 19 | 28 | "react": "19.3.0", |
| 20 | 29 | "react-dom": "19.3.0", |
| 21 | 30 | "recharts": "^3.10.1", |
| 31 | + "resend": "^6.34.1", | |
| 32 | + "sharp": "^0.35.5", | |
| 22 | 33 | "zod": "^4.6.7" |
| 23 | 34 | }, |
| 24 | 35 | "devDependencies": { |
| 25 | 36 | "@tailwindcss/turbopack": "^4", |
| 26 | 37 | "@types/better-sqlite3": "^9.6.0", |
| 38 | + "@types/heic-convert": "^2.1.1", | |
| 27 | 39 | "@types/node": "^20", |
| 40 | + "@types/pg": "^8.23.1", | |
| 28 | 41 | "@types/react": "^19", |
| 29 | 42 | "@types/react-dom": "^19", |
| 30 | 43 | "@vitejs/plugin-react": "^6.1.2", |
| 44 | + "drizzle-kit": "^0.31.11", | |
| 31 | 45 | "eslint": "^9", |
| 32 | 46 | "eslint-config-next": "16.4.2", |
| 33 | 47 | "tailwindcss": "^4", |
modified
pnpm-lock.yaml
+832 −3
@@ -14,12 +14,27 @@ importers: | ||
| 14 | 14 | better-sqlite3: |
| 15 | 15 | specifier: ^13.0.3 |
| 16 | 16 | version: 13.0.3 |
| 17 | + drizzle-orm: | |
| 18 | + specifier: ^0.45.4 | |
| 19 | + version: 0.45.4(@opentelemetry/api@1.9.1)(@types/better-sqlite3@9.6.0)(@types/pg@8.23.1)(better-sqlite3@13.0.3)(postgres@3.4.9) | |
| 20 | + heic-convert: | |
| 21 | + specifier: ^2.1.0 | |
| 22 | + version: 2.1.0 | |
| 17 | 23 | maplibre-gl: |
| 18 | 24 | specifier: ^6.14.0 |
| 19 | 25 | version: 6.14.0 |
| 26 | + nanoid: | |
| 27 | + specifier: ^6.0.2 | |
| 28 | + version: 6.0.2 | |
| 20 | 29 | next: |
| 21 | 30 | specifier: 16.4.2 |
| 22 | 31 | version: 16.4.2(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@20.19.43)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) |
| 32 | + otpauth: | |
| 33 | + specifier: ^9.5.2 | |
| 34 | + version: 9.5.2 | |
| 35 | + postgres: | |
| 36 | + specifier: ^3.4.9 | |
| 37 | + version: 3.4.9 | |
| 23 | 38 | react: |
| 24 | 39 | specifier: 19.3.0 |
| 25 | 40 | version: 19.3.0 |
@@ -29,6 +44,12 @@ importers: | ||
| 29 | 44 | recharts: |
| 30 | 45 | specifier: ^3.10.1 |
| 31 | 46 | version: 3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@16.13.1)(react@19.3.0)(redux@5.0.1) |
| 47 | + resend: | |
| 48 | + specifier: ^6.34.1 | |
| 49 | + version: 6.34.1 | |
| 50 | + sharp: | |
| 51 | + specifier: ^0.35.5 | |
| 52 | + version: 0.35.5(@types/node@20.19.43) | |
| 32 | 53 | zod: |
| 33 | 54 | specifier: ^4.6.7 |
| 34 | 55 | version: 4.6.7 |
@@ -39,9 +60,15 @@ importers: | ||
| 39 | 60 | '@types/better-sqlite3': |
| 40 | 61 | specifier: ^9.6.0 |
| 41 | 62 | version: 9.6.0 |
| 63 | + '@types/heic-convert': | |
| 64 | + specifier: ^2.1.1 | |
| 65 | + version: 2.1.1 | |
| 42 | 66 | '@types/node': |
| 43 | 67 | specifier: ^20 |
| 44 | 68 | version: 20.19.43 |
| 69 | + '@types/pg': | |
| 70 | + specifier: ^8.23.1 | |
| 71 | + version: 8.23.1 | |
| 45 | 72 | '@types/react': |
| 46 | 73 | specifier: ^19 |
| 47 | 74 | version: 19.3.0 |
@@ -51,6 +78,9 @@ importers: | ||
| 51 | 78 | '@vitejs/plugin-react': |
| 52 | 79 | specifier: ^6.1.2 |
| 53 | 80 | version: 6.1.2(vite@8.3.4(@types/node@20.19.43)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)) |
| 81 | + drizzle-kit: | |
| 82 | + specifier: ^0.31.11 | |
| 83 | + version: 0.31.11 | |
| 54 | 84 | eslint: |
| 55 | 85 | specifier: ^9 |
| 56 | 86 | version: 9.39.5(jiti@2.7.0) |
@@ -156,6 +186,9 @@ packages: | ||
| 156 | 186 | resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} |
| 157 | 187 | engines: {node: '>=6.9.0'} |
| 158 | 188 | |
| 189 | + '@drizzle-team/brocli@0.10.2': | |
| 190 | + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} | |
| 191 | + | |
| 159 | 192 | '@emnapi/core@1.10.0': |
| 160 | 193 | resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} |
| 161 | 194 | |
@@ -168,156 +201,452 @@ packages: | ||
| 168 | 201 | '@emnapi/wasi-threads@1.2.1': |
| 169 | 202 | resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} |
| 170 | 203 | |
| 204 | + '@esbuild-kit/core-utils@3.3.2': | |
| 205 | + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} | |
| 206 | + deprecated: 'Merged into tsx: https://tsx.hirok.io' | |
| 207 | + | |
| 208 | + '@esbuild-kit/esm-loader@2.6.5': | |
| 209 | + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} | |
| 210 | + deprecated: 'Merged into tsx: https://tsx.hirok.io' | |
| 211 | + | |
| 212 | + '@esbuild/aix-ppc64@0.25.12': | |
| 213 | + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} | |
| 214 | + engines: {node: '>=18'} | |
| 215 | + cpu: [ppc64] | |
| 216 | + os: [aix] | |
| 217 | + | |
| 171 | 218 | '@esbuild/aix-ppc64@0.28.2': |
| 172 | 219 | resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} |
| 173 | 220 | engines: {node: '>=18'} |
| 174 | 221 | cpu: [ppc64] |
| 175 | 222 | os: [aix] |
| 176 | 223 | |
| 224 | + '@esbuild/android-arm64@0.18.20': | |
| 225 | + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} | |
| 226 | + engines: {node: '>=12'} | |
| 227 | + cpu: [arm64] | |
| 228 | + os: [android] | |
| 229 | + | |
| 230 | + '@esbuild/android-arm64@0.25.12': | |
| 231 | + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} | |
| 232 | + engines: {node: '>=18'} | |
| 233 | + cpu: [arm64] | |
| 234 | + os: [android] | |
| 235 | + | |
| 177 | 236 | '@esbuild/android-arm64@0.28.2': |
| 178 | 237 | resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} |
| 179 | 238 | engines: {node: '>=18'} |
| 180 | 239 | cpu: [arm64] |
| 181 | 240 | os: [android] |
| 182 | 241 | |
| 242 | + '@esbuild/android-arm@0.18.20': | |
| 243 | + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} | |
| 244 | + engines: {node: '>=12'} | |
| 245 | + cpu: [arm] | |
| 246 | + os: [android] | |
| 247 | + | |
| 248 | + '@esbuild/android-arm@0.25.12': | |
| 249 | + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} | |
| 250 | + engines: {node: '>=18'} | |
| 251 | + cpu: [arm] | |
| 252 | + os: [android] | |
| 253 | + | |
| 183 | 254 | '@esbuild/android-arm@0.28.2': |
| 184 | 255 | resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} |
| 185 | 256 | engines: {node: '>=18'} |
| 186 | 257 | cpu: [arm] |
| 187 | 258 | os: [android] |
| 188 | 259 | |
| 260 | + '@esbuild/android-x64@0.18.20': | |
| 261 | + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} | |
| 262 | + engines: {node: '>=12'} | |
| 263 | + cpu: [x64] | |
| 264 | + os: [android] | |
| 265 | + | |
| 266 | + '@esbuild/android-x64@0.25.12': | |
| 267 | + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} | |
| 268 | + engines: {node: '>=18'} | |
| 269 | + cpu: [x64] | |
| 270 | + os: [android] | |
| 271 | + | |
| 189 | 272 | '@esbuild/android-x64@0.28.2': |
| 190 | 273 | resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} |
| 191 | 274 | engines: {node: '>=18'} |
| 192 | 275 | cpu: [x64] |
| 193 | 276 | os: [android] |
| 194 | 277 | |
| 278 | + '@esbuild/darwin-arm64@0.18.20': | |
| 279 | + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} | |
| 280 | + engines: {node: '>=12'} | |
| 281 | + cpu: [arm64] | |
| 282 | + os: [darwin] | |
| 283 | + | |
| 284 | + '@esbuild/darwin-arm64@0.25.12': | |
| 285 | + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} | |
| 286 | + engines: {node: '>=18'} | |
| 287 | + cpu: [arm64] | |
| 288 | + os: [darwin] | |
| 289 | + | |
| 195 | 290 | '@esbuild/darwin-arm64@0.28.2': |
| 196 | 291 | resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} |
| 197 | 292 | engines: {node: '>=18'} |
| 198 | 293 | cpu: [arm64] |
| 199 | 294 | os: [darwin] |
| 200 | 295 | |
| 296 | + '@esbuild/darwin-x64@0.18.20': | |
| 297 | + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} | |
| 298 | + engines: {node: '>=12'} | |
| 299 | + cpu: [x64] | |
| 300 | + os: [darwin] | |
| 301 | + | |
| 302 | + '@esbuild/darwin-x64@0.25.12': | |
| 303 | + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} | |
| 304 | + engines: {node: '>=18'} | |
| 305 | + cpu: [x64] | |
| 306 | + os: [darwin] | |
| 307 | + | |
| 201 | 308 | '@esbuild/darwin-x64@0.28.2': |
| 202 | 309 | resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} |
| 203 | 310 | engines: {node: '>=18'} |
| 204 | 311 | cpu: [x64] |
| 205 | 312 | os: [darwin] |
| 206 | 313 | |
| 314 | + '@esbuild/freebsd-arm64@0.18.20': | |
| 315 | + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} | |
| 316 | + engines: {node: '>=12'} | |
| 317 | + cpu: [arm64] | |
| 318 | + os: [freebsd] | |
| 319 | + | |
| 320 | + '@esbuild/freebsd-arm64@0.25.12': | |
| 321 | + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} | |
| 322 | + engines: {node: '>=18'} | |
| 323 | + cpu: [arm64] | |
| 324 | + os: [freebsd] | |
| 325 | + | |
| 207 | 326 | '@esbuild/freebsd-arm64@0.28.2': |
| 208 | 327 | resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} |
| 209 | 328 | engines: {node: '>=18'} |
| 210 | 329 | cpu: [arm64] |
| 211 | 330 | os: [freebsd] |
| 212 | 331 | |
| 332 | + '@esbuild/freebsd-x64@0.18.20': | |
| 333 | + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} | |
| 334 | + engines: {node: '>=12'} | |
| 335 | + cpu: [x64] | |
| 336 | + os: [freebsd] | |
| 337 | + | |
| 338 | + '@esbuild/freebsd-x64@0.25.12': | |
| 339 | + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} | |
| 340 | + engines: {node: '>=18'} | |
| 341 | + cpu: [x64] | |
| 342 | + os: [freebsd] | |
| 343 | + | |
| 213 | 344 | '@esbuild/freebsd-x64@0.28.2': |
| 214 | 345 | resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} |
| 215 | 346 | engines: {node: '>=18'} |
| 216 | 347 | cpu: [x64] |
| 217 | 348 | os: [freebsd] |
| 218 | 349 | |
| 350 | + '@esbuild/linux-arm64@0.18.20': | |
| 351 | + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} | |
| 352 | + engines: {node: '>=12'} | |
| 353 | + cpu: [arm64] | |
| 354 | + os: [linux] | |
| 355 | + | |
| 356 | + '@esbuild/linux-arm64@0.25.12': | |
| 357 | + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} | |
| 358 | + engines: {node: '>=18'} | |
| 359 | + cpu: [arm64] | |
| 360 | + os: [linux] | |
| 361 | + | |
| 219 | 362 | '@esbuild/linux-arm64@0.28.2': |
| 220 | 363 | resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} |
| 221 | 364 | engines: {node: '>=18'} |
| 222 | 365 | cpu: [arm64] |
| 223 | 366 | os: [linux] |
| 224 | 367 | |
| 368 | + '@esbuild/linux-arm@0.18.20': | |
| 369 | + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} | |
| 370 | + engines: {node: '>=12'} | |
| 371 | + cpu: [arm] | |
| 372 | + os: [linux] | |
| 373 | + | |
| 374 | + '@esbuild/linux-arm@0.25.12': | |
| 375 | + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} | |
| 376 | + engines: {node: '>=18'} | |
| 377 | + cpu: [arm] | |
| 378 | + os: [linux] | |
| 379 | + | |
| 225 | 380 | '@esbuild/linux-arm@0.28.2': |
| 226 | 381 | resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} |
| 227 | 382 | engines: {node: '>=18'} |
| 228 | 383 | cpu: [arm] |
| 229 | 384 | os: [linux] |
| 230 | 385 | |
| 386 | + '@esbuild/linux-ia32@0.18.20': | |
| 387 | + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} | |
| 388 | + engines: {node: '>=12'} | |
| 389 | + cpu: [ia32] | |
| 390 | + os: [linux] | |
| 391 | + | |
| 392 | + '@esbuild/linux-ia32@0.25.12': | |
| 393 | + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} | |
| 394 | + engines: {node: '>=18'} | |
| 395 | + cpu: [ia32] | |
| 396 | + os: [linux] | |
| 397 | + | |
| 231 | 398 | '@esbuild/linux-ia32@0.28.2': |
| 232 | 399 | resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} |
| 233 | 400 | engines: {node: '>=18'} |
| 234 | 401 | cpu: [ia32] |
| 235 | 402 | os: [linux] |
| 236 | 403 | |
| 404 | + '@esbuild/linux-loong64@0.18.20': | |
| 405 | + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} | |
| 406 | + engines: {node: '>=12'} | |
| 407 | + cpu: [loong64] | |
| 408 | + os: [linux] | |
| 409 | + | |
| 410 | + '@esbuild/linux-loong64@0.25.12': | |
| 411 | + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} | |
| 412 | + engines: {node: '>=18'} | |
| 413 | + cpu: [loong64] | |
| 414 | + os: [linux] | |
| 415 | + | |
| 237 | 416 | '@esbuild/linux-loong64@0.28.2': |
| 238 | 417 | resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} |
| 239 | 418 | engines: {node: '>=18'} |
| 240 | 419 | cpu: [loong64] |
| 241 | 420 | os: [linux] |
| 242 | 421 | |
| 422 | + '@esbuild/linux-mips64el@0.18.20': | |
| 423 | + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} | |
| 424 | + engines: {node: '>=12'} | |
| 425 | + cpu: [mips64el] | |
| 426 | + os: [linux] | |
| 427 | + | |
| 428 | + '@esbuild/linux-mips64el@0.25.12': | |
| 429 | + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} | |
| 430 | + engines: {node: '>=18'} | |
| 431 | + cpu: [mips64el] | |
| 432 | + os: [linux] | |
| 433 | + | |
| 243 | 434 | '@esbuild/linux-mips64el@0.28.2': |
| 244 | 435 | resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} |
| 245 | 436 | engines: {node: '>=18'} |
| 246 | 437 | cpu: [mips64el] |
| 247 | 438 | os: [linux] |
| 248 | 439 | |
| 440 | + '@esbuild/linux-ppc64@0.18.20': | |
| 441 | + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} | |
| 442 | + engines: {node: '>=12'} | |
| 443 | + cpu: [ppc64] | |
| 444 | + os: [linux] | |
| 445 | + | |
| 446 | + '@esbuild/linux-ppc64@0.25.12': | |
| 447 | + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} | |
| 448 | + engines: {node: '>=18'} | |
| 449 | + cpu: [ppc64] | |
| 450 | + os: [linux] | |
| 451 | + | |
| 249 | 452 | '@esbuild/linux-ppc64@0.28.2': |
| 250 | 453 | resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} |
| 251 | 454 | engines: {node: '>=18'} |
| 252 | 455 | cpu: [ppc64] |
| 253 | 456 | os: [linux] |
| 254 | 457 | |
| 458 | + '@esbuild/linux-riscv64@0.18.20': | |
| 459 | + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} | |
| 460 | + engines: {node: '>=12'} | |
| 461 | + cpu: [riscv64] | |
| 462 | + os: [linux] | |
| 463 | + | |
| 464 | + '@esbuild/linux-riscv64@0.25.12': | |
| 465 | + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} | |
| 466 | + engines: {node: '>=18'} | |
| 467 | + cpu: [riscv64] | |
| 468 | + os: [linux] | |
| 469 | + | |
| 255 | 470 | '@esbuild/linux-riscv64@0.28.2': |
| 256 | 471 | resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} |
| 257 | 472 | engines: {node: '>=18'} |
| 258 | 473 | cpu: [riscv64] |
| 259 | 474 | os: [linux] |
| 260 | 475 | |
| 476 | + '@esbuild/linux-s390x@0.18.20': | |
| 477 | + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} | |
| 478 | + engines: {node: '>=12'} | |
| 479 | + cpu: [s390x] | |
| 480 | + os: [linux] | |
| 481 | + | |
| 482 | + '@esbuild/linux-s390x@0.25.12': | |
| 483 | + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} | |
| 484 | + engines: {node: '>=18'} | |
| 485 | + cpu: [s390x] | |
| 486 | + os: [linux] | |
| 487 | + | |
| 261 | 488 | '@esbuild/linux-s390x@0.28.2': |
| 262 | 489 | resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} |
| 263 | 490 | engines: {node: '>=18'} |
| 264 | 491 | cpu: [s390x] |
| 265 | 492 | os: [linux] |
| 266 | 493 | |
| 494 | + '@esbuild/linux-x64@0.18.20': | |
| 495 | + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} | |
| 496 | + engines: {node: '>=12'} | |
| 497 | + cpu: [x64] | |
| 498 | + os: [linux] | |
| 499 | + | |
| 500 | + '@esbuild/linux-x64@0.25.12': | |
| 501 | + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} | |
| 502 | + engines: {node: '>=18'} | |
| 503 | + cpu: [x64] | |
| 504 | + os: [linux] | |
| 505 | + | |
| 267 | 506 | '@esbuild/linux-x64@0.28.2': |
| 268 | 507 | resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} |
| 269 | 508 | engines: {node: '>=18'} |
| 270 | 509 | cpu: [x64] |
| 271 | 510 | os: [linux] |
| 272 | 511 | |
| 512 | + '@esbuild/netbsd-arm64@0.25.12': | |
| 513 | + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} | |
| 514 | + engines: {node: '>=18'} | |
| 515 | + cpu: [arm64] | |
| 516 | + os: [netbsd] | |
| 517 | + | |
| 273 | 518 | '@esbuild/netbsd-arm64@0.28.2': |
| 274 | 519 | resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} |
| 275 | 520 | engines: {node: '>=18'} |
| 276 | 521 | cpu: [arm64] |
| 277 | 522 | os: [netbsd] |
| 278 | 523 | |
| 524 | + '@esbuild/netbsd-x64@0.18.20': | |
| 525 | + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} | |
| 526 | + engines: {node: '>=12'} | |
| 527 | + cpu: [x64] | |
| 528 | + os: [netbsd] | |
| 529 | + | |
| 530 | + '@esbuild/netbsd-x64@0.25.12': | |
| 531 | + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} | |
| 532 | + engines: {node: '>=18'} | |
| 533 | + cpu: [x64] | |
| 534 | + os: [netbsd] | |
| 535 | + | |
| 279 | 536 | '@esbuild/netbsd-x64@0.28.2': |
| 280 | 537 | resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} |
| 281 | 538 | engines: {node: '>=18'} |
| 282 | 539 | cpu: [x64] |
| 283 | 540 | os: [netbsd] |
| 284 | 541 | |
| 542 | + '@esbuild/openbsd-arm64@0.25.12': | |
| 543 | + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} | |
| 544 | + engines: {node: '>=18'} | |
| 545 | + cpu: [arm64] | |
| 546 | + os: [openbsd] | |
| 547 | + | |
| 285 | 548 | '@esbuild/openbsd-arm64@0.28.2': |
| 286 | 549 | resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} |
| 287 | 550 | engines: {node: '>=18'} |
| 288 | 551 | cpu: [arm64] |
| 289 | 552 | os: [openbsd] |
| 290 | 553 | |
| 554 | + '@esbuild/openbsd-x64@0.18.20': | |
| 555 | + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} | |
| 556 | + engines: {node: '>=12'} | |
| 557 | + cpu: [x64] | |
| 558 | + os: [openbsd] | |
| 559 | + | |
| 560 | + '@esbuild/openbsd-x64@0.25.12': | |
| 561 | + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} | |
| 562 | + engines: {node: '>=18'} | |
| 563 | + cpu: [x64] | |
| 564 | + os: [openbsd] | |
| 565 | + | |
| 291 | 566 | '@esbuild/openbsd-x64@0.28.2': |
| 292 | 567 | resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} |
| 293 | 568 | engines: {node: '>=18'} |
| 294 | 569 | cpu: [x64] |
| 295 | 570 | os: [openbsd] |
| 296 | 571 | |
| 572 | + '@esbuild/openharmony-arm64@0.25.12': | |
| 573 | + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} | |
| 574 | + engines: {node: '>=18'} | |
| 575 | + cpu: [arm64] | |
| 576 | + os: [openharmony] | |
| 577 | + | |
| 297 | 578 | '@esbuild/openharmony-arm64@0.28.2': |
| 298 | 579 | resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} |
| 299 | 580 | engines: {node: '>=18'} |
| 300 | 581 | cpu: [arm64] |
| 301 | 582 | os: [openharmony] |
| 302 | 583 | |
| 584 | + '@esbuild/sunos-x64@0.18.20': | |
| 585 | + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} | |
| 586 | + engines: {node: '>=12'} | |
| 587 | + cpu: [x64] | |
| 588 | + os: [sunos] | |
| 589 | + | |
| 590 | + '@esbuild/sunos-x64@0.25.12': | |
| 591 | + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} | |
| 592 | + engines: {node: '>=18'} | |
| 593 | + cpu: [x64] | |
| 594 | + os: [sunos] | |
| 595 | + | |
| 303 | 596 | '@esbuild/sunos-x64@0.28.2': |
| 304 | 597 | resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} |
| 305 | 598 | engines: {node: '>=18'} |
| 306 | 599 | cpu: [x64] |
| 307 | 600 | os: [sunos] |
| 308 | 601 | |
| 602 | + '@esbuild/win32-arm64@0.18.20': | |
| 603 | + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} | |
| 604 | + engines: {node: '>=12'} | |
| 605 | + cpu: [arm64] | |
| 606 | + os: [win32] | |
| 607 | + | |
| 608 | + '@esbuild/win32-arm64@0.25.12': | |
| 609 | + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} | |
| 610 | + engines: {node: '>=18'} | |
| 611 | + cpu: [arm64] | |
| 612 | + os: [win32] | |
| 613 | + | |
| 309 | 614 | '@esbuild/win32-arm64@0.28.2': |
| 310 | 615 | resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} |
| 311 | 616 | engines: {node: '>=18'} |
| 312 | 617 | cpu: [arm64] |
| 313 | 618 | os: [win32] |
| 314 | 619 | |
| 620 | + '@esbuild/win32-ia32@0.18.20': | |
| 621 | + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} | |
| 622 | + engines: {node: '>=12'} | |
| 623 | + cpu: [ia32] | |
| 624 | + os: [win32] | |
| 625 | + | |
| 626 | + '@esbuild/win32-ia32@0.25.12': | |
| 627 | + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} | |
| 628 | + engines: {node: '>=18'} | |
| 629 | + cpu: [ia32] | |
| 630 | + os: [win32] | |
| 631 | + | |
| 315 | 632 | '@esbuild/win32-ia32@0.28.2': |
| 316 | 633 | resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} |
| 317 | 634 | engines: {node: '>=18'} |
| 318 | 635 | cpu: [ia32] |
| 319 | 636 | os: [win32] |
| 320 | 637 | |
| 638 | + '@esbuild/win32-x64@0.18.20': | |
| 639 | + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} | |
| 640 | + engines: {node: '>=12'} | |
| 641 | + cpu: [x64] | |
| 642 | + os: [win32] | |
| 643 | + | |
| 644 | + '@esbuild/win32-x64@0.25.12': | |
| 645 | + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} | |
| 646 | + engines: {node: '>=18'} | |
| 647 | + cpu: [x64] | |
| 648 | + os: [win32] | |
| 649 | + | |
| 321 | 650 | '@esbuild/win32-x64@0.28.2': |
| 322 | 651 | resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} |
| 323 | 652 | engines: {node: '>=18'} |
@@ -660,6 +989,10 @@ packages: | ||
| 660 | 989 | cpu: [x64] |
| 661 | 990 | os: [win32] |
| 662 | 991 | |
| 992 | + '@noble/hashes@2.4.0': | |
| 993 | + resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} | |
| 994 | + engines: {node: '>= 20.19.0'} | |
| 995 | + | |
| 663 | 996 | '@nodelib/fs.scandir@2.1.5': |
| 664 | 997 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} |
| 665 | 998 | engines: {node: '>= 8'} |
@@ -945,6 +1278,9 @@ packages: | ||
| 945 | 1278 | '@types/geojson@7946.0.16': |
| 946 | 1279 | resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} |
| 947 | 1280 | |
| 1281 | + '@types/heic-convert@2.1.1': | |
| 1282 | + resolution: {integrity: sha512-+s14762Nf62z9zziIs7ItvAkSUCS3ls4Z5XPT9BlVve3Q3S4DAnf6qekffMTvEWycSUF+kulkGaSN65fK6eKvg==} | |
| 1283 | + | |
| 948 | 1284 | '@types/json-schema@7.0.15': |
| 949 | 1285 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} |
| 950 | 1286 | |
@@ -954,6 +1290,9 @@ packages: | ||
| 954 | 1290 | '@types/node@20.19.43': |
| 955 | 1291 | resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} |
| 956 | 1292 | |
| 1293 | + '@types/pg@8.23.1': | |
| 1294 | + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} | |
| 1295 | + | |
| 957 | 1296 | '@types/react-dom@19.3.0': |
| 958 | 1297 | resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} |
| 959 | 1298 | peerDependencies: |
@@ -1288,6 +1627,9 @@ packages: | ||
| 1288 | 1627 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} |
| 1289 | 1628 | hasBin: true |
| 1290 | 1629 | |
| 1630 | + buffer-from@1.1.2: | |
| 1631 | + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} | |
| 1632 | + | |
| 1291 | 1633 | call-bind-apply-helpers@1.0.2: |
| 1292 | 1634 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} |
| 1293 | 1635 | engines: {node: '>= 0.4'} |
@@ -1440,6 +1782,105 @@ packages: | ||
| 1440 | 1782 | resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} |
| 1441 | 1783 | engines: {node: '>=0.10.0'} |
| 1442 | 1784 | |
| 1785 | + drizzle-kit@0.31.11: | |
| 1786 | + resolution: {integrity: sha512-YCYqxTLIB2OCqf6w9/Vef13baBVbwQIPcbT4Y56AfO6B9ajZhDhD8Jkveg/hJvT/iuMloKD/IYYkyMMNhr7Kqg==} | |
| 1787 | + hasBin: true | |
| 1788 | + | |
| 1789 | + drizzle-orm@0.45.4: | |
| 1790 | + resolution: {integrity: sha512-NiM1YZpJ2UxyeE67DpDDZ+Tsw4YLX3trSlkNT7Tm9eKoUNb1LvaAaQzXgYQsWs+AQ9TUmmwMmBOCXuqqxUWKKQ==} | |
| 1791 | + peerDependencies: | |
| 1792 | + '@aws-sdk/client-rds-data': '>=3' | |
| 1793 | + '@cloudflare/workers-types': '>=4' | |
| 1794 | + '@electric-sql/pglite': '>=0.2.0' | |
| 1795 | + '@libsql/client': '>=0.10.0' | |
| 1796 | + '@libsql/client-wasm': '>=0.10.0' | |
| 1797 | + '@neondatabase/serverless': '>=0.10.0' | |
| 1798 | + '@netlify/db': '>=0.4.0' | |
| 1799 | + '@op-engineering/op-sqlite': '>=2' | |
| 1800 | + '@opentelemetry/api': ^1.4.1 | |
| 1801 | + '@planetscale/database': '>=1.13' | |
| 1802 | + '@prisma/client': '*' | |
| 1803 | + '@tidbcloud/serverless': '*' | |
| 1804 | + '@types/better-sqlite3': '*' | |
| 1805 | + '@types/pg': '*' | |
| 1806 | + '@types/sql.js': '*' | |
| 1807 | + '@upstash/redis': '>=1.34.7' | |
| 1808 | + '@vercel/postgres': '>=0.8.0' | |
| 1809 | + '@xata.io/client': '*' | |
| 1810 | + better-sqlite3: '>=7' | |
| 1811 | + bun-types: '*' | |
| 1812 | + expo-sqlite: '>=14.0.0' | |
| 1813 | + gel: '>=2' | |
| 1814 | + knex: '*' | |
| 1815 | + kysely: '*' | |
| 1816 | + mysql2: '>=2' | |
| 1817 | + pg: '>=8' | |
| 1818 | + postgres: '>=3' | |
| 1819 | + prisma: '*' | |
| 1820 | + sql.js: '>=1' | |
| 1821 | + sqlite3: '>=5' | |
| 1822 | + peerDependenciesMeta: | |
| 1823 | + '@aws-sdk/client-rds-data': | |
| 1824 | + optional: true | |
| 1825 | + '@cloudflare/workers-types': | |
| 1826 | + optional: true | |
| 1827 | + '@electric-sql/pglite': | |
| 1828 | + optional: true | |
| 1829 | + '@libsql/client': | |
| 1830 | + optional: true | |
| 1831 | + '@libsql/client-wasm': | |
| 1832 | + optional: true | |
| 1833 | + '@neondatabase/serverless': | |
| 1834 | + optional: true | |
| 1835 | + '@netlify/db': | |
| 1836 | + optional: true | |
| 1837 | + '@op-engineering/op-sqlite': | |
| 1838 | + optional: true | |
| 1839 | + '@opentelemetry/api': | |
| 1840 | + optional: true | |
| 1841 | + '@planetscale/database': | |
| 1842 | + optional: true | |
| 1843 | + '@prisma/client': | |
| 1844 | + optional: true | |
| 1845 | + '@tidbcloud/serverless': | |
| 1846 | + optional: true | |
| 1847 | + '@types/better-sqlite3': | |
| 1848 | + optional: true | |
| 1849 | + '@types/pg': | |
| 1850 | + optional: true | |
| 1851 | + '@types/sql.js': | |
| 1852 | + optional: true | |
| 1853 | + '@upstash/redis': | |
| 1854 | + optional: true | |
| 1855 | + '@vercel/postgres': | |
| 1856 | + optional: true | |
| 1857 | + '@xata.io/client': | |
| 1858 | + optional: true | |
| 1859 | + better-sqlite3: | |
| 1860 | + optional: true | |
| 1861 | + bun-types: | |
| 1862 | + optional: true | |
| 1863 | + expo-sqlite: | |
| 1864 | + optional: true | |
| 1865 | + gel: | |
| 1866 | + optional: true | |
| 1867 | + knex: | |
| 1868 | + optional: true | |
| 1869 | + kysely: | |
| 1870 | + optional: true | |
| 1871 | + mysql2: | |
| 1872 | + optional: true | |
| 1873 | + pg: | |
| 1874 | + optional: true | |
| 1875 | + postgres: | |
| 1876 | + optional: true | |
| 1877 | + prisma: | |
| 1878 | + optional: true | |
| 1879 | + sql.js: | |
| 1880 | + optional: true | |
| 1881 | + sqlite3: | |
| 1882 | + optional: true | |
| 1883 | + | |
| 1443 | 1884 | dunder-proto@1.0.1: |
| 1444 | 1885 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} |
| 1445 | 1886 | engines: {node: '>= 0.4'} |
@@ -1499,6 +1940,16 @@ packages: | ||
| 1499 | 1940 | es-toolkit@1.52.0: |
| 1500 | 1941 | resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} |
| 1501 | 1942 | |
| 1943 | + esbuild@0.18.20: | |
| 1944 | + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} | |
| 1945 | + engines: {node: '>=12'} | |
| 1946 | + hasBin: true | |
| 1947 | + | |
| 1948 | + esbuild@0.25.12: | |
| 1949 | + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} | |
| 1950 | + engines: {node: '>=18'} | |
| 1951 | + hasBin: true | |
| 1952 | + | |
| 1502 | 1953 | esbuild@0.28.2: |
| 1503 | 1954 | resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} |
| 1504 | 1955 | engines: {node: '>=18'} |
@@ -1789,6 +2240,14 @@ packages: | ||
| 1789 | 2240 | resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} |
| 1790 | 2241 | engines: {node: '>= 0.4'} |
| 1791 | 2242 | |
| 2243 | + heic-convert@2.1.0: | |
| 2244 | + resolution: {integrity: sha512-1qDuRvEHifTVAj3pFIgkqGgJIr0M3X7cxEPjEp0oG4mo8GFjq99DpCo8Eg3kg17Cy0MTjxpFdoBHOatj7ZVKtg==} | |
| 2245 | + engines: {node: '>=12.0.0'} | |
| 2246 | + | |
| 2247 | + heic-decode@2.1.0: | |
| 2248 | + resolution: {integrity: sha512-0fB3O3WMk38+PScbHLVp66jcNhsZ/ErtQ6u2lMYu/YxXgbBtl+oKOhGQHa4RpvE68k8IzbWkABzHnyAIjR758A==} | |
| 2249 | + engines: {node: '>=8.0.0'} | |
| 2250 | + | |
| 1792 | 2251 | hermes-estree@0.25.1: |
| 1793 | 2252 | resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} |
| 1794 | 2253 | |
@@ -1943,6 +2402,9 @@ packages: | ||
| 1943 | 2402 | resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} |
| 1944 | 2403 | hasBin: true |
| 1945 | 2404 | |
| 2405 | + jpeg-js@0.4.4: | |
| 2406 | + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} | |
| 2407 | + | |
| 1946 | 2408 | js-tokens@4.0.0: |
| 1947 | 2409 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} |
| 1948 | 2410 | |
@@ -2001,6 +2463,10 @@ packages: | ||
| 2001 | 2463 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} |
| 2002 | 2464 | engines: {node: '>= 0.8.0'} |
| 2003 | 2465 | |
| 2466 | + libheif-js@1.23.5: | |
| 2467 | + resolution: {integrity: sha512-umXZPthnWZtF3iG/5mQG+AZ2V6i0UeYpo3z2CkggTEgL8dKmIIwJT6TDuAMuabXa1yTW8uJ1ZAgR+CxNTklYYw==} | |
| 2468 | + engines: {node: '>=8.0.0'} | |
| 2469 | + | |
| 2004 | 2470 | lightningcss-android-arm64@1.32.0: |
| 2005 | 2471 | resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} |
| 2006 | 2472 | engines: {node: '>= 12.0.0'} |
@@ -2206,6 +2672,11 @@ packages: | ||
| 2206 | 2672 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} |
| 2207 | 2673 | hasBin: true |
| 2208 | 2674 | |
| 2675 | + nanoid@6.0.2: | |
| 2676 | + resolution: {integrity: sha512-hIvVOb9Qr1p/9CNNLOe0OlWvgx/ziTsVwKN/GDSsuArN2K0wDiafP3s0SWmsm7o2BdnyS8Z9Z64NFBUbn6ziBg==} | |
| 2677 | + engines: {node: ^22 || ^24 || >=26} | |
| 2678 | + hasBin: true | |
| 2679 | + | |
| 2209 | 2680 | napi-postinstall@0.3.4: |
| 2210 | 2681 | resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} |
| 2211 | 2682 | engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} |
@@ -2287,6 +2758,9 @@ packages: | ||
| 2287 | 2758 | resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} |
| 2288 | 2759 | engines: {node: '>= 0.8.0'} |
| 2289 | 2760 | |
| 2761 | + otpauth@9.5.2: | |
| 2762 | + resolution: {integrity: sha512-GQ5emWR/x1tcExT62IBT0UfO95wZzJZyxYOJOGVeQF47SYEN9vmh0vISvDZaNMuFJRG+IaWCKtfm+t9Bfoal6w==} | |
| 2763 | + | |
| 2290 | 2764 | own-keys@1.0.2: |
| 2291 | 2765 | resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} |
| 2292 | 2766 | engines: {node: '>= 0.4'} |
@@ -2318,6 +2792,17 @@ packages: | ||
| 2318 | 2792 | resolution: {integrity: sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w==} |
| 2319 | 2793 | hasBin: true |
| 2320 | 2794 | |
| 2795 | + pg-int8@1.0.1: | |
| 2796 | + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} | |
| 2797 | + engines: {node: '>=4.0.0'} | |
| 2798 | + | |
| 2799 | + pg-protocol@1.16.1: | |
| 2800 | + resolution: {integrity: sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==} | |
| 2801 | + | |
| 2802 | + pg-types@2.2.0: | |
| 2803 | + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} | |
| 2804 | + engines: {node: '>=4'} | |
| 2805 | + | |
| 2321 | 2806 | picocolors@1.1.1: |
| 2322 | 2807 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} |
| 2323 | 2808 | |
@@ -2329,10 +2814,17 @@ packages: | ||
| 2329 | 2814 | resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} |
| 2330 | 2815 | engines: {node: '>=12'} |
| 2331 | 2816 | |
| 2817 | + pngjs@6.0.0: | |
| 2818 | + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} | |
| 2819 | + engines: {node: '>=12.13.0'} | |
| 2820 | + | |
| 2332 | 2821 | possible-typed-array-names@1.1.0: |
| 2333 | 2822 | resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} |
| 2334 | 2823 | engines: {node: '>= 0.4'} |
| 2335 | 2824 | |
| 2825 | + postal-mime@2.7.6: | |
| 2826 | + resolution: {integrity: sha512-UUlyE2KlxmvwMGq060onF/VWU3zD6dVW31FwokQ5v26jWd35fbs2MoTFzh+jXnPwAyV2MULMKXcBInGOl5TO6Q==} | |
| 2827 | + | |
| 2336 | 2828 | postcss@8.5.23: |
| 2337 | 2829 | resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} |
| 2338 | 2830 | engines: {node: ^10 || ^12 || >=14} |
@@ -2341,6 +2833,26 @@ packages: | ||
| 2341 | 2833 | resolution: {integrity: sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==} |
| 2342 | 2834 | engines: {node: ^10 || ^12 || >=14} |
| 2343 | 2835 | |
| 2836 | + postgres-array@2.0.0: | |
| 2837 | + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} | |
| 2838 | + engines: {node: '>=4'} | |
| 2839 | + | |
| 2840 | + postgres-bytea@1.0.1: | |
| 2841 | + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} | |
| 2842 | + engines: {node: '>=0.10.0'} | |
| 2843 | + | |
| 2844 | + postgres-date@1.0.7: | |
| 2845 | + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} | |
| 2846 | + engines: {node: '>=0.10.0'} | |
| 2847 | + | |
| 2848 | + postgres-interval@1.2.0: | |
| 2849 | + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} | |
| 2850 | + engines: {node: '>=0.10.0'} | |
| 2851 | + | |
| 2852 | + postgres@3.4.9: | |
| 2853 | + resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==} | |
| 2854 | + engines: {node: '>=12'} | |
| 2855 | + | |
| 2344 | 2856 | potpack@2.1.0: |
| 2345 | 2857 | resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} |
| 2346 | 2858 | |
@@ -2419,6 +2931,15 @@ packages: | ||
| 2419 | 2931 | reselect@5.2.0: |
| 2420 | 2932 | resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} |
| 2421 | 2933 | |
| 2934 | + resend@6.34.1: | |
| 2935 | + resolution: {integrity: sha512-3jo/j1WAW7CBkE6pzK4qLjZCfHRLIhY3ph3x/wzU+MN51cSw3EtReGce8S1cA1R0ipNDOdFYU5Lmt5r563lvYw==} | |
| 2936 | + engines: {node: '>=20'} | |
| 2937 | + peerDependencies: | |
| 2938 | + '@react-email/render': '*' | |
| 2939 | + peerDependenciesMeta: | |
| 2940 | + '@react-email/render': | |
| 2941 | + optional: true | |
| 2942 | + | |
| 2422 | 2943 | resolve-from@4.0.0: |
| 2423 | 2944 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} |
| 2424 | 2945 | engines: {node: '>=4'} |
@@ -2519,6 +3040,13 @@ packages: | ||
| 2519 | 3040 | resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} |
| 2520 | 3041 | engines: {node: '>=0.10.0'} |
| 2521 | 3042 | |
| 3043 | + source-map-support@0.5.21: | |
| 3044 | + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} | |
| 3045 | + | |
| 3046 | + source-map@0.6.1: | |
| 3047 | + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} | |
| 3048 | + engines: {node: '>=0.10.0'} | |
| 3049 | + | |
| 2522 | 3050 | stable-hash@0.0.5: |
| 2523 | 3051 | resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} |
| 2524 | 3052 | |
@@ -2806,6 +3334,10 @@ packages: | ||
| 2806 | 3334 | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} |
| 2807 | 3335 | engines: {node: '>=0.10.0'} |
| 2808 | 3336 | |
| 3337 | + xtend@4.0.2: | |
| 3338 | + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} | |
| 3339 | + engines: {node: '>=0.4'} | |
| 3340 | + | |
| 2809 | 3341 | yallist@3.1.1: |
| 2810 | 3342 | resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} |
| 2811 | 3343 | |
@@ -2936,6 +3468,8 @@ snapshots: | ||
| 2936 | 3468 | '@babel/helper-string-parser': 7.29.7 |
| 2937 | 3469 | '@babel/helper-validator-identifier': 7.29.7 |
| 2938 | 3470 | |
| 3471 | + '@drizzle-team/brocli@0.10.2': {} | |
| 3472 | + | |
| 2939 | 3473 | '@emnapi/core@1.10.0': |
| 2940 | 3474 | dependencies: |
| 2941 | 3475 | '@emnapi/wasi-threads': 1.2.1 |
@@ -2957,81 +3491,235 @@ snapshots: | ||
| 2957 | 3491 | tslib: 2.8.1 |
| 2958 | 3492 | optional: true |
| 2959 | 3493 | |
| 3494 | + '@esbuild-kit/core-utils@3.3.2': | |
| 3495 | + dependencies: | |
| 3496 | + esbuild: 0.18.20 | |
| 3497 | + source-map-support: 0.5.21 | |
| 3498 | + | |
| 3499 | + '@esbuild-kit/esm-loader@2.6.5': | |
| 3500 | + dependencies: | |
| 3501 | + '@esbuild-kit/core-utils': 3.3.2 | |
| 3502 | + get-tsconfig: 4.14.3 | |
| 3503 | + | |
| 3504 | + '@esbuild/aix-ppc64@0.25.12': | |
| 3505 | + optional: true | |
| 3506 | + | |
| 2960 | 3507 | '@esbuild/aix-ppc64@0.28.2': |
| 2961 | 3508 | optional: true |
| 2962 | 3509 | |
| 3510 | + '@esbuild/android-arm64@0.18.20': | |
| 3511 | + optional: true | |
| 3512 | + | |
| 3513 | + '@esbuild/android-arm64@0.25.12': | |
| 3514 | + optional: true | |
| 3515 | + | |
| 2963 | 3516 | '@esbuild/android-arm64@0.28.2': |
| 2964 | 3517 | optional: true |
| 2965 | 3518 | |
| 3519 | + '@esbuild/android-arm@0.18.20': | |
| 3520 | + optional: true | |
| 3521 | + | |
| 3522 | + '@esbuild/android-arm@0.25.12': | |
| 3523 | + optional: true | |
| 3524 | + | |
| 2966 | 3525 | '@esbuild/android-arm@0.28.2': |
| 2967 | 3526 | optional: true |
| 2968 | 3527 | |
| 3528 | + '@esbuild/android-x64@0.18.20': | |
| 3529 | + optional: true | |
| 3530 | + | |
| 3531 | + '@esbuild/android-x64@0.25.12': | |
| 3532 | + optional: true | |
| 3533 | + | |
| 2969 | 3534 | '@esbuild/android-x64@0.28.2': |
| 2970 | 3535 | optional: true |
| 2971 | 3536 | |
| 3537 | + '@esbuild/darwin-arm64@0.18.20': | |
| 3538 | + optional: true | |
| 3539 | + | |
| 3540 | + '@esbuild/darwin-arm64@0.25.12': | |
| 3541 | + optional: true | |
| 3542 | + | |
| 2972 | 3543 | '@esbuild/darwin-arm64@0.28.2': |
| 2973 | 3544 | optional: true |
| 2974 | 3545 | |
| 3546 | + '@esbuild/darwin-x64@0.18.20': | |
| 3547 | + optional: true | |
| 3548 | + | |
| 3549 | + '@esbuild/darwin-x64@0.25.12': | |
| 3550 | + optional: true | |
| 3551 | + | |
| 2975 | 3552 | '@esbuild/darwin-x64@0.28.2': |
| 2976 | 3553 | optional: true |
| 2977 | 3554 | |
| 3555 | + '@esbuild/freebsd-arm64@0.18.20': | |
| 3556 | + optional: true | |
| 3557 | + | |
| 3558 | + '@esbuild/freebsd-arm64@0.25.12': | |
| 3559 | + optional: true | |
| 3560 | + | |
| 2978 | 3561 | '@esbuild/freebsd-arm64@0.28.2': |
| 2979 | 3562 | optional: true |
| 2980 | 3563 | |
| 3564 | + '@esbuild/freebsd-x64@0.18.20': | |
| 3565 | + optional: true | |
| 3566 | + | |
| 3567 | + '@esbuild/freebsd-x64@0.25.12': | |
| 3568 | + optional: true | |
| 3569 | + | |
| 2981 | 3570 | '@esbuild/freebsd-x64@0.28.2': |
| 2982 | 3571 | optional: true |
| 2983 | 3572 | |
| 3573 | + '@esbuild/linux-arm64@0.18.20': | |
| 3574 | + optional: true | |
| 3575 | + | |
| 3576 | + '@esbuild/linux-arm64@0.25.12': | |
| 3577 | + optional: true | |
| 3578 | + | |
| 2984 | 3579 | '@esbuild/linux-arm64@0.28.2': |
| 2985 | 3580 | optional: true |
| 2986 | 3581 | |
| 3582 | + '@esbuild/linux-arm@0.18.20': | |
| 3583 | + optional: true | |
| 3584 | + | |
| 3585 | + '@esbuild/linux-arm@0.25.12': | |
| 3586 | + optional: true | |
| 3587 | + | |
| 2987 | 3588 | '@esbuild/linux-arm@0.28.2': |
| 2988 | 3589 | optional: true |
| 2989 | 3590 | |
| 3591 | + '@esbuild/linux-ia32@0.18.20': | |
| 3592 | + optional: true | |
| 3593 | + | |
| 3594 | + '@esbuild/linux-ia32@0.25.12': | |
| 3595 | + optional: true | |
| 3596 | + | |
| 2990 | 3597 | '@esbuild/linux-ia32@0.28.2': |
| 2991 | 3598 | optional: true |
| 2992 | 3599 | |
| 3600 | + '@esbuild/linux-loong64@0.18.20': | |
| 3601 | + optional: true | |
| 3602 | + | |
| 3603 | + '@esbuild/linux-loong64@0.25.12': | |
| 3604 | + optional: true | |
| 3605 | + | |
| 2993 | 3606 | '@esbuild/linux-loong64@0.28.2': |
| 2994 | 3607 | optional: true |
| 2995 | 3608 | |
| 3609 | + '@esbuild/linux-mips64el@0.18.20': | |
| 3610 | + optional: true | |
| 3611 | + | |
| 3612 | + '@esbuild/linux-mips64el@0.25.12': | |
| 3613 | + optional: true | |
| 3614 | + | |
| 2996 | 3615 | '@esbuild/linux-mips64el@0.28.2': |
| 2997 | 3616 | optional: true |
| 2998 | 3617 | |
| 3618 | + '@esbuild/linux-ppc64@0.18.20': | |
| 3619 | + optional: true | |
| 3620 | + | |
| 3621 | + '@esbuild/linux-ppc64@0.25.12': | |
| 3622 | + optional: true | |
| 3623 | + | |
| 2999 | 3624 | '@esbuild/linux-ppc64@0.28.2': |
| 3000 | 3625 | optional: true |
| 3001 | 3626 | |
| 3627 | + '@esbuild/linux-riscv64@0.18.20': | |
| 3628 | + optional: true | |
| 3629 | + | |
| 3630 | + '@esbuild/linux-riscv64@0.25.12': | |
| 3631 | + optional: true | |
| 3632 | + | |
| 3002 | 3633 | '@esbuild/linux-riscv64@0.28.2': |
| 3003 | 3634 | optional: true |
| 3004 | 3635 | |
| 3636 | + '@esbuild/linux-s390x@0.18.20': | |
| 3637 | + optional: true | |
| 3638 | + | |
| 3639 | + '@esbuild/linux-s390x@0.25.12': | |
| 3640 | + optional: true | |
| 3641 | + | |
| 3005 | 3642 | '@esbuild/linux-s390x@0.28.2': |
| 3006 | 3643 | optional: true |
| 3007 | 3644 | |
| 3645 | + '@esbuild/linux-x64@0.18.20': | |
| 3646 | + optional: true | |
| 3647 | + | |
| 3648 | + '@esbuild/linux-x64@0.25.12': | |
| 3649 | + optional: true | |
| 3650 | + | |
| 3008 | 3651 | '@esbuild/linux-x64@0.28.2': |
| 3009 | 3652 | optional: true |
| 3010 | 3653 | |
| 3654 | + '@esbuild/netbsd-arm64@0.25.12': | |
| 3655 | + optional: true | |
| 3656 | + | |
| 3011 | 3657 | '@esbuild/netbsd-arm64@0.28.2': |
| 3012 | 3658 | optional: true |
| 3013 | 3659 | |
| 3660 | + '@esbuild/netbsd-x64@0.18.20': | |
| 3661 | + optional: true | |
| 3662 | + | |
| 3663 | + '@esbuild/netbsd-x64@0.25.12': | |
| 3664 | + optional: true | |
| 3665 | + | |
| 3014 | 3666 | '@esbuild/netbsd-x64@0.28.2': |
| 3015 | 3667 | optional: true |
| 3016 | 3668 | |
| 3669 | + '@esbuild/openbsd-arm64@0.25.12': | |
| 3670 | + optional: true | |
| 3671 | + | |
| 3017 | 3672 | '@esbuild/openbsd-arm64@0.28.2': |
| 3018 | 3673 | optional: true |
| 3019 | 3674 | |
| 3675 | + '@esbuild/openbsd-x64@0.18.20': | |
| 3676 | + optional: true | |
| 3677 | + | |
| 3678 | + '@esbuild/openbsd-x64@0.25.12': | |
| 3679 | + optional: true | |
| 3680 | + | |
| 3020 | 3681 | '@esbuild/openbsd-x64@0.28.2': |
| 3021 | 3682 | optional: true |
| 3022 | 3683 | |
| 3684 | + '@esbuild/openharmony-arm64@0.25.12': | |
| 3685 | + optional: true | |
| 3686 | + | |
| 3023 | 3687 | '@esbuild/openharmony-arm64@0.28.2': |
| 3024 | 3688 | optional: true |
| 3025 | 3689 | |
| 3690 | + '@esbuild/sunos-x64@0.18.20': | |
| 3691 | + optional: true | |
| 3692 | + | |
| 3693 | + '@esbuild/sunos-x64@0.25.12': | |
| 3694 | + optional: true | |
| 3695 | + | |
| 3026 | 3696 | '@esbuild/sunos-x64@0.28.2': |
| 3027 | 3697 | optional: true |
| 3028 | 3698 | |
| 3699 | + '@esbuild/win32-arm64@0.18.20': | |
| 3700 | + optional: true | |
| 3701 | + | |
| 3702 | + '@esbuild/win32-arm64@0.25.12': | |
| 3703 | + optional: true | |
| 3704 | + | |
| 3029 | 3705 | '@esbuild/win32-arm64@0.28.2': |
| 3030 | 3706 | optional: true |
| 3031 | 3707 | |
| 3708 | + '@esbuild/win32-ia32@0.18.20': | |
| 3709 | + optional: true | |
| 3710 | + | |
| 3711 | + '@esbuild/win32-ia32@0.25.12': | |
| 3712 | + optional: true | |
| 3713 | + | |
| 3032 | 3714 | '@esbuild/win32-ia32@0.28.2': |
| 3033 | 3715 | optional: true |
| 3034 | 3716 | |
| 3717 | + '@esbuild/win32-x64@0.18.20': | |
| 3718 | + optional: true | |
| 3719 | + | |
| 3720 | + '@esbuild/win32-x64@0.25.12': | |
| 3721 | + optional: true | |
| 3722 | + | |
| 3035 | 3723 | '@esbuild/win32-x64@0.28.2': |
| 3036 | 3724 | optional: true |
| 3037 | 3725 | |
@@ -3102,8 +3790,7 @@ snapshots: | ||
| 3102 | 3790 | |
| 3103 | 3791 | '@humanwhocodes/retry@0.4.3': {} |
| 3104 | 3792 | |
| 3105 | − '@img/colour@1.1.0': | |
| 3106 | − optional: true | |
| 3793 | + '@img/colour@1.1.0': {} | |
| 3107 | 3794 | |
| 3108 | 3795 | '@img/sharp-darwin-arm64@0.35.5': |
| 3109 | 3796 | optionalDependencies: |
@@ -3305,6 +3992,8 @@ snapshots: | ||
| 3305 | 3992 | '@next/swc-win32-x64-msvc@16.4.2': |
| 3306 | 3993 | optional: true |
| 3307 | 3994 | |
| 3995 | + '@noble/hashes@2.4.0': {} | |
| 3996 | + | |
| 3308 | 3997 | '@nodelib/fs.scandir@2.1.5': |
| 3309 | 3998 | dependencies: |
| 3310 | 3999 | '@nodelib/fs.stat': 2.0.5 |
@@ -3506,6 +4195,8 @@ snapshots: | ||
| 3506 | 4195 | |
| 3507 | 4196 | '@types/geojson@7946.0.16': {} |
| 3508 | 4197 | |
| 4198 | + '@types/heic-convert@2.1.1': {} | |
| 4199 | + | |
| 3509 | 4200 | '@types/json-schema@7.0.15': {} |
| 3510 | 4201 | |
| 3511 | 4202 | '@types/json5@0.0.29': {} |
@@ -3514,6 +4205,12 @@ snapshots: | ||
| 3514 | 4205 | dependencies: |
| 3515 | 4206 | undici-types: 6.21.0 |
| 3516 | 4207 | |
| 4208 | + '@types/pg@8.23.1': | |
| 4209 | + dependencies: | |
| 4210 | + '@types/node': 20.19.43 | |
| 4211 | + pg-protocol: 1.16.1 | |
| 4212 | + pg-types: 2.2.0 | |
| 4213 | + | |
| 3517 | 4214 | '@types/react-dom@19.3.0(@types/react@19.3.0)': |
| 3518 | 4215 | dependencies: |
| 3519 | 4216 | '@types/react': 19.3.0 |
@@ -3838,6 +4535,8 @@ snapshots: | ||
| 3838 | 4535 | node-releases: 2.0.59 |
| 3839 | 4536 | update-browserslist-db: 1.3.4(browserslist@4.29.3) |
| 3840 | 4537 | |
| 4538 | + buffer-from@1.1.2: {} | |
| 4539 | + | |
| 3841 | 4540 | call-bind-apply-helpers@1.0.2: |
| 3842 | 4541 | dependencies: |
| 3843 | 4542 | es-errors: 1.3.0 |
@@ -3976,6 +4675,21 @@ snapshots: | ||
| 3976 | 4675 | dependencies: |
| 3977 | 4676 | esutils: 2.0.3 |
| 3978 | 4677 | |
| 4678 | + drizzle-kit@0.31.11: | |
| 4679 | + dependencies: | |
| 4680 | + '@drizzle-team/brocli': 0.10.2 | |
| 4681 | + '@esbuild-kit/esm-loader': 2.6.5 | |
| 4682 | + esbuild: 0.25.12 | |
| 4683 | + tsx: 4.23.15 | |
| 4684 | + | |
| 4685 | + drizzle-orm@0.45.4(@opentelemetry/api@1.9.1)(@types/better-sqlite3@9.6.0)(@types/pg@8.23.1)(better-sqlite3@13.0.3)(postgres@3.4.9): | |
| 4686 | + optionalDependencies: | |
| 4687 | + '@opentelemetry/api': 1.9.1 | |
| 4688 | + '@types/better-sqlite3': 9.6.0 | |
| 4689 | + '@types/pg': 8.23.1 | |
| 4690 | + better-sqlite3: 13.0.3 | |
| 4691 | + postgres: 3.4.9 | |
| 4692 | + | |
| 3979 | 4693 | dunder-proto@1.0.1: |
| 3980 | 4694 | dependencies: |
| 3981 | 4695 | call-bind-apply-helpers: 1.0.2 |
@@ -4108,6 +4822,60 @@ snapshots: | ||
| 4108 | 4822 | |
| 4109 | 4823 | es-toolkit@1.52.0: {} |
| 4110 | 4824 | |
| 4825 | + esbuild@0.18.20: | |
| 4826 | + optionalDependencies: | |
| 4827 | + '@esbuild/android-arm': 0.18.20 | |
| 4828 | + '@esbuild/android-arm64': 0.18.20 | |
| 4829 | + '@esbuild/android-x64': 0.18.20 | |
| 4830 | + '@esbuild/darwin-arm64': 0.18.20 | |
| 4831 | + '@esbuild/darwin-x64': 0.18.20 | |
| 4832 | + '@esbuild/freebsd-arm64': 0.18.20 | |
| 4833 | + '@esbuild/freebsd-x64': 0.18.20 | |
| 4834 | + '@esbuild/linux-arm': 0.18.20 | |
| 4835 | + '@esbuild/linux-arm64': 0.18.20 | |
| 4836 | + '@esbuild/linux-ia32': 0.18.20 | |
| 4837 | + '@esbuild/linux-loong64': 0.18.20 | |
| 4838 | + '@esbuild/linux-mips64el': 0.18.20 | |
| 4839 | + '@esbuild/linux-ppc64': 0.18.20 | |
| 4840 | + '@esbuild/linux-riscv64': 0.18.20 | |
| 4841 | + '@esbuild/linux-s390x': 0.18.20 | |
| 4842 | + '@esbuild/linux-x64': 0.18.20 | |
| 4843 | + '@esbuild/netbsd-x64': 0.18.20 | |
| 4844 | + '@esbuild/openbsd-x64': 0.18.20 | |
| 4845 | + '@esbuild/sunos-x64': 0.18.20 | |
| 4846 | + '@esbuild/win32-arm64': 0.18.20 | |
| 4847 | + '@esbuild/win32-ia32': 0.18.20 | |
| 4848 | + '@esbuild/win32-x64': 0.18.20 | |
| 4849 | + | |
| 4850 | + esbuild@0.25.12: | |
| 4851 | + optionalDependencies: | |
| 4852 | + '@esbuild/aix-ppc64': 0.25.12 | |
| 4853 | + '@esbuild/android-arm': 0.25.12 | |
| 4854 | + '@esbuild/android-arm64': 0.25.12 | |
| 4855 | + '@esbuild/android-x64': 0.25.12 | |
| 4856 | + '@esbuild/darwin-arm64': 0.25.12 | |
| 4857 | + '@esbuild/darwin-x64': 0.25.12 | |
| 4858 | + '@esbuild/freebsd-arm64': 0.25.12 | |
| 4859 | + '@esbuild/freebsd-x64': 0.25.12 | |
| 4860 | + '@esbuild/linux-arm': 0.25.12 | |
| 4861 | + '@esbuild/linux-arm64': 0.25.12 | |
| 4862 | + '@esbuild/linux-ia32': 0.25.12 | |
| 4863 | + '@esbuild/linux-loong64': 0.25.12 | |
| 4864 | + '@esbuild/linux-mips64el': 0.25.12 | |
| 4865 | + '@esbuild/linux-ppc64': 0.25.12 | |
| 4866 | + '@esbuild/linux-riscv64': 0.25.12 | |
| 4867 | + '@esbuild/linux-s390x': 0.25.12 | |
| 4868 | + '@esbuild/linux-x64': 0.25.12 | |
| 4869 | + '@esbuild/netbsd-arm64': 0.25.12 | |
| 4870 | + '@esbuild/netbsd-x64': 0.25.12 | |
| 4871 | + '@esbuild/openbsd-arm64': 0.25.12 | |
| 4872 | + '@esbuild/openbsd-x64': 0.25.12 | |
| 4873 | + '@esbuild/openharmony-arm64': 0.25.12 | |
| 4874 | + '@esbuild/sunos-x64': 0.25.12 | |
| 4875 | + '@esbuild/win32-arm64': 0.25.12 | |
| 4876 | + '@esbuild/win32-ia32': 0.25.12 | |
| 4877 | + '@esbuild/win32-x64': 0.25.12 | |
| 4878 | + | |
| 4111 | 4879 | esbuild@0.28.2: |
| 4112 | 4880 | optionalDependencies: |
| 4113 | 4881 | '@esbuild/aix-ppc64': 0.28.2 |
@@ -4498,6 +5266,16 @@ snapshots: | ||
| 4498 | 5266 | dependencies: |
| 4499 | 5267 | function-bind: 1.1.2 |
| 4500 | 5268 | |
| 5269 | + heic-convert@2.1.0: | |
| 5270 | + dependencies: | |
| 5271 | + heic-decode: 2.1.0 | |
| 5272 | + jpeg-js: 0.4.4 | |
| 5273 | + pngjs: 6.0.0 | |
| 5274 | + | |
| 5275 | + heic-decode@2.1.0: | |
| 5276 | + dependencies: | |
| 5277 | + libheif-js: 1.23.5 | |
| 5278 | + | |
| 4501 | 5279 | hermes-estree@0.25.1: {} |
| 4502 | 5280 | |
| 4503 | 5281 | hermes-parser@0.25.1: |
@@ -4656,6 +5434,8 @@ snapshots: | ||
| 4656 | 5434 | |
| 4657 | 5435 | jiti@2.7.0: {} |
| 4658 | 5436 | |
| 5437 | + jpeg-js@0.4.4: {} | |
| 5438 | + | |
| 4659 | 5439 | js-tokens@4.0.0: {} |
| 4660 | 5440 | |
| 4661 | 5441 | js-yaml@4.3.2: |
@@ -4707,6 +5487,8 @@ snapshots: | ||
| 4707 | 5487 | prelude-ls: 1.2.1 |
| 4708 | 5488 | type-check: 0.4.0 |
| 4709 | 5489 | |
| 5490 | + libheif-js@1.23.5: {} | |
| 5491 | + | |
| 4710 | 5492 | lightningcss-android-arm64@1.32.0: |
| 4711 | 5493 | optional: true |
| 4712 | 5494 | |
@@ -4873,6 +5655,8 @@ snapshots: | ||
| 4873 | 5655 | |
| 4874 | 5656 | nanoid@3.3.20: {} |
| 4875 | 5657 | |
| 5658 | + nanoid@6.0.2: {} | |
| 5659 | + | |
| 4876 | 5660 | napi-postinstall@0.3.4: {} |
| 4877 | 5661 | |
| 4878 | 5662 | natural-compare@1.4.0: {} |
@@ -4967,6 +5751,10 @@ snapshots: | ||
| 4967 | 5751 | type-check: 0.4.0 |
| 4968 | 5752 | word-wrap: 1.2.5 |
| 4969 | 5753 | |
| 5754 | + otpauth@9.5.2: | |
| 5755 | + dependencies: | |
| 5756 | + '@noble/hashes': 2.4.0 | |
| 5757 | + | |
| 4970 | 5758 | own-keys@1.0.2: |
| 4971 | 5759 | dependencies: |
| 4972 | 5760 | call-bound: 1.0.4 |
@@ -4996,14 +5784,30 @@ snapshots: | ||
| 4996 | 5784 | dependencies: |
| 4997 | 5785 | resolve-protobuf-schema: 2.1.0 |
| 4998 | 5786 | |
| 5787 | + pg-int8@1.0.1: {} | |
| 5788 | + | |
| 5789 | + pg-protocol@1.16.1: {} | |
| 5790 | + | |
| 5791 | + pg-types@2.2.0: | |
| 5792 | + dependencies: | |
| 5793 | + pg-int8: 1.0.1 | |
| 5794 | + postgres-array: 2.0.0 | |
| 5795 | + postgres-bytea: 1.0.1 | |
| 5796 | + postgres-date: 1.0.7 | |
| 5797 | + postgres-interval: 1.2.0 | |
| 5798 | + | |
| 4999 | 5799 | picocolors@1.1.1: {} |
| 5000 | 5800 | |
| 5001 | 5801 | picomatch@2.3.2: {} |
| 5002 | 5802 | |
| 5003 | 5803 | picomatch@4.0.7: {} |
| 5004 | 5804 | |
| 5805 | + pngjs@6.0.0: {} | |
| 5806 | + | |
| 5005 | 5807 | possible-typed-array-names@1.1.0: {} |
| 5006 | 5808 | |
| 5809 | + postal-mime@2.7.6: {} | |
| 5810 | + | |
| 5007 | 5811 | postcss@8.5.23: |
| 5008 | 5812 | dependencies: |
| 5009 | 5813 | nanoid: 3.3.20 |
@@ -5016,6 +5820,18 @@ snapshots: | ||
| 5016 | 5820 | picocolors: 1.1.1 |
| 5017 | 5821 | source-map-js: 1.2.2 |
| 5018 | 5822 | |
| 5823 | + postgres-array@2.0.0: {} | |
| 5824 | + | |
| 5825 | + postgres-bytea@1.0.1: {} | |
| 5826 | + | |
| 5827 | + postgres-date@1.0.7: {} | |
| 5828 | + | |
| 5829 | + postgres-interval@1.2.0: | |
| 5830 | + dependencies: | |
| 5831 | + xtend: 4.0.2 | |
| 5832 | + | |
| 5833 | + postgres@3.4.9: {} | |
| 5834 | + | |
| 5019 | 5835 | potpack@2.1.0: {} |
| 5020 | 5836 | |
| 5021 | 5837 | prelude-ls@1.2.1: {} |
@@ -5102,6 +5918,11 @@ snapshots: | ||
| 5102 | 5918 | |
| 5103 | 5919 | reselect@5.2.0: {} |
| 5104 | 5920 | |
| 5921 | + resend@6.34.1: | |
| 5922 | + dependencies: | |
| 5923 | + postal-mime: 2.7.6 | |
| 5924 | + standardwebhooks: 1.1.1 | |
| 5925 | + | |
| 5105 | 5926 | resolve-from@4.0.0: {} |
| 5106 | 5927 | |
| 5107 | 5928 | resolve-pkg-maps@1.0.0: {} |
@@ -5225,7 +6046,6 @@ snapshots: | ||
| 5225 | 6046 | '@img/sharp-win32-ia32': 0.35.5 |
| 5226 | 6047 | '@img/sharp-win32-x64': 0.35.5 |
| 5227 | 6048 | '@types/node': 20.19.43 |
| 5228 | − optional: true | |
| 5229 | 6049 | |
| 5230 | 6050 | shebang-command@2.0.0: |
| 5231 | 6051 | dependencies: |
@@ -5263,6 +6083,13 @@ snapshots: | ||
| 5263 | 6083 | |
| 5264 | 6084 | source-map-js@1.2.2: {} |
| 5265 | 6085 | |
| 6086 | + source-map-support@0.5.21: | |
| 6087 | + dependencies: | |
| 6088 | + buffer-from: 1.1.2 | |
| 6089 | + source-map: 0.6.1 | |
| 6090 | + | |
| 6091 | + source-map@0.6.1: {} | |
| 6092 | + | |
| 5266 | 6093 | stable-hash@0.0.5: {} |
| 5267 | 6094 | |
| 5268 | 6095 | standardwebhooks@1.1.1: |
@@ -5588,6 +6415,8 @@ snapshots: | ||
| 5588 | 6415 | |
| 5589 | 6416 | word-wrap@1.2.5: {} |
| 5590 | 6417 | |
| 6418 | + xtend@4.0.2: {} | |
| 6419 | + | |
| 5591 | 6420 | yallist@3.1.1: {} |
| 5592 | 6421 | |
| 5593 | 6422 | yocto-queue@0.1.0: {} |
added
scripts/migrate.ts
+12 −0
@@ -0,0 +1,12 @@ | ||
| 1 | +// Applique les migrations Drizzle (idempotent, journal __drizzle_migrations). Usage : DATABASE_URL=… pnpm db:migrate | |
| 2 | +import { drizzle } from "drizzle-orm/postgres-js"; | |
| 3 | +import { migrate } from "drizzle-orm/postgres-js/migrator"; | |
| 4 | +import postgres from "postgres"; | |
| 5 | +async function main() { | |
| 6 | + const url = process.env.DATABASE_URL; if (!url) { console.error("DATABASE_URL manquante"); process.exit(1); } | |
| 7 | + const sql = postgres(url, { max: 1 }); | |
| 8 | + await migrate(drizzle(sql), { migrationsFolder: "./drizzle" }); | |
| 9 | + console.log("migrations OK →", url.replace(/:\/\/([^:]+):[^@]+@/, "://$1:***@")); | |
| 10 | + await sql.end(); | |
| 11 | +} | |
| 12 | +main().catch((e) => { console.error(e); process.exit(1); }); | |
added
src/db/client.ts
+16 −0
@@ -0,0 +1,16 @@ | ||
| 1 | +import { drizzle } from "drizzle-orm/postgres-js"; | |
| 2 | +import postgres from "postgres"; | |
| 3 | +import * as schema from "./schema"; | |
| 4 | + | |
| 5 | +// Compte applicatif à privilèges minimaux (toveli_app), connexion locale uniquement (DATABASE_URL jamais exposée au client). | |
| 6 | +const url = process.env.DATABASE_URL; | |
| 7 | +declare global { var __toveliSql: ReturnType<typeof postgres> | undefined; } | |
| 8 | +function sqlClient() { | |
| 9 | + if (!url) throw new Error("DATABASE_URL manquante"); | |
| 10 | + if (!globalThis.__toveliSql) globalThis.__toveliSql = postgres(url, { max: Number(process.env.PG_POOL_MAX ?? 8), idle_timeout: 30, connect_timeout: 10, prepare: true }); | |
| 11 | + return globalThis.__toveliSql; | |
| 12 | +} | |
| 13 | +export function pgAvailable(): boolean { return !!url; } | |
| 14 | +export function db() { return drizzle(sqlClient(), { schema }); } | |
| 15 | +export function rawSql() { return sqlClient(); } | |
| 16 | +export { schema }; | |
added
src/db/schema.ts
+271 −0
@@ -0,0 +1,271 @@ | ||
| 1 | +// Toveli — schéma PostgreSQL (identité, publications directes, échanges). Drizzle ORM. | |
| 2 | +// Les annonces agrégées restent dans la base canonique SQLite (lecture seule) ; ici ne vivent que les données Toveli. | |
| 3 | +import { pgTable, uuid, text, timestamp, integer, boolean, jsonb, numeric, doublePrecision, index, uniqueIndex, primaryKey, check, bigserial } from "drizzle-orm/pg-core"; | |
| 4 | +import { sql } from "drizzle-orm"; | |
| 5 | + | |
| 6 | +export const users = pgTable("users", { | |
| 7 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 8 | + email: text("email").notNull(), // adresse normalisée (minuscules, espaces retirés — points et + conservés) | |
| 9 | + emailVerifiedAt: timestamp("email_verified_at", { withTimezone: true }), | |
| 10 | + displayName: text("display_name"), | |
| 11 | + phoneEncrypted: text("phone_encrypted"), | |
| 12 | + locale: text("locale").notNull().default("fr-CA"), | |
| 13 | + status: text("status").notNull().default("ACTIVE"), // ACTIVE | SUSPENDED | DELETED | |
| 14 | + prefs: jsonb("prefs").notNull().default(sql`'{}'::jsonb`), // notifications, confidentialité | |
| 15 | + totpSecretEncrypted: text("totp_secret_encrypted"), // MFA forte requise pour les rôles élevés | |
| 16 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 17 | + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), | |
| 18 | + deletedAt: timestamp("deleted_at", { withTimezone: true }), | |
| 19 | +}, (t) => [uniqueIndex("users_email_uq").on(t.email), check("users_status_ck", sql`${t.status} in ('ACTIVE','SUSPENDED','DELETED')`)]); | |
| 20 | + | |
| 21 | +export const userProfiles = pgTable("user_profiles", { | |
| 22 | + userId: uuid("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }), | |
| 23 | + publicName: text("public_name"), | |
| 24 | + bio: text("bio"), | |
| 25 | + avatarKey: text("avatar_key"), | |
| 26 | + sellerType: text("seller_type").notNull().default("OWNER"), // OWNER | AGENT (déclaratif) — AGENT_VERIFIED est un rôle attribué côté serveur | |
| 27 | + verificationStatus: text("verification_status").notNull().default("NONE"), | |
| 28 | + contactPreferences: jsonb("contact_preferences").notNull().default(sql`'{"email":true,"phone":false}'::jsonb`), | |
| 29 | + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), | |
| 30 | +}); | |
| 31 | + | |
| 32 | +export const otpChallenges = pgTable("otp_challenges", { | |
| 33 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 34 | + emailNormalized: text("email_normalized").notNull(), | |
| 35 | + purpose: text("purpose").notNull().default("LOGIN"), // LOGIN | EMAIL_CHANGE | |
| 36 | + secretDigest: text("secret_digest").notNull(), // HMAC-SHA256(code, OTP_HMAC_SECRET, id) — jamais le code | |
| 37 | + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), | |
| 38 | + attempts: integer("attempts").notNull().default(0), | |
| 39 | + consumedAt: timestamp("consumed_at", { withTimezone: true }), | |
| 40 | + requestedIpHash: text("requested_ip_hash"), | |
| 41 | + meta: jsonb("meta").notNull().default(sql`'{}'::jsonb`), | |
| 42 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 43 | +}, (t) => [index("otp_email_idx").on(t.emailNormalized, t.createdAt), index("otp_expires_idx").on(t.expiresAt)]); | |
| 44 | + | |
| 45 | +export const sessions = pgTable("sessions", { | |
| 46 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 47 | + userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 48 | + tokenDigest: text("token_digest").notNull(), // sha256(token) — le jeton n'est jamais stocké en clair | |
| 49 | + issuedAt: timestamp("issued_at", { withTimezone: true }).notNull().defaultNow(), | |
| 50 | + lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(), | |
| 51 | + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), | |
| 52 | + revokedAt: timestamp("revoked_at", { withTimezone: true }), | |
| 53 | + deviceLabel: text("device_label"), | |
| 54 | + ipHash: text("ip_hash"), | |
| 55 | + mfaAt: timestamp("mfa_at", { withTimezone: true }), // dernière vérification TOTP (actions admin) | |
| 56 | +}, (t) => [uniqueIndex("sessions_token_uq").on(t.tokenDigest), index("sessions_user_idx").on(t.userId)]); | |
| 57 | + | |
| 58 | +export const userRoles = pgTable("user_roles", { | |
| 59 | + userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 60 | + role: text("role").notNull(), // ADMIN | MODERATOR | AGENT_VERIFIED | |
| 61 | + grantedBy: uuid("granted_by"), | |
| 62 | + grantedAt: timestamp("granted_at", { withTimezone: true }).notNull().defaultNow(), | |
| 63 | + note: text("note"), | |
| 64 | +}, (t) => [primaryKey({ columns: [t.userId, t.role] }), check("user_roles_role_ck", sql`${t.role} in ('ADMIN','MODERATOR','AGENT_VERIFIED')`)]); | |
| 65 | + | |
| 66 | +export const properties = pgTable("properties", { | |
| 67 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 68 | + canonicalAddress: text("canonical_address"), | |
| 69 | + unit: text("unit"), | |
| 70 | + city: text("city"), | |
| 71 | + region: text("region"), | |
| 72 | + postalCode: text("postal_code"), | |
| 73 | + lat: doublePrecision("lat"), | |
| 74 | + lng: doublePrecision("lng"), | |
| 75 | + geographicPrecision: text("geographic_precision").notNull().default("EXACT"), // EXACT | STREET | AREA | |
| 76 | + geoSource: text("geo_source"), // role_unit:<id> | manual | geocoder | |
| 77 | + roleUnitId: text("role_unit_id"), | |
| 78 | + propertyType: text("property_type").notNull(), // appartement | condo | maison | jumele | plex | terrain | commercial | chalet | autre | |
| 79 | + bedrooms: numeric("bedrooms", { precision: 4, scale: 1 }), | |
| 80 | + bathrooms: numeric("bathrooms", { precision: 4, scale: 1 }), | |
| 81 | + areaSqft: integer("area_sqft"), | |
| 82 | + lotSqft: integer("lot_sqft"), | |
| 83 | + yearBuilt: integer("year_built"), | |
| 84 | + privacyLevel: text("privacy_level").notNull().default("EXACT"), // EXACT | STREET_ONLY | AREA_ONLY | |
| 85 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 86 | +}, (t) => [index("properties_geo_idx").on(t.lat, t.lng), index("properties_city_idx").on(t.city)]); | |
| 87 | + | |
| 88 | +export const listings = pgTable("listings", { | |
| 89 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 90 | + slug: text("slug").notNull(), | |
| 91 | + propertyId: uuid("property_id").notNull().references(() => properties.id, { onDelete: "restrict" }), | |
| 92 | + ownerUserId: uuid("owner_user_id").references(() => users.id, { onDelete: "set null" }), | |
| 93 | + listingType: text("listing_type").notNull(), // SALE | RENT | |
| 94 | + sourceType: text("source_type").notNull().default("DIRECT_OWNER"), // DIRECT_OWNER | DIRECT_AGENT | AGGREGATED_PARTNER | |
| 95 | + status: text("status").notNull().default("DRAFT"), // DRAFT | PENDING_REVIEW | PUBLISHED | PAUSED | REJECTED | ARCHIVED | SOLD | RENTED | |
| 96 | + title: text("title"), | |
| 97 | + description: text("description"), | |
| 98 | + priceAmount: numeric("price_amount", { precision: 12, scale: 2 }), | |
| 99 | + currency: text("currency").notNull().default("CAD"), | |
| 100 | + rentalPeriod: text("rental_period"), // MONTH (location) | |
| 101 | + availabilityDate: text("availability_date"), // YYYY-MM-DD ou 'now' | |
| 102 | + furnished: boolean("furnished"), | |
| 103 | + petsPolicy: text("pets_policy"), // allowed | not_allowed | ask | |
| 104 | + smokingPolicy: text("smoking_policy"), | |
| 105 | + parking: text("parking"), | |
| 106 | + leaseTerms: text("lease_terms"), | |
| 107 | + inclusions: jsonb("inclusions").notNull().default(sql`'[]'::jsonb`), | |
| 108 | + features: jsonb("features").notNull().default(sql`'{}'::jsonb`), // champs extensibles par type | |
| 109 | + publicationAt: timestamp("publication_at", { withTimezone: true }), | |
| 110 | + expiresAt: timestamp("expires_at", { withTimezone: true }), | |
| 111 | + moderationState: text("moderation_state").notNull().default("NONE"), // NONE | AUTO_OK | FLAGGED | HUMAN_OK | REJECTED | |
| 112 | + moderationNote: text("moderation_note"), | |
| 113 | + externalUrl: text("external_url"), | |
| 114 | + upstreamSource: text("upstream_source"), | |
| 115 | + upstreamId: text("upstream_id"), | |
| 116 | + upstreamSyncAt: timestamp("upstream_sync_at", { withTimezone: true }), | |
| 117 | + wizardStep: integer("wizard_step").notNull().default(0), | |
| 118 | + attestedAt: timestamp("attested_at", { withTimezone: true }), // attestation du droit de publier | |
| 119 | + version: integer("version").notNull().default(1), // contrôle de concurrence optimiste | |
| 120 | + viewsCount: integer("views_count").notNull().default(0), | |
| 121 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 122 | + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), | |
| 123 | +}, (t) => [ | |
| 124 | + uniqueIndex("listings_slug_uq").on(t.slug), | |
| 125 | + index("listings_status_idx").on(t.status, t.listingType, t.publicationAt), | |
| 126 | + index("listings_owner_idx").on(t.ownerUserId), | |
| 127 | + uniqueIndex("listings_upstream_uq").on(t.upstreamSource, t.upstreamId), | |
| 128 | + check("listings_type_ck", sql`${t.listingType} in ('SALE','RENT')`), | |
| 129 | + check("listings_source_ck", sql`${t.sourceType} in ('DIRECT_OWNER','DIRECT_AGENT','AGGREGATED_PARTNER')`), | |
| 130 | + check("listings_status_ck", sql`${t.status} in ('DRAFT','PENDING_REVIEW','PUBLISHED','PAUSED','REJECTED','ARCHIVED','SOLD','RENTED')`), | |
| 131 | + check("listings_price_ck", sql`${t.priceAmount} is null or ${t.priceAmount} >= 0`), | |
| 132 | + check("listings_owner_direct_ck", sql`${t.sourceType} = 'AGGREGATED_PARTNER' or ${t.ownerUserId} is not null`), | |
| 133 | + check("listings_rent_period_ck", sql`${t.listingType} <> 'RENT' or ${t.rentalPeriod} is null or ${t.rentalPeriod} in ('MONTH','WEEK')`), | |
| 134 | +]); | |
| 135 | + | |
| 136 | +export const listingMedia = pgTable("listing_media", { | |
| 137 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 138 | + listingId: uuid("listing_id").notNull().references(() => listings.id, { onDelete: "cascade" }), | |
| 139 | + storageKey: text("storage_key").notNull(), | |
| 140 | + mediaType: text("media_type").notNull().default("IMAGE"), | |
| 141 | + mimeType: text("mime_type"), | |
| 142 | + position: integer("position").notNull().default(0), | |
| 143 | + altText: text("alt_text"), | |
| 144 | + licenseStatus: text("license_status").notNull().default("OWNER_DECLARED"), | |
| 145 | + scanStatus: text("scan_status").notNull().default("PENDING"), // PENDING | CLEAN | REJECTED | |
| 146 | + width: integer("width"), height: integer("height"), bytes: integer("bytes"), | |
| 147 | + status: text("status").notNull().default("UPLOADING"), // UPLOADING | READY | DELETED | |
| 148 | + uploadToken: text("upload_token"), | |
| 149 | + uploadedAt: timestamp("uploaded_at", { withTimezone: true }).notNull().defaultNow(), | |
| 150 | +}, (t) => [index("listing_media_listing_idx").on(t.listingId, t.position)]); | |
| 151 | + | |
| 152 | +export const listingVersions = pgTable("listing_versions", { | |
| 153 | + id: bigserial("id", { mode: "number" }).primaryKey(), | |
| 154 | + listingId: uuid("listing_id").notNull().references(() => listings.id, { onDelete: "cascade" }), | |
| 155 | + version: integer("version").notNull(), | |
| 156 | + actorId: uuid("actor_id"), | |
| 157 | + snapshot: jsonb("snapshot").notNull(), | |
| 158 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 159 | +}, (t) => [index("listing_versions_listing_idx").on(t.listingId, t.version)]); | |
| 160 | + | |
| 161 | +export const listingClaims = pgTable("listing_claims", { | |
| 162 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 163 | + aggregatedUid: text("aggregated_uid").notNull(), // uid de l'annonce agrégée (base canonique) | |
| 164 | + requesterId: uuid("requester_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 165 | + status: text("status").notNull().default("PENDING"), // PENDING | APPROVED | REJECTED | |
| 166 | + message: text("message"), | |
| 167 | + proofKeys: jsonb("proof_keys").notNull().default(sql`'[]'::jsonb`), | |
| 168 | + reviewedBy: uuid("reviewed_by"), | |
| 169 | + reviewedAt: timestamp("reviewed_at", { withTimezone: true }), | |
| 170 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 171 | +}, (t) => [index("listing_claims_uid_idx").on(t.aggregatedUid)]); | |
| 172 | + | |
| 173 | +export const favorites = pgTable("favorites", { | |
| 174 | + userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 175 | + listingUid: text("listing_uid").notNull(), // 'toveli:<id>' ou uid agrégé | |
| 176 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 177 | +}, (t) => [primaryKey({ columns: [t.userId, t.listingUid] })]); | |
| 178 | + | |
| 179 | +export const savedSearches = pgTable("saved_searches", { | |
| 180 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 181 | + userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 182 | + label: text("label").notNull(), | |
| 183 | + query: jsonb("query").notNull(), | |
| 184 | + alertEnabled: boolean("alert_enabled").notNull().default(false), | |
| 185 | + alertFrequency: text("alert_frequency").notNull().default("DAILY"), // INSTANT | DAILY | WEEKLY | OFF | |
| 186 | + lastAlertAt: timestamp("last_alert_at", { withTimezone: true }), | |
| 187 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 188 | +}, (t) => [index("saved_searches_user_idx").on(t.userId)]); | |
| 189 | + | |
| 190 | +export const emailDeliveries = pgTable("email_deliveries", { | |
| 191 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 192 | + toHash: text("to_hash").notNull(), | |
| 193 | + template: text("template").notNull(), | |
| 194 | + providerId: text("provider_id"), | |
| 195 | + status: text("status").notNull(), // QUEUED | SENT | MOCKED | FAILED | |
| 196 | + error: text("error"), | |
| 197 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 198 | +}, (t) => [index("email_deliveries_created_idx").on(t.createdAt)]); | |
| 199 | + | |
| 200 | +export const inquiries = pgTable("inquiries", { | |
| 201 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 202 | + listingId: uuid("listing_id").notNull().references(() => listings.id, { onDelete: "cascade" }), | |
| 203 | + senderUserId: uuid("sender_user_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 204 | + recipientUserId: uuid("recipient_user_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 205 | + category: text("category").notNull().default("INFO"), // INFO | VISIT | APPLICATION | OFFER | |
| 206 | + status: text("status").notNull().default("OPEN"), // OPEN | ARCHIVED | BLOCKED | |
| 207 | + lastMessageAt: timestamp("last_message_at", { withTimezone: true }).notNull().defaultNow(), | |
| 208 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 209 | +}, (t) => [index("inquiries_listing_idx").on(t.listingId), index("inquiries_sender_idx").on(t.senderUserId), index("inquiries_recipient_idx").on(t.recipientUserId), uniqueIndex("inquiries_unique_thread").on(t.listingId, t.senderUserId)]); | |
| 210 | + | |
| 211 | +export const messages = pgTable("messages", { | |
| 212 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 213 | + inquiryId: uuid("inquiry_id").notNull().references(() => inquiries.id, { onDelete: "cascade" }), | |
| 214 | + senderId: uuid("sender_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 215 | + body: text("body").notNull(), | |
| 216 | + idempotencyKey: text("idempotency_key"), | |
| 217 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 218 | + readAt: timestamp("read_at", { withTimezone: true }), | |
| 219 | +}, (t) => [index("messages_inquiry_idx").on(t.inquiryId, t.createdAt), uniqueIndex("messages_idem_uq").on(t.inquiryId, t.idempotencyKey)]); | |
| 220 | + | |
| 221 | +export const viewingRequests = pgTable("viewing_requests", { | |
| 222 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 223 | + listingId: uuid("listing_id").notNull().references(() => listings.id, { onDelete: "cascade" }), | |
| 224 | + inquiryId: uuid("inquiry_id").references(() => inquiries.id, { onDelete: "set null" }), | |
| 225 | + requesterId: uuid("requester_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 226 | + ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 227 | + proposedSlots: jsonb("proposed_slots").notNull(), // [ISO America/Toronto] | |
| 228 | + confirmedSlot: timestamp("confirmed_slot", { withTimezone: true }), | |
| 229 | + status: text("status").notNull().default("REQUESTED"), // REQUESTED | CONFIRMED | CANCELLED | DONE | |
| 230 | + note: text("note"), | |
| 231 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 232 | + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), | |
| 233 | +}, (t) => [index("viewings_listing_idx").on(t.listingId), index("viewings_requester_idx").on(t.requesterId), index("viewings_owner_idx").on(t.ownerId), check("viewings_status_ck", sql`${t.status} in ('REQUESTED','CONFIRMED','CANCELLED','DONE')`)]); | |
| 234 | + | |
| 235 | +export const reports = pgTable("reports", { | |
| 236 | + id: uuid("id").primaryKey().defaultRandom(), | |
| 237 | + targetType: text("target_type").notNull(), // LISTING | USER | MESSAGE | AGGREGATED_LISTING | |
| 238 | + targetId: text("target_id").notNull(), | |
| 239 | + reporterId: uuid("reporter_id").references(() => users.id, { onDelete: "set null" }), | |
| 240 | + reason: text("reason").notNull(), | |
| 241 | + details: text("details"), | |
| 242 | + status: text("status").notNull().default("OPEN"), // OPEN | RESOLVED | DISMISSED | |
| 243 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 244 | +}, (t) => [index("reports_status_idx").on(t.status, t.createdAt)]); | |
| 245 | + | |
| 246 | +export const moderationActions = pgTable("moderation_actions", { | |
| 247 | + id: bigserial("id", { mode: "number" }).primaryKey(), | |
| 248 | + actorId: uuid("actor_id").notNull(), | |
| 249 | + targetType: text("target_type").notNull(), | |
| 250 | + targetId: text("target_id").notNull(), | |
| 251 | + action: text("action").notNull(), | |
| 252 | + note: text("note"), | |
| 253 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 254 | +}); | |
| 255 | + | |
| 256 | +export const auditEvents = pgTable("audit_events", { | |
| 257 | + id: bigserial("id", { mode: "number" }).primaryKey(), | |
| 258 | + actorId: uuid("actor_id"), | |
| 259 | + action: text("action").notNull(), | |
| 260 | + objectType: text("object_type"), | |
| 261 | + objectId: text("object_id"), | |
| 262 | + ipHash: text("ip_hash"), | |
| 263 | + metadataRedacted: jsonb("metadata_redacted").notNull().default(sql`'{}'::jsonb`), | |
| 264 | + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), | |
| 265 | +}, (t) => [index("audit_created_idx").on(t.createdAt), index("audit_actor_idx").on(t.actorId)]); | |
| 266 | + | |
| 267 | +export const rateLimits = pgTable("rate_limits", { | |
| 268 | + key: text("key").notNull(), // ex. otp:ip:<hash> | otp:email:<hash> | |
| 269 | + windowStart: timestamp("window_start", { withTimezone: true }).notNull(), | |
| 270 | + count: integer("count").notNull().default(0), | |
| 271 | +}, (t) => [primaryKey({ columns: [t.key, t.windowStart] })]); | |
added
src/lib/auth/audit.ts
+7 −0
@@ -0,0 +1,7 @@ | ||
| 1 | +import { db } from "@/db/client"; | |
| 2 | +import { auditEvents } from "@/db/schema"; | |
| 3 | +/** Journal append-only ; métadonnées minimisées (jamais de code OTP, de jeton, d'adresse en clair). */ | |
| 4 | +export async function audit(action: string, opts: { actorId?: string | null; objectType?: string; objectId?: string; ipHash?: string; meta?: Record<string, unknown> } = {}) { | |
| 5 | + try { await db().insert(auditEvents).values({ action, actorId: opts.actorId ?? null, objectType: opts.objectType ?? null, objectId: opts.objectId ?? null, ipHash: opts.ipHash ?? null, metadataRedacted: opts.meta ?? {} }); } | |
| 6 | + catch (e) { console.error("[audit]", action, (e as Error).message); } | |
| 7 | +} | |
added
src/lib/auth/crypto.ts
+28 −0
@@ -0,0 +1,28 @@ | ||
| 1 | +import { createHash, createHmac, randomBytes, randomInt, timingSafeEqual, createCipheriv, createDecipheriv } from "node:crypto"; | |
| 2 | + | |
| 3 | +function secret(name: "OTP_HMAC_SECRET" | "SESSION_SECRET"): string { | |
| 4 | + const v = process.env[name]; if (!v || v.length < 16) throw new Error(`${name} manquante ou trop courte`); return v; | |
| 5 | +} | |
| 6 | +/** Adresse normalisée : minuscules, espaces retirés ; points et « + » conservés (pas de suppression arbitraire). */ | |
| 7 | +export function normalizeEmail(raw: string): string | null { | |
| 8 | + const e = raw.trim().toLowerCase().replace(/\s+/g, ""); | |
| 9 | + if (!/^[^@\s]{1,64}@[^@\s]+\.[a-z]{2,}$/i.test(e) || e.length > 254) return null; | |
| 10 | + return e; | |
| 11 | +} | |
| 12 | +export function sha256(s: string): string { return createHash("sha256").update(s).digest("hex"); } | |
| 13 | +export function ipHash(ip: string | null | undefined): string { return sha256(`ip:${ip ?? "?"}:${secret("SESSION_SECRET").slice(0, 8)}`).slice(0, 32); } | |
| 14 | +export function emailHash(email: string): string { return sha256(`email:${email}`).slice(0, 32); } | |
| 15 | + | |
| 16 | +/** Code OTP à 6 chiffres par CSPRNG (randomInt est uniforme). */ | |
| 17 | +export function generateOtp(): string { return String(randomInt(0, 1_000_000)).padStart(6, "0"); } | |
| 18 | +/** Empreinte HMAC-SHA256 du code, liée à l'identifiant du défi (évite les tables arc-en-ciel sur 10^6 codes). */ | |
| 19 | +export function otpDigest(code: string, challengeId: string): string { return createHmac("sha256", secret("OTP_HMAC_SECRET")).update(`${challengeId}:${code}`).digest("hex"); } | |
| 20 | +export function safeEqualHex(a: string, b: string): boolean { const ba = Buffer.from(a, "hex"), bb = Buffer.from(b, "hex"); return ba.length === bb.length && timingSafeEqual(ba, bb); } | |
| 21 | + | |
| 22 | +export function newSessionToken(): string { return randomBytes(32).toString("base64url"); } | |
| 23 | +export function sessionDigest(token: string): string { return createHmac("sha256", secret("SESSION_SECRET")).update(token).digest("hex"); } | |
| 24 | + | |
| 25 | +/** Chiffrement symétrique (AES-256-GCM) pour champs sensibles (téléphone, secret TOTP) — clé dérivée de SESSION_SECRET. */ | |
| 26 | +function encKey(): Buffer { return createHash("sha256").update(`enc:${secret("SESSION_SECRET")}`).digest(); } | |
| 27 | +export function encrypt(plain: string): string { const iv = randomBytes(12); const c = createCipheriv("aes-256-gcm", encKey(), iv); const ct = Buffer.concat([c.update(plain, "utf8"), c.final()]); return `${iv.toString("base64url")}.${ct.toString("base64url")}.${c.getAuthTag().toString("base64url")}`; } | |
| 28 | +export function decrypt(blob: string): string { const [iv, ct, tag] = blob.split(".").map((x) => Buffer.from(x, "base64url")); const d = createDecipheriv("aes-256-gcm", encKey(), iv); d.setAuthTag(tag); return Buffer.concat([d.update(ct), d.final()]).toString("utf8"); } | |
added
src/lib/auth/email.ts
+48 −0
@@ -0,0 +1,48 @@ | ||
| 1 | +// Transport courriel : Resend (SDK officiel, clé serveur) ou simulation locale (dev/tests : écrit dans data/outbox, jamais d'envoi réel). | |
| 2 | +import { Resend } from "resend"; | |
| 3 | +import { appendFileSync, mkdirSync } from "node:fs"; | |
| 4 | +import path from "node:path"; | |
| 5 | +import { db } from "@/db/client"; | |
| 6 | +import { emailDeliveries } from "@/db/schema"; | |
| 7 | +import { emailHash } from "./crypto"; | |
| 8 | + | |
| 9 | +export interface Mail { to: string; subject: string; html: string; text: string; template: string } | |
| 10 | +export type Transport = { name: string; send(m: Mail): Promise<{ id: string | null }> }; | |
| 11 | + | |
| 12 | +const outboxDir = path.join(process.env.STORAGE_DIR ? path.dirname(process.env.STORAGE_DIR) : path.join(process.cwd(), "data"), "outbox"); | |
| 13 | +export const mockTransport: Transport = { | |
| 14 | + name: "mock", | |
| 15 | + async send(m) { mkdirSync(outboxDir, { recursive: true }); const id = `mock-${Date.now()}`; appendFileSync(path.join(outboxDir, "mails.jsonl"), JSON.stringify({ id, at: new Date().toISOString(), ...m }) + "\n"); if (process.env.NODE_ENV !== "production") console.log(`[mail:mock] → ${m.to} « ${m.subject} »`); return { id }; }, | |
| 16 | +}; | |
| 17 | +export const lastMockMail = { set: null as Mail | null }; // utilisé par les tests | |
| 18 | +function resendTransport(key: string): Transport { | |
| 19 | + const r = new Resend(key); const from = process.env.RESEND_FROM_EMAIL ?? "connexion@toveli.ca"; | |
| 20 | + return { name: "resend", async send(m) { const res = await r.emails.send({ from: `Toveli <${from}>`, to: m.to, subject: m.subject, html: m.html, text: m.text }); if (res.error) throw new Error(res.error.message); return { id: res.data?.id ?? null }; } }; | |
| 21 | +} | |
| 22 | +let override: Transport | null = null; | |
| 23 | +export function setTransportForTests(t: Transport | null) { override = t; } | |
| 24 | +export function transport(): Transport { | |
| 25 | + if (override) return override; | |
| 26 | + const key = process.env.RESEND_API_KEY; | |
| 27 | + if (key && process.env.NODE_ENV === "production") return resendTransport(key); | |
| 28 | + if (key && process.env.RESEND_FORCE === "1") return resendTransport(key); | |
| 29 | + return mockTransport; | |
| 30 | +} | |
| 31 | +export async function sendMail(m: Mail): Promise<void> { | |
| 32 | + const t = transport(); let status = "SENT", providerId: string | null = null, error: string | null = null; | |
| 33 | + try { const r = await t.send(m); providerId = r.id; if (t.name === "mock") { status = "MOCKED"; lastMockMail.set = m; } } | |
| 34 | + catch (e) { status = "FAILED"; error = (e as Error).message.slice(0, 300); console.error("[mail] échec", m.template, error); } | |
| 35 | + try { await db().insert(emailDeliveries).values({ toHash: emailHash(m.to), template: m.template, providerId, status, error }); } catch { /* journal best-effort */ } | |
| 36 | + if (status === "FAILED") throw new Error("mail_failed"); | |
| 37 | +} | |
| 38 | + | |
| 39 | +const layout = (title: string, body: string) => `<!doctype html><html lang="fr"><body style="margin:0;background:#f6f7f9;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#15181d"><div style="max-width:520px;margin:0 auto;padding:32px 16px"><div style="font-size:28px;font-weight:800;letter-spacing:-.02em">toveli<span style="color:#e0263c">.</span></div><div style="background:#fff;border:1px solid #e6e8ec;border-radius:14px;padding:24px;margin-top:16px"><h1 style="font-size:20px;margin:0 0 12px">${title}</h1>${body}</div><p style="font-size:12px;color:#7a8190;margin-top:16px">Toveli · toveli.ca · Ce message est envoyé automatiquement, merci de ne pas y répondre.</p></div></body></html>`; | |
| 40 | + | |
| 41 | +export function otpMail(to: string, code: string): Mail { | |
| 42 | + const html = layout("Votre code de connexion", `<p style="margin:0 0 16px;color:#4b5260">Entrez ce code sur Toveli pour vous connecter. Il est valable <strong>10 minutes</strong> et ne peut servir qu'une fois.</p><p style="font-size:36px;font-weight:800;letter-spacing:.3em;text-align:center;margin:8px 0 16px;font-variant-numeric:tabular-nums">${code}</p><p style="font-size:13px;color:#7a8190;margin:0">Si vous n'avez rien demandé, ignorez ce message : personne ne peut se connecter sans ce code.</p>`); | |
| 43 | + return { to, subject: "Votre code de connexion Toveli", html, text: `Votre code de connexion Toveli : ${code}\nValable 10 minutes, usage unique.\nSi vous n'avez rien demandé, ignorez ce message.`, template: "otp" }; | |
| 44 | +} | |
| 45 | +export function notifyMail(to: string, title: string, body: string, ctaUrl: string, ctaLabel: string, template: string): Mail { | |
| 46 | + const html = layout(title, `<p style="margin:0 0 16px;color:#4b5260">${body}</p><p style="text-align:center;margin:8px 0"><a href="${ctaUrl}" style="display:inline-block;background:#e0263c;color:#fff;text-decoration:none;font-weight:600;padding:12px 20px;border-radius:999px">${ctaLabel}</a></p><p style="font-size:13px;color:#7a8190;margin:12px 0 0">Vous recevez ce message parce que vous avez un compte Toveli. Préférences : ${process.env.APP_BASE_URL ?? "https://www.toveli.ca"}/membre/profil</p>`); | |
| 47 | + return { to, subject: `${title} · Toveli`, html, text: `${title}\n\n${body}\n\n${ctaUrl}`, template }; | |
| 48 | +} | |
added
src/lib/auth/otp.ts
+56 −0
@@ -0,0 +1,56 @@ | ||
| 1 | +import { and, eq, isNull, sql, lt } from "drizzle-orm"; | |
| 2 | +import { db } from "@/db/client"; | |
| 3 | +import { otpChallenges, users, userProfiles } from "@/db/schema"; | |
| 4 | +import { generateOtp, otpDigest, safeEqualHex, normalizeEmail, ipHash } from "./crypto"; | |
| 5 | +import { sendMail, otpMail } from "./email"; | |
| 6 | +import { hit } from "./ratelimit"; | |
| 7 | +import { audit } from "./audit"; | |
| 8 | + | |
| 9 | +const TTL_MIN = 10, MAX_ATTEMPTS = 5, RESEND_DELAY_SEC = 60; | |
| 10 | +export type RequestResult = { ok: true; challengeId: string; retryAfterSec: number } | { ok: false; reason: "invalid_email" | "rate_limited" | "cooldown"; retryAfterSec: number }; | |
| 11 | + | |
| 12 | +/** Demande de code : réponse neutre côté API (n'indique pas si le compte existe). */ | |
| 13 | +export async function requestCode(rawEmail: string, ip: string | null, purpose: "LOGIN" | "EMAIL_CHANGE" = "LOGIN", meta: Record<string, unknown> = {}): Promise<RequestResult> { | |
| 14 | + const email = normalizeEmail(rawEmail); if (!email) return { ok: false, reason: "invalid_email", retryAfterSec: 0 }; | |
| 15 | + const iph = ipHash(ip); | |
| 16 | + const [byIp, byEmailHour, byEmailDay, byIpDay] = await Promise.all([hit(`otp:ip:${iph}`, 10, 600), hit(`otp:email:${email}`, 5, 3600), hit(`otp:email:day:${email}`, 12, 86400), hit(`otp:ip:day:${iph}`, 60, 86400)]); | |
| 17 | + if (!byIp.ok || !byEmailHour.ok || !byEmailDay.ok || !byIpDay.ok) { await audit("otp.rate_limited", { ipHash: iph }); return { ok: false, reason: "rate_limited", retryAfterSec: Math.max(byIp.retryAfterSec, byEmailHour.retryAfterSec, 60) }; } | |
| 18 | + const d = db(); | |
| 19 | + const [recent] = await d.select({ createdAt: otpChallenges.createdAt }).from(otpChallenges).where(and(eq(otpChallenges.emailNormalized, email), eq(otpChallenges.purpose, purpose), isNull(otpChallenges.consumedAt), sql`${otpChallenges.createdAt} > now() - interval '${sql.raw(String(RESEND_DELAY_SEC))} seconds'`)).limit(1); | |
| 20 | + if (recent) return { ok: false, reason: "cooldown", retryAfterSec: Math.max(1, RESEND_DELAY_SEC - Math.floor((Date.now() - recent.createdAt.getTime()) / 1000)) }; | |
| 21 | + // Invalide les défis précédents encore ouverts pour cette adresse/finalité | |
| 22 | + await d.update(otpChallenges).set({ consumedAt: new Date() }).where(and(eq(otpChallenges.emailNormalized, email), eq(otpChallenges.purpose, purpose), isNull(otpChallenges.consumedAt))); | |
| 23 | + const code = generateOtp(); | |
| 24 | + const [ch] = await d.insert(otpChallenges).values({ emailNormalized: email, purpose, secretDigest: "pending", expiresAt: new Date(Date.now() + TTL_MIN * 60000), requestedIpHash: iph, meta }).returning({ id: otpChallenges.id }); | |
| 25 | + await d.update(otpChallenges).set({ secretDigest: otpDigest(code, ch.id) }).where(eq(otpChallenges.id, ch.id)); | |
| 26 | + await sendMail(otpMail(email, code)); | |
| 27 | + await audit("otp.requested", { objectType: "otp_challenge", objectId: ch.id, ipHash: iph, meta: { purpose } }); | |
| 28 | + if (Math.random() < 0.05) await d.delete(otpChallenges).where(lt(otpChallenges.expiresAt, new Date(Date.now() - 86400000))); | |
| 29 | + return { ok: true, challengeId: ch.id, retryAfterSec: RESEND_DELAY_SEC }; | |
| 30 | +} | |
| 31 | + | |
| 32 | +export type VerifyResult = { ok: true; userId: string; email: string; created: boolean } | { ok: false; reason: "invalid" | "expired" | "too_many_attempts" | "not_found" }; | |
| 33 | + | |
| 34 | +/** Vérification atomique : une seule transaction, verrou de ligne, compteur de tentatives, usage unique. */ | |
| 35 | +export async function verifyCode(challengeId: string, code: string, ip: string | null, acceptTerms: boolean): Promise<VerifyResult> { | |
| 36 | + if (!/^\d{6}$/.test(code) || !/^[0-9a-f-]{36}$/.test(challengeId)) return { ok: false, reason: "invalid" }; | |
| 37 | + const d = db(); const iph = ipHash(ip); | |
| 38 | + return d.transaction(async (tx) => { | |
| 39 | + const [ch] = await tx.select().from(otpChallenges).where(eq(otpChallenges.id, challengeId)).for("update"); | |
| 40 | + if (!ch) return { ok: false, reason: "not_found" as const }; | |
| 41 | + if (ch.consumedAt) { await audit("otp.reuse_blocked", { objectId: ch.id, ipHash: iph }); return { ok: false, reason: "invalid" as const }; } | |
| 42 | + if (ch.expiresAt.getTime() < Date.now()) { await tx.update(otpChallenges).set({ consumedAt: new Date() }).where(eq(otpChallenges.id, ch.id)); return { ok: false, reason: "expired" as const }; } | |
| 43 | + if (ch.attempts >= MAX_ATTEMPTS) { await tx.update(otpChallenges).set({ consumedAt: new Date() }).where(eq(otpChallenges.id, ch.id)); await audit("otp.too_many_attempts", { objectId: ch.id, ipHash: iph }); return { ok: false, reason: "too_many_attempts" as const }; } | |
| 44 | + const good = safeEqualHex(ch.secretDigest, otpDigest(code, ch.id)); | |
| 45 | + if (!good) { const attempts = ch.attempts + 1; await tx.update(otpChallenges).set({ attempts, consumedAt: attempts >= MAX_ATTEMPTS ? new Date() : null }).where(eq(otpChallenges.id, ch.id)); await audit("otp.failed", { objectId: ch.id, ipHash: iph, meta: { attempts } }); return { ok: false, reason: attempts >= MAX_ATTEMPTS ? ("too_many_attempts" as const) : ("invalid" as const) }; } | |
| 46 | + await tx.update(otpChallenges).set({ consumedAt: new Date(), attempts: ch.attempts + 1 }).where(eq(otpChallenges.id, ch.id)); | |
| 47 | + if (ch.purpose !== "LOGIN") return { ok: true as const, userId: String(ch.meta && (ch.meta as { userId?: string }).userId), email: ch.emailNormalized, created: false }; | |
| 48 | + const [existing] = await tx.select({ id: users.id, status: users.status }).from(users).where(eq(users.email, ch.emailNormalized)).limit(1); | |
| 49 | + if (existing) { if (existing.status !== "ACTIVE") return { ok: false, reason: "invalid" as const }; await tx.update(users).set({ emailVerifiedAt: sql`coalesce(${users.emailVerifiedAt}, now())`, updatedAt: new Date() }).where(eq(users.id, existing.id)); await audit("auth.login", { actorId: existing.id, ipHash: iph }); return { ok: true as const, userId: existing.id, email: ch.emailNormalized, created: false }; } | |
| 50 | + if (!acceptTerms) return { ok: false, reason: "invalid" as const }; | |
| 51 | + const [u] = await tx.insert(users).values({ email: ch.emailNormalized, emailVerifiedAt: new Date(), prefs: { termsAcceptedAt: new Date().toISOString(), notifications: { inquiries: true, viewings: true, alerts: true, marketing: false } } }).returning({ id: users.id }); | |
| 52 | + await tx.insert(userProfiles).values({ userId: u.id }); | |
| 53 | + await audit("auth.signup", { actorId: u.id, ipHash: iph }); | |
| 54 | + return { ok: true as const, userId: u.id, email: ch.emailNormalized, created: true }; | |
| 55 | + }); | |
| 56 | +} | |
added
src/lib/auth/ratelimit.ts
+15 −0
@@ -0,0 +1,15 @@ | ||
| 1 | +import { sql } from "drizzle-orm"; | |
| 2 | +import { db } from "@/db/client"; | |
| 3 | +import { rateLimits } from "@/db/schema"; | |
| 4 | + | |
| 5 | +/** Fenêtre glissante approximée par tranches : compte les tranches actives, incrémente la courante. Jamais de blocage irréversible. */ | |
| 6 | +export async function hit(key: string, limit: number, windowSec: number, slices = 6): Promise<{ ok: boolean; remaining: number; retryAfterSec: number }> { | |
| 7 | + const sliceMs = Math.max(1000, Math.floor((windowSec * 1000) / slices)); | |
| 8 | + const now = Date.now(); const cur = new Date(Math.floor(now / sliceMs) * sliceMs); const since = new Date(now - windowSec * 1000); | |
| 9 | + const d = db(); | |
| 10 | + await d.insert(rateLimits).values({ key, windowStart: cur, count: 1 }).onConflictDoUpdate({ target: [rateLimits.key, rateLimits.windowStart], set: { count: sql`${rateLimits.count} + 1` } }); | |
| 11 | + const [row] = await d.select({ total: sql<number>`coalesce(sum(${rateLimits.count}), 0)::int` }).from(rateLimits).where(sql`${rateLimits.key} = ${key} and ${rateLimits.windowStart} >= ${since}`); | |
| 12 | + const total = Number(row?.total ?? 0); | |
| 13 | + if (Math.random() < 0.02) await d.execute(sql`delete from ${rateLimits} where ${rateLimits.windowStart} < now() - interval '2 days'`); | |
| 14 | + return { ok: total <= limit, remaining: Math.max(0, limit - total), retryAfterSec: total <= limit ? 0 : Math.ceil(sliceMs / 1000) }; | |
| 15 | +} | |
added
src/lib/auth/session.ts
+50 −0
@@ -0,0 +1,50 @@ | ||
| 1 | +import { and, eq, gt, isNull, sql } from "drizzle-orm"; | |
| 2 | +import { cookies, headers } from "next/headers"; | |
| 3 | +import { db } from "@/db/client"; | |
| 4 | +import { sessions, users, userRoles, userProfiles } from "@/db/schema"; | |
| 5 | +import { newSessionToken, sessionDigest, ipHash } from "./crypto"; | |
| 6 | + | |
| 7 | +export const COOKIE = "tv_session"; | |
| 8 | +const TTL_DAYS = Number(process.env.SESSION_TTL_DAYS ?? 30); | |
| 9 | +const INACTIVITY_DAYS = Number(process.env.SESSION_INACTIVITY_DAYS ?? 14); | |
| 10 | + | |
| 11 | +export interface SessionUser { id: string; email: string; displayName: string | null; roles: string[]; sessionId: string; mfaAt: Date | null; prefs: Record<string, unknown>; publicName: string | null; sellerType: string } | |
| 12 | + | |
| 13 | +export async function createSession(userId: string, opts: { ip?: string | null; device?: string | null }): Promise<{ token: string; expiresAt: Date }> { | |
| 14 | + const token = newSessionToken(); const expiresAt = new Date(Date.now() + TTL_DAYS * 86400000); | |
| 15 | + await db().insert(sessions).values({ userId, tokenDigest: sessionDigest(token), expiresAt, deviceLabel: opts.device?.slice(0, 120) ?? null, ipHash: ipHash(opts.ip) }); | |
| 16 | + return { token, expiresAt }; | |
| 17 | +} | |
| 18 | +export function cookieOptions(expiresAt: Date) { | |
| 19 | + return { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax" as const, path: "/", expires: expiresAt }; | |
| 20 | +} | |
| 21 | +export async function currentUser(): Promise<SessionUser | null> { | |
| 22 | + const token = (await cookies()).get(COOKIE)?.value; if (!token) return null; | |
| 23 | + const d = db(); const digest = sessionDigest(token); const now = new Date(); | |
| 24 | + const [row] = await d.select({ s: sessions, u: users, p: userProfiles }).from(sessions).innerJoin(users, eq(users.id, sessions.userId)).leftJoin(userProfiles, eq(userProfiles.userId, users.id)) | |
| 25 | + .where(and(eq(sessions.tokenDigest, digest), isNull(sessions.revokedAt), gt(sessions.expiresAt, now))).limit(1); | |
| 26 | + if (!row || row.u.status !== "ACTIVE") return null; | |
| 27 | + if (now.getTime() - row.s.lastSeenAt.getTime() > INACTIVITY_DAYS * 86400000) { await d.update(sessions).set({ revokedAt: now }).where(eq(sessions.id, row.s.id)); return null; } | |
| 28 | + if (now.getTime() - row.s.lastSeenAt.getTime() > 5 * 60000) await d.update(sessions).set({ lastSeenAt: now }).where(eq(sessions.id, row.s.id)); | |
| 29 | + const roles = (await d.select({ role: userRoles.role }).from(userRoles).where(eq(userRoles.userId, row.u.id))).map((r) => r.role); | |
| 30 | + return { id: row.u.id, email: row.u.email, displayName: row.u.displayName, roles, sessionId: row.s.id, mfaAt: row.s.mfaAt, prefs: (row.u.prefs ?? {}) as Record<string, unknown>, publicName: row.p?.publicName ?? null, sellerType: row.p?.sellerType ?? "OWNER" }; | |
| 31 | +} | |
| 32 | +export async function requireUser(): Promise<SessionUser> { const u = await currentUser(); if (!u) throw new AuthError(401, "Connexion requise"); return u; } | |
| 33 | +export async function requireRole(role: "ADMIN" | "MODERATOR"): Promise<SessionUser> { const u = await requireUser(); if (!u.roles.includes(role) && !u.roles.includes("ADMIN")) throw new AuthError(403, "Accès refusé"); return u; } | |
| 34 | +export class AuthError extends Error { constructor(public status: number, msg: string) { super(msg); } } | |
| 35 | + | |
| 36 | +export async function revokeSession(sessionId: string, userId: string) { await db().update(sessions).set({ revokedAt: new Date() }).where(and(eq(sessions.id, sessionId), eq(sessions.userId, userId))); } | |
| 37 | +export async function revokeAllSessions(userId: string, exceptSessionId?: string) { await db().update(sessions).set({ revokedAt: new Date() }).where(and(eq(sessions.userId, userId), isNull(sessions.revokedAt), exceptSessionId ? sql`${sessions.id} <> ${exceptSessionId}` : sql`true`)); } | |
| 38 | +export async function listSessions(userId: string) { return db().select({ id: sessions.id, issuedAt: sessions.issuedAt, lastSeenAt: sessions.lastSeenAt, expiresAt: sessions.expiresAt, deviceLabel: sessions.deviceLabel, revokedAt: sessions.revokedAt }).from(sessions).where(and(eq(sessions.userId, userId), isNull(sessions.revokedAt), gt(sessions.expiresAt, new Date()))).orderBy(sql`${sessions.lastSeenAt} desc`); } | |
| 39 | + | |
| 40 | +/** Protection CSRF des mutations : même origine obligatoire (cookie SameSite=Lax + vérification Origin/Referer). */ | |
| 41 | +export async function assertSameOrigin(): Promise<void> { | |
| 42 | + const h = await headers(); const origin = h.get("origin") ?? (h.get("referer") ? new URL(h.get("referer")!).origin : null); | |
| 43 | + const base = process.env.APP_BASE_URL; const host = h.get("x-forwarded-host") ?? h.get("host"); | |
| 44 | + if (!origin) throw new AuthError(403, "Origine manquante"); | |
| 45 | + const o = new URL(origin); | |
| 46 | + if ((base && origin === base.replace(/\/$/, "")) || (host && o.host === host)) return; | |
| 47 | + throw new AuthError(403, "Origine non autorisée"); | |
| 48 | +} | |
| 49 | +export async function clientIp(): Promise<string | null> { const h = await headers(); return h.get("x-forwarded-for")?.split(",")[0].trim() ?? h.get("x-real-ip") ?? null; } | |
| 50 | +export async function userAgent(): Promise<string | null> { return (await headers()).get("user-agent"); } | |
| 51 | ||