mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat(access): add guest chat ACL (#235)
This commit is contained in:
@@ -12,6 +12,7 @@ DROP TABLE IF EXISTS bot_history_messages;
|
||||
DROP TABLE IF EXISTS bot_channel_routes;
|
||||
DROP TABLE IF EXISTS channel_identity_bind_codes;
|
||||
DROP TABLE IF EXISTS bot_preauth_keys;
|
||||
DROP TABLE IF EXISTS bot_acl_rules;
|
||||
DROP TABLE IF EXISTS bot_channel_configs;
|
||||
DROP TABLE IF EXISTS mcp_connections;
|
||||
DROP TABLE IF EXISTS bot_members;
|
||||
|
||||
@@ -163,7 +163,6 @@ CREATE TABLE IF NOT EXISTS bots (
|
||||
max_context_load_time INTEGER NOT NULL DEFAULT 1440,
|
||||
max_context_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
language TEXT NOT NULL DEFAULT 'auto',
|
||||
allow_guest BOOLEAN NOT NULL DEFAULT false,
|
||||
reasoning_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
reasoning_effort TEXT NOT NULL DEFAULT 'medium',
|
||||
max_inbox_items INTEGER NOT NULL DEFAULT 50,
|
||||
@@ -186,16 +185,52 @@ CREATE TABLE IF NOT EXISTS bots (
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bots_owner_user_id ON bots(owner_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_members (
|
||||
CREATE TABLE IF NOT EXISTS bot_acl_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL DEFAULT 'member',
|
||||
action TEXT NOT NULL,
|
||||
effect TEXT NOT NULL,
|
||||
subject_kind TEXT NOT NULL,
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
channel_identity_id UUID REFERENCES channel_identities(id) ON DELETE CASCADE,
|
||||
source_channel TEXT,
|
||||
source_conversation_type TEXT,
|
||||
source_conversation_id TEXT,
|
||||
source_thread_id TEXT,
|
||||
created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT bot_members_role_check CHECK (role IN ('owner', 'admin', 'member')),
|
||||
CONSTRAINT bot_members_unique UNIQUE (bot_id, user_id)
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT bot_acl_rules_action_check CHECK (action IN ('chat.trigger')),
|
||||
CONSTRAINT bot_acl_rules_effect_check CHECK (effect IN ('allow', 'deny')),
|
||||
CONSTRAINT bot_acl_rules_subject_kind_check CHECK (subject_kind IN ('guest_all', 'user', 'channel_identity')),
|
||||
CONSTRAINT bot_acl_rules_source_conversation_type_check CHECK (
|
||||
source_conversation_type IS NULL OR source_conversation_type IN ('private', 'group', 'thread')
|
||||
),
|
||||
CONSTRAINT bot_acl_rules_source_scope_check CHECK (
|
||||
(source_conversation_id IS NULL AND source_thread_id IS NULL)
|
||||
OR source_channel IS NOT NULL
|
||||
),
|
||||
CONSTRAINT bot_acl_rules_source_thread_check CHECK (
|
||||
source_thread_id IS NULL OR source_conversation_id IS NOT NULL
|
||||
),
|
||||
CONSTRAINT bot_acl_rules_subject_value_check CHECK (
|
||||
(subject_kind = 'guest_all' AND user_id IS NULL AND channel_identity_id IS NULL) OR
|
||||
(subject_kind = 'user' AND user_id IS NOT NULL AND channel_identity_id IS NULL) OR
|
||||
(subject_kind = 'channel_identity' AND user_id IS NULL AND channel_identity_id IS NOT NULL)
|
||||
),
|
||||
CONSTRAINT bot_acl_rules_unique_user UNIQUE NULLS NOT DISTINCT (
|
||||
bot_id, action, effect, subject_kind, user_id,
|
||||
source_channel, source_conversation_type, source_conversation_id, source_thread_id
|
||||
),
|
||||
CONSTRAINT bot_acl_rules_unique_channel_identity UNIQUE NULLS NOT DISTINCT (
|
||||
bot_id, action, effect, subject_kind, channel_identity_id,
|
||||
source_channel, source_conversation_type, source_conversation_id, source_thread_id
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_members_user_id ON bot_members(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_acl_rules_bot_id ON bot_acl_rules(bot_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_acl_rules_user_id ON bot_acl_rules(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_acl_rules_channel_identity_id ON bot_acl_rules(channel_identity_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mcp_connections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
@@ -266,20 +301,6 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_bot_channel_external_identity
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_channel_bot_id ON bot_channel_configs(bot_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_preauth_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
||||
token TEXT NOT NULL,
|
||||
issued_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
expires_at TIMESTAMPTZ,
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT bot_preauth_keys_unique UNIQUE (token)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_preauth_keys_bot_id ON bot_preauth_keys(bot_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_preauth_keys_expires ON bot_preauth_keys(expires_at);
|
||||
|
||||
-- channel_identity_bind_codes: one-time codes for channel identity->user linking
|
||||
CREATE TABLE IF NOT EXISTS channel_identity_bind_codes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
@@ -338,6 +359,8 @@ CREATE INDEX IF NOT EXISTS idx_bot_history_messages_source_lookup
|
||||
ON bot_history_messages(channel_type, source_message_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_history_messages_reply_lookup
|
||||
ON bot_history_messages(channel_type, source_reply_to_message_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_history_messages_identity_route_created
|
||||
ON bot_history_messages(bot_id, sender_channel_identity_id, route_id, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS containers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
-- 0031_chat_acl_remove_bot_members
|
||||
-- Restore allow_guest, preauth, and bot_members, then drop bot ACL rules.
|
||||
|
||||
ALTER TABLE bots ADD COLUMN IF NOT EXISTS allow_guest BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE bots
|
||||
SET allow_guest = true
|
||||
WHERE type = 'public'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_acl_rules r
|
||||
WHERE r.bot_id = bots.id
|
||||
AND r.action = 'chat.trigger'
|
||||
AND r.effect = 'allow'
|
||||
AND r.subject_kind = 'guest_all'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_preauth_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
||||
token TEXT NOT NULL,
|
||||
issued_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
expires_at TIMESTAMPTZ,
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT bot_preauth_keys_unique UNIQUE (token)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_preauth_keys_bot_id ON bot_preauth_keys(bot_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_preauth_keys_expires ON bot_preauth_keys(expires_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_members (
|
||||
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL DEFAULT 'member',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT bot_members_role_check CHECK (role IN ('owner', 'admin', 'member')),
|
||||
CONSTRAINT bot_members_unique UNIQUE (bot_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_members_user_id ON bot_members(user_id);
|
||||
|
||||
DROP TABLE IF EXISTS bot_acl_rules;
|
||||
@@ -0,0 +1,53 @@
|
||||
-- 0031_chat_acl_remove_bot_members
|
||||
-- Add bot ACL rules, migrate allow_guest into ACL, and remove legacy bot sharing tables.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_acl_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL,
|
||||
effect TEXT NOT NULL,
|
||||
subject_kind TEXT NOT NULL,
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
channel_identity_id UUID REFERENCES channel_identities(id) ON DELETE CASCADE,
|
||||
created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT bot_acl_rules_action_check CHECK (action IN ('chat.trigger')),
|
||||
CONSTRAINT bot_acl_rules_effect_check CHECK (effect IN ('allow', 'deny')),
|
||||
CONSTRAINT bot_acl_rules_subject_kind_check CHECK (subject_kind IN ('guest_all', 'user', 'channel_identity')),
|
||||
CONSTRAINT bot_acl_rules_subject_value_check CHECK (
|
||||
(subject_kind = 'guest_all' AND user_id IS NULL AND channel_identity_id IS NULL) OR
|
||||
(subject_kind = 'user' AND user_id IS NOT NULL AND channel_identity_id IS NULL) OR
|
||||
(subject_kind = 'channel_identity' AND user_id IS NULL AND channel_identity_id IS NOT NULL)
|
||||
),
|
||||
CONSTRAINT bot_acl_rules_unique_user UNIQUE NULLS NOT DISTINCT (bot_id, action, effect, subject_kind, user_id),
|
||||
CONSTRAINT bot_acl_rules_unique_channel_identity UNIQUE NULLS NOT DISTINCT (bot_id, action, effect, subject_kind, channel_identity_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_acl_rules_bot_id ON bot_acl_rules(bot_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_acl_rules_user_id ON bot_acl_rules(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_acl_rules_channel_identity_id ON bot_acl_rules(channel_identity_id);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns c
|
||||
WHERE c.table_schema = 'public'
|
||||
AND c.table_name = 'bots'
|
||||
AND c.column_name = 'allow_guest'
|
||||
) THEN
|
||||
EXECUTE $migrate$
|
||||
INSERT INTO bot_acl_rules (bot_id, action, effect, subject_kind, created_by_user_id)
|
||||
SELECT b.id, 'chat.trigger', 'allow', 'guest_all', b.owner_user_id
|
||||
FROM bots b
|
||||
WHERE b.type = 'public'
|
||||
AND b.allow_guest = true
|
||||
ON CONFLICT DO NOTHING
|
||||
$migrate$;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE bots DROP COLUMN IF EXISTS allow_guest;
|
||||
DROP TABLE IF EXISTS bot_preauth_keys;
|
||||
DROP TABLE IF EXISTS bot_members;
|
||||
@@ -0,0 +1,52 @@
|
||||
-- 0032_source_aware_acl_scope
|
||||
-- Drop source-aware ACL scope fields after ensuring no scoped rules remain.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_acl_rules
|
||||
WHERE source_channel IS NOT NULL
|
||||
OR source_conversation_type IS NOT NULL
|
||||
OR source_conversation_id IS NOT NULL
|
||||
OR source_thread_id IS NOT NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION 'cannot rollback 0032_source_aware_acl_scope while scoped ACL rules exist';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DROP INDEX IF EXISTS idx_bot_history_messages_identity_route_created;
|
||||
|
||||
ALTER TABLE bot_acl_rules
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_unique_user,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_unique_channel_identity,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_source_conversation_type_check,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_source_scope_check,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_source_thread_check;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_unique_user'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_unique_user UNIQUE NULLS NOT DISTINCT (
|
||||
bot_id, action, effect, subject_kind, user_id
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_unique_channel_identity'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_unique_channel_identity UNIQUE NULLS NOT DISTINCT (
|
||||
bot_id, action, effect, subject_kind, channel_identity_id
|
||||
);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE bot_acl_rules
|
||||
DROP COLUMN IF EXISTS source_channel,
|
||||
DROP COLUMN IF EXISTS source_conversation_type,
|
||||
DROP COLUMN IF EXISTS source_conversation_id,
|
||||
DROP COLUMN IF EXISTS source_thread_id;
|
||||
@@ -0,0 +1,69 @@
|
||||
-- 0032_source_aware_acl_scope
|
||||
-- Add source-aware scope fields to bot ACL rules and index observed conversations.
|
||||
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD COLUMN IF NOT EXISTS source_channel TEXT,
|
||||
ADD COLUMN IF NOT EXISTS source_conversation_type TEXT,
|
||||
ADD COLUMN IF NOT EXISTS source_conversation_id TEXT,
|
||||
ADD COLUMN IF NOT EXISTS source_thread_id TEXT;
|
||||
|
||||
ALTER TABLE bot_acl_rules
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_unique_user,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_unique_channel_identity,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_source_conversation_type_check,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_source_scope_check,
|
||||
DROP CONSTRAINT IF EXISTS bot_acl_rules_source_thread_check;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_source_conversation_type_check'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_source_conversation_type_check CHECK (
|
||||
source_conversation_type IS NULL OR source_conversation_type IN ('private', 'group', 'thread')
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_source_scope_check'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_source_scope_check CHECK (
|
||||
(source_conversation_id IS NULL AND source_thread_id IS NULL)
|
||||
OR source_channel IS NOT NULL
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_source_thread_check'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_source_thread_check CHECK (
|
||||
source_thread_id IS NULL OR source_conversation_id IS NOT NULL
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_unique_user'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_unique_user UNIQUE NULLS NOT DISTINCT (
|
||||
bot_id, action, effect, subject_kind, user_id,
|
||||
source_channel, source_conversation_type, source_conversation_id, source_thread_id
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'bot_acl_rules_unique_channel_identity'
|
||||
) THEN
|
||||
ALTER TABLE bot_acl_rules
|
||||
ADD CONSTRAINT bot_acl_rules_unique_channel_identity UNIQUE NULLS NOT DISTINCT (
|
||||
bot_id, action, effect, subject_kind, channel_identity_id,
|
||||
source_channel, source_conversation_type, source_conversation_id, source_thread_id
|
||||
);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_history_messages_identity_route_created
|
||||
ON bot_history_messages(bot_id, sender_channel_identity_id, route_id, created_at DESC);
|
||||
@@ -0,0 +1,136 @@
|
||||
-- name: UpsertBotACLGuestAllAllowRule :one
|
||||
INSERT INTO bot_acl_rules (bot_id, action, effect, subject_kind, created_by_user_id)
|
||||
VALUES ($1, 'chat.trigger', 'allow', 'guest_all', $2)
|
||||
ON CONFLICT ON CONSTRAINT bot_acl_rules_unique_user
|
||||
DO UPDATE SET
|
||||
created_by_user_id = COALESCE(EXCLUDED.created_by_user_id, bot_acl_rules.created_by_user_id),
|
||||
updated_at = now()
|
||||
RETURNING id, bot_id, action, effect, subject_kind, user_id, channel_identity_id, source_channel, source_conversation_type, source_conversation_id, source_thread_id, created_by_user_id, created_at, updated_at;
|
||||
|
||||
-- name: UpsertBotACLUserRule :one
|
||||
INSERT INTO bot_acl_rules (
|
||||
bot_id, action, effect, subject_kind, user_id,
|
||||
source_channel, source_conversation_type, source_conversation_id, source_thread_id,
|
||||
created_by_user_id
|
||||
)
|
||||
VALUES (
|
||||
$1, 'chat.trigger', $2, 'user', $3,
|
||||
sqlc.narg(source_channel)::text,
|
||||
sqlc.narg(source_conversation_type)::text,
|
||||
sqlc.narg(source_conversation_id)::text,
|
||||
sqlc.narg(source_thread_id)::text,
|
||||
$4
|
||||
)
|
||||
ON CONFLICT ON CONSTRAINT bot_acl_rules_unique_user
|
||||
DO UPDATE SET
|
||||
created_by_user_id = COALESCE(EXCLUDED.created_by_user_id, bot_acl_rules.created_by_user_id),
|
||||
updated_at = now()
|
||||
RETURNING id, bot_id, action, effect, subject_kind, user_id, channel_identity_id, source_channel, source_conversation_type, source_conversation_id, source_thread_id, created_by_user_id, created_at, updated_at;
|
||||
|
||||
-- name: UpsertBotACLChannelIdentityRule :one
|
||||
INSERT INTO bot_acl_rules (
|
||||
bot_id, action, effect, subject_kind, channel_identity_id,
|
||||
source_channel, source_conversation_type, source_conversation_id, source_thread_id,
|
||||
created_by_user_id
|
||||
)
|
||||
VALUES (
|
||||
$1, 'chat.trigger', $2, 'channel_identity', $3,
|
||||
sqlc.narg(source_channel)::text,
|
||||
sqlc.narg(source_conversation_type)::text,
|
||||
sqlc.narg(source_conversation_id)::text,
|
||||
sqlc.narg(source_thread_id)::text,
|
||||
$4
|
||||
)
|
||||
ON CONFLICT ON CONSTRAINT bot_acl_rules_unique_channel_identity
|
||||
DO UPDATE SET
|
||||
created_by_user_id = COALESCE(EXCLUDED.created_by_user_id, bot_acl_rules.created_by_user_id),
|
||||
updated_at = now()
|
||||
RETURNING id, bot_id, action, effect, subject_kind, user_id, channel_identity_id, source_channel, source_conversation_type, source_conversation_id, source_thread_id, created_by_user_id, created_at, updated_at;
|
||||
|
||||
-- name: DeleteBotACLGuestAllAllowRule :exec
|
||||
DELETE FROM bot_acl_rules
|
||||
WHERE bot_id = $1
|
||||
AND action = 'chat.trigger'
|
||||
AND effect = 'allow'
|
||||
AND subject_kind = 'guest_all';
|
||||
|
||||
-- name: DeleteBotACLRuleByID :exec
|
||||
DELETE FROM bot_acl_rules
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: HasBotACLGuestAllAllowRule :one
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_acl_rules
|
||||
WHERE bot_id = $1
|
||||
AND action = 'chat.trigger'
|
||||
AND effect = 'allow'
|
||||
AND subject_kind = 'guest_all'
|
||||
) AS allowed;
|
||||
|
||||
-- name: HasBotACLUserRule :one
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_acl_rules
|
||||
WHERE bot_id = $1
|
||||
AND action = 'chat.trigger'
|
||||
AND effect = $2
|
||||
AND subject_kind = 'user'
|
||||
AND user_id = $3
|
||||
AND (source_channel IS NULL OR source_channel = sqlc.narg(source_channel)::text)
|
||||
AND (source_conversation_type IS NULL OR source_conversation_type = sqlc.narg(source_conversation_type)::text)
|
||||
AND (source_conversation_id IS NULL OR source_conversation_id = sqlc.narg(source_conversation_id)::text)
|
||||
AND (source_thread_id IS NULL OR source_thread_id = sqlc.narg(source_thread_id)::text)
|
||||
) AS matched;
|
||||
|
||||
-- name: HasBotACLChannelIdentityRule :one
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_acl_rules
|
||||
WHERE bot_id = $1
|
||||
AND action = 'chat.trigger'
|
||||
AND effect = $2
|
||||
AND subject_kind = 'channel_identity'
|
||||
AND channel_identity_id = $3
|
||||
AND (source_channel IS NULL OR source_channel = sqlc.narg(source_channel)::text)
|
||||
AND (source_conversation_type IS NULL OR source_conversation_type = sqlc.narg(source_conversation_type)::text)
|
||||
AND (source_conversation_id IS NULL OR source_conversation_id = sqlc.narg(source_conversation_id)::text)
|
||||
AND (source_thread_id IS NULL OR source_thread_id = sqlc.narg(source_thread_id)::text)
|
||||
) AS matched;
|
||||
|
||||
-- name: ListBotACLSubjectRulesByEffect :many
|
||||
SELECT
|
||||
r.id,
|
||||
r.bot_id,
|
||||
r.action,
|
||||
r.effect,
|
||||
r.subject_kind,
|
||||
r.user_id,
|
||||
r.channel_identity_id,
|
||||
r.source_channel,
|
||||
r.source_conversation_type,
|
||||
r.source_conversation_id,
|
||||
r.source_thread_id,
|
||||
r.created_by_user_id,
|
||||
r.created_at,
|
||||
r.updated_at,
|
||||
u.username AS user_username,
|
||||
u.display_name AS user_display_name,
|
||||
u.avatar_url AS user_avatar_url,
|
||||
ci.channel_type,
|
||||
ci.channel_subject_id,
|
||||
ci.display_name AS channel_identity_display_name,
|
||||
ci.avatar_url AS channel_identity_avatar_url,
|
||||
linked.id AS linked_user_id,
|
||||
linked.username AS linked_user_username,
|
||||
linked.display_name AS linked_user_display_name,
|
||||
linked.avatar_url AS linked_user_avatar_url
|
||||
FROM bot_acl_rules r
|
||||
LEFT JOIN users u ON u.id = r.user_id
|
||||
LEFT JOIN channel_identities ci ON ci.id = r.channel_identity_id
|
||||
LEFT JOIN users linked ON linked.id = ci.user_id
|
||||
WHERE r.bot_id = $1
|
||||
AND r.action = 'chat.trigger'
|
||||
AND r.effect = $2
|
||||
AND r.subject_kind IN ('user', 'channel_identity')
|
||||
ORDER BY r.created_at DESC;
|
||||
+5
-34
@@ -1,26 +1,19 @@
|
||||
-- name: CreateBot :one
|
||||
INSERT INTO bots (owner_user_id, type, display_name, avatar_url, is_active, metadata, status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, allow_guest, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
|
||||
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
|
||||
|
||||
-- name: GetBotByID :one
|
||||
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, allow_guest, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
|
||||
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
|
||||
FROM bots
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ListBotsByOwner :many
|
||||
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, allow_guest, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
|
||||
SELECT id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at
|
||||
FROM bots
|
||||
WHERE owner_user_id = $1
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListBotsByMember :many
|
||||
SELECT b.id, b.owner_user_id, b.type, b.display_name, b.avatar_url, b.is_active, b.status, b.max_context_load_time, b.max_context_tokens, b.max_inbox_items, b.language, b.allow_guest, b.reasoning_enabled, b.reasoning_effort, b.chat_model_id, b.search_provider_id, b.memory_provider_id, b.heartbeat_enabled, b.heartbeat_interval, b.heartbeat_prompt, b.metadata, b.created_at, b.updated_at
|
||||
FROM bots b
|
||||
JOIN bot_members m ON m.bot_id = b.id
|
||||
WHERE m.user_id = $1
|
||||
ORDER BY b.created_at DESC;
|
||||
|
||||
-- name: UpdateBotProfile :one
|
||||
UPDATE bots
|
||||
SET display_name = $2,
|
||||
@@ -29,14 +22,14 @@ SET display_name = $2,
|
||||
metadata = $5,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, allow_guest, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
|
||||
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
|
||||
|
||||
-- name: UpdateBotOwner :one
|
||||
UPDATE bots
|
||||
SET owner_user_id = $2,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, allow_guest, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
|
||||
RETURNING id, owner_user_id, type, display_name, avatar_url, is_active, status, max_context_load_time, max_context_tokens, max_inbox_items, language, reasoning_enabled, reasoning_effort, chat_model_id, search_provider_id, memory_provider_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt, metadata, created_at, updated_at;
|
||||
|
||||
-- name: UpdateBotStatus :exec
|
||||
UPDATE bots
|
||||
@@ -47,28 +40,6 @@ WHERE id = $1;
|
||||
-- name: DeleteBotByID :exec
|
||||
DELETE FROM bots WHERE id = $1;
|
||||
|
||||
-- name: UpsertBotMember :one
|
||||
INSERT INTO bot_members (bot_id, user_id, role)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (bot_id, user_id) DO UPDATE SET
|
||||
role = EXCLUDED.role
|
||||
RETURNING bot_id, user_id, role, created_at;
|
||||
|
||||
-- name: ListBotMembers :many
|
||||
SELECT bot_id, user_id, role, created_at
|
||||
FROM bot_members
|
||||
WHERE bot_id = $1
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: GetBotMember :one
|
||||
SELECT bot_id, user_id, role, created_at
|
||||
FROM bot_members
|
||||
WHERE bot_id = $1 AND user_id = $2
|
||||
LIMIT 1;
|
||||
|
||||
-- name: DeleteBotMember :exec
|
||||
DELETE FROM bot_members WHERE bot_id = $1 AND user_id = $2;
|
||||
|
||||
-- name: ListHeartbeatEnabledBots :many
|
||||
SELECT id, owner_user_id, heartbeat_enabled, heartbeat_interval, heartbeat_prompt
|
||||
FROM bots
|
||||
|
||||
@@ -37,6 +37,32 @@ FROM channel_identities
|
||||
WHERE user_id = $1
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: SearchChannelIdentities :many
|
||||
SELECT
|
||||
ci.id,
|
||||
ci.user_id,
|
||||
ci.channel_type,
|
||||
ci.channel_subject_id,
|
||||
ci.display_name,
|
||||
ci.avatar_url,
|
||||
ci.metadata,
|
||||
ci.created_at,
|
||||
ci.updated_at,
|
||||
u.username AS linked_username,
|
||||
u.display_name AS linked_display_name,
|
||||
u.avatar_url AS linked_avatar_url
|
||||
FROM channel_identities ci
|
||||
LEFT JOIN users u ON u.id = ci.user_id
|
||||
WHERE
|
||||
sqlc.arg(query)::text = ''
|
||||
OR ci.channel_type ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
OR ci.channel_subject_id ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
OR COALESCE(ci.display_name, '') ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
OR COALESCE(u.username, '') ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
OR COALESCE(u.display_name, '') ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
ORDER BY ci.updated_at DESC
|
||||
LIMIT sqlc.arg(limit_count);
|
||||
|
||||
-- name: SetChannelIdentityLinkedUser :one
|
||||
UPDATE channel_identities
|
||||
SET user_id = $2, updated_at = now()
|
||||
|
||||
@@ -44,10 +44,9 @@ SELECT
|
||||
b.created_at,
|
||||
b.updated_at
|
||||
FROM bots b
|
||||
LEFT JOIN bot_members bm ON bm.bot_id = b.id AND bm.user_id = sqlc.arg(user_id)
|
||||
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
||||
WHERE b.id = sqlc.arg(bot_id)
|
||||
AND (b.owner_user_id = sqlc.arg(user_id) OR bm.user_id IS NOT NULL)
|
||||
AND b.owner_user_id = sqlc.arg(user_id)
|
||||
ORDER BY b.updated_at DESC;
|
||||
|
||||
-- name: ListVisibleChatsByBotAndUser :many
|
||||
@@ -69,24 +68,19 @@ SELECT
|
||||
END)::text AS participant_role,
|
||||
NULL::timestamptz AS last_observed_at
|
||||
FROM bots b
|
||||
LEFT JOIN bot_members bm ON bm.bot_id = b.id AND bm.user_id = sqlc.arg(user_id)
|
||||
LEFT JOIN models chat_models ON chat_models.id = b.chat_model_id
|
||||
WHERE b.id = sqlc.arg(bot_id)
|
||||
AND (b.owner_user_id = sqlc.arg(user_id) OR bm.user_id IS NOT NULL)
|
||||
AND b.owner_user_id = sqlc.arg(user_id)
|
||||
ORDER BY b.updated_at DESC;
|
||||
|
||||
-- name: GetChatReadAccessByUser :one
|
||||
SELECT
|
||||
'participant'::text AS access_mode,
|
||||
(CASE
|
||||
WHEN b.owner_user_id = sqlc.arg(user_id) THEN 'owner'
|
||||
ELSE COALESCE(bm.role, ''::text)
|
||||
END)::text AS participant_role,
|
||||
'owner'::text AS participant_role,
|
||||
NULL::timestamptz AS last_observed_at
|
||||
FROM bots b
|
||||
LEFT JOIN bot_members bm ON bm.bot_id = b.id AND bm.user_id = sqlc.arg(user_id)
|
||||
WHERE b.id = sqlc.arg(chat_id)
|
||||
AND (b.owner_user_id = sqlc.arg(user_id) OR bm.user_id IS NOT NULL)
|
||||
AND b.owner_user_id = sqlc.arg(user_id)
|
||||
LIMIT 1;
|
||||
|
||||
-- name: ListThreadsByParent :many
|
||||
@@ -141,66 +135,26 @@ WITH deleted_messages AS (
|
||||
DELETE FROM bot_channel_routes bcr
|
||||
WHERE bcr.bot_id = sqlc.arg(chat_id);
|
||||
|
||||
-- chat_participants
|
||||
|
||||
-- name: AddChatParticipant :one
|
||||
INSERT INTO bot_members (bot_id, user_id, role)
|
||||
VALUES (sqlc.arg(chat_id), sqlc.arg(user_id), sqlc.arg(role))
|
||||
ON CONFLICT (bot_id, user_id) DO UPDATE SET role = EXCLUDED.role
|
||||
RETURNING bot_id AS chat_id, user_id, role, created_at AS joined_at;
|
||||
|
||||
-- name: GetChatParticipant :one
|
||||
WITH owner_participant AS (
|
||||
SELECT b.id AS chat_id, b.owner_user_id AS user_id, 'owner'::text AS role, b.created_at AS joined_at
|
||||
FROM bots b
|
||||
WHERE b.id = sqlc.arg(chat_id) AND b.owner_user_id = sqlc.arg(user_id)
|
||||
),
|
||||
member_participant AS (
|
||||
SELECT bm.bot_id AS chat_id, bm.user_id, bm.role, bm.created_at AS joined_at
|
||||
FROM bot_members bm
|
||||
WHERE bm.bot_id = sqlc.arg(chat_id) AND bm.user_id = sqlc.arg(user_id)
|
||||
)
|
||||
SELECT chat_id, user_id, role, joined_at
|
||||
FROM (
|
||||
SELECT * FROM owner_participant
|
||||
UNION ALL
|
||||
SELECT * FROM member_participant
|
||||
) p
|
||||
ORDER BY CASE WHEN role = 'owner' THEN 0 ELSE 1 END
|
||||
SELECT b.id AS chat_id, b.owner_user_id AS user_id, 'owner'::text AS role, b.created_at AS joined_at
|
||||
FROM bots b
|
||||
WHERE b.id = sqlc.arg(chat_id) AND b.owner_user_id = sqlc.arg(user_id)
|
||||
LIMIT 1;
|
||||
|
||||
-- name: ListChatParticipants :many
|
||||
WITH owner_participant AS (
|
||||
SELECT b.id AS chat_id, b.owner_user_id AS user_id, 'owner'::text AS role, b.created_at AS joined_at
|
||||
FROM bots b
|
||||
WHERE b.id = sqlc.arg(chat_id)
|
||||
),
|
||||
member_participant AS (
|
||||
SELECT bm.bot_id AS chat_id, bm.user_id, bm.role, bm.created_at AS joined_at
|
||||
FROM bot_members bm
|
||||
WHERE bm.bot_id = sqlc.arg(chat_id)
|
||||
AND bm.user_id <> (SELECT owner_user_id FROM bots WHERE id = sqlc.arg(chat_id))
|
||||
)
|
||||
SELECT chat_id, user_id, role, joined_at
|
||||
FROM (
|
||||
SELECT * FROM owner_participant
|
||||
UNION ALL
|
||||
SELECT * FROM member_participant
|
||||
) p
|
||||
SELECT b.id AS chat_id, b.owner_user_id AS user_id, 'owner'::text AS role, b.created_at AS joined_at
|
||||
FROM bots b
|
||||
WHERE b.id = sqlc.arg(chat_id)
|
||||
ORDER BY joined_at ASC;
|
||||
|
||||
-- name: RemoveChatParticipant :exec
|
||||
DELETE FROM bot_members
|
||||
WHERE bot_id = sqlc.arg(chat_id)
|
||||
AND user_id = sqlc.arg(user_id)
|
||||
AND user_id <> (SELECT owner_user_id FROM bots WHERE id = sqlc.arg(chat_id));
|
||||
|
||||
-- name: CopyParticipantsToChat :exec
|
||||
INSERT INTO bot_members (bot_id, user_id, role)
|
||||
SELECT sqlc.arg(chat_id_2), bm.user_id, bm.role
|
||||
FROM bot_members bm
|
||||
WHERE bm.bot_id = sqlc.arg(chat_id)
|
||||
ON CONFLICT (bot_id, user_id) DO NOTHING;
|
||||
SELECT 1
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM bots b
|
||||
WHERE b.id = sqlc.arg(chat_id)
|
||||
AND b.owner_user_id = sqlc.arg(user_id)
|
||||
);
|
||||
|
||||
-- chat_settings
|
||||
|
||||
|
||||
@@ -162,3 +162,25 @@ LIMIT sqlc.arg(max_count);
|
||||
-- name: DeleteMessagesByBot :exec
|
||||
DELETE FROM bot_history_messages
|
||||
WHERE bot_id = sqlc.arg(bot_id);
|
||||
|
||||
-- name: ListObservedConversationsByChannelIdentity :many
|
||||
SELECT
|
||||
r.id AS route_id,
|
||||
r.channel_type AS channel,
|
||||
COALESCE(r.conversation_type, '') AS conversation_type,
|
||||
r.external_conversation_id AS conversation_id,
|
||||
COALESCE(r.external_thread_id, '') AS thread_id,
|
||||
COALESCE(r.metadata->>'conversation_name', '')::text AS conversation_name,
|
||||
MAX(m.created_at)::timestamptz AS last_observed_at
|
||||
FROM bot_history_messages m
|
||||
JOIN bot_channel_routes r ON r.id = m.route_id
|
||||
WHERE m.bot_id = sqlc.arg(bot_id)
|
||||
AND m.sender_channel_identity_id = sqlc.arg(channel_identity_id)
|
||||
GROUP BY
|
||||
r.id,
|
||||
r.channel_type,
|
||||
r.conversation_type,
|
||||
r.external_conversation_id,
|
||||
r.external_thread_id,
|
||||
r.metadata
|
||||
ORDER BY MAX(m.created_at) DESC;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
-- name: CreateBotPreauthKey :one
|
||||
INSERT INTO bot_preauth_keys (bot_id, token, issued_by_user_id, expires_at)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, bot_id, token, issued_by_user_id, expires_at, used_at, created_at;
|
||||
|
||||
-- name: GetBotPreauthKey :one
|
||||
SELECT id, bot_id, token, issued_by_user_id, expires_at, used_at, created_at
|
||||
FROM bot_preauth_keys
|
||||
WHERE token = $1
|
||||
AND used_at IS NULL
|
||||
AND (expires_at IS NULL OR expires_at > now())
|
||||
LIMIT 1;
|
||||
|
||||
-- name: MarkBotPreauthKeyUsed :one
|
||||
UPDATE bot_preauth_keys
|
||||
SET used_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, bot_id, token, issued_by_user_id, expires_at, used_at, created_at;
|
||||
@@ -5,7 +5,6 @@ SELECT
|
||||
bots.max_context_tokens,
|
||||
bots.max_inbox_items,
|
||||
bots.language,
|
||||
bots.allow_guest,
|
||||
bots.reasoning_enabled,
|
||||
bots.reasoning_effort,
|
||||
bots.heartbeat_enabled,
|
||||
@@ -33,7 +32,6 @@ WITH updated AS (
|
||||
max_context_tokens = sqlc.arg(max_context_tokens),
|
||||
max_inbox_items = sqlc.arg(max_inbox_items),
|
||||
language = sqlc.arg(language),
|
||||
allow_guest = sqlc.arg(allow_guest),
|
||||
reasoning_enabled = sqlc.arg(reasoning_enabled),
|
||||
reasoning_effort = sqlc.arg(reasoning_effort),
|
||||
heartbeat_enabled = sqlc.arg(heartbeat_enabled),
|
||||
@@ -47,7 +45,7 @@ WITH updated AS (
|
||||
browser_context_id = COALESCE(sqlc.narg(browser_context_id)::uuid, bots.browser_context_id),
|
||||
updated_at = now()
|
||||
WHERE bots.id = sqlc.arg(id)
|
||||
RETURNING bots.id, bots.max_context_load_time, bots.max_context_tokens, bots.max_inbox_items, bots.language, bots.allow_guest, bots.reasoning_enabled, bots.reasoning_effort, bots.heartbeat_enabled, bots.heartbeat_interval, bots.heartbeat_prompt, bots.chat_model_id, bots.heartbeat_model_id, bots.search_provider_id, bots.memory_provider_id, bots.tts_model_id, bots.browser_context_id
|
||||
RETURNING bots.id, bots.max_context_load_time, bots.max_context_tokens, bots.max_inbox_items, bots.language, bots.reasoning_enabled, bots.reasoning_effort, bots.heartbeat_enabled, bots.heartbeat_interval, bots.heartbeat_prompt, bots.chat_model_id, bots.heartbeat_model_id, bots.search_provider_id, bots.memory_provider_id, bots.tts_model_id, bots.browser_context_id
|
||||
)
|
||||
SELECT
|
||||
updated.id AS bot_id,
|
||||
@@ -55,7 +53,6 @@ SELECT
|
||||
updated.max_context_tokens,
|
||||
updated.max_inbox_items,
|
||||
updated.language,
|
||||
updated.allow_guest,
|
||||
updated.reasoning_enabled,
|
||||
updated.reasoning_effort,
|
||||
updated.heartbeat_enabled,
|
||||
@@ -81,7 +78,6 @@ SET max_context_load_time = 1440,
|
||||
max_context_tokens = 0,
|
||||
max_inbox_items = 50,
|
||||
language = 'auto',
|
||||
allow_guest = false,
|
||||
reasoning_enabled = false,
|
||||
reasoning_effort = 'medium',
|
||||
heartbeat_enabled = false,
|
||||
|
||||
@@ -64,6 +64,19 @@ SELECT * FROM users
|
||||
WHERE username IS NOT NULL
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: SearchAccounts :many
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE username IS NOT NULL
|
||||
AND (
|
||||
sqlc.arg(query)::text = ''
|
||||
OR username ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
OR COALESCE(display_name, '') ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
OR COALESCE(email, '') ILIKE '%' || sqlc.arg(query)::text || '%'
|
||||
)
|
||||
ORDER BY last_login_at DESC NULLS LAST, created_at DESC
|
||||
LIMIT sqlc.arg(limit_count);
|
||||
|
||||
-- name: UpdateAccountProfile :one
|
||||
UPDATE users
|
||||
SET display_name = $2,
|
||||
|
||||
Reference in New Issue
Block a user