From 6785c64f83b3538e6e83dfb4421c3ba117e4d27a Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Tue, 21 Apr 2026 08:28:04 +0900 Subject: [PATCH 1/3] [#878] Create 6 airdrop campaign DB tables Add migration 00035 with: pl_points (append-only ledger), pl_referrals, pl_referral_codes, pl_streaks, pl_daily_prices, pl_weekly_snapshots. Add corresponding TypeScript types to lib/supabase.ts. Fixes #878 Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/supabase.ts | 159 +++++++++++++++++++ supabase/migrations/00035_airdrop_tables.sql | 62 ++++++++ 2 files changed, 221 insertions(+) create mode 100644 supabase/migrations/00035_airdrop_tables.sql diff --git a/lib/supabase.ts b/lib/supabase.ts index 3084f2c2..d3312477 100644 --- a/lib/supabase.ts +++ b/lib/supabase.ts @@ -343,6 +343,159 @@ export interface Database { }; Relationships: []; }; + pl_points: { + Row: { + id: string; + address: string; + action: string; + points: number; + metadata: Record | null; + created_at: string; + }; + Insert: { + id?: string; + address: string; + action: string; + points: number; + metadata?: Record | null; + created_at?: string; + }; + Update: { + id?: string; + address?: string; + action?: string; + points?: number; + metadata?: Record | null; + created_at?: string; + }; + Relationships: []; + }; + pl_referrals: { + Row: { + id: string; + referrer_address: string; + referred_address: string; + referral_code: string; + created_at: string; + }; + Insert: { + id?: string; + referrer_address: string; + referred_address: string; + referral_code: string; + created_at?: string; + }; + Update: { + id?: string; + referrer_address?: string; + referred_address?: string; + referral_code?: string; + created_at?: string; + }; + Relationships: []; + }; + pl_referral_codes: { + Row: { + address: string; + code: string; + is_farcaster_username: boolean; + }; + Insert: { + address: string; + code: string; + is_farcaster_username?: boolean; + }; + Update: { + address?: string; + code?: string; + is_farcaster_username?: boolean; + }; + Relationships: []; + }; + pl_streaks: { + Row: { + address: string; + current_streak: number; + last_checkin: string | null; + longest_streak: number; + }; + Insert: { + address: string; + current_streak?: number; + last_checkin?: string | null; + longest_streak?: number; + }; + Update: { + address?: string; + current_streak?: number; + last_checkin?: string | null; + longest_streak?: number; + }; + Relationships: []; + }; + pl_daily_prices: { + Row: { + id: number; + price_usd: number; + supply: number; + mcap_usd: number; + recorded_at: string; + }; + Insert: { + id?: never; + price_usd: number; + supply: number; + mcap_usd: number; + recorded_at?: string; + }; + Update: { + id?: never; + price_usd?: number; + supply?: number; + mcap_usd?: number; + recorded_at?: string; + }; + Relationships: []; + }; + pl_weekly_snapshots: { + Row: { + id: number; + week_number: number; + week_start: string; + new_stories: number; + token_buys: number; + new_referrals: number; + mcap_start: number | null; + mcap_end: number | null; + total_pl_earned: number; + created_at: string; + }; + Insert: { + id?: never; + week_number: number; + week_start: string; + new_stories?: number; + token_buys?: number; + new_referrals?: number; + mcap_start?: number | null; + mcap_end?: number | null; + total_pl_earned?: number; + created_at?: string; + }; + Update: { + id?: never; + week_number?: number; + week_start?: string; + new_stories?: number; + token_buys?: number; + new_referrals?: number; + mcap_start?: number | null; + mcap_end?: number | null; + total_pl_earned?: number; + created_at?: string; + }; + Relationships: []; + }; trade_history: { Row: { id: number; @@ -562,3 +715,9 @@ export type Rating = Database["public"]["Tables"]["ratings"]["Row"]; export type Comment = Database["public"]["Tables"]["comments"]["Row"]; export type TradeHistory = Database["public"]["Tables"]["trade_history"]["Row"]; export type User = Database["public"]["Tables"]["users"]["Row"]; +export type PlPoint = Database["public"]["Tables"]["pl_points"]["Row"]; +export type PlReferral = Database["public"]["Tables"]["pl_referrals"]["Row"]; +export type PlReferralCode = Database["public"]["Tables"]["pl_referral_codes"]["Row"]; +export type PlStreak = Database["public"]["Tables"]["pl_streaks"]["Row"]; +export type PlDailyPrice = Database["public"]["Tables"]["pl_daily_prices"]["Row"]; +export type PlWeeklySnapshot = Database["public"]["Tables"]["pl_weekly_snapshots"]["Row"]; diff --git a/supabase/migrations/00035_airdrop_tables.sql b/supabase/migrations/00035_airdrop_tables.sql new file mode 100644 index 00000000..200f0cd3 --- /dev/null +++ b/supabase/migrations/00035_airdrop_tables.sql @@ -0,0 +1,62 @@ +-- Airdrop campaign tables (#878) +-- Parent: #877 + +-- PL Point ledger (append-only) +CREATE TABLE pl_points ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + address TEXT NOT NULL, + action TEXT NOT NULL, -- 'buy', 'referral', 'write', 'rate' + points NUMERIC NOT NULL, + metadata JSONB, -- { tx_hash, storyline_id, referred_address, trade_id } + created_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE INDEX idx_pl_points_address ON pl_points (address); +CREATE INDEX idx_pl_points_action ON pl_points (action); + +-- Referral relationships +CREATE TABLE pl_referrals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + referrer_address TEXT NOT NULL, + referred_address TEXT NOT NULL UNIQUE, + referral_code TEXT NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE INDEX idx_pl_referrals_referrer ON pl_referrals (referrer_address); + +-- Referral codes (one per wallet, immutable once set) +CREATE TABLE pl_referral_codes ( + address TEXT PRIMARY KEY, + code TEXT NOT NULL UNIQUE, + is_farcaster_username BOOLEAN DEFAULT FALSE +); + +-- Daily check-in streaks +CREATE TABLE pl_streaks ( + address TEXT PRIMARY KEY, + current_streak INTEGER DEFAULT 0, + last_checkin TIMESTAMPTZ, + longest_streak INTEGER DEFAULT 0 +); + +-- Daily PLOT price snapshots (for TWAP) +CREATE TABLE pl_daily_prices ( + id SERIAL PRIMARY KEY, + price_usd NUMERIC NOT NULL, + supply NUMERIC NOT NULL, + mcap_usd NUMERIC NOT NULL, + recorded_at DATE NOT NULL UNIQUE DEFAULT CURRENT_DATE +); + +-- Weekly campaign stats +CREATE TABLE pl_weekly_snapshots ( + id SERIAL PRIMARY KEY, + week_number INTEGER NOT NULL UNIQUE, + week_start DATE NOT NULL, + new_stories INTEGER DEFAULT 0, + token_buys INTEGER DEFAULT 0, + new_referrals INTEGER DEFAULT 0, + mcap_start NUMERIC, + mcap_end NUMERIC, + total_pl_earned NUMERIC DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW() +); From d618b78feaecbffdbadb2a088276f15c97ef533d Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Tue, 21 Apr 2026 08:29:22 +0900 Subject: [PATCH 2/3] [#878] Bump version to 0.1.27 Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d394ced..b5cd963d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.26", + "version": "0.1.27", "private": true, "workspaces": [ "packages/*" From b7c3c04ee8f39decc9800178c2633c03e3d4198d Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Tue, 21 Apr 2026 08:30:15 +0900 Subject: [PATCH 3/3] [#878] Add NOT NULL to all defaulted columns in airdrop tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align SQL schema with TypeScript types — columns with DEFAULT values should be NOT NULL since they always have values. Co-Authored-By: Claude Opus 4.6 (1M context) --- supabase/migrations/00035_airdrop_tables.sql | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/supabase/migrations/00035_airdrop_tables.sql b/supabase/migrations/00035_airdrop_tables.sql index 200f0cd3..3bd409a1 100644 --- a/supabase/migrations/00035_airdrop_tables.sql +++ b/supabase/migrations/00035_airdrop_tables.sql @@ -8,7 +8,7 @@ CREATE TABLE pl_points ( action TEXT NOT NULL, -- 'buy', 'referral', 'write', 'rate' points NUMERIC NOT NULL, metadata JSONB, -- { tx_hash, storyline_id, referred_address, trade_id } - created_at TIMESTAMPTZ DEFAULT NOW() + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_pl_points_address ON pl_points (address); CREATE INDEX idx_pl_points_action ON pl_points (action); @@ -19,7 +19,7 @@ CREATE TABLE pl_referrals ( referrer_address TEXT NOT NULL, referred_address TEXT NOT NULL UNIQUE, referral_code TEXT NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_pl_referrals_referrer ON pl_referrals (referrer_address); @@ -27,15 +27,15 @@ CREATE INDEX idx_pl_referrals_referrer ON pl_referrals (referrer_address); CREATE TABLE pl_referral_codes ( address TEXT PRIMARY KEY, code TEXT NOT NULL UNIQUE, - is_farcaster_username BOOLEAN DEFAULT FALSE + is_farcaster_username BOOLEAN NOT NULL DEFAULT FALSE ); -- Daily check-in streaks CREATE TABLE pl_streaks ( address TEXT PRIMARY KEY, - current_streak INTEGER DEFAULT 0, + current_streak INTEGER NOT NULL DEFAULT 0, last_checkin TIMESTAMPTZ, - longest_streak INTEGER DEFAULT 0 + longest_streak INTEGER NOT NULL DEFAULT 0 ); -- Daily PLOT price snapshots (for TWAP) @@ -52,11 +52,11 @@ CREATE TABLE pl_weekly_snapshots ( id SERIAL PRIMARY KEY, week_number INTEGER NOT NULL UNIQUE, week_start DATE NOT NULL, - new_stories INTEGER DEFAULT 0, - token_buys INTEGER DEFAULT 0, - new_referrals INTEGER DEFAULT 0, + new_stories INTEGER NOT NULL DEFAULT 0, + token_buys INTEGER NOT NULL DEFAULT 0, + new_referrals INTEGER NOT NULL DEFAULT 0, mcap_start NUMERIC, mcap_end NUMERIC, - total_pl_earned NUMERIC DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT NOW() + total_pl_earned NUMERIC NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() );