+ )
+}
diff --git a/src/components/Auth/LoginForm.tsx b/src/components/Auth/LoginForm.tsx
new file mode 100644
index 0000000..32cbd5e
--- /dev/null
+++ b/src/components/Auth/LoginForm.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import React, { useActionState} from "react";
+import { FiEye, FiEyeOff } from "react-icons/fi";
+import { AuthActionState, login } from "@/app/login/actions";
+import { useSearchParams } from "next/navigation";
+
+export default function LoginForm() {
+ const [email, setEmail] = React.useState("");
+ const [password, setPassword] = React.useState("");
+ const [showPassword, setShowPassword] = React.useState(false);
+ const searchParams = useSearchParams();
+ const urlError = searchParams.get("error");
+
+ const [state, formAction] = useActionState(login, { error: null });
+ const errorMessage = urlError === "EMAIL_CONFIRMATION_ERROR" ?
+ "Email verification failed. Try again or request a new link." :
+ state?.error || null;
+
+ const emailValid = /.+@.+\..+/.test(email);
+ const passwordValid = password.length > 1;
+ const isValid = emailValid && passwordValid;
+
+ return (
+
+ );
+}
+
+
diff --git a/src/components/Auth/SignupForm.tsx b/src/components/Auth/SignupForm.tsx
new file mode 100644
index 0000000..878d162
--- /dev/null
+++ b/src/components/Auth/SignupForm.tsx
@@ -0,0 +1,136 @@
+"use client";
+
+import React, { useActionState, useEffect } from "react";
+import { FiEye, FiEyeOff } from "react-icons/fi";
+import { AuthActionState, signup } from "@/app/signup/actions";
+import { validateEmail, validatePassword } from "@/utils/auth";
+
+export default function SignupForm() {
+ const [email, setEmail] = React.useState("");
+ const [password, setPassword] = React.useState("");
+ const [confirm, setConfirm] = React.useState("");
+ const [showPassword, setShowPassword] = React.useState(false);
+ const [emailError, setEmailError] = React.useState(null);
+ const [passwordError, setPasswordError] = React.useState(null);
+
+ const [state, formAction] = useActionState(signup, { error: null });
+ const passwordsMatch = password === confirm;
+ const isValid = !emailError && !passwordError && passwordsMatch;
+
+ useEffect(() => {
+ const { error } = validateEmail(email);
+ setEmailError(error);
+ }, [email]);
+
+ useEffect(() => {
+ const { error } = validatePassword(password);
+ setPasswordError(error);
+ }, [password]);
+
+ return (
+
+ );
+}
+
+
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..e3c0192
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,20 @@
+import { type NextRequest } from 'next/server'
+import { updateSession } from '@/utils/supabase/middleware'
+
+export async function middleware(request: NextRequest) {
+ // update user's auth session
+ return await updateSession(request)
+}
+
+export const config = {
+ matcher: [
+ /*
+ * Match all request paths except for the ones starting with:
+ * - _next/static (static files)
+ * - _next/image (image optimization files)
+ * - favicon.ico (favicon file)
+ * Feel free to modify this pattern to include more paths.
+ */
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
+ ],
+}
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
new file mode 100644
index 0000000..3680f79
--- /dev/null
+++ b/src/utils/auth.ts
@@ -0,0 +1,16 @@
+export const validatePassword = (password: string): { error: string | null } => {
+ if (password.length < 6) {
+ return { error: 'Password must be at least 6 characters long.' };
+ }
+ if (!/^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?:[^\s])+$/.test(password)) {
+ return { error: 'Password must contain at least one uppercase letter, one lowercase letter, and one number.' };
+ }
+ return { error: null };
+};
+
+export const validateEmail = (email: string): { error: string | null } => {
+ if (!/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_'+\-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/.test(email)) {
+ return { error: 'Invalid email address.' };
+ }
+ return { error: null };
+};
diff --git a/src/utils/supabase/client.ts b/src/utils/supabase/client.ts
new file mode 100644
index 0000000..30f697a
--- /dev/null
+++ b/src/utils/supabase/client.ts
@@ -0,0 +1,9 @@
+import { createBrowserClient } from "@supabase/ssr";
+
+export function createClient() {
+ // Create a supabase client on the browser with project's credentials
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
+ );
+}
diff --git a/src/utils/supabase/middleware.ts b/src/utils/supabase/middleware.ts
new file mode 100644
index 0000000..cccf4c6
--- /dev/null
+++ b/src/utils/supabase/middleware.ts
@@ -0,0 +1,34 @@
+import { createServerClient } from '@supabase/ssr'
+import { NextResponse, type NextRequest } from 'next/server'
+
+export async function updateSession(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({
+ request,
+ })
+
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return request.cookies.getAll()
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
+ supabaseResponse = NextResponse.next({
+ request,
+ })
+ cookiesToSet.forEach(({ name, value, options }) =>
+ supabaseResponse.cookies.set(name, value, options)
+ )
+ },
+ },
+ }
+ )
+
+ // refreshing the auth token
+ await supabase.auth.getUser()
+
+ return supabaseResponse
+}
diff --git a/src/utils/supabase/server.ts b/src/utils/supabase/server.ts
new file mode 100644
index 0000000..5b32e5b
--- /dev/null
+++ b/src/utils/supabase/server.ts
@@ -0,0 +1,31 @@
+import { createServerClient } from "@supabase/ssr";
+import { cookies } from "next/headers";
+
+export async function createClient() {
+ const cookieStore = await cookies();
+
+ // Create a server's supabase client with newly configured cookie,
+ // which could be used to maintain user's session
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return cookieStore.getAll();
+ },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ );
+ } catch {
+ // The `setAll` method was called from a Server Component.
+ // This can be ignored if you have middleware refreshing
+ // user sessions.
+ }
+ },
+ },
+ }
+ );
+}
diff --git a/supabase/.gitignore b/supabase/.gitignore
new file mode 100644
index 0000000..ad9264f
--- /dev/null
+++ b/supabase/.gitignore
@@ -0,0 +1,8 @@
+# Supabase
+.branches
+.temp
+
+# dotenvx
+.env.keys
+.env.local
+.env.*.local
diff --git a/supabase/config.toml b/supabase/config.toml
new file mode 100644
index 0000000..3eca269
--- /dev/null
+++ b/supabase/config.toml
@@ -0,0 +1,347 @@
+# For detailed configuration reference documentation, visit:
+# https://supabase.com/docs/guides/local-development/cli/config
+# A string used to distinguish different Supabase projects on the same host. Defaults to the
+# working directory name when running `supabase init`.
+project_id = "pokemon-romhack-platform"
+
+[api]
+enabled = true
+# Port to use for the API URL.
+port = 54321
+# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
+# endpoints. `public` and `graphql_public` schemas are included by default.
+schemas = ["public", "graphql_public"]
+# Extra schemas to add to the search_path of every request.
+extra_search_path = ["public", "extensions"]
+# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
+# for accidental or malicious requests.
+max_rows = 1000
+
+[api.tls]
+# Enable HTTPS endpoints locally using a self-signed certificate.
+enabled = false
+# Paths to self-signed certificate pair.
+# cert_path = "../certs/my-cert.pem"
+# key_path = "../certs/my-key.pem"
+
+[db]
+# Port to use for the local database URL.
+port = 54322
+# Port used by db diff command to initialize the shadow database.
+shadow_port = 54320
+# The database major version to use. This has to be the same as your remote database's. Run `SHOW
+# server_version;` on the remote database to check.
+major_version = 17
+
+[db.pooler]
+enabled = false
+# Port to use for the local connection pooler.
+port = 54329
+# Specifies when a server connection can be reused by other clients.
+# Configure one of the supported pooler modes: `transaction`, `session`.
+pool_mode = "transaction"
+# How many server connections to allow per user/database pair.
+default_pool_size = 20
+# Maximum number of client connections allowed.
+max_client_conn = 100
+
+# [db.vault]
+# secret_key = "env(SECRET_VALUE)"
+
+[db.migrations]
+# If disabled, migrations will be skipped during a db push or reset.
+enabled = true
+# Specifies an ordered list of schema files that describe your database.
+# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
+schema_paths = []
+
+[db.seed]
+# If enabled, seeds the database after migrations during a db reset.
+enabled = true
+# Specifies an ordered list of seed files to load during db reset.
+# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
+sql_paths = ["./seed.sql"]
+
+[db.network_restrictions]
+# Enable management of network restrictions.
+enabled = false
+# List of IPv4 CIDR blocks allowed to connect to the database.
+# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
+allowed_cidrs = ["0.0.0.0/0"]
+# List of IPv6 CIDR blocks allowed to connect to the database.
+# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
+allowed_cidrs_v6 = ["::/0"]
+
+[realtime]
+enabled = true
+# Bind realtime via either IPv4 or IPv6. (default: IPv4)
+# ip_version = "IPv6"
+# The maximum length in bytes of HTTP request headers. (default: 4096)
+# max_header_length = 4096
+
+[studio]
+enabled = true
+# Port to use for Supabase Studio.
+port = 54323
+# External URL of the API server that frontend connects to.
+api_url = "http://127.0.0.1"
+# OpenAI API Key to use for Supabase AI in the Supabase Studio.
+openai_api_key = "env(OPENAI_API_KEY)"
+
+# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
+# are monitored, and you can view the emails that would have been sent from the web interface.
+[inbucket]
+enabled = true
+# Port to use for the email testing server web interface.
+port = 54324
+# Uncomment to expose additional ports for testing user applications that send emails.
+# smtp_port = 54325
+# pop3_port = 54326
+# admin_email = "admin@email.com"
+# sender_name = "Admin"
+
+[storage]
+enabled = true
+# The maximum file size allowed (e.g. "5MB", "500KB").
+file_size_limit = "50MiB"
+
+# Image transformation API is available to Supabase Pro plan.
+# [storage.image_transformation]
+# enabled = true
+
+# Uncomment to configure local storage buckets
+# [storage.buckets.images]
+# public = false
+# file_size_limit = "50MiB"
+# allowed_mime_types = ["image/png", "image/jpeg"]
+# objects_path = "./images"
+
+[auth]
+enabled = true
+# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
+# in emails.
+site_url = "http://127.0.0.1:3000"
+# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
+additional_redirect_urls = ["https://127.0.0.1:3000"]
+# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
+jwt_expiry = 3600
+# Path to JWT signing key. DO NOT commit your signing keys file to git.
+# signing_keys_path = "./signing_keys.json"
+# If disabled, the refresh token will never expire.
+enable_refresh_token_rotation = true
+# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
+# Requires enable_refresh_token_rotation = true.
+refresh_token_reuse_interval = 10
+# Allow/disallow new user signups to your project.
+enable_signup = true
+# Allow/disallow anonymous sign-ins to your project.
+enable_anonymous_sign_ins = false
+# Allow/disallow testing manual linking of accounts
+enable_manual_linking = false
+# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
+minimum_password_length = 6
+# Passwords that do not meet the following requirements will be rejected as weak. Supported values
+# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
+password_requirements = ""
+
+[auth.rate_limit]
+# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
+email_sent = 2
+# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
+sms_sent = 30
+# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
+anonymous_users = 30
+# Number of sessions that can be refreshed in a 5 minute interval per IP address.
+token_refresh = 150
+# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
+sign_in_sign_ups = 30
+# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
+token_verifications = 30
+# Number of Web3 logins that can be made in a 5 minute interval per IP address.
+web3 = 30
+
+# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
+# [auth.captcha]
+# enabled = true
+# provider = "hcaptcha"
+# secret = ""
+
+[auth.email]
+# Allow/disallow new user signups via email to your project.
+enable_signup = true
+# If enabled, a user will be required to confirm any email change on both the old, and new email
+# addresses. If disabled, only the new email is required to confirm.
+double_confirm_changes = true
+# If enabled, users need to confirm their email address before signing in.
+enable_confirmations = false
+# If enabled, users will need to reauthenticate or have logged in recently to change their password.
+secure_password_change = false
+# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
+max_frequency = "1s"
+# Number of characters used in the email OTP.
+otp_length = 6
+# Number of seconds before the email OTP expires (defaults to 1 hour).
+otp_expiry = 3600
+
+# Use a production-ready SMTP server
+# [auth.email.smtp]
+# enabled = true
+# host = "smtp.sendgrid.net"
+# port = 587
+# user = "apikey"
+# pass = "env(SENDGRID_API_KEY)"
+# admin_email = "admin@email.com"
+# sender_name = "Admin"
+
+# Uncomment to customize email template
+# [auth.email.template.invite]
+# subject = "You have been invited"
+# content_path = "./supabase/templates/invite.html"
+
+[auth.sms]
+# Allow/disallow new user signups via SMS to your project.
+enable_signup = false
+# If enabled, users need to confirm their phone number before signing in.
+enable_confirmations = false
+# Template for sending OTP to users
+template = "Your code is {{ .Code }}"
+# Controls the minimum amount of time that must pass before sending another sms otp.
+max_frequency = "5s"
+
+# Use pre-defined map of phone number to OTP for testing.
+# [auth.sms.test_otp]
+# 4152127777 = "123456"
+
+# Configure logged in session timeouts.
+# [auth.sessions]
+# Force log out after the specified duration.
+# timebox = "24h"
+# Force log out if the user has been inactive longer than the specified duration.
+# inactivity_timeout = "8h"
+
+# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
+# [auth.hook.before_user_created]
+# enabled = true
+# uri = "pg-functions://postgres/auth/before-user-created-hook"
+
+# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
+# [auth.hook.custom_access_token]
+# enabled = true
+# uri = "pg-functions:////"
+
+# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
+[auth.sms.twilio]
+enabled = false
+account_sid = ""
+message_service_sid = ""
+# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
+auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
+
+# Multi-factor-authentication is available to Supabase Pro plan.
+[auth.mfa]
+# Control how many MFA factors can be enrolled at once per user.
+max_enrolled_factors = 10
+
+# Control MFA via App Authenticator (TOTP)
+[auth.mfa.totp]
+enroll_enabled = false
+verify_enabled = false
+
+# Configure MFA via Phone Messaging
+[auth.mfa.phone]
+enroll_enabled = false
+verify_enabled = false
+otp_length = 6
+template = "Your code is {{ .Code }}"
+max_frequency = "5s"
+
+# Configure MFA via WebAuthn
+# [auth.mfa.web_authn]
+# enroll_enabled = true
+# verify_enabled = true
+
+# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
+# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
+# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
+[auth.external.apple]
+enabled = false
+client_id = ""
+# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
+secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
+# Overrides the default auth redirectUrl.
+redirect_uri = ""
+# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
+# or any other third-party OIDC providers.
+url = ""
+# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
+skip_nonce_check = false
+
+# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
+# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
+[auth.web3.solana]
+enabled = false
+
+# Use Firebase Auth as a third-party provider alongside Supabase Auth.
+[auth.third_party.firebase]
+enabled = false
+# project_id = "my-firebase-project"
+
+# Use Auth0 as a third-party provider alongside Supabase Auth.
+[auth.third_party.auth0]
+enabled = false
+# tenant = "my-auth0-tenant"
+# tenant_region = "us"
+
+# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
+[auth.third_party.aws_cognito]
+enabled = false
+# user_pool_id = "my-user-pool-id"
+# user_pool_region = "us-east-1"
+
+# Use Clerk as a third-party provider alongside Supabase Auth.
+[auth.third_party.clerk]
+enabled = false
+# Obtain from https://clerk.com/setup/supabase
+# domain = "example.clerk.accounts.dev"
+
+# OAuth server configuration
+[auth.oauth_server]
+# Enable OAuth server functionality
+enabled = false
+# Path for OAuth consent flow UI
+authorization_url_path = "/oauth/consent"
+# Allow dynamic client registration
+allow_dynamic_registration = false
+
+[edge_runtime]
+enabled = true
+# Supported request policies: `oneshot`, `per_worker`.
+# `per_worker` (default) — enables hot reload during local development.
+# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks).
+policy = "per_worker"
+# Port to attach the Chrome inspector for debugging edge functions.
+inspector_port = 8083
+# The Deno major version to use.
+deno_version = 2
+
+# [edge_runtime.secrets]
+# secret_key = "env(SECRET_VALUE)"
+
+[analytics]
+enabled = true
+port = 54327
+# Configure one of the supported backends: `postgres`, `bigquery`.
+backend = "postgres"
+
+# Experimental features may be deprecated any time
+[experimental]
+# Configures Postgres storage engine to use OrioleDB (S3)
+orioledb_version = ""
+# Configures S3 bucket URL, eg. .s3-.amazonaws.com
+s3_host = "env(S3_HOST)"
+# Configures S3 bucket region, eg. us-east-1
+s3_region = "env(S3_REGION)"
+# Configures AWS_ACCESS_KEY_ID for S3 bucket
+s3_access_key = "env(S3_ACCESS_KEY)"
+# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
+s3_secret_key = "env(S3_SECRET_KEY)"
diff --git a/supabase/migrations/20251008081035_remote_schema.sql b/supabase/migrations/20251008081035_remote_schema.sql
new file mode 100644
index 0000000..ff04acc
--- /dev/null
+++ b/supabase/migrations/20251008081035_remote_schema.sql
@@ -0,0 +1,1036 @@
+
+
+
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
+
+
+COMMENT ON SCHEMA "public" IS 'standard public schema';
+
+
+
+CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
+
+
+
+
+
+
+CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions";
+
+
+
+
+
+
+CREATE EXTENSION IF NOT EXISTS "pg_trgm" WITH SCHEMA "public";
+
+
+
+
+
+
+CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions";
+
+
+
+
+
+
+CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
+
+
+
+
+
+
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
+
+
+
+
+
+
+CREATE OR REPLACE FUNCTION "public"."hacks_update_guard"() RETURNS "trigger"
+ LANGUAGE "plpgsql"
+ AS $$
+begin
+ if not public.is_admin() then
+ if new.slug is distinct from old.slug then
+ raise exception 'non-admins cannot change slug';
+ end if;
+ if new.created_by is distinct from old.created_by then
+ raise exception 'non-admins cannot change created_by';
+ end if;
+ if new.approved is distinct from old.approved then
+ raise exception 'non-admins cannot change approved';
+ end if;
+ end if;
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."hacks_update_guard"() OWNER TO "postgres";
+
+
+CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+begin
+ insert into public.profiles (id, full_name, avatar_url)
+ values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."handle_new_user"() OWNER TO "postgres";
+
+
+CREATE OR REPLACE FUNCTION "public"."is_admin"() RETURNS boolean
+ LANGUAGE "sql" STABLE
+ AS $$
+ select
+ -- If using Supabase auth, service role bypasses RLS anyway, but keep for completeness
+ (current_setting('request.jwt.claims', true)::jsonb ? 'role' and
+ (current_setting('request.jwt.claims', true)::jsonb ->> 'role') = 'service_role')
+ or
+ (
+ -- Check common locations for an admin flag/role
+ coalesce((current_setting('request.jwt.claims', true)::jsonb #>> '{app_metadata,role}'), '') = 'admin'
+ or coalesce((current_setting('request.jwt.claims', true)::jsonb #>> '{user_metadata,role}'), '') = 'admin'
+ or coalesce((current_setting('request.jwt.claims', true)::jsonb #>> '{claims,role}'), '') = 'admin'
+ or coalesce((current_setting('request.jwt.claims', true)::jsonb ->> 'admin'), 'false')::boolean = true
+ );
+$$;
+
+
+ALTER FUNCTION "public"."is_admin"() OWNER TO "postgres";
+
+
+CREATE OR REPLACE FUNCTION "public"."set_updated_at"() RETURNS "trigger"
+ LANGUAGE "plpgsql"
+ AS $$
+begin
+ new.updated_at = now();
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."set_updated_at"() OWNER TO "postgres";
+
+SET default_tablespace = '';
+
+SET default_table_access_method = "heap";
+
+
+CREATE TABLE IF NOT EXISTS "public"."hack_covers" (
+ "id" bigint NOT NULL,
+ "hack_slug" "text" NOT NULL,
+ "url" "text" NOT NULL,
+ "position" integer DEFAULT 0 NOT NULL,
+ "alt" "text"
+);
+
+
+ALTER TABLE "public"."hack_covers" OWNER TO "postgres";
+
+
+CREATE SEQUENCE IF NOT EXISTS "public"."hack_covers_id_seq"
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+ALTER SEQUENCE "public"."hack_covers_id_seq" OWNER TO "postgres";
+
+
+ALTER SEQUENCE "public"."hack_covers_id_seq" OWNED BY "public"."hack_covers"."id";
+
+
+
+CREATE TABLE IF NOT EXISTS "public"."hack_tags" (
+ "hack_slug" "text" NOT NULL,
+ "tag_id" bigint NOT NULL
+);
+
+
+ALTER TABLE "public"."hack_tags" OWNER TO "postgres";
+
+
+CREATE TABLE IF NOT EXISTS "public"."hacks" (
+ "slug" "text" NOT NULL,
+ "title" "text" NOT NULL,
+ "author" "text" NOT NULL,
+ "covers" "text"[] DEFAULT '{}'::"text"[] NOT NULL,
+ "summary" "text" NOT NULL,
+ "description" "text" NOT NULL,
+ "tags" "text"[] DEFAULT '{}'::"text"[] NOT NULL,
+ "downloads" integer DEFAULT 0 NOT NULL,
+ "base_rom" "text" NOT NULL,
+ "patch_url" "text" NOT NULL,
+ "version" "text" NOT NULL,
+ "approved" boolean DEFAULT false NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "updated_at" timestamp with time zone,
+ "social_links" "jsonb",
+ "box_art" "text",
+ "created_by" "uuid" NOT NULL,
+ "search" "tsvector" GENERATED ALWAYS AS ("to_tsvector"('"simple"'::"regconfig", ((((COALESCE("title", ''::"text") || ' '::"text") || COALESCE("summary", ''::"text")) || ' '::"text") || COALESCE("description", ''::"text")))) STORED,
+ CONSTRAINT "downloads_nonneg" CHECK (("downloads" >= 0)),
+ CONSTRAINT "summary_length" CHECK (("char_length"("summary") <= 120))
+);
+
+
+ALTER TABLE "public"."hacks" OWNER TO "postgres";
+
+
+CREATE TABLE IF NOT EXISTS "public"."profiles" (
+ "id" "uuid" NOT NULL,
+ "updated_at" timestamp with time zone,
+ "username" "text",
+ "full_name" "text",
+ "avatar_url" "text",
+ "website" "text",
+ CONSTRAINT "username_length" CHECK (("char_length"("username") >= 3))
+);
+
+
+ALTER TABLE "public"."profiles" OWNER TO "postgres";
+
+
+CREATE TABLE IF NOT EXISTS "public"."tags" (
+ "id" bigint NOT NULL,
+ "name" "text" NOT NULL
+);
+
+
+ALTER TABLE "public"."tags" OWNER TO "postgres";
+
+
+CREATE SEQUENCE IF NOT EXISTS "public"."tags_id_seq"
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+ALTER SEQUENCE "public"."tags_id_seq" OWNER TO "postgres";
+
+
+ALTER SEQUENCE "public"."tags_id_seq" OWNED BY "public"."tags"."id";
+
+
+
+ALTER TABLE ONLY "public"."hack_covers" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."hack_covers_id_seq"'::"regclass");
+
+
+
+ALTER TABLE ONLY "public"."tags" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."tags_id_seq"'::"regclass");
+
+
+
+ALTER TABLE ONLY "public"."hack_covers"
+ ADD CONSTRAINT "hack_covers_pkey" PRIMARY KEY ("id");
+
+
+
+ALTER TABLE ONLY "public"."hack_covers"
+ ADD CONSTRAINT "hack_covers_unique_position" UNIQUE ("hack_slug", "position");
+
+
+
+ALTER TABLE ONLY "public"."hack_tags"
+ ADD CONSTRAINT "hack_tags_pkey" PRIMARY KEY ("hack_slug", "tag_id");
+
+
+
+ALTER TABLE ONLY "public"."hacks"
+ ADD CONSTRAINT "hacks_pkey" PRIMARY KEY ("slug");
+
+
+
+ALTER TABLE ONLY "public"."hacks"
+ ADD CONSTRAINT "hacks_unique_author_title" UNIQUE ("author", "title");
+
+
+
+ALTER TABLE ONLY "public"."profiles"
+ ADD CONSTRAINT "profiles_pkey" PRIMARY KEY ("id");
+
+
+
+ALTER TABLE ONLY "public"."profiles"
+ ADD CONSTRAINT "profiles_username_key" UNIQUE ("username");
+
+
+
+ALTER TABLE ONLY "public"."tags"
+ ADD CONSTRAINT "tags_name_key" UNIQUE ("name");
+
+
+
+ALTER TABLE ONLY "public"."tags"
+ ADD CONSTRAINT "tags_pkey" PRIMARY KEY ("id");
+
+
+
+CREATE INDEX "hacks_approved_created_at_idx" ON "public"."hacks" USING "btree" ("approved", "created_at" DESC);
+
+
+
+CREATE INDEX "hacks_created_by_idx" ON "public"."hacks" USING "btree" ("created_by");
+
+
+
+CREATE INDEX "hacks_search_idx" ON "public"."hacks" USING "gin" ("search");
+
+
+
+CREATE INDEX "hacks_summary_trgm_idx" ON "public"."hacks" USING "gin" ("summary" "public"."gin_trgm_ops");
+
+
+
+CREATE INDEX "hacks_tags_gin_idx" ON "public"."hacks" USING "gin" ("tags");
+
+
+
+CREATE INDEX "hacks_title_trgm_idx" ON "public"."hacks" USING "gin" ("title" "public"."gin_trgm_ops");
+
+
+
+CREATE OR REPLACE TRIGGER "hacks_update_guard" BEFORE UPDATE ON "public"."hacks" FOR EACH ROW EXECUTE FUNCTION "public"."hacks_update_guard"();
+
+
+
+CREATE OR REPLACE TRIGGER "set_hacks_updated_at" BEFORE UPDATE ON "public"."hacks" FOR EACH ROW EXECUTE FUNCTION "public"."set_updated_at"();
+
+
+
+ALTER TABLE ONLY "public"."hack_covers"
+ ADD CONSTRAINT "hack_covers_hack_slug_fkey" FOREIGN KEY ("hack_slug") REFERENCES "public"."hacks"("slug") ON DELETE CASCADE;
+
+
+
+ALTER TABLE ONLY "public"."hack_tags"
+ ADD CONSTRAINT "hack_tags_hack_slug_fkey" FOREIGN KEY ("hack_slug") REFERENCES "public"."hacks"("slug") ON DELETE CASCADE;
+
+
+
+ALTER TABLE ONLY "public"."hack_tags"
+ ADD CONSTRAINT "hack_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON DELETE CASCADE;
+
+
+
+ALTER TABLE ONLY "public"."hacks"
+ ADD CONSTRAINT "hacks_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+
+ALTER TABLE ONLY "public"."profiles"
+ ADD CONSTRAINT "profiles_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+
+CREATE POLICY "Admins can insert hacks for any user." ON "public"."hacks" FOR INSERT WITH CHECK ("public"."is_admin"());
+
+
+
+CREATE POLICY "Admins can update any hack." ON "public"."hacks" FOR UPDATE USING ("public"."is_admin"()) WITH CHECK (true);
+
+
+
+CREATE POLICY "Admins can view any hack." ON "public"."hacks" FOR SELECT USING ("public"."is_admin"());
+
+
+
+CREATE POLICY "Authenticated users can insert hacks for themselves." ON "public"."hacks" FOR INSERT WITH CHECK (((( SELECT "auth"."uid"() AS "uid") = "created_by") AND ("approved" = false)));
+
+
+
+CREATE POLICY "Only admins can modify tags." ON "public"."tags" USING ("public"."is_admin"()) WITH CHECK ("public"."is_admin"());
+
+
+
+CREATE POLICY "Owners can add covers to own hacks." ON "public"."hack_covers" FOR INSERT WITH CHECK (("public"."is_admin"() OR (EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_covers"."hack_slug") AND ("h"."created_by" = "auth"."uid"()))))));
+
+
+
+CREATE POLICY "Owners can add tags to own hacks." ON "public"."hack_tags" FOR INSERT WITH CHECK (("public"."is_admin"() OR (EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_tags"."hack_slug") AND ("h"."created_by" = "auth"."uid"()))))));
+
+
+
+CREATE POLICY "Owners can remove covers from own hacks." ON "public"."hack_covers" FOR DELETE USING (("public"."is_admin"() OR (EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_covers"."hack_slug") AND ("h"."created_by" = "auth"."uid"()))))));
+
+
+
+CREATE POLICY "Owners can remove tags from own hacks." ON "public"."hack_tags" FOR DELETE USING (("public"."is_admin"() OR (EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_tags"."hack_slug") AND ("h"."created_by" = "auth"."uid"()))))));
+
+
+
+CREATE POLICY "Owners can update covers on own hacks." ON "public"."hack_covers" FOR UPDATE USING (("public"."is_admin"() OR (EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_covers"."hack_slug") AND ("h"."created_by" = "auth"."uid"()))))));
+
+
+
+CREATE POLICY "Owners can view their unapproved hacks." ON "public"."hacks" FOR SELECT USING ((( SELECT "auth"."uid"() AS "uid") = "created_by"));
+
+
+
+CREATE POLICY "Public can view approved hacks." ON "public"."hacks" FOR SELECT USING (("approved" = true));
+
+
+
+CREATE POLICY "Public can view covers for approved hacks." ON "public"."hack_covers" FOR SELECT USING ((EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_covers"."hack_slug") AND (("h"."approved" = true) OR ("h"."created_by" = "auth"."uid"()) OR "public"."is_admin"())))));
+
+
+
+CREATE POLICY "Public can view tags for approved hacks." ON "public"."hack_tags" FOR SELECT USING ((EXISTS ( SELECT 1
+ FROM "public"."hacks" "h"
+ WHERE (("h"."slug" = "hack_tags"."hack_slug") AND (("h"."approved" = true) OR ("h"."created_by" = "auth"."uid"()) OR "public"."is_admin"())))));
+
+
+
+CREATE POLICY "Public profiles are viewable by everyone." ON "public"."profiles" FOR SELECT USING (true);
+
+
+
+CREATE POLICY "Tags are viewable by everyone." ON "public"."tags" FOR SELECT USING (true);
+
+
+
+CREATE POLICY "Users can delete own hacks." ON "public"."hacks" FOR DELETE USING ((( SELECT "auth"."uid"() AS "uid") = "created_by"));
+
+
+
+CREATE POLICY "Users can insert their own profile." ON "public"."profiles" FOR INSERT WITH CHECK ((( SELECT "auth"."uid"() AS "uid") = "id"));
+
+
+
+CREATE POLICY "Users can update own hacks." ON "public"."hacks" FOR UPDATE USING ((( SELECT "auth"."uid"() AS "uid") = "created_by")) WITH CHECK ((( SELECT "auth"."uid"() AS "uid") = "created_by"));
+
+
+
+CREATE POLICY "Users can update own profile." ON "public"."profiles" FOR UPDATE USING ((( SELECT "auth"."uid"() AS "uid") = "id"));
+
+
+
+ALTER TABLE "public"."hack_covers" ENABLE ROW LEVEL SECURITY;
+
+
+ALTER TABLE "public"."hack_tags" ENABLE ROW LEVEL SECURITY;
+
+
+ALTER TABLE "public"."hacks" ENABLE ROW LEVEL SECURITY;
+
+
+ALTER TABLE "public"."profiles" ENABLE ROW LEVEL SECURITY;
+
+
+ALTER TABLE "public"."tags" ENABLE ROW LEVEL SECURITY;
+
+
+
+
+ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres";
+
+
+GRANT USAGE ON SCHEMA "public" TO "postgres";
+GRANT USAGE ON SCHEMA "public" TO "anon";
+GRANT USAGE ON SCHEMA "public" TO "authenticated";
+GRANT USAGE ON SCHEMA "public" TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_in"("cstring") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_in"("cstring") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_in"("cstring") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_in"("cstring") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_out"("public"."gtrgm") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_out"("public"."gtrgm") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_out"("public"."gtrgm") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_out"("public"."gtrgm") TO "service_role";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+GRANT ALL ON FUNCTION "public"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gin_extract_value_trgm"("text", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gin_extract_value_trgm"("text", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gin_extract_value_trgm"("text", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gin_extract_value_trgm"("text", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_compress"("internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_compress"("internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_compress"("internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_compress"("internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_decompress"("internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_decompress"("internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_decompress"("internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_decompress"("internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_options"("internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_options"("internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_options"("internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_options"("internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_penalty"("internal", "internal", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_penalty"("internal", "internal", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_penalty"("internal", "internal", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_penalty"("internal", "internal", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_picksplit"("internal", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_picksplit"("internal", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_picksplit"("internal", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_picksplit"("internal", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_same"("public"."gtrgm", "public"."gtrgm", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_same"("public"."gtrgm", "public"."gtrgm", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_same"("public"."gtrgm", "public"."gtrgm", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_same"("public"."gtrgm", "public"."gtrgm", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."gtrgm_union"("internal", "internal") TO "postgres";
+GRANT ALL ON FUNCTION "public"."gtrgm_union"("internal", "internal") TO "anon";
+GRANT ALL ON FUNCTION "public"."gtrgm_union"("internal", "internal") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."gtrgm_union"("internal", "internal") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."hacks_update_guard"() TO "anon";
+GRANT ALL ON FUNCTION "public"."hacks_update_guard"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."hacks_update_guard"() TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "anon";
+GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."is_admin"() TO "anon";
+GRANT ALL ON FUNCTION "public"."is_admin"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."is_admin"() TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."set_limit"(real) TO "postgres";
+GRANT ALL ON FUNCTION "public"."set_limit"(real) TO "anon";
+GRANT ALL ON FUNCTION "public"."set_limit"(real) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."set_limit"(real) TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."set_updated_at"() TO "anon";
+GRANT ALL ON FUNCTION "public"."set_updated_at"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."set_updated_at"() TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."show_limit"() TO "postgres";
+GRANT ALL ON FUNCTION "public"."show_limit"() TO "anon";
+GRANT ALL ON FUNCTION "public"."show_limit"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."show_limit"() TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."show_trgm"("text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."show_trgm"("text") TO "anon";
+GRANT ALL ON FUNCTION "public"."show_trgm"("text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."show_trgm"("text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."similarity"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."similarity"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."similarity"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."similarity"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."similarity_dist"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."similarity_dist"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."similarity_dist"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."similarity_dist"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."similarity_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."similarity_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."similarity_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."similarity_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."strict_word_similarity"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_commutator_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_commutator_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_commutator_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_commutator_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_commutator_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_commutator_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_commutator_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_commutator_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_dist_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."strict_word_similarity_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."word_similarity"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."word_similarity"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."word_similarity"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."word_similarity"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."word_similarity_commutator_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."word_similarity_commutator_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."word_similarity_commutator_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."word_similarity_commutator_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_commutator_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_commutator_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_commutator_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_commutator_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."word_similarity_dist_op"("text", "text") TO "service_role";
+
+
+
+GRANT ALL ON FUNCTION "public"."word_similarity_op"("text", "text") TO "postgres";
+GRANT ALL ON FUNCTION "public"."word_similarity_op"("text", "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."word_similarity_op"("text", "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."word_similarity_op"("text", "text") TO "service_role";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+GRANT ALL ON TABLE "public"."hack_covers" TO "anon";
+GRANT ALL ON TABLE "public"."hack_covers" TO "authenticated";
+GRANT ALL ON TABLE "public"."hack_covers" TO "service_role";
+
+
+
+GRANT ALL ON SEQUENCE "public"."hack_covers_id_seq" TO "anon";
+GRANT ALL ON SEQUENCE "public"."hack_covers_id_seq" TO "authenticated";
+GRANT ALL ON SEQUENCE "public"."hack_covers_id_seq" TO "service_role";
+
+
+
+GRANT ALL ON TABLE "public"."hack_tags" TO "anon";
+GRANT ALL ON TABLE "public"."hack_tags" TO "authenticated";
+GRANT ALL ON TABLE "public"."hack_tags" TO "service_role";
+
+
+
+GRANT ALL ON TABLE "public"."hacks" TO "anon";
+GRANT ALL ON TABLE "public"."hacks" TO "authenticated";
+GRANT ALL ON TABLE "public"."hacks" TO "service_role";
+
+
+
+GRANT ALL ON TABLE "public"."profiles" TO "anon";
+GRANT ALL ON TABLE "public"."profiles" TO "authenticated";
+GRANT ALL ON TABLE "public"."profiles" TO "service_role";
+
+
+
+GRANT ALL ON TABLE "public"."tags" TO "anon";
+GRANT ALL ON TABLE "public"."tags" TO "authenticated";
+GRANT ALL ON TABLE "public"."tags" TO "service_role";
+
+
+
+GRANT ALL ON SEQUENCE "public"."tags_id_seq" TO "anon";
+GRANT ALL ON SEQUENCE "public"."tags_id_seq" TO "authenticated";
+GRANT ALL ON SEQUENCE "public"."tags_id_seq" TO "service_role";
+
+
+
+
+
+
+
+
+
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role";
+
+
+
+
+
+
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role";
+
+
+
+
+
+
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated";
+ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+RESET ALL;
+CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION handle_new_user();
+
+
+ create policy "Anyone can upload an avatar."
+ on "storage"."objects"
+ as permissive
+ for insert
+ to public
+with check ((bucket_id = 'avatars'::text));
+
+
+
+ create policy "Authenticated users can upload hack boxart."
+ on "storage"."objects"
+ as permissive
+ for insert
+ to public
+with check (((bucket_id = 'hack-boxart'::text) AND (( SELECT auth.uid() AS uid) IS NOT NULL)));
+
+
+
+ create policy "Authenticated users can upload hack covers."
+ on "storage"."objects"
+ as permissive
+ for insert
+ to public
+with check (((bucket_id = 'hack-covers'::text) AND (( SELECT auth.uid() AS uid) IS NOT NULL)));
+
+
+
+ create policy "Avatar images are publicly accessible."
+ on "storage"."objects"
+ as permissive
+ for select
+ to public
+using ((bucket_id = 'avatars'::text));
+
+
+
+ create policy "Hack boxart is publicly accessible."
+ on "storage"."objects"
+ as permissive
+ for select
+ to public
+using ((bucket_id = 'hack-boxart'::text));
+
+
+
+ create policy "Hack covers are publicly accessible."
+ on "storage"."objects"
+ as permissive
+ for select
+ to public
+using ((bucket_id = 'hack-covers'::text));
+
+
+