forked from wrenn/wrenn
Prototype with single host server and no admin panel (#2)
Reviewed-on: wrenn/sandbox#2 Co-authored-by: pptx704 <rafeed@omukk.dev> Co-committed-by: pptx704 <rafeed@omukk.dev>
This commit is contained in:
25
db/migrations/20260310094104_initial.sql
Normal file
25
db/migrations/20260310094104_initial.sql
Normal file
@ -0,0 +1,25 @@
|
||||
-- +goose Up
|
||||
|
||||
CREATE TABLE sandboxes (
|
||||
id TEXT PRIMARY KEY,
|
||||
owner_id TEXT NOT NULL DEFAULT '',
|
||||
host_id TEXT NOT NULL DEFAULT 'default',
|
||||
template TEXT NOT NULL DEFAULT 'minimal',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
vcpus INTEGER NOT NULL DEFAULT 1,
|
||||
memory_mb INTEGER NOT NULL DEFAULT 512,
|
||||
timeout_sec INTEGER NOT NULL DEFAULT 0,
|
||||
guest_ip TEXT NOT NULL DEFAULT '',
|
||||
host_ip TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
started_at TIMESTAMPTZ,
|
||||
last_active_at TIMESTAMPTZ,
|
||||
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sandboxes_status ON sandboxes(status);
|
||||
CREATE INDEX idx_sandboxes_host_status ON sandboxes(host_id, status);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE sandboxes;
|
||||
14
db/migrations/20260311224925_snapshots.sql
Normal file
14
db/migrations/20260311224925_snapshots.sql
Normal file
@ -0,0 +1,14 @@
|
||||
-- +goose Up
|
||||
|
||||
CREATE TABLE templates (
|
||||
name TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL DEFAULT 'base', -- 'base' or 'snapshot'
|
||||
vcpus INTEGER,
|
||||
memory_mb INTEGER,
|
||||
size_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE templates;
|
||||
46
db/migrations/20260313210608_auth.sql
Normal file
46
db/migrations/20260313210608_auth.sql
Normal file
@ -0,0 +1,46 @@
|
||||
-- +goose Up
|
||||
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE teams (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE users_teams (
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
is_default BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
role TEXT NOT NULL DEFAULT 'owner',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (team_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_teams_user ON users_teams(user_id);
|
||||
|
||||
CREATE TABLE team_api_keys (
|
||||
id TEXT PRIMARY KEY,
|
||||
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
key_hash TEXT NOT NULL UNIQUE,
|
||||
key_prefix TEXT NOT NULL DEFAULT '',
|
||||
created_by TEXT NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_used TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_team_api_keys_team ON team_api_keys(team_id);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE team_api_keys;
|
||||
DROP TABLE users_teams;
|
||||
DROP TABLE teams;
|
||||
DROP TABLE users;
|
||||
31
db/migrations/20260313210611_team_ownership.sql
Normal file
31
db/migrations/20260313210611_team_ownership.sql
Normal file
@ -0,0 +1,31 @@
|
||||
-- +goose Up
|
||||
|
||||
ALTER TABLE sandboxes
|
||||
ADD COLUMN team_id TEXT NOT NULL DEFAULT '';
|
||||
|
||||
UPDATE sandboxes SET team_id = owner_id WHERE owner_id != '';
|
||||
|
||||
ALTER TABLE sandboxes
|
||||
DROP COLUMN owner_id;
|
||||
|
||||
ALTER TABLE templates
|
||||
ADD COLUMN team_id TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE INDEX idx_sandboxes_team ON sandboxes(team_id);
|
||||
CREATE INDEX idx_templates_team ON templates(team_id);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
ALTER TABLE sandboxes
|
||||
ADD COLUMN owner_id TEXT NOT NULL DEFAULT '';
|
||||
|
||||
UPDATE sandboxes SET owner_id = team_id WHERE team_id != '';
|
||||
|
||||
ALTER TABLE sandboxes
|
||||
DROP COLUMN team_id;
|
||||
|
||||
ALTER TABLE templates
|
||||
DROP COLUMN team_id;
|
||||
|
||||
DROP INDEX IF EXISTS idx_sandboxes_team;
|
||||
DROP INDEX IF EXISTS idx_templates_team;
|
||||
22
db/migrations/20260315001514_oauth.sql
Normal file
22
db/migrations/20260315001514_oauth.sql
Normal file
@ -0,0 +1,22 @@
|
||||
-- +goose Up
|
||||
|
||||
ALTER TABLE users
|
||||
ALTER COLUMN password_hash DROP NOT NULL;
|
||||
|
||||
CREATE TABLE oauth_providers (
|
||||
provider TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
email TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (provider, provider_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_oauth_providers_user ON oauth_providers(user_id);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE oauth_providers;
|
||||
|
||||
UPDATE users SET password_hash = '' WHERE password_hash IS NULL;
|
||||
ALTER TABLE users ALTER COLUMN password_hash SET NOT NULL;
|
||||
21
db/migrations/20260316203135_admin_users.sql
Normal file
21
db/migrations/20260316203135_admin_users.sql
Normal file
@ -0,0 +1,21 @@
|
||||
-- +goose Up
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
CREATE TABLE admin_permissions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
permission TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (user_id, permission)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_admin_permissions_user ON admin_permissions(user_id);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE admin_permissions;
|
||||
|
||||
ALTER TABLE users
|
||||
DROP COLUMN is_admin;
|
||||
9
db/migrations/20260316203138_byoc_teams.sql
Normal file
9
db/migrations/20260316203138_byoc_teams.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
|
||||
ALTER TABLE teams
|
||||
ADD COLUMN is_byoc BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- +goose Down
|
||||
|
||||
ALTER TABLE teams
|
||||
DROP COLUMN is_byoc;
|
||||
47
db/migrations/20260316203142_hosts.sql
Normal file
47
db/migrations/20260316203142_hosts.sql
Normal file
@ -0,0 +1,47 @@
|
||||
-- +goose Up
|
||||
|
||||
CREATE TABLE hosts (
|
||||
id TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL DEFAULT 'regular', -- 'regular' or 'byoc'
|
||||
team_id TEXT REFERENCES teams(id) ON DELETE SET NULL,
|
||||
provider TEXT,
|
||||
availability_zone TEXT,
|
||||
arch TEXT,
|
||||
cpu_cores INTEGER,
|
||||
memory_mb INTEGER,
|
||||
disk_gb INTEGER,
|
||||
address TEXT, -- ip:port of host agent
|
||||
status TEXT NOT NULL DEFAULT 'pending', -- 'pending', 'online', 'offline', 'draining'
|
||||
last_heartbeat_at TIMESTAMPTZ,
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_by TEXT NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE host_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
||||
created_by TEXT NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE host_tags (
|
||||
host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
||||
tag TEXT NOT NULL,
|
||||
PRIMARY KEY (host_id, tag)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_hosts_type ON hosts(type);
|
||||
CREATE INDEX idx_hosts_team ON hosts(team_id);
|
||||
CREATE INDEX idx_hosts_status ON hosts(status);
|
||||
CREATE INDEX idx_host_tokens_host ON host_tokens(host_id);
|
||||
CREATE INDEX idx_host_tags_tag ON host_tags(tag);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE host_tags;
|
||||
DROP TABLE host_tokens;
|
||||
DROP TABLE hosts;
|
||||
11
db/migrations/20260316223629_host_mtls.sql
Normal file
11
db/migrations/20260316223629_host_mtls.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- +goose Up
|
||||
|
||||
ALTER TABLE hosts
|
||||
ADD COLUMN cert_fingerprint TEXT,
|
||||
ADD COLUMN mtls_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- +goose Down
|
||||
|
||||
ALTER TABLE hosts
|
||||
DROP COLUMN cert_fingerprint,
|
||||
DROP COLUMN mtls_enabled;
|
||||
24
db/queries/api_keys.sql
Normal file
24
db/queries/api_keys.sql
Normal file
@ -0,0 +1,24 @@
|
||||
-- name: InsertAPIKey :one
|
||||
INSERT INTO team_api_keys (id, team_id, name, key_hash, key_prefix, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetAPIKeyByHash :one
|
||||
SELECT * FROM team_api_keys WHERE key_hash = $1;
|
||||
|
||||
-- name: ListAPIKeysByTeam :many
|
||||
SELECT * FROM team_api_keys WHERE team_id = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListAPIKeysByTeamWithCreator :many
|
||||
SELECT k.id, k.team_id, k.name, k.key_hash, k.key_prefix, k.created_by, k.created_at, k.last_used,
|
||||
u.email AS creator_email
|
||||
FROM team_api_keys k
|
||||
JOIN users u ON u.id = k.created_by
|
||||
WHERE k.team_id = $1
|
||||
ORDER BY k.created_at DESC;
|
||||
|
||||
-- name: DeleteAPIKey :exec
|
||||
DELETE FROM team_api_keys WHERE id = $1 AND team_id = $2;
|
||||
|
||||
-- name: UpdateAPIKeyLastUsed :exec
|
||||
UPDATE team_api_keys SET last_used = NOW() WHERE id = $1;
|
||||
@ -0,0 +1,69 @@
|
||||
-- name: InsertHost :one
|
||||
INSERT INTO hosts (id, type, team_id, provider, availability_zone, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetHost :one
|
||||
SELECT * FROM hosts WHERE id = $1;
|
||||
|
||||
-- name: ListHosts :many
|
||||
SELECT * FROM hosts ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListHostsByType :many
|
||||
SELECT * FROM hosts WHERE type = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListHostsByTeam :many
|
||||
SELECT * FROM hosts WHERE team_id = $1 AND type = 'byoc' ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListHostsByStatus :many
|
||||
SELECT * FROM hosts WHERE status = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: RegisterHost :execrows
|
||||
UPDATE hosts
|
||||
SET arch = $2,
|
||||
cpu_cores = $3,
|
||||
memory_mb = $4,
|
||||
disk_gb = $5,
|
||||
address = $6,
|
||||
status = 'online',
|
||||
last_heartbeat_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1 AND status = 'pending';
|
||||
|
||||
-- name: UpdateHostStatus :exec
|
||||
UPDATE hosts SET status = $2, updated_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: UpdateHostHeartbeat :exec
|
||||
UPDATE hosts SET last_heartbeat_at = NOW(), updated_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: DeleteHost :exec
|
||||
DELETE FROM hosts WHERE id = $1;
|
||||
|
||||
-- name: AddHostTag :exec
|
||||
INSERT INTO host_tags (host_id, tag) VALUES ($1, $2) ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: RemoveHostTag :exec
|
||||
DELETE FROM host_tags WHERE host_id = $1 AND tag = $2;
|
||||
|
||||
-- name: GetHostTags :many
|
||||
SELECT tag FROM host_tags WHERE host_id = $1 ORDER BY tag;
|
||||
|
||||
-- name: ListHostsByTag :many
|
||||
SELECT h.* FROM hosts h
|
||||
JOIN host_tags ht ON ht.host_id = h.id
|
||||
WHERE ht.tag = $1
|
||||
ORDER BY h.created_at DESC;
|
||||
|
||||
-- name: InsertHostToken :one
|
||||
INSERT INTO host_tokens (id, host_id, created_by, expires_at)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *;
|
||||
|
||||
-- name: MarkHostTokenUsed :exec
|
||||
UPDATE host_tokens SET used_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: GetHostTokensByHost :many
|
||||
SELECT * FROM host_tokens WHERE host_id = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: GetHostByTeam :one
|
||||
SELECT * FROM hosts WHERE id = $1 AND team_id = $2;
|
||||
|
||||
7
db/queries/oauth.sql
Normal file
7
db/queries/oauth.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- name: InsertOAuthProvider :exec
|
||||
INSERT INTO oauth_providers (provider, provider_id, user_id, email)
|
||||
VALUES ($1, $2, $3, $4);
|
||||
|
||||
-- name: GetOAuthProvider :one
|
||||
SELECT * FROM oauth_providers
|
||||
WHERE provider = $1 AND provider_id = $2;
|
||||
@ -0,0 +1,53 @@
|
||||
-- name: InsertSandbox :one
|
||||
INSERT INTO sandboxes (id, team_id, host_id, template, status, vcpus, memory_mb, timeout_sec)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetSandbox :one
|
||||
SELECT * FROM sandboxes WHERE id = $1;
|
||||
|
||||
-- name: GetSandboxByTeam :one
|
||||
SELECT * FROM sandboxes WHERE id = $1 AND team_id = $2;
|
||||
|
||||
-- name: ListSandboxes :many
|
||||
SELECT * FROM sandboxes ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListSandboxesByTeam :many
|
||||
SELECT * FROM sandboxes
|
||||
WHERE team_id = $1 AND status NOT IN ('stopped', 'error')
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListSandboxesByHostAndStatus :many
|
||||
SELECT * FROM sandboxes
|
||||
WHERE host_id = $1 AND status = ANY($2::text[])
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: UpdateSandboxRunning :one
|
||||
UPDATE sandboxes
|
||||
SET status = 'running',
|
||||
host_ip = $2,
|
||||
guest_ip = $3,
|
||||
started_at = $4,
|
||||
last_active_at = $4,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateSandboxStatus :one
|
||||
UPDATE sandboxes
|
||||
SET status = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateLastActive :exec
|
||||
UPDATE sandboxes
|
||||
SET last_active_at = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: BulkUpdateStatusByIDs :exec
|
||||
UPDATE sandboxes
|
||||
SET status = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = ANY($1::text[]);
|
||||
|
||||
26
db/queries/teams.sql
Normal file
26
db/queries/teams.sql
Normal file
@ -0,0 +1,26 @@
|
||||
-- name: InsertTeam :one
|
||||
INSERT INTO teams (id, name)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetTeam :one
|
||||
SELECT * FROM teams WHERE id = $1;
|
||||
|
||||
-- name: InsertTeamMember :exec
|
||||
INSERT INTO users_teams (user_id, team_id, is_default, role)
|
||||
VALUES ($1, $2, $3, $4);
|
||||
|
||||
-- name: GetDefaultTeamForUser :one
|
||||
SELECT t.* FROM teams t
|
||||
JOIN users_teams ut ON ut.team_id = t.id
|
||||
WHERE ut.user_id = $1 AND ut.is_default = TRUE
|
||||
LIMIT 1;
|
||||
|
||||
-- name: SetTeamBYOC :exec
|
||||
UPDATE teams SET is_byoc = $2 WHERE id = $1;
|
||||
|
||||
-- name: GetBYOCTeams :many
|
||||
SELECT * FROM teams WHERE is_byoc = TRUE ORDER BY created_at;
|
||||
|
||||
-- name: GetTeamMembership :one
|
||||
SELECT * FROM users_teams WHERE user_id = $1 AND team_id = $2;
|
||||
28
db/queries/templates.sql
Normal file
28
db/queries/templates.sql
Normal file
@ -0,0 +1,28 @@
|
||||
-- name: InsertTemplate :one
|
||||
INSERT INTO templates (name, type, vcpus, memory_mb, size_bytes, team_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetTemplate :one
|
||||
SELECT * FROM templates WHERE name = $1;
|
||||
|
||||
-- name: GetTemplateByTeam :one
|
||||
SELECT * FROM templates WHERE name = $1 AND team_id = $2;
|
||||
|
||||
-- name: ListTemplates :many
|
||||
SELECT * FROM templates ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListTemplatesByType :many
|
||||
SELECT * FROM templates WHERE type = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListTemplatesByTeam :many
|
||||
SELECT * FROM templates WHERE team_id = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListTemplatesByTeamAndType :many
|
||||
SELECT * FROM templates WHERE team_id = $1 AND type = $2 ORDER BY created_at DESC;
|
||||
|
||||
-- name: DeleteTemplate :exec
|
||||
DELETE FROM templates WHERE name = $1;
|
||||
|
||||
-- name: DeleteTemplateByTeam :exec
|
||||
DELETE FROM templates WHERE name = $1 AND team_id = $2;
|
||||
36
db/queries/users.sql
Normal file
36
db/queries/users.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- name: InsertUser :one
|
||||
INSERT INTO users (id, email, password_hash)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetUserByEmail :one
|
||||
SELECT * FROM users WHERE email = $1;
|
||||
|
||||
-- name: GetUserByID :one
|
||||
SELECT * FROM users WHERE id = $1;
|
||||
|
||||
-- name: InsertUserOAuth :one
|
||||
INSERT INTO users (id, email)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *;
|
||||
|
||||
-- name: SetUserAdmin :exec
|
||||
UPDATE users SET is_admin = $2, updated_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: GetAdminUsers :many
|
||||
SELECT * FROM users WHERE is_admin = TRUE ORDER BY created_at;
|
||||
|
||||
-- name: InsertAdminPermission :exec
|
||||
INSERT INTO admin_permissions (id, user_id, permission)
|
||||
VALUES ($1, $2, $3);
|
||||
|
||||
-- name: DeleteAdminPermission :exec
|
||||
DELETE FROM admin_permissions WHERE user_id = $1 AND permission = $2;
|
||||
|
||||
-- name: GetAdminPermissions :many
|
||||
SELECT * FROM admin_permissions WHERE user_id = $1 ORDER BY permission;
|
||||
|
||||
-- name: HasAdminPermission :one
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM admin_permissions WHERE user_id = $1 AND permission = $2
|
||||
) AS has_permission;
|
||||
Reference in New Issue
Block a user