2023-06-11 22:11:43 +03:00
|
|
|
-- Create users table
|
2023-08-21 15:05:13 +03:00
|
|
|
CREATE TABLE IF NOT EXISTS user_daily_usage(
|
2023-06-28 20:39:27 +03:00
|
|
|
user_id UUID REFERENCES auth.users (id),
|
2023-06-11 22:11:43 +03:00
|
|
|
email TEXT,
|
|
|
|
date TEXT,
|
2023-08-21 15:05:13 +03:00
|
|
|
daily_requests_count INT,
|
2023-06-28 20:39:27 +03:00
|
|
|
PRIMARY KEY (user_id, date)
|
2023-06-11 22:11:43 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
-- Create chats table
|
|
|
|
CREATE TABLE IF NOT EXISTS chats(
|
|
|
|
chat_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
2023-06-28 20:39:27 +03:00
|
|
|
user_id UUID REFERENCES auth.users (id),
|
2023-06-11 22:11:43 +03:00
|
|
|
creation_time TIMESTAMP DEFAULT current_timestamp,
|
|
|
|
history JSONB,
|
|
|
|
chat_name TEXT
|
|
|
|
);
|
|
|
|
|
2023-06-22 18:50:06 +03:00
|
|
|
|
2023-06-11 22:11:43 +03:00
|
|
|
-- Create vector extension
|
|
|
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
|
|
|
|
|
-- Create vectors table
|
|
|
|
CREATE TABLE IF NOT EXISTS vectors (
|
2023-07-12 13:44:34 +03:00
|
|
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
2023-06-11 22:11:43 +03:00
|
|
|
content TEXT,
|
2023-09-16 00:39:29 +03:00
|
|
|
file_sha1 TEXT,
|
2023-06-11 22:11:43 +03:00
|
|
|
metadata JSONB,
|
|
|
|
embedding VECTOR(1536)
|
|
|
|
);
|
|
|
|
|
|
|
|
-- Create function to match vectors
|
2023-06-28 20:39:27 +03:00
|
|
|
CREATE OR REPLACE FUNCTION match_vectors(query_embedding VECTOR(1536), match_count INT, p_brain_id UUID)
|
2023-06-11 22:11:43 +03:00
|
|
|
RETURNS TABLE(
|
2023-07-12 13:44:34 +03:00
|
|
|
id UUID,
|
2023-06-28 20:39:27 +03:00
|
|
|
brain_id UUID,
|
2023-06-11 22:11:43 +03:00
|
|
|
content TEXT,
|
|
|
|
metadata JSONB,
|
|
|
|
embedding VECTOR(1536),
|
|
|
|
similarity FLOAT
|
|
|
|
) LANGUAGE plpgsql AS $$
|
|
|
|
#variable_conflict use_column
|
|
|
|
BEGIN
|
|
|
|
RETURN QUERY
|
|
|
|
SELECT
|
2023-06-28 20:39:27 +03:00
|
|
|
vectors.id,
|
|
|
|
brains_vectors.brain_id,
|
|
|
|
vectors.content,
|
|
|
|
vectors.metadata,
|
|
|
|
vectors.embedding,
|
2023-06-11 22:11:43 +03:00
|
|
|
1 - (vectors.embedding <=> query_embedding) AS similarity
|
|
|
|
FROM
|
|
|
|
vectors
|
2023-06-28 20:39:27 +03:00
|
|
|
INNER JOIN
|
|
|
|
brains_vectors ON vectors.id = brains_vectors.vector_id
|
|
|
|
WHERE brains_vectors.brain_id = p_brain_id
|
2023-06-11 22:11:43 +03:00
|
|
|
ORDER BY
|
|
|
|
vectors.embedding <=> query_embedding
|
|
|
|
LIMIT match_count;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
-- Create stats table
|
2023-06-12 18:58:05 +03:00
|
|
|
CREATE TABLE IF NOT EXISTS stats (
|
2023-06-11 22:11:43 +03:00
|
|
|
time TIMESTAMP,
|
|
|
|
chat BOOLEAN,
|
|
|
|
embedding BOOLEAN,
|
|
|
|
details TEXT,
|
|
|
|
metadata JSONB,
|
|
|
|
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY
|
|
|
|
);
|
|
|
|
|
|
|
|
-- Create summaries table
|
|
|
|
CREATE TABLE IF NOT EXISTS summaries (
|
|
|
|
id BIGSERIAL PRIMARY KEY,
|
2023-07-12 13:44:34 +03:00
|
|
|
document_id UUID REFERENCES vectors(id),
|
2023-06-11 22:11:43 +03:00
|
|
|
content TEXT,
|
|
|
|
metadata JSONB,
|
|
|
|
embedding VECTOR(1536)
|
|
|
|
);
|
|
|
|
|
|
|
|
-- Create function to match summaries
|
|
|
|
CREATE OR REPLACE FUNCTION match_summaries(query_embedding VECTOR(1536), match_count INT, match_threshold FLOAT)
|
|
|
|
RETURNS TABLE(
|
|
|
|
id BIGINT,
|
2023-07-12 13:44:34 +03:00
|
|
|
document_id UUID,
|
2023-06-11 22:11:43 +03:00
|
|
|
content TEXT,
|
|
|
|
metadata JSONB,
|
|
|
|
embedding VECTOR(1536),
|
|
|
|
similarity FLOAT
|
|
|
|
) LANGUAGE plpgsql AS $$
|
|
|
|
#variable_conflict use_column
|
|
|
|
BEGIN
|
|
|
|
RETURN QUERY
|
|
|
|
SELECT
|
|
|
|
id,
|
|
|
|
document_id,
|
|
|
|
content,
|
|
|
|
metadata,
|
|
|
|
embedding,
|
|
|
|
1 - (summaries.embedding <=> query_embedding) AS similarity
|
|
|
|
FROM
|
|
|
|
summaries
|
|
|
|
WHERE 1 - (summaries.embedding <=> query_embedding) > match_threshold
|
|
|
|
ORDER BY
|
|
|
|
summaries.embedding <=> query_embedding
|
|
|
|
LIMIT match_count;
|
|
|
|
END;
|
|
|
|
$$;
|
2023-06-14 22:21:13 +03:00
|
|
|
|
|
|
|
-- Create api_keys table
|
|
|
|
CREATE TABLE IF NOT EXISTS api_keys(
|
|
|
|
key_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
2023-06-28 20:39:27 +03:00
|
|
|
user_id UUID REFERENCES auth.users (id),
|
2023-06-14 22:21:13 +03:00
|
|
|
api_key TEXT UNIQUE,
|
|
|
|
creation_time TIMESTAMP DEFAULT current_timestamp,
|
|
|
|
deleted_time TIMESTAMP,
|
|
|
|
is_active BOOLEAN DEFAULT true
|
|
|
|
);
|
|
|
|
|
2023-08-03 16:41:24 +03:00
|
|
|
--- Create prompts table
|
|
|
|
CREATE TABLE IF NOT EXISTS prompts (
|
|
|
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
|
|
title VARCHAR(255),
|
|
|
|
content TEXT,
|
|
|
|
status VARCHAR(255) DEFAULT 'private'
|
|
|
|
);
|
|
|
|
|
|
|
|
--- Create brains table
|
2023-07-25 16:22:17 +03:00
|
|
|
CREATE TABLE IF NOT EXISTS brains (
|
2023-06-17 00:36:53 +03:00
|
|
|
brain_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
2023-07-25 16:22:17 +03:00
|
|
|
name TEXT NOT NULL,
|
2023-06-17 00:36:53 +03:00
|
|
|
status TEXT,
|
2023-07-25 16:22:17 +03:00
|
|
|
description TEXT,
|
2023-06-17 00:36:53 +03:00
|
|
|
model TEXT,
|
2023-07-25 16:22:17 +03:00
|
|
|
max_tokens INT,
|
|
|
|
temperature FLOAT,
|
2023-08-03 11:37:13 +03:00
|
|
|
openai_api_key TEXT,
|
2023-09-25 12:33:23 +03:00
|
|
|
prompt_id UUID REFERENCES prompts(id),
|
|
|
|
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
2023-06-17 00:36:53 +03:00
|
|
|
);
|
|
|
|
|
2023-08-03 11:37:13 +03:00
|
|
|
|
2023-08-10 11:25:08 +03:00
|
|
|
-- Create chat_history table
|
|
|
|
CREATE TABLE IF NOT EXISTS chat_history (
|
|
|
|
message_id UUID DEFAULT uuid_generate_v4(),
|
|
|
|
chat_id UUID REFERENCES chats(chat_id),
|
|
|
|
user_message TEXT,
|
|
|
|
assistant TEXT,
|
|
|
|
message_time TIMESTAMP DEFAULT current_timestamp,
|
|
|
|
PRIMARY KEY (chat_id, message_id),
|
|
|
|
prompt_id UUID REFERENCES prompts(id),
|
|
|
|
brain_id UUID REFERENCES brains(brain_id)
|
|
|
|
);
|
|
|
|
|
2023-09-07 14:22:06 +03:00
|
|
|
-- Create notification table
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
|
|
datetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
chat_id UUID REFERENCES chats(chat_id),
|
|
|
|
message TEXT,
|
|
|
|
action VARCHAR(255) NOT NULL,
|
|
|
|
status VARCHAR(255) NOT NULL
|
|
|
|
);
|
|
|
|
|
|
|
|
|
2023-06-17 00:36:53 +03:00
|
|
|
-- Create brains X users table
|
|
|
|
CREATE TABLE IF NOT EXISTS brains_users (
|
|
|
|
brain_id UUID,
|
|
|
|
user_id UUID,
|
|
|
|
rights VARCHAR(255),
|
2023-06-28 20:39:27 +03:00
|
|
|
default_brain BOOLEAN DEFAULT false,
|
2023-06-17 00:36:53 +03:00
|
|
|
PRIMARY KEY (brain_id, user_id),
|
2023-06-28 20:39:27 +03:00
|
|
|
FOREIGN KEY (user_id) REFERENCES auth.users (id),
|
2023-07-12 13:44:34 +03:00
|
|
|
FOREIGN KEY (brain_id) REFERENCES brains (brain_id)
|
2023-06-17 00:36:53 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
-- Create brains X vectors table
|
|
|
|
CREATE TABLE IF NOT EXISTS brains_vectors (
|
|
|
|
brain_id UUID,
|
2023-07-12 13:44:34 +03:00
|
|
|
vector_id UUID,
|
2023-06-30 11:09:50 +03:00
|
|
|
file_sha1 TEXT,
|
2023-06-17 00:36:53 +03:00
|
|
|
PRIMARY KEY (brain_id, vector_id),
|
2023-06-28 20:39:27 +03:00
|
|
|
FOREIGN KEY (vector_id) REFERENCES vectors (id),
|
|
|
|
FOREIGN KEY (brain_id) REFERENCES brains (brain_id)
|
2023-06-17 00:36:53 +03:00
|
|
|
);
|
2023-07-03 12:11:29 +03:00
|
|
|
|
2023-07-11 19:20:31 +03:00
|
|
|
-- Create brains X vectors table
|
|
|
|
CREATE TABLE IF NOT EXISTS brain_subscription_invitations (
|
|
|
|
brain_id UUID,
|
|
|
|
email VARCHAR(255),
|
|
|
|
rights VARCHAR(255),
|
|
|
|
PRIMARY KEY (brain_id, email),
|
2023-07-12 13:44:34 +03:00
|
|
|
FOREIGN KEY (brain_id) REFERENCES brains (brain_id)
|
2023-07-11 19:20:31 +03:00
|
|
|
);
|
|
|
|
|
2023-08-01 10:24:57 +03:00
|
|
|
--- Create user_identity table
|
|
|
|
CREATE TABLE IF NOT EXISTS user_identity (
|
|
|
|
user_id UUID PRIMARY KEY,
|
|
|
|
openai_api_key VARCHAR(255)
|
|
|
|
);
|
|
|
|
|
2023-11-07 16:03:50 +03:00
|
|
|
-- Create the new table with 6 columns
|
|
|
|
CREATE TABLE IF NOT EXISTS api_brain_definition (
|
|
|
|
brain_id UUID REFERENCES brains(brain_id),
|
|
|
|
method VARCHAR(255) CHECK (method IN ('GET', 'POST', 'PUT', 'DELETE')),
|
|
|
|
url VARCHAR(255),
|
|
|
|
params JSON,
|
|
|
|
search_params JSON,
|
|
|
|
secrets JSON
|
|
|
|
);
|
2023-08-03 10:53:38 +03:00
|
|
|
|
2023-07-17 18:24:31 +03:00
|
|
|
CREATE OR REPLACE FUNCTION public.get_user_email_by_user_id(user_id uuid)
|
|
|
|
RETURNS TABLE (email text)
|
|
|
|
SECURITY definer
|
|
|
|
AS $$
|
|
|
|
BEGIN
|
|
|
|
RETURN QUERY SELECT au.email::text FROM auth.users au WHERE au.id = user_id;
|
|
|
|
END;
|
|
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
|
2023-07-18 15:30:19 +03:00
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION public.get_user_id_by_user_email(user_email text)
|
|
|
|
RETURNS TABLE (user_id uuid)
|
|
|
|
SECURITY DEFINER
|
|
|
|
AS $$
|
|
|
|
BEGIN
|
|
|
|
RETURN QUERY SELECT au.id::uuid FROM auth.users au WHERE au.email = user_email;
|
|
|
|
END;
|
|
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-07-03 12:11:29 +03:00
|
|
|
CREATE TABLE IF NOT EXISTS migrations (
|
2023-07-03 15:41:33 +03:00
|
|
|
name VARCHAR(255) PRIMARY KEY,
|
2023-07-03 12:11:29 +03:00
|
|
|
executed_at TIMESTAMPTZ DEFAULT current_timestamp
|
|
|
|
);
|
|
|
|
|
2023-09-13 14:47:12 +03:00
|
|
|
CREATE TABLE IF NOT EXISTS user_settings (
|
|
|
|
user_id UUID PRIMARY KEY,
|
2023-10-05 11:09:56 +03:00
|
|
|
models JSONB DEFAULT '["gpt-3.5-turbo","huggingface/mistralai/Mistral-7B-Instruct-v0.1"]'::jsonb,
|
2023-09-30 23:32:53 +03:00
|
|
|
daily_chat_credit INT DEFAULT 20,
|
|
|
|
max_brains INT DEFAULT 3,
|
2023-09-20 18:43:46 +03:00
|
|
|
max_brain_size INT DEFAULT 10000000
|
2023-09-13 14:47:12 +03:00
|
|
|
);
|
|
|
|
|
2023-09-20 10:35:37 +03:00
|
|
|
-- knowledge table
|
|
|
|
CREATE TABLE IF NOT EXISTS knowledge (
|
|
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
|
|
file_name TEXT,
|
|
|
|
url TEXT,
|
|
|
|
brain_id UUID NOT NULL REFERENCES brains(brain_id),
|
|
|
|
extension TEXT NOT NULL,
|
|
|
|
CHECK ((file_name IS NOT NULL AND url IS NULL) OR (file_name IS NULL AND url IS NOT NULL))
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
-- knowledge_vectors table
|
|
|
|
CREATE TABLE IF NOT EXISTS knowledge_vectors (
|
|
|
|
knowledge_id UUID NOT NULL REFERENCES knowledge(id),
|
|
|
|
vector_id UUID NOT NULL REFERENCES vectors(id),
|
|
|
|
embedding_model TEXT NOT NULL,
|
|
|
|
PRIMARY KEY (knowledge_id, vector_id, embedding_model)
|
|
|
|
);
|
|
|
|
|
2023-10-10 18:21:05 +03:00
|
|
|
-- Create the function to add user_id to the onboardings table
|
|
|
|
CREATE OR REPLACE FUNCTION public.create_user_onboarding() RETURNS TRIGGER AS $$
|
|
|
|
BEGIN
|
|
|
|
INSERT INTO public.onboardings (user_id)
|
|
|
|
VALUES (NEW.id);
|
|
|
|
RETURN NEW;
|
|
|
|
END;
|
|
|
|
$$ LANGUAGE plpgsql SECURITY definer;
|
|
|
|
|
|
|
|
-- Revoke all on function handle_new_user_onboarding() from PUBLIC;
|
|
|
|
REVOKE ALL ON FUNCTION create_user_onboarding() FROM PUBLIC;
|
|
|
|
|
|
|
|
-- Drop the trigger if it exists
|
|
|
|
DROP TRIGGER IF EXISTS create_user_onboarding_trigger ON auth.users;
|
|
|
|
|
|
|
|
-- Create the trigger on the insert into the auth.users table
|
|
|
|
CREATE TRIGGER create_user_onboarding_trigger
|
|
|
|
AFTER INSERT ON auth.users
|
|
|
|
FOR EACH ROW
|
|
|
|
EXECUTE FUNCTION public.create_user_onboarding();
|
2023-09-20 10:35:37 +03:00
|
|
|
|
2023-10-05 10:47:29 +03:00
|
|
|
-- Create the onboarding table
|
|
|
|
CREATE TABLE IF NOT EXISTS onboardings (
|
|
|
|
user_id UUID NOT NULL REFERENCES auth.users (id),
|
2023-10-06 13:37:12 +03:00
|
|
|
onboarding_a BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
onboarding_b1 BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
onboarding_b2 BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
onboarding_b3 BOOLEAN NOT NULL DEFAULT true,
|
2023-10-16 17:11:34 +03:00
|
|
|
creation_time TIMESTAMP DEFAULT current_timestamp,
|
2023-10-05 10:47:29 +03:00
|
|
|
PRIMARY KEY (user_id)
|
2023-10-06 21:32:49 +03:00
|
|
|
);
|
2023-10-05 10:47:29 +03:00
|
|
|
|
2023-10-23 19:19:04 +03:00
|
|
|
|
|
|
|
-- Stripe settings --
|
|
|
|
-- Create extension 'wrappers' if it doesn't exist
|
|
|
|
CREATE EXTENSION IF NOT EXISTS wrappers;
|
|
|
|
|
|
|
|
-- Create foreign data wrapper 'stripe_wrapper' if it doesn't exist
|
|
|
|
DO $$
|
|
|
|
BEGIN
|
|
|
|
IF NOT EXISTS (
|
|
|
|
SELECT 1
|
|
|
|
FROM information_schema.foreign_data_wrappers
|
|
|
|
WHERE foreign_data_wrapper_name = 'stripe_wrapper'
|
|
|
|
) THEN
|
|
|
|
CREATE FOREIGN DATA WRAPPER stripe_wrapper
|
|
|
|
HANDLER stripe_fdw_handler;
|
|
|
|
END IF;
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
-- Check if the server 'stripe_server' exists before creating it
|
|
|
|
DO $$
|
|
|
|
BEGIN
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM pg_foreign_server WHERE srvname = 'stripe_server') THEN
|
|
|
|
CREATE SERVER stripe_server
|
|
|
|
FOREIGN DATA WRAPPER stripe_wrapper
|
|
|
|
OPTIONS (
|
|
|
|
api_key 'your_stripe_api_key' -- Replace with your Stripe API key
|
|
|
|
);
|
|
|
|
END IF;
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
-- Create foreign table 'public.customers' if it doesn't exist
|
|
|
|
DO $$
|
|
|
|
BEGIN
|
|
|
|
IF NOT EXISTS (
|
|
|
|
SELECT 1
|
|
|
|
FROM information_schema.tables
|
|
|
|
WHERE table_name = 'customers'
|
|
|
|
) THEN
|
|
|
|
CREATE FOREIGN TABLE public.customers (
|
|
|
|
id text,
|
|
|
|
email text,
|
|
|
|
name text,
|
|
|
|
description text,
|
|
|
|
created timestamp,
|
|
|
|
attrs jsonb
|
|
|
|
)
|
|
|
|
SERVER stripe_server
|
|
|
|
OPTIONS (
|
|
|
|
OBJECT 'customers',
|
|
|
|
ROWID_COLUMN 'id'
|
|
|
|
);
|
|
|
|
END IF;
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
-- Create table 'users' if it doesn't exist
|
|
|
|
CREATE TABLE IF NOT EXISTS public.users (
|
|
|
|
id uuid REFERENCES auth.users NOT NULL PRIMARY KEY,
|
|
|
|
email text
|
|
|
|
);
|
|
|
|
|
|
|
|
-- Create or replace function 'public.handle_new_user'
|
|
|
|
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
|
|
RETURNS TRIGGER AS $$
|
|
|
|
BEGIN
|
|
|
|
INSERT INTO public.users (id, email)
|
|
|
|
VALUES (NEW.id, NEW.email);
|
|
|
|
RETURN NEW;
|
|
|
|
END;
|
|
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
|
|
|
|
-- Check if the trigger 'on_auth_user_created' exists before creating it
|
|
|
|
DO $$
|
|
|
|
BEGIN
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'on_auth_user_created') THEN
|
|
|
|
CREATE TRIGGER on_auth_user_created
|
|
|
|
AFTER INSERT ON auth.users
|
|
|
|
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
|
|
|
END IF;
|
|
|
|
END $$;
|
|
|
|
|
2023-09-14 12:56:59 +03:00
|
|
|
insert into
|
|
|
|
storage.buckets (id, name)
|
|
|
|
values
|
2023-09-14 17:45:15 +03:00
|
|
|
('quivr', 'quivr');
|
2023-09-14 12:56:59 +03:00
|
|
|
|
|
|
|
CREATE POLICY "Access Quivr Storage 1jccrwz_0" ON storage.objects FOR INSERT TO anon WITH CHECK (bucket_id = 'quivr');
|
|
|
|
|
|
|
|
CREATE POLICY "Access Quivr Storage 1jccrwz_1" ON storage.objects FOR SELECT TO anon USING (bucket_id = 'quivr');
|
|
|
|
|
|
|
|
CREATE POLICY "Access Quivr Storage 1jccrwz_2" ON storage.objects FOR UPDATE TO anon USING (bucket_id = 'quivr');
|
|
|
|
|
|
|
|
CREATE POLICY "Access Quivr Storage 1jccrwz_3" ON storage.objects FOR DELETE TO anon USING (bucket_id = 'quivr');
|
|
|
|
|
2023-07-03 12:11:29 +03:00
|
|
|
INSERT INTO migrations (name)
|
2023-11-07 16:03:50 +03:00
|
|
|
SELECT '2023110607100000_add_api_brain_definition_table'
|
2023-07-03 12:11:29 +03:00
|
|
|
WHERE NOT EXISTS (
|
2023-11-07 16:03:50 +03:00
|
|
|
SELECT 1 FROM migrations WHERE name = '2023110607100000_add_api_brain_definition_table'
|
2023-07-12 13:44:34 +03:00
|
|
|
);
|
2023-09-14 12:56:59 +03:00
|
|
|
|
|
|
|
|