Install
openclaw skills install supabase-hakkeSupabase integration for Hakke Studio projects. Auth, database, storage, edge functions. Use with vercel skill for full-stack deployment.
openclaw skills install supabase-hakkeSupabase integration específica para proyectos de Hakke Studio.
| Proyecto | Supabase URL | Uso |
|---|---|---|
| hakke-app | https://[project].supabase.co | SaaS multi-tenant |
# Supabase CLI (ya instalado)
supabase --version
# Si no está instalado
npm install -g supabase
supabase login
This opens browser for OAuth login with contacto@hakke.cl.
cd /home/bastianberrios/proyectos/HAKKE/hakke-app
supabase link --project-ref <project-id>
supabase gen types typescript --local > lib/supabase/database.types.ts
supabase migration new <migration_name>
supabase db push
supabase db reset
supabase status
Returns:
anon key - Public key (client-side)service_role key - Secret key (server-side ONLY)-- In Supabase Dashboard SQL Editor
INSERT INTO auth.users (email, encrypted_password, email_confirmed_at)
VALUES (
'user@example.com',
crypt('password123', gen_salt('bf')),
NOW()
);
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Users can only see their own data
CREATE POLICY "Users can view own data"
ON users
FOR SELECT
USING (auth.uid() = id);
-- Users can update own data
CREATE POLICY "Users can update own data"
ON users
FOR UPDATE
USING (auth.uid() = id);
-- In Supabase Dashboard SQL Editor
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', false);
-- Users can upload to own folder
CREATE POLICY "Users can upload avatars"
ON storage.objects
FOR INSERT
WITH CHECK (
bucket_id = 'avatars'
AND auth.uid()::text = (storage.foldername(name))[1]
);
supabase functions new <function-name>
supabase functions deploy <function-name>
curl -i -L --request POST 'https://<project>.supabase.co/functions/v1/<function-name>' \
--header 'Authorization: Bearer <anon-key>' \
--header 'Content-Type: application/json' \
--data '{"name":"Functions"}'
┌─────────────────────────────────────────┐
│ hakke-app (SaaS) │
├─────────────────────────────────────────┤
│ tenants table (multi-tenant) │
│ - id, slug, name, plan │
│ - owner_id (FK → auth.users) │
│ - subscription_status │
├─────────────────────────────────────────┤
│ Row Level Security (RLS) │
│ - tenant_id = current_tenant() │
└─────────────────────────────────────────┘
-- Function to get current tenant
CREATE OR REPLACE FUNCTION current_tenant_id()
RETURNS uuid AS $$
BEGIN
RETURN (auth.jwt()->>'tenant_id')::uuid;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- RLS Policy example
CREATE POLICY "Users can only see own tenant data"
ON appointments
FOR ALL
USING (tenant_id = current_tenant_id());
-- Check subscription status
CREATE OR REPLACE FUNCTION has_active_subscription()
RETURNS boolean AS $$
DECLARE
tenant_record tenants%ROWTYPE;
BEGIN
SELECT * INTO tenant_record
FROM tenants
WHERE id = current_tenant_id();
RETURN tenant_record.subscription_status = 'active';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public';
SELECT
column_name,
data_type,
is_nullable,
column_default
FROM information_schema.columns
WHERE table_name = 'users';
SELECT
schemaname,
tablename,
rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
SUPABASE_SERVICE_ROLE_KEY=<service-role-key>
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
// lib/supabase/server.ts
import { createClient } from '@supabase/supabase-js';
import { cookies } from 'next/headers';
export function createServerClient() {
const cookieStore = cookies();
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
auth: {
persistSession: false,
},
}
);
}
// lib/supabase/client.ts
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
# Check if Supabase is accessible
curl -I https://<project>.supabase.co/rest/v1/
# Expected: 401 Unauthorized (means API is working, just need auth)
-- Check if RLS is enabled
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
-- Check policies
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd,
qual,
with_check
FROM pg_policies
WHERE schemaname = 'public';
# Check if user exists
supabase auth list
# Check logs
supabase logs auth
# In hakke-app directory
vercel
# Add env vars in Vercel Dashboard or CLI
vercel env add NEXT_PUBLIC_SUPABASE_URL
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY
vercel env add SUPABASE_SERVICE_ROLE_KEY
| Practice | Why |
|---|---|
| RLS always on | Security first |
| Service key server-only | Never expose to client |
| Type generation | Type-safe queries |
| Migrations in git | Reproducible DB |
| Separate anon/service keys | Principle of least privilege |
Supabase Hakke v1.1.0 - Production-ready for Hakke Studio