Fix ship physics init order and Supabase RLS user sync
All checks were successful
Build / build (push) Successful in 1m47s
All checks were successful
Build / build (push) Successful in 1m47s
- Pass initial position to ship.initialize() to set position BEFORE creating physics body, preventing collision race condition on reload - Use get_or_create_user_id RPC (security definer) to bypass RLS for user profile sync in both authService and cloudLeaderboardService - Sync user to Supabase on Auth0 login to ensure profile exists - Add Supabase schema.sql and policies.sql for documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d3d6175360
commit
c87b85de40
@ -353,12 +353,10 @@ export class Level1 implements Level {
|
||||
log.error('Initialize called twice');
|
||||
return;
|
||||
}
|
||||
await this._ship.initialize();
|
||||
setLoadingMessage("Loading level from configuration...");
|
||||
|
||||
// Apply ship configuration from level config
|
||||
// Get ship config BEFORE initialize to pass position (avoids physics race condition)
|
||||
const shipConfig = this._deserializer.getShipConfig();
|
||||
this._ship.position = new Vector3(...shipConfig.position);
|
||||
await this._ship.initialize(new Vector3(...shipConfig.position));
|
||||
setLoadingMessage("Loading level from configuration...");
|
||||
|
||||
if (shipConfig.linearVelocity) {
|
||||
this._ship.setLinearVelocity(new Vector3(...shipConfig.linearVelocity));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createAuth0Client, Auth0Client, User } from '@auth0/auth0-spa-js';
|
||||
import log from '../core/logger';
|
||||
import { SupabaseService } from './supabaseService';
|
||||
|
||||
/**
|
||||
* Singleton service for managing Auth0 authentication
|
||||
@ -89,6 +90,9 @@ export class AuthService {
|
||||
email: this._user?.email,
|
||||
sub: this._user?.sub
|
||||
});
|
||||
|
||||
// Sync user to Supabase (fire and forget - don't block init)
|
||||
this.syncUserToSupabase();
|
||||
} else {
|
||||
log.info('[AuthService] User not authenticated');
|
||||
}
|
||||
@ -156,6 +160,32 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync user to Supabase users table
|
||||
* Called after successful authentication
|
||||
* Uses RPC to bypass RLS via security definer function
|
||||
*/
|
||||
private async syncUserToSupabase(): Promise<void> {
|
||||
if (!this._user?.sub) return;
|
||||
|
||||
const supabase = SupabaseService.getInstance();
|
||||
if (!supabase.isConfigured()) return;
|
||||
|
||||
const client = await supabase.getAuthenticatedClient();
|
||||
if (!client) return;
|
||||
|
||||
// Use security definer function to create/get user (bypasses RLS)
|
||||
const { data, error } = await client.rpc('get_or_create_user_id', {
|
||||
p_auth0_id: this._user.sub
|
||||
});
|
||||
|
||||
if (error) {
|
||||
log.warn('[AuthService] Failed to sync user to Supabase:', error);
|
||||
} else {
|
||||
log.info('[AuthService] User synced to Supabase, UUID:', data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user logged in via Facebook
|
||||
* Auth0 stores the identity provider in the user's sub claim
|
||||
|
||||
@ -62,35 +62,32 @@ export class CloudLeaderboardService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure user exists in the users table with current display name
|
||||
* Ensure user exists in the users table
|
||||
* Called before submitting scores
|
||||
* Uses RPC to bypass RLS via security definer function
|
||||
* @returns The internal UUID of the user, or null on failure
|
||||
*/
|
||||
private async ensureUserProfile(userId: string, displayName: string): Promise<boolean> {
|
||||
private async ensureUserProfile(auth0Id: string): Promise<string | null> {
|
||||
const supabase = SupabaseService.getInstance();
|
||||
const client = await supabase.getAuthenticatedClient();
|
||||
|
||||
if (!client) {
|
||||
log.warn('[CloudLeaderboardService] Not authenticated - cannot sync user');
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Upsert the user (insert or update if exists)
|
||||
const { error } = await client
|
||||
.from('users')
|
||||
.upsert({
|
||||
user_id: userId,
|
||||
display_name: displayName
|
||||
}, {
|
||||
onConflict: 'user_id'
|
||||
// Use security definer function to create/get user (bypasses RLS)
|
||||
const { data, error } = await client.rpc('get_or_create_user_id', {
|
||||
p_auth0_id: auth0Id
|
||||
});
|
||||
|
||||
if (error) {
|
||||
log.error('[CloudLeaderboardService] Failed to sync user:', error);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info('[CloudLeaderboardService] User synced:', userId);
|
||||
return true;
|
||||
log.info('[CloudLeaderboardService] User synced:', auth0Id, '-> UUID:', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,11 +120,15 @@ export class CloudLeaderboardService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure user profile exists with current display name
|
||||
await this.ensureUserProfile(user.sub, result.playerName);
|
||||
// Ensure user profile exists and get the internal UUID
|
||||
const internalUserId = await this.ensureUserProfile(user.sub);
|
||||
if (!internalUserId) {
|
||||
log.warn('[CloudLeaderboardService] Failed to get/create user profile');
|
||||
return false;
|
||||
}
|
||||
|
||||
const entry = {
|
||||
user_id: user.sub,
|
||||
user_id: internalUserId,
|
||||
level_id: result.levelId,
|
||||
level_name: result.levelName,
|
||||
completed: result.completed,
|
||||
|
||||
@ -148,16 +148,18 @@ export class Ship {
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
public async initialize(initialPosition?: Vector3) {
|
||||
this._scoreboard = new Scoreboard();
|
||||
this._scoreboard.setShip(this); // Pass ship reference for velocity reading
|
||||
this._gameStats = new GameStats();
|
||||
this._ship = new TransformNode("shipBase", DefaultScene.MainScene);
|
||||
const data = await loadAsset("ship.glb");
|
||||
this._ship = data.container.transformNodes[0];
|
||||
//this._ship.rotation = new Vector3(0, Math.PI, 0);
|
||||
// this._ship.id = "Ship"; // Set ID so mission brief can find it
|
||||
// Position is now set from level config in Level1.initialize()
|
||||
|
||||
// Set position BEFORE creating physics body to avoid collision race condition
|
||||
if (initialPosition) {
|
||||
this._ship.position.copyFrom(initialPosition);
|
||||
}
|
||||
|
||||
// Create physics if enabled
|
||||
const config = GameConfig.getInstance();
|
||||
|
||||
310
supabase/policies.sql
Normal file
310
supabase/policies.sql
Normal file
@ -0,0 +1,310 @@
|
||||
create policy "Anyone can read leaderboard" on public.leaderboard
|
||||
as permissive
|
||||
for select
|
||||
using true;
|
||||
|
||||
create policy "Allow all inserts" on public.leaderboard
|
||||
as permissive
|
||||
for insert
|
||||
with check true;
|
||||
|
||||
create function public.is_admin() returns boolean
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
RETURN EXISTS (
|
||||
SELECT 1 FROM admins
|
||||
WHERE user_id = auth.uid()::text
|
||||
AND is_active = true
|
||||
AND (expires_at IS NULL OR expires_at > NOW())
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.is_admin() owner to postgres;
|
||||
|
||||
grant execute on function public.is_admin() to anon;
|
||||
|
||||
grant execute on function public.is_admin() to authenticated;
|
||||
|
||||
grant execute on function public.is_admin() to service_role;
|
||||
|
||||
create function public.has_admin_permission(permission text) returns boolean
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
DECLARE
|
||||
admin_record admins%ROWTYPE;
|
||||
BEGIN
|
||||
SELECT * INTO admin_record FROM admins
|
||||
WHERE user_id = auth.uid()::text
|
||||
AND is_active = true
|
||||
AND (expires_at IS NULL OR expires_at > NOW());
|
||||
|
||||
IF admin_record IS NULL THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
CASE permission
|
||||
WHEN 'review_levels' THEN RETURN admin_record.can_review_levels;
|
||||
WHEN 'manage_admins' THEN RETURN admin_record.can_manage_admins;
|
||||
WHEN 'manage_official' THEN RETURN admin_record.can_manage_official;
|
||||
WHEN 'view_analytics' THEN RETURN admin_record.can_view_analytics;
|
||||
ELSE RETURN false;
|
||||
END CASE;
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.has_admin_permission(text) owner to postgres;
|
||||
|
||||
grant execute on function public.has_admin_permission(text) to anon;
|
||||
|
||||
grant execute on function public.has_admin_permission(text) to authenticated;
|
||||
|
||||
grant execute on function public.has_admin_permission(text) to service_role;
|
||||
|
||||
create function public.validate_slug(slug text) returns boolean
|
||||
immutable
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
-- Allow NULL slugs (optional)
|
||||
IF slug IS NULL THEN
|
||||
RETURN true;
|
||||
END IF;
|
||||
|
||||
-- Must be 3-50 chars, lowercase alphanumeric with hyphens, no leading/trailing hyphens
|
||||
RETURN slug ~ '^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$';
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.validate_slug(text) owner to postgres;
|
||||
|
||||
grant execute on function public.validate_slug(text) to anon;
|
||||
|
||||
grant execute on function public.validate_slug(text) to authenticated;
|
||||
|
||||
grant execute on function public.validate_slug(text) to service_role;
|
||||
|
||||
create function public.is_slug_available(check_slug text, exclude_level_id uuid DEFAULT NULL::uuid) returns boolean
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
IF check_slug IS NULL THEN
|
||||
RETURN true;
|
||||
END IF;
|
||||
|
||||
RETURN NOT EXISTS (
|
||||
SELECT 1 FROM levels
|
||||
WHERE slug = check_slug
|
||||
AND (exclude_level_id IS NULL OR id != exclude_level_id)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.is_slug_available(text, uuid) owner to postgres;
|
||||
|
||||
grant execute on function public.is_slug_available(text, uuid) to anon;
|
||||
|
||||
grant execute on function public.is_slug_available(text, uuid) to authenticated;
|
||||
|
||||
grant execute on function public.is_slug_available(text, uuid) to service_role;
|
||||
|
||||
create function public.submit_level_for_review(level_id uuid) returns void
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
UPDATE levels
|
||||
SET level_type = 'pending_review',
|
||||
submitted_at = NOW()
|
||||
WHERE id = level_id
|
||||
AND user_id = auth_user_id()
|
||||
AND level_type IN ('private', 'rejected');
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.submit_level_for_review(uuid) owner to postgres;
|
||||
|
||||
grant execute on function public.submit_level_for_review(uuid) to anon;
|
||||
|
||||
grant execute on function public.submit_level_for_review(uuid) to authenticated;
|
||||
|
||||
grant execute on function public.submit_level_for_review(uuid) to service_role;
|
||||
|
||||
create function public.approve_level(p_level_id uuid, p_notes text DEFAULT NULL::text) returns void
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT has_admin_permission('review_levels') THEN
|
||||
RAISE EXCEPTION 'Permission denied';
|
||||
END IF;
|
||||
|
||||
UPDATE levels
|
||||
SET
|
||||
level_type = 'published',
|
||||
reviewed_at = NOW(),
|
||||
reviewed_by = auth.uid()::text,
|
||||
review_notes = p_notes,
|
||||
updated_at = NOW()
|
||||
WHERE id = p_level_id
|
||||
AND level_type = 'pending_review';
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.approve_level(uuid, text) owner to postgres;
|
||||
|
||||
grant execute on function public.approve_level(uuid, text) to anon;
|
||||
|
||||
grant execute on function public.approve_level(uuid, text) to authenticated;
|
||||
|
||||
grant execute on function public.approve_level(uuid, text) to service_role;
|
||||
|
||||
create function public.reject_level(p_level_id uuid, p_notes text) returns void
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT has_admin_permission('review_levels') THEN
|
||||
RAISE EXCEPTION 'Permission denied';
|
||||
END IF;
|
||||
|
||||
UPDATE levels
|
||||
SET
|
||||
level_type = 'rejected',
|
||||
reviewed_at = NOW(),
|
||||
reviewed_by = auth.uid()::text,
|
||||
review_notes = p_notes,
|
||||
updated_at = NOW()
|
||||
WHERE id = p_level_id
|
||||
AND level_type = 'pending_review';
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.reject_level(uuid, text) owner to postgres;
|
||||
|
||||
grant execute on function public.reject_level(uuid, text) to anon;
|
||||
|
||||
grant execute on function public.reject_level(uuid, text) to authenticated;
|
||||
|
||||
grant execute on function public.reject_level(uuid, text) to service_role;
|
||||
|
||||
create function public.update_updated_at() returns trigger
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.update_updated_at() owner to postgres;
|
||||
|
||||
grant execute on function public.update_updated_at() to anon;
|
||||
|
||||
grant execute on function public.update_updated_at() to authenticated;
|
||||
|
||||
grant execute on function public.update_updated_at() to service_role;
|
||||
|
||||
create function public.increment_play_count(p_level_id uuid) returns void
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
UPDATE levels
|
||||
SET play_count = play_count + 1
|
||||
WHERE id = p_level_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.increment_play_count(uuid) owner to postgres;
|
||||
|
||||
grant execute on function public.increment_play_count(uuid) to anon;
|
||||
|
||||
grant execute on function public.increment_play_count(uuid) to authenticated;
|
||||
|
||||
grant execute on function public.increment_play_count(uuid) to service_role;
|
||||
|
||||
create function public.increment_completion_count(p_level_id uuid) returns void
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
UPDATE levels
|
||||
SET completion_count = completion_count + 1
|
||||
WHERE id = p_level_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.increment_completion_count(uuid) owner to postgres;
|
||||
|
||||
grant execute on function public.increment_completion_count(uuid) to anon;
|
||||
|
||||
grant execute on function public.increment_completion_count(uuid) to authenticated;
|
||||
|
||||
grant execute on function public.increment_completion_count(uuid) to service_role;
|
||||
|
||||
create function public.get_or_create_user_id(p_auth0_id text) returns uuid
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
BEGIN
|
||||
-- Try to find existing user
|
||||
SELECT id INTO v_user_id FROM users WHERE auth0_id = p_auth0_id;
|
||||
|
||||
-- Create if not found
|
||||
IF v_user_id IS NULL THEN
|
||||
INSERT INTO users (auth0_id) VALUES (p_auth0_id)
|
||||
RETURNING id INTO v_user_id;
|
||||
END IF;
|
||||
|
||||
RETURN v_user_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.get_or_create_user_id(text) owner to postgres;
|
||||
|
||||
grant execute on function public.get_or_create_user_id(text) to anon;
|
||||
|
||||
grant execute on function public.get_or_create_user_id(text) to authenticated;
|
||||
|
||||
grant execute on function public.get_or_create_user_id(text) to service_role;
|
||||
|
||||
create function public.auth_user_id() returns uuid
|
||||
stable
|
||||
security definer
|
||||
language plpgsql
|
||||
as
|
||||
$$
|
||||
BEGIN
|
||||
RETURN (
|
||||
SELECT id FROM users
|
||||
WHERE auth0_id = auth.jwt() ->> 'sub'
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
alter function public.auth_user_id() owner to postgres;
|
||||
|
||||
grant execute on function public.auth_user_id() to anon;
|
||||
|
||||
grant execute on function public.auth_user_id() to authenticated;
|
||||
|
||||
grant execute on function public.auth_user_id() to service_role;
|
||||
|
||||
228
supabase/schema.sql
Normal file
228
supabase/schema.sql
Normal file
@ -0,0 +1,228 @@
|
||||
create table public._migrations
|
||||
(
|
||||
id serial
|
||||
primary key,
|
||||
name text not null
|
||||
unique,
|
||||
executed_at timestamp with time zone default now()
|
||||
);
|
||||
|
||||
alter table public._migrations
|
||||
owner to postgres;
|
||||
|
||||
grant select, update, usage on sequence public._migrations_id_seq to anon;
|
||||
|
||||
grant select, update, usage on sequence public._migrations_id_seq to authenticated;
|
||||
|
||||
grant select, update, usage on sequence public._migrations_id_seq to service_role;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public._migrations to anon;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public._migrations to authenticated;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public._migrations to service_role;
|
||||
|
||||
create table public.users
|
||||
(
|
||||
id uuid default gen_random_uuid() not null
|
||||
primary key,
|
||||
auth0_id text not null
|
||||
unique,
|
||||
display_name text,
|
||||
email text,
|
||||
avatar_url text,
|
||||
created_at timestamp with time zone default now(),
|
||||
last_login_at timestamp with time zone default now()
|
||||
);
|
||||
|
||||
alter table public.users
|
||||
owner to postgres;
|
||||
|
||||
create table public.leaderboard
|
||||
(
|
||||
id uuid default gen_random_uuid() not null
|
||||
primary key,
|
||||
level_id text not null,
|
||||
level_name text not null,
|
||||
completed boolean not null,
|
||||
end_reason text not null,
|
||||
game_time_seconds numeric not null,
|
||||
asteroids_destroyed integer not null,
|
||||
total_asteroids integer not null,
|
||||
accuracy numeric not null,
|
||||
hull_damage_taken numeric not null,
|
||||
fuel_consumed numeric not null,
|
||||
final_score integer not null,
|
||||
star_rating integer not null,
|
||||
created_at timestamp with time zone default now(),
|
||||
is_test_data boolean default false not null,
|
||||
user_id uuid
|
||||
constraint leaderboard_internal_user_id_fkey
|
||||
references public.users
|
||||
);
|
||||
|
||||
alter table public.leaderboard
|
||||
owner to postgres;
|
||||
|
||||
create index idx_leaderboard_score
|
||||
on public.leaderboard (final_score desc);
|
||||
|
||||
create index idx_leaderboard_level
|
||||
on public.leaderboard (level_id);
|
||||
|
||||
create index idx_leaderboard_test_data
|
||||
on public.leaderboard (is_test_data)
|
||||
where (is_test_data = true);
|
||||
|
||||
create index idx_leaderboard_user_id
|
||||
on public.leaderboard (user_id);
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.leaderboard to anon;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.leaderboard to authenticated;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.leaderboard to service_role;
|
||||
|
||||
create table public.admins
|
||||
(
|
||||
id uuid default gen_random_uuid() not null
|
||||
primary key,
|
||||
user_id text not null
|
||||
unique,
|
||||
display_name text,
|
||||
email text,
|
||||
can_review_levels boolean default true,
|
||||
can_manage_admins boolean default false,
|
||||
can_manage_official boolean default false,
|
||||
can_view_analytics boolean default false,
|
||||
is_active boolean default true,
|
||||
expires_at timestamp with time zone,
|
||||
created_at timestamp with time zone default now(),
|
||||
created_by text,
|
||||
notes text,
|
||||
internal_user_id uuid
|
||||
references public.users
|
||||
);
|
||||
|
||||
alter table public.admins
|
||||
owner to postgres;
|
||||
|
||||
create index idx_admins_user_id
|
||||
on public.admins (user_id);
|
||||
|
||||
create index idx_admins_active
|
||||
on public.admins (is_active)
|
||||
where (is_active = true);
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.admins to anon;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.admins to authenticated;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.admins to service_role;
|
||||
|
||||
create table public.levels
|
||||
(
|
||||
id uuid default gen_random_uuid() not null
|
||||
primary key,
|
||||
slug text
|
||||
unique
|
||||
constraint valid_slug_format
|
||||
check (validate_slug(slug)),
|
||||
name text not null,
|
||||
description text,
|
||||
difficulty text not null
|
||||
constraint valid_difficulty
|
||||
check (difficulty = ANY
|
||||
(ARRAY ['recruit'::text, 'pilot'::text, 'captain'::text, 'commander'::text, 'test'::text])),
|
||||
estimated_time text,
|
||||
tags text[] default '{}'::text[],
|
||||
config jsonb not null,
|
||||
mission_brief text[] default '{}'::text[],
|
||||
level_type text default 'private'::text not null
|
||||
constraint valid_level_type
|
||||
check (level_type = ANY
|
||||
(ARRAY ['official'::text, 'private'::text, 'pending_review'::text, 'published'::text, 'rejected'::text])),
|
||||
sort_order integer default 0,
|
||||
unlock_requirements text[] default '{}'::text[],
|
||||
default_locked boolean default false,
|
||||
submitted_at timestamp with time zone,
|
||||
reviewed_at timestamp with time zone,
|
||||
reviewed_by text,
|
||||
review_notes text,
|
||||
play_count integer default 0,
|
||||
completion_count integer default 0,
|
||||
avg_rating numeric(3, 2) default 0,
|
||||
rating_count integer default 0,
|
||||
created_at timestamp with time zone default now(),
|
||||
updated_at timestamp with time zone default now(),
|
||||
user_id uuid
|
||||
references public.users
|
||||
);
|
||||
|
||||
alter table public.levels
|
||||
owner to postgres;
|
||||
|
||||
create index idx_levels_type
|
||||
on public.levels (level_type);
|
||||
|
||||
create index idx_levels_slug
|
||||
on public.levels (slug);
|
||||
|
||||
create index idx_levels_official_order
|
||||
on public.levels (sort_order)
|
||||
where (level_type = 'official'::text);
|
||||
|
||||
create index idx_levels_published
|
||||
on public.levels (created_at desc)
|
||||
where (level_type = 'published'::text);
|
||||
|
||||
create index idx_levels_pending
|
||||
on public.levels (submitted_at)
|
||||
where (level_type = 'pending_review'::text);
|
||||
|
||||
create index idx_levels_user_id
|
||||
on public.levels (user_id);
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.levels to anon;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.levels to authenticated;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.levels to service_role;
|
||||
|
||||
create table public.level_ratings
|
||||
(
|
||||
id uuid default gen_random_uuid() not null
|
||||
primary key,
|
||||
level_id uuid not null
|
||||
references public.levels
|
||||
on delete cascade,
|
||||
rating integer not null
|
||||
constraint level_ratings_rating_check
|
||||
check ((rating >= 1) AND (rating <= 5)),
|
||||
created_at timestamp with time zone default now(),
|
||||
user_id uuid
|
||||
references public.users,
|
||||
unique (level_id, user_id)
|
||||
);
|
||||
|
||||
alter table public.level_ratings
|
||||
owner to postgres;
|
||||
|
||||
create index idx_ratings_level
|
||||
on public.level_ratings (level_id);
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.level_ratings to anon;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.level_ratings to authenticated;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.level_ratings to service_role;
|
||||
|
||||
create index idx_users_auth0_id
|
||||
on public.users (auth0_id);
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.users to anon;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.users to authenticated;
|
||||
|
||||
grant delete, insert, references, select, trigger, truncate, update on public.users to service_role;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user