{JSON.stringify(state.data, null, 2)};
+}
diff --git a/apps/supabase-auth/src/index.ts b/apps/supabase-auth/src/index.ts
index 9d1ff37..9a36d0c 100644
--- a/apps/supabase-auth/src/index.ts
+++ b/apps/supabase-auth/src/index.ts
@@ -37,9 +37,9 @@ app.post('/api/invite/:role/:email', async (c) => {
INTERNAL_SERVER_ERROR
);
}
- // role should be between 1 and 7
+ // role should be between 1 and 8
// maybe we shouldn't hardcode this, but I'm not sure how to change this for now
- if (role > 0 && role < 8) {
+ if (role > 0 && role < 9) {
const result = await supabase.from('user_invites').insert({
role: role,
email: email,
@@ -52,7 +52,7 @@ app.post('/api/invite/:role/:email', async (c) => {
return c.json(
{
error: 'PARAMETER_ERROR',
- message: 'Role number not recognized should be between 1 and 7',
+ message: 'Role number not recognized should be between 1 and 8',
},
INTERNAL_SERVER_ERROR
);
diff --git a/libs/styles/src/lib/colors.ts b/libs/styles/src/lib/colors.ts
index a960d66..ca41cb5 100644
--- a/libs/styles/src/lib/colors.ts
+++ b/libs/styles/src/lib/colors.ts
@@ -67,5 +67,9 @@ export namespace Colors2023 {
light: BLUE.LIGHT,
standard: BLUE.STANDARD,
},
+ [HibiscusRole.FINALIST]: {
+ light: PURPLE.LIGHT,
+ standard: PURPLE.STANDARD,
+ },
};
}
diff --git a/libs/types/src/lib/roles.ts b/libs/types/src/lib/roles.ts
index 42667c0..4db9ed2 100644
--- a/libs/types/src/lib/roles.ts
+++ b/libs/types/src/lib/roles.ts
@@ -6,4 +6,5 @@ export enum HibiscusRole {
HACKER = 'HACKER',
APPLICANT = 'APPLICANT',
JUDGE = 'JUDGE',
+ FINALIST = 'FINALIST',
}
diff --git a/libs/types/src/lib/supabase.gen.ts b/libs/types/src/lib/supabase.gen.ts
index 082b322..2c51b05 100644
--- a/libs/types/src/lib/supabase.gen.ts
+++ b/libs/types/src/lib/supabase.gen.ts
@@ -272,116 +272,6 @@ export type Database = {
};
Relationships: [];
};
- email_jobs: {
- Row: {
- completed_at: string | null;
- created_at: string;
- created_by: string | null;
- custom_body: string | null;
- error_message: string | null;
- failed_count: number;
- id: string;
- sent_count: number;
- started_at: string | null;
- status: string;
- subject: string;
- target_status: number[];
- template: string;
- total_recipients: number;
- };
- Insert: {
- completed_at?: string | null;
- created_at?: string;
- created_by?: string | null;
- custom_body?: string | null;
- error_message?: string | null;
- failed_count?: number;
- id?: string;
- sent_count?: number;
- started_at?: string | null;
- status?: string;
- subject: string;
- target_status: number[];
- template: string;
- total_recipients?: number;
- };
- Update: {
- completed_at?: string | null;
- created_at?: string;
- created_by?: string | null;
- custom_body?: string | null;
- error_message?: string | null;
- failed_count?: number;
- id?: string;
- sent_count?: number;
- started_at?: string | null;
- status?: string;
- subject?: string;
- target_status?: number[];
- template?: string;
- total_recipients?: number;
- };
- Relationships: [
- {
- foreignKeyName: 'email_jobs_created_by_fkey';
- columns: ['created_by'];
- isOneToOne: false;
- referencedRelation: 'user_profiles';
- referencedColumns: ['user_id'];
- }
- ];
- };
- email_logs: {
- Row: {
- created_at: string;
- error_message: string | null;
- id: string;
- job_id: string;
- recipient_email: string;
- recipient_id: string | null;
- resend_id: string | null;
- sent_at: string | null;
- status: string;
- };
- Insert: {
- created_at?: string;
- error_message?: string | null;
- id?: string;
- job_id: string;
- recipient_email: string;
- recipient_id?: string | null;
- resend_id?: string | null;
- sent_at?: string | null;
- status?: string;
- };
- Update: {
- created_at?: string;
- error_message?: string | null;
- id?: string;
- job_id?: string;
- recipient_email?: string;
- recipient_id?: string | null;
- resend_id?: string | null;
- sent_at?: string | null;
- status?: string;
- };
- Relationships: [
- {
- foreignKeyName: 'email_logs_job_id_fkey';
- columns: ['job_id'];
- isOneToOne: false;
- referencedRelation: 'email_jobs';
- referencedColumns: ['id'];
- },
- {
- foreignKeyName: 'email_logs_recipient_id_fkey';
- columns: ['recipient_id'];
- isOneToOne: false;
- referencedRelation: 'user_profiles';
- referencedColumns: ['user_id'];
- }
- ];
- };
event_log: {
Row: {
check_in_time: string;
@@ -694,6 +584,21 @@ export type Database = {
};
Relationships: [];
};
+ rsvp_status: {
+ Row: {
+ id: number;
+ status: string;
+ };
+ Insert: {
+ id?: number;
+ status: string;
+ };
+ Update: {
+ id?: number;
+ status?: string;
+ };
+ Relationships: [];
+ };
sponsor_user_bridge_company: {
Row: {
company_id: string;
@@ -1020,9 +925,15 @@ export type Database = {
email: string | null;
first_name: string;
last_name: string;
+ memento_uuid: string;
+ monkeytype_duel_otp: string;
+ monkeytype_duel_settings: Json;
+ monkeytype_wpm: number | null;
+ mygate_otp: number | null;
referral_code: string | null;
referred_by: string | null;
role: number | null;
+ rsvp_status: number;
submission_id: string | null;
submission_status: number | null;
submitted_at: string | null;
@@ -1038,9 +949,15 @@ export type Database = {
email?: string | null;
first_name: string;
last_name: string;
+ memento_uuid?: string;
+ monkeytype_duel_otp?: string;
+ monkeytype_duel_settings?: Json;
+ monkeytype_wpm?: number | null;
+ mygate_otp?: number | null;
referral_code?: string | null;
referred_by?: string | null;
role?: number | null;
+ rsvp_status?: number;
submission_id?: string | null;
submission_status?: number | null;
submitted_at?: string | null;
@@ -1056,9 +973,15 @@ export type Database = {
email?: string | null;
first_name?: string;
last_name?: string;
+ memento_uuid?: string;
+ monkeytype_duel_otp?: string;
+ monkeytype_duel_settings?: Json;
+ monkeytype_wpm?: number | null;
+ mygate_otp?: number | null;
referral_code?: string | null;
referred_by?: string | null;
role?: number | null;
+ rsvp_status?: number;
submission_id?: string | null;
submission_status?: number | null;
submitted_at?: string | null;
@@ -1087,6 +1010,13 @@ export type Database = {
referencedRelation: 'roles';
referencedColumns: ['id'];
},
+ {
+ foreignKeyName: 'user_profiles_rsvp_status_fkey';
+ columns: ['rsvp_status'];
+ isOneToOne: false;
+ referencedRelation: 'rsvp_status';
+ referencedColumns: ['id'];
+ },
{
foreignKeyName: 'user_profiles_submission_status_fkey';
columns: ['submission_status'];
@@ -1163,9 +1093,12 @@ export type Database = {
[_ in never]: never;
};
Functions: {
+ gen_monkeytype_otp: { Args: never; Returns: string };
+ gen_unique_monkeytype_otp: { Args: never; Returns: string };
get_sponsors: { Args: never; Returns: string[] };
get_volunteers: { Args: never; Returns: string[] };
is_valid_url: { Args: { url: string }; Returns: boolean };
+ set_monkeytype_wpm_bulk: { Args: { updates: Json }; Returns: Json };
swap_stamp: {
Args: {
p_giver_id: string;
diff --git a/supabase/migrations/20260120000001_add_user_profiles_otp_and_monkeytype_settings.sql b/supabase/migrations/20260120000001_add_user_profiles_otp_and_monkeytype_settings.sql
new file mode 100644
index 0000000..5881716
--- /dev/null
+++ b/supabase/migrations/20260120000001_add_user_profiles_otp_and_monkeytype_settings.sql
@@ -0,0 +1,32 @@
+-- Migration: 20260120000001_add_user_profiles_otp_and_monkeytype_settings.sql
+-- Adds:
+-- - otp: 4-digit numeric code (stored as text to preserve leading zeros)
+-- - monkeytype_duel_settings: jsonb blob for Monkeytype duel settings
+
+CREATE OR REPLACE FUNCTION public.gen_monkeytype_otp()
+RETURNS text
+LANGUAGE sql
+VOLATILE
+AS $$
+ SELECT lpad((floor(random() * 10000))::int::text, 4, '0');
+$$;
+
+
+ALTER TABLE user_profiles
+ADD COLUMN IF NOT EXISTS monkeytype_duel_otp text;
+
+-- Default: random 4-digit code (0000-9999) with leading zeros
+ALTER TABLE user_profiles
+ALTER COLUMN monkeytype_duel_otp
+SET DEFAULT public.gen_monkeytype_otp();
+
+-- Backfill existing rows that don't yet have an OTP
+UPDATE user_profiles
+SET monkeytype_duel_otp = public.gen_monkeytype_otp()
+WHERE monkeytype_duel_otp IS NULL;
+
+ALTER TABLE user_profiles
+ADD COLUMN IF NOT EXISTS monkeytype_duel_settings jsonb NOT NULL DEFAULT '{}'::jsonb;
+
+COMMENT ON COLUMN user_profiles.monkeytype_duel_otp IS '[Monkeytype Duel] Authentication OTP (regenerated on request)';
+COMMENT ON COLUMN user_profiles.monkeytype_duel_settings IS '[Monkeytype Duel] Settings & customization';
\ No newline at end of file
diff --git a/supabase/migrations/20260123000001_make_monkeytype_duel_otp_unique.sql b/supabase/migrations/20260123000001_make_monkeytype_duel_otp_unique.sql
new file mode 100644
index 0000000..b45c20e
--- /dev/null
+++ b/supabase/migrations/20260123000001_make_monkeytype_duel_otp_unique.sql
@@ -0,0 +1,106 @@
+-- Migration: 20260123000001_make_monkeytype_duel_otp_unique.sql
+-- Purpose:
+-- - Ensure monkeytype_duel_otp is unique (used for authentication)
+-- - De-dupe any existing collisions
+-- - Prevent future collisions on INSERT/UPDATE by regenerating OTP
+
+-- 1) Generate a unique 4-digit OTP (serialized via advisory lock to avoid races)
+CREATE OR REPLACE FUNCTION public.gen_unique_monkeytype_otp()
+RETURNS text
+LANGUAGE plpgsql
+VOLATILE
+AS $$
+DECLARE
+ candidate text;
+BEGIN
+ -- Serialize OTP generation to avoid concurrent collisions.
+ PERFORM pg_advisory_xact_lock(hashtext('user_profiles.monkeytype_duel_otp'));
+
+ LOOP
+ candidate := public.gen_monkeytype_otp();
+ EXIT WHEN NOT EXISTS (
+ SELECT 1
+ FROM public.user_profiles up
+ WHERE up.monkeytype_duel_otp = candidate
+ );
+ END LOOP;
+
+ RETURN candidate;
+END;
+$$;
+
+-- 2) Trigger: if INSERT/UPDATE would result in a collision (or NULL/empty), regenerate
+CREATE OR REPLACE FUNCTION public.ensure_unique_monkeytype_duel_otp()
+RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF NEW.monkeytype_duel_otp IS NULL OR NEW.monkeytype_duel_otp = '' THEN
+ NEW.monkeytype_duel_otp := public.gen_unique_monkeytype_otp();
+ RETURN NEW;
+ END IF;
+
+ IF EXISTS (
+ SELECT 1
+ FROM public.user_profiles up
+ WHERE up.monkeytype_duel_otp = NEW.monkeytype_duel_otp
+ AND up.user_id <> NEW.user_id
+ ) THEN
+ NEW.monkeytype_duel_otp := public.gen_unique_monkeytype_otp();
+ END IF;
+
+ RETURN NEW;
+END;
+$$;
+
+DROP TRIGGER IF EXISTS trg_ensure_unique_monkeytype_duel_otp ON public.user_profiles;
+CREATE TRIGGER trg_ensure_unique_monkeytype_duel_otp
+BEFORE INSERT OR UPDATE ON public.user_profiles
+FOR EACH ROW
+EXECUTE FUNCTION public.ensure_unique_monkeytype_duel_otp();
+
+-- 3) Clean up existing data:
+-- - Backfill NULL/empty OTPs
+-- - Resolve collisions by regenerating for all but one row per duplicated OTP
+DO $$
+DECLARE
+ r record;
+BEGIN
+ -- Backfill NULL/empty
+ UPDATE public.user_profiles
+ SET monkeytype_duel_otp = public.gen_unique_monkeytype_otp()
+ WHERE monkeytype_duel_otp IS NULL OR monkeytype_duel_otp = '';
+
+ -- De-dupe collisions (keep first by user_id)
+ FOR r IN
+ SELECT user_id
+ FROM (
+ SELECT
+ user_id,
+ monkeytype_duel_otp,
+ row_number() OVER (
+ PARTITION BY monkeytype_duel_otp
+ ORDER BY user_id
+ ) AS rn
+ FROM public.user_profiles
+ WHERE monkeytype_duel_otp IS NOT NULL AND monkeytype_duel_otp <> ''
+ ) t
+ WHERE t.rn > 1
+ LOOP
+ UPDATE public.user_profiles
+ SET monkeytype_duel_otp = public.gen_unique_monkeytype_otp()
+ WHERE user_id = r.user_id;
+ END LOOP;
+END $$;
+
+-- 4) Enforce uniqueness at the database level
+CREATE UNIQUE INDEX IF NOT EXISTS user_profiles_monkeytype_duel_otp_unique_idx
+ ON public.user_profiles (monkeytype_duel_otp);
+
+-- 5) Make it non-nullable and set a safe default going forward
+ALTER TABLE public.user_profiles
+ ALTER COLUMN monkeytype_duel_otp SET DEFAULT public.gen_unique_monkeytype_otp();
+
+ALTER TABLE public.user_profiles
+ ALTER COLUMN monkeytype_duel_otp SET NOT NULL;
+
diff --git a/supabase/migrations/20260127000001_add_finalist_role.sql b/supabase/migrations/20260127000001_add_finalist_role.sql
new file mode 100644
index 0000000..935aa84
--- /dev/null
+++ b/supabase/migrations/20260127000001_add_finalist_role.sql
@@ -0,0 +1,6 @@
+-- Add FINALIST role (id=8).
+-- NOTE: Some dashboard code maps role ids to the HibiscusRole enum by index,
+-- so this role must be appended as the next sequential id.
+INSERT INTO public.roles (id, name)
+VALUES (8, 'FINALIST')
+ON CONFLICT DO NOTHING;
diff --git a/supabase/migrations/20260128000001_add_monkeytype_wpm_to_user_profiles.sql b/supabase/migrations/20260128000001_add_monkeytype_wpm_to_user_profiles.sql
new file mode 100644
index 0000000..ae2e7d2
--- /dev/null
+++ b/supabase/migrations/20260128000001_add_monkeytype_wpm_to_user_profiles.sql
@@ -0,0 +1,8 @@
+-- Migration: 20260128000001_add_monkeytype_wpm_to_user_profiles.sql
+-- Adds:
+-- - monkeytype_wpm: integer WPM for Monkeytype
+
+ALTER TABLE user_profiles
+ADD COLUMN IF NOT EXISTS monkeytype_wpm integer;
+
+COMMENT ON COLUMN user_profiles.monkeytype_wpm IS '[Monkeytype] User WPM';
diff --git a/supabase/migrations/20260128000003_change_set_monkeytype_wpm_bulk_input_to_array.sql b/supabase/migrations/20260128000003_change_set_monkeytype_wpm_bulk_input_to_array.sql
new file mode 100644
index 0000000..d938e00
--- /dev/null
+++ b/supabase/migrations/20260128000003_change_set_monkeytype_wpm_bulk_input_to_array.sql
@@ -0,0 +1,48 @@
+-- Migration: 20260128000003_change_set_monkeytype_wpm_bulk_input_to_array.sql
+-- Adds/Defines:
+-- - set_monkeytype_wpm_bulk(jsonb) expecting a JSON array of objects:
+-- [{ "user_id": "