commit 6fae65c3d7b4b42ca4fd8707ee9048d4b0f26916 Author: Badanin Maksim Date: Sun Jul 21 13:26:21 2024 +0300 init diff --git a/.env b/.env new file mode 100644 index 0000000..acd8867 --- /dev/null +++ b/.env @@ -0,0 +1,128 @@ + # This file is a template for docker compose deployment +# Copy this file to .env and change the values as needed + +#----------ADDED------------ +SECRET_PASSWORD= +POSTGRES_PASSWORD=$SECRET_PASSWORD +#--------------------------- + +# AppFlowy Cloud +## URL that connects to the gotrue docker container +APPFLOWY_GOTRUE_BASE_URL=http://gotrue:9999 +## URL that connects to the postgres docker container +APPFLOWY_DATABASE_URL=postgres://postgres:$POSTGRES_PASSWORD@postgres:5432/postgres #CHANGED +APPFLOWY_ACCESS_CONTROL=true +APPFLOWY_WEBSOCKET_MAILBOX_SIZE=6000 +APPFLOWY_DATABASE_MAX_CONNECTIONS=40 + +# admin frontend +## URL that connects to redis docker container +ADMIN_FRONTEND_REDIS_URL=redis://redis:6379 +## URL that connects to gotrue docker container +ADMIN_FRONTEND_GOTRUE_URL=http://gotrue:9999 + +# authentication key, change this and keep the key safe and secret +# self defined key, you can use any string +GOTRUE_JWT_SECRET=$SECRET_PASSWORD #CHANGED +# Expiration time in seconds for the JWT token +GOTRUE_JWT_EXP=7200 + +# User sign up will automatically be confirmed if this is set to true. +# If you have OAuth2 set up or smtp configured, you can set this to false +# to enforce email confirmation or OAuth2 login instead. +# If you set this to false, you need to either set up SMTP +GOTRUE_MAILER_AUTOCONFIRM=false +# Number of emails that can be per minute +GOTRUE_RATE_LIMIT_EMAIL_SENT=100 + +# If you intend to use mail confirmation, you need to set the SMTP configuration below +# You would then need to set GOTRUE_MAILER_AUTOCONFIRM=false +# Check for logs in gotrue service if there are any issues with email confirmation +GOTRUE_SMTP_HOST= +GOTRUE_SMTP_PORT= +GOTRUE_SMTP_USER= +GOTRUE_SMTP_PASS= +GOTRUE_SMTP_ADMIN_EMAIL= + +# This user will be created when AppFlowy Cloud starts successfully +# You can use this user to login to the admin panel +GOTRUE_ADMIN_EMAIL= +GOTRUE_ADMIN_PASSWORD=$SECRET_PASSWORD #CHANGED + +# User will be redirected to this after Email or OAuth login +# Change this to your own domain where you host the docker-compose or gotrue +# If you are using a different domain, you need to change the redirect_uri in the OAuth2 configuration +# Make sure that this domain is accessible to the user +API_EXTERNAL_URL= + +# In docker environment, `postgres` is the hostname of the postgres service +# GoTrue connect to postgres using this url +GOTRUE_DATABASE_URL=postgres://supabase_auth_admin:root@postgres:5432/postgres + +# Refer to this for details: https://github.com/AppFlowy-IO/AppFlowy-Cloud/blob/main/doc/AUTHENTICATION.md +# Google OAuth2 +GOTRUE_EXTERNAL_GOOGLE_ENABLED=false +GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID= +GOTRUE_EXTERNAL_GOOGLE_SECRET= +GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=http://your-host/gotrue/callback +# GitHub OAuth2 +GOTRUE_EXTERNAL_GITHUB_ENABLED=false +GOTRUE_EXTERNAL_GITHUB_CLIENT_ID= +GOTRUE_EXTERNAL_GITHUB_SECRET= +GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=http://your-host/gotrue/callback +# Discord OAuth2 +GOTRUE_EXTERNAL_DISCORD_ENABLED=false +GOTRUE_EXTERNAL_DISCORD_CLIENT_ID= +GOTRUE_EXTERNAL_DISCORD_SECRET= +GOTRUE_EXTERNAL_DISCORD_REDIRECT_URI=http://your-host/gotrue/callback + +# File Storage +# This is where storage like images, files, etc. will be stored +# By default, Minio is used as the default file storage which uses host's file system +APPFLOWY_S3_USE_MINIO=true +APPFLOWY_S3_MINIO_URL=http://minio:9000 # change this if you are using a different address for minio +APPFLOWY_S3_ACCESS_KEY=minioadmin +APPFLOWY_S3_SECRET_KEY=$SECRET_PASSWORD #CHANGED +APPFLOWY_S3_BUCKET=appflowy +#APPFLOWY_S3_REGION=us-east-1 + +# AppFlowy Cloud Mailer +APPFLOWY_MAILER_SMTP_HOST=$GOTRUE_SMTP_HOST #CHANGED +APPFLOWY_MAILER_SMTP_PORT=$GOTRUE_SMTP_PORT #CHANGED +APPFLOWY_MAILER_SMTP_USERNAME=$GOTRUE_SMTP_USER #CHANGED +APPFLOWY_MAILER_SMTP_PASSWORD=$GOTRUE_SMTP_PASS #CHANGED + +# Log level for the appflowy-cloud service +RUST_LOG=info + +# PgAdmin +# Optional module to manage the postgres database +# You can access the pgadmin at http://your-host/pgadmin +# Refer to the APPFLOWY_DATABASE_URL for password when connecting to the database +PGADMIN_DEFAULT_EMAIL=$GOTRUE_ADMIN_EMAIL #CHANGED +PGADMIN_DEFAULT_PASSWORD=$SECRET_PASSWORD #CHANGED + +# Portainer (username: admin) +PORTAINER_PASSWORD=$SECRET_PASSWORD #CHANGED + +# Cloudflare tunnel token +CLOUDFLARE_TUNNEL_TOKEN= + +# If you are using a different postgres database, change the following values +# GOTRUE_DATABASE_URL=postgres://supabase_auth_admin:root@:/$POSTGRES_DB +# APPFLOWY_DATABASE_URL=postgres://POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:5432/$POSTGRES_DB + +# AppFlowy AI +APPFLOWY_AI_OPENAI_API_KEY= +APPFLOWY_AI_SERVER_PORT=5001 +APPFLOWY_AI_SERVER_HOST=ai +APPFLOWY_AI_DATABASE_URL=postgresql+psycopg://postgres:$POSTGRES_PASSWORD@postgres:5432/postgres #CHANGED + +# AppFlowy History +APPFLOWY_HISTORY_URL=http://localhost:50051 +APPFLOWY_HISTORY_REDIS_URL=redis://redis:6379 +APPFLOWY_HISTORY_DATABASE_URL=postgres://postgres:$POSTGRES_PASSWORD@postgres:5432/postgres #CHANGED + +# AppFlowy Indexer +APPFLOWY_INDEXER_DATABASE_URL=postgres://postgres:$POSTGRES_PASSWORD@postgres:5432/postgres #CHANGED +APPFLOWY_INDEXER_REDIS_URL=redis://redis:6379 diff --git a/README.md b/README.md new file mode 100644 index 0000000..721e08a --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Установка AppFlowy-Cloud + +Основан на [https://github.com/AppFlowy-IO/AppFlowy-Cloud](https://github.com/AppFlowy-IO/AppFlowy-Cloud) + + +#### Параметры переменных в файле .env + + +``` + +SECRET_PASSWORD= # Админские пароли и JWT + +GOTRUE_MAILER_AUTOCONFIRM=false # Отключить автоподтвержение почты + +GOTRUE_SMTP_HOST и APPFLOWY_MAILER_SMTP_HOST # имя почтового сервера. Например: mail.example.com +GOTRUE_SMTP_PORT и APPFLOWY_MAILER_SMTP_PORT # порт почтового сервера. Например: 25 +GOTRUE_SMTP_USER и APPFLOWY_MAILER_SMTP_USERNAME # учетная запись +GOTRUE_SMTP_PASS и APPFLOWY_MAILER_SMTP_PASSWORD # пароль +GOTRUE_SMTP_ADMIN_EMAIL # адрес рассылки. Например: admin@example.com + +GOTRUE_ADMIN_EMAIL и PGADMIN_DEFAULT_EMAIL # Почта входа администратора. Например: admin@example.com + +API_EXTERNAL_URL # Домен сайта. Например: https://af.example.com + +``` + +#### Запуск + +``` +git clone https://git.badms.ru/bms/appflowy.git +cd appflowy +# Предварительно отредактировать переменные +docker compose up -d + +``` \ No newline at end of file diff --git a/configs/migrations/20230312043023_user.sql b/configs/migrations/20230312043023_user.sql new file mode 100644 index 0000000..07bb72b --- /dev/null +++ b/configs/migrations/20230312043023_user.sql @@ -0,0 +1,46 @@ +-- Add migration script here +-- Required by uuid_generate_v4() +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +-- user table +CREATE TABLE IF NOT EXISTS af_user ( + uid BIGINT PRIMARY KEY, + uuid UUID NOT NULL , -- related to gotrue + email TEXT NOT NULL DEFAULT '' UNIQUE, -- not needed when authenticated with gotrue + password TEXT NOT NULL DEFAULT '', -- not needed when authenticated with gotrue + name TEXT NOT NULL DEFAULT '', + metadata JSONB DEFAULT '{}'::JSONB, -- used to user's metadata such as avatar, OpenAI key, etc. + encryption_sign TEXT DEFAULT NULL, -- used to encrypt the user's data + deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); +CREATE OR REPLACE FUNCTION update_updated_at_column_func() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); +RETURN NEW; +END; +$$ language 'plpgsql'; +CREATE TRIGGER update_af_user_modtime BEFORE +UPDATE ON af_user FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column_func(); +CREATE OR REPLACE FUNCTION prevent_reset_encryption_sign_func() RETURNS TRIGGER AS $$ BEGIN IF OLD.encryption_sign IS NOT NULL + AND NEW.encryption_sign IS DISTINCT +FROM OLD.encryption_sign THEN RAISE EXCEPTION 'The encryption sign can not be reset once it has been set'; +END IF; +RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trigger_prevent_reset_encryption_sign BEFORE +UPDATE ON af_user FOR EACH ROW EXECUTE FUNCTION prevent_reset_encryption_sign_func(); + +-- Enable RLS on the af_user table +-- Policy for INSERT +ALTER TABLE af_user ENABLE ROW LEVEL SECURITY; +CREATE POLICY af_user_insert_policy ON public.af_user FOR +INSERT TO anon, + authenticated WITH CHECK (true); +-- Policy for UPDATE +CREATE POLICY af_user_update_policy ON public.af_user FOR +UPDATE USING (true) WITH CHECK (true); +-- Policy for SELECT +CREATE POLICY af_user_select_policy ON public.af_user FOR +SELECT TO anon, + authenticated USING (true); +ALTER TABLE af_user FORCE ROW LEVEL SECURITY; diff --git a/configs/migrations/20230906101032_permission.sql b/configs/migrations/20230906101032_permission.sql new file mode 100644 index 0000000..b168fe0 --- /dev/null +++ b/configs/migrations/20230906101032_permission.sql @@ -0,0 +1,70 @@ +-- Create the af_roles table +CREATE TABLE IF NOT EXISTS af_roles ( + id SERIAL PRIMARY KEY, + name TEXT UNIQUE NOT NULL +); +-- Insert default roles +INSERT INTO af_roles (name) +VALUES ('Owner'), + ('Member'), + ('Guest'); +CREATE TABLE af_permissions ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + access_level INTEGER NOT NULL, + description TEXT +); +-- Insert default permissions +INSERT INTO af_permissions (name, description, access_level) +VALUES ('Read only', 'Can read', 10), + ( + 'Read and comment', + 'Can read and comment, but not edit', + 20 + ), + ( + 'Read and write', + 'Can read and edit, but not share with others', + 30 + ), + ( + 'Full access', + 'Can edit and share with others', + 50 + ); +-- Represents a permission that a role has. The list of all permissions a role has can be obtained by querying this table for all rows with a given role_id. +CREATE TABLE af_role_permissions ( + role_id INT REFERENCES af_roles(id), + permission_id INT REFERENCES af_permissions(id), + PRIMARY KEY (role_id, permission_id) +); +-- Associate permissions with roles +WITH role_ids AS ( + SELECT id, + name + FROM af_roles + WHERE name IN ('Owner', 'Member', 'Guest') +), +permission_ids AS ( + SELECT id, + name + FROM af_permissions + WHERE name IN ('Full access', 'Read and write', 'Read only') +) +INSERT INTO af_role_permissions (role_id, permission_id) +SELECT r.id, + p.id +FROM role_ids r + CROSS JOIN permission_ids p +WHERE ( + r.name = 'Owner' + AND p.name = 'Full access' + ) + OR ( + r.name = 'Member' + AND p.name = 'Read and write' + ) + OR ( + r.name = 'Guest' + AND p.name = 'Read only' + ); \ No newline at end of file diff --git a/configs/migrations/20230906101222_workspace.sql b/configs/migrations/20230906101222_workspace.sql new file mode 100644 index 0000000..cab781e --- /dev/null +++ b/configs/migrations/20230906101222_workspace.sql @@ -0,0 +1,81 @@ +-- af_workspace contains all the workspaces. Each workspace contains a list of members defined in af_workspace_member +CREATE TABLE IF NOT EXISTS af_workspace ( + workspace_id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(), + database_storage_id UUID NOT NULL DEFAULT uuid_generate_v4(), + owner_uid BIGINT NOT NULL REFERENCES af_user(uid) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + -- 0: Free + workspace_type INTEGER NOT NULL DEFAULT 0, + deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + workspace_name TEXT DEFAULT 'My Workspace' +); + +-- Enable RLS on the af_workspace table +ALTER TABLE af_workspace ENABLE ROW LEVEL SECURITY; +CREATE POLICY af_workspace_policy ON af_workspace FOR ALL TO anon, + authenticated USING (true); +ALTER TABLE af_workspace FORCE ROW LEVEL SECURITY; + +-- af_workspace_member contains all the members associated with a workspace and their roles. +CREATE TABLE IF NOT EXISTS af_workspace_member ( + uid BIGINT NOT NULL, + role_id INT NOT NULL REFERENCES af_roles(id), + workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (uid, workspace_id) +); + +-- Enable RLS on the af_workspace_member table +ALTER TABLE af_workspace_member ENABLE ROW LEVEL SECURITY; +CREATE POLICY af_workspace_member_policy ON af_workspace_member FOR ALL TO anon, + authenticated USING (true); +ALTER TABLE af_workspace_member FORCE ROW LEVEL SECURITY; + +-- Listener for af_workspace_member table +DROP TRIGGER IF EXISTS af_workspace_member_change_trigger ON af_workspace_member; + +CREATE OR REPLACE FUNCTION notify_af_workspace_member_change() RETURNS trigger AS $$ +DECLARE + payload TEXT; +BEGIN + payload := json_build_object( + 'old', row_to_json(OLD), + 'new', row_to_json(NEW), + 'action_type', TG_OP + )::text; + + PERFORM pg_notify('af_workspace_member_channel', payload); + -- Return the new row state for INSERT/UPDATE, and the old state for DELETE. + IF TG_OP = 'DELETE' THEN + RETURN OLD; + ELSE + RETURN NEW; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER af_workspace_member_change_trigger + AFTER INSERT OR UPDATE OR DELETE ON af_workspace_member + FOR EACH ROW EXECUTE FUNCTION notify_af_workspace_member_change(); + +-- Index +CREATE UNIQUE INDEX idx_af_workspace_member ON af_workspace_member (uid, workspace_id, role_id); +-- Insert a workspace member if the user with given uid is the owner of the workspace +CREATE OR REPLACE FUNCTION insert_af_workspace_member_if_owner( + p_uid BIGINT, + p_role_id INT, + p_workspace_id UUID + ) RETURNS VOID AS $$ BEGIN -- If user is the owner, proceed with the insert operation +INSERT INTO af_workspace_member (uid, role_id, workspace_id) +SELECT p_uid, + p_role_id, + p_workspace_id +FROM af_workspace +WHERE workspace_id = p_workspace_id + AND owner_uid = p_uid; +-- Check if the insert operation was successful. If not, user is not the owner of the workspace. +IF NOT FOUND THEN RAISE EXCEPTION 'Unsupported operation: User is not the owner of the workspace.'; +END IF; +END; +$$ LANGUAGE plpgsql; diff --git a/configs/migrations/20230906101555_user_profile.sql b/configs/migrations/20230906101555_user_profile.sql new file mode 100644 index 0000000..5f8de47 --- /dev/null +++ b/configs/migrations/20230906101555_user_profile.sql @@ -0,0 +1,18 @@ +-- af_user_profile_view is a view that contains all the user profiles and their latest workspace_id. +-- a subquery is first used to find the workspace_id of the workspace with the latest updated_at timestamp for each +-- user. This subquery is then joined with the af_user table to create the view. Note that a LEFT JOIN is used in +-- case there are users without workspaces, in which case latest_workspace_id will be NULL for those users. +CREATE OR REPLACE VIEW af_user_profile_view AS +SELECT u.*, + w.workspace_id AS latest_workspace_id +FROM af_user u + INNER JOIN ( + SELECT uid, + workspace_id, + rank() OVER ( + PARTITION BY uid + ORDER BY updated_at DESC + ) AS rn + FROM af_workspace_member + ) w ON u.uid = w.uid + AND w.rn = 1; \ No newline at end of file diff --git a/configs/migrations/20230906102651_collab.sql b/configs/migrations/20230906102651_collab.sql new file mode 100644 index 0000000..2f971ae --- /dev/null +++ b/configs/migrations/20230906102651_collab.sql @@ -0,0 +1,77 @@ +-- collab update table. +CREATE TABLE IF NOT EXISTS af_collab ( + oid TEXT NOT NULL, + blob BYTEA NOT NULL, + len INTEGER, + partition_key INTEGER NOT NULL, + encrypt INTEGER DEFAULT 0, + owner_uid BIGINT NOT NULL, + deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE, + PRIMARY KEY (oid, partition_key) +) PARTITION BY LIST (partition_key); +CREATE TABLE af_collab_document PARTITION OF af_collab FOR +VALUES IN (0); +CREATE TABLE af_collab_database PARTITION OF af_collab FOR +VALUES IN (1); +CREATE TABLE af_collab_w_database PARTITION OF af_collab FOR +VALUES IN (2); +CREATE TABLE af_collab_folder PARTITION OF af_collab FOR +VALUES IN (3); +CREATE TABLE af_collab_database_row PARTITION OF af_collab FOR +VALUES IN (4); +CREATE TABLE af_collab_user_awareness PARTITION OF af_collab FOR +VALUES IN (5); + +CREATE TABLE af_collab_member ( + uid BIGINT REFERENCES af_user(uid) ON DELETE CASCADE, + oid TEXT NOT NULL, + permission_id INTEGER REFERENCES af_permissions(id) NOT NULL, + PRIMARY KEY(uid, oid) +); + +-- Listener +DROP TRIGGER IF EXISTS af_collab_member_change_trigger ON af_collab_member; + +CREATE OR REPLACE FUNCTION notify_af_collab_member_change() RETURNS trigger AS $$ +DECLARE +payload TEXT; +BEGIN + payload := json_build_object( + 'old', row_to_json(OLD), + 'new', row_to_json(NEW), + 'action_type', TG_OP + )::text; + + PERFORM pg_notify('af_collab_member_channel', payload); + -- Return the new row state for INSERT/UPDATE, and the old state for DELETE. + IF TG_OP = 'DELETE' THEN + RETURN OLD; +ELSE + RETURN NEW; +END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER af_collab_member_change_trigger + AFTER INSERT OR UPDATE OR DELETE ON af_collab_member + FOR EACH ROW EXECUTE FUNCTION notify_af_collab_member_change(); + +-- collab snapshot. It will be used to store the snapshots of the collab. +CREATE TABLE IF NOT EXISTS af_collab_snapshot ( + sid BIGSERIAL PRIMARY KEY,-- snapshot id + oid TEXT NOT NULL, + blob BYTEA NOT NULL, + len INTEGER NOT NULL, + encrypt INTEGER DEFAULT 0, + deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); +CREATE INDEX idx_af_collab_snapshot_oid ON af_collab_snapshot(oid); +-- Enable RLS on the af_collab table +ALTER TABLE af_collab ENABLE ROW LEVEL SECURITY; +CREATE POLICY af_collab_policy ON af_collab FOR ALL TO anon, + authenticated USING (true); +ALTER TABLE af_collab FORCE ROW LEVEL SECURITY; diff --git a/configs/migrations/20230926145155_blob_storage.sql b/configs/migrations/20230926145155_blob_storage.sql new file mode 100644 index 0000000..0f5a9ff --- /dev/null +++ b/configs/migrations/20230926145155_blob_storage.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS af_blob_metadata ( + workspace_id UUID REFERENCES af_workspace(workspace_id) ON DELETE CASCADE NOT NULL, + file_id VARCHAR NOT NULL, + file_type VARCHAR NOT NULL, + file_size BIGINT NOT NULL, + modified_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + UNIQUE (workspace_id, file_id) +); diff --git a/configs/migrations/20231113074418_user_change.sql b/configs/migrations/20231113074418_user_change.sql new file mode 100644 index 0000000..4ebb8ec --- /dev/null +++ b/configs/migrations/20231113074418_user_change.sql @@ -0,0 +1,25 @@ +-- Add migration script here +-- Drop the existing trigger if it exists +DROP TRIGGER IF EXISTS af_user_change_trigger ON af_user; + +-- Create or replace the function +CREATE OR REPLACE FUNCTION notify_af_user_change() RETURNS TRIGGER AS $$ +DECLARE + payload TEXT; +BEGIN + payload := json_build_object( + 'payload', row_to_json(NEW), + 'action_type', TG_OP + )::text; + + PERFORM pg_notify('af_user_channel', payload); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create the trigger +CREATE TRIGGER af_user_change_trigger + AFTER UPDATE ON af_user + FOR EACH ROW +EXECUTE FUNCTION notify_af_user_change(); + diff --git a/configs/migrations/20231130140000_user_delete_trigger.sql b/configs/migrations/20231130140000_user_delete_trigger.sql new file mode 100644 index 0000000..c89c669 --- /dev/null +++ b/configs/migrations/20231130140000_user_delete_trigger.sql @@ -0,0 +1,35 @@ +GRANT SELECT, INSERT, UPDATE, DELETE ON public.af_user TO supabase_auth_admin; + +-- Trigger Function to delete a user from the pulic.af_user table +-- when a user is deleted from auth.users table (with matching uuid) field +CREATE OR REPLACE FUNCTION public.delete_user() +RETURNS TRIGGER AS $$ +BEGIN + DELETE FROM public.af_user WHERE uuid = OLD.id; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER delete_user_trigger +AFTER DELETE ON auth.users +FOR EACH ROW EXECUTE FUNCTION public.delete_user(); + +-- Trigger Function to update the 'deleted_at' field in the pulic.af_user table +-- (Soft Delete) +CREATE OR REPLACE FUNCTION public.update_af_user_deleted_at() +RETURNS TRIGGER AS $$ +BEGIN + -- Check if 'deleted_at' field is modified + IF OLD.deleted_at IS DISTINCT FROM NEW.deleted_at THEN + -- Update 'deleted_at' in public.af_user + UPDATE public.af_user + SET deleted_at = NEW.deleted_at + WHERE uuid = NEW.id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_af_user_deleted_at_trigger +AFTER UPDATE OF deleted_at ON auth.users +FOR EACH ROW EXECUTE FUNCTION public.update_af_user_deleted_at(); diff --git a/configs/migrations/20231130150000_user_id_foreign_key.sql b/configs/migrations/20231130150000_user_id_foreign_key.sql new file mode 100644 index 0000000..12be9e8 --- /dev/null +++ b/configs/migrations/20231130150000_user_id_foreign_key.sql @@ -0,0 +1,21 @@ +-- Revert the last migration +REVOKE SELECT, INSERT, UPDATE, DELETE ON public.af_user FROM supabase_auth_admin; +DROP TRIGGER delete_user_trigger ON auth.users; +DROP TRIGGER update_af_user_deleted_at_trigger ON auth.users; +DROP FUNCTION public.delete_user(); +DROP FUNCTION public.update_af_user_deleted_at(); + +-- Delete all users from public.af_user table that are not in auth.users table +DELETE FROM public.af_user +WHERE NOT EXISTS ( + SELECT 1 + FROM auth.users + WHERE af_user.uuid = users.id +); + +-- Add foreign key constraint to public.af_user table +ALTER TABLE public.af_user +ADD CONSTRAINT af_user_email_foreign_key +FOREIGN KEY (uuid) +REFERENCES auth.users(id) +ON DELETE CASCADE; diff --git a/configs/migrations/20240123140707_workspace_owner_trigger.sql b/configs/migrations/20240123140707_workspace_owner_trigger.sql new file mode 100644 index 0000000..3ae74b5 --- /dev/null +++ b/configs/migrations/20240123140707_workspace_owner_trigger.sql @@ -0,0 +1,20 @@ +CREATE OR REPLACE FUNCTION af_workspace_insert_trigger() +RETURNS TRIGGER AS $$ +BEGIN + -- Insert a record into af_workspace_member + INSERT INTO public.af_workspace_member ( + uid, role_id, + workspace_id, created_at, updated_at) + VALUES ( + NEW.owner_uid, (SELECT id FROM public.af_roles WHERE name = 'Owner'), + NEW.workspace_id, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); + + -- Return the new record to complete the insert operation + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER af_workspace_after_insert +AFTER INSERT ON public.af_workspace +FOR EACH ROW +EXECUTE FUNCTION af_workspace_insert_trigger(); diff --git a/configs/migrations/20240227000000_workspace_icon.sql b/configs/migrations/20240227000000_workspace_icon.sql new file mode 100644 index 0000000..067d569 --- /dev/null +++ b/configs/migrations/20240227000000_workspace_icon.sql @@ -0,0 +1 @@ +ALTER TABLE af_workspace ADD COLUMN icon TEXT NOT NULL DEFAULT ''; diff --git a/configs/migrations/20240303003711_collab_member_timestamp.sql b/configs/migrations/20240303003711_collab_member_timestamp.sql new file mode 100644 index 0000000..2767efe --- /dev/null +++ b/configs/migrations/20240303003711_collab_member_timestamp.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE af_collab_member +ADD COLUMN created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (NOW()); \ No newline at end of file diff --git a/configs/migrations/20240304173938_workspace_invitation.sql b/configs/migrations/20240304173938_workspace_invitation.sql new file mode 100644 index 0000000..998cee1 --- /dev/null +++ b/configs/migrations/20240304173938_workspace_invitation.sql @@ -0,0 +1,63 @@ +CREATE TABLE IF NOT EXISTS af_workspace_invitation ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + workspace_id UUID NOT NULL, + inviter BIGINT NOT NULL, + invitee BIGINT NOT NULL, + role_id INT NOT NULL, + status SMALLINT NOT NULL DEFAULT 0, -- 0: pending, 1: accepted, 2: rejected + + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); +CREATE INDEX idx_af_workspace_invitation_inviter ON af_workspace_invitation (inviter); +CREATE INDEX idx_af_workspace_invitation_invitee ON af_workspace_invitation (invitee); + +-- Auto update updated_at column upon status change +CREATE OR REPLACE FUNCTION update_af_workspace_invitation_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + IF OLD.status IS DISTINCT FROM NEW.status THEN + NEW.updated_at = CURRENT_TIMESTAMP; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER af_workspace_invitation_status_update +BEFORE UPDATE ON af_workspace_invitation +FOR EACH ROW +WHEN (OLD.status IS DISTINCT FROM NEW.status) +EXECUTE FUNCTION update_af_workspace_invitation_updated_at_column(); + +-- Auto add to af_workspace_member upon invitation accepted +CREATE OR REPLACE FUNCTION add_to_af_workspace_member() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.status = 1 THEN + -- workspace permission + INSERT INTO af_workspace_member (workspace_id, uid, role_id) + VALUES (NEW.workspace_id, NEW.invitee, NEW.role_id) + ON CONFLICT (workspace_id, uid) DO NOTHING; + + -- collab permission + INSERT INTO af_collab_member (uid, oid, permission_id) + VALUES ( + NEW.invitee, + NEW.workspace_id, + (SELECT permission_id + FROM public.af_role_permissions + WHERE public.af_role_permissions.role_id = NEW.role_id) + ) + ON CONFLICT (uid, oid) + DO UPDATE + SET permission_id = excluded.permission_id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER af_workspace_invitation_accepted +BEFORE UPDATE ON af_workspace_invitation +FOR EACH ROW +WHEN (OLD.status IS DISTINCT FROM NEW.status AND NEW.status = 1) +EXECUTE FUNCTION add_to_af_workspace_member(); diff --git a/configs/migrations/20240306110000_workspace_invitation_2.sql b/configs/migrations/20240306110000_workspace_invitation_2.sql new file mode 100644 index 0000000..8c301d3 --- /dev/null +++ b/configs/migrations/20240306110000_workspace_invitation_2.sql @@ -0,0 +1,34 @@ +ALTER TABLE af_workspace_invitation ADD COLUMN invitee_email TEXT NOT NULL; + +-- Auto add to af_workspace_member upon invitation accepted +CREATE OR REPLACE FUNCTION add_to_af_workspace_member() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.status = 1 THEN + -- workspace permission + INSERT INTO af_workspace_member (workspace_id, uid, role_id) + VALUES ( + NEW.workspace_id, + (SELECT uid FROM af_user WHERE email = NEW.invitee_email), + NEW.role_id + ) + ON CONFLICT (workspace_id, uid) DO NOTHING; + + -- collab permission + INSERT INTO af_collab_member (uid, oid, permission_id) + VALUES ( + (SELECT uid FROM af_user WHERE email = NEW.invitee_email), + NEW.workspace_id, + (SELECT permission_id + FROM public.af_role_permissions + WHERE public.af_role_permissions.role_id = NEW.role_id) + ) + ON CONFLICT (uid, oid) + DO UPDATE + SET permission_id = excluded.permission_id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +ALTER TABLE af_workspace_invitation DROP COLUMN invitee; diff --git a/configs/migrations/20240412083446_history_init.sql b/configs/migrations/20240412083446_history_init.sql new file mode 100644 index 0000000..9ee102b --- /dev/null +++ b/configs/migrations/20240412083446_history_init.sql @@ -0,0 +1,53 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- create af_snapshot_meta table +CREATE TABLE IF NOT EXISTS af_snapshot_meta( + oid TEXT NOT NULL, + workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE, + snapshot BYTEA NOT NULL, + snapshot_version INTEGER NOT NULL, + partition_key INTEGER NOT NULL, + created_at BIGINT NOT NULL, + metadata JSONB, + PRIMARY KEY (oid, created_at, partition_key) +) PARTITION BY LIST (partition_key); +CREATE TABLE af_snapshot_meta_document PARTITION OF af_snapshot_meta FOR +VALUES IN (0); +CREATE TABLE af_snapshot_meta_database PARTITION OF af_snapshot_meta FOR +VALUES IN (1); +CREATE TABLE af_snapshot_meta_workspace_database PARTITION OF af_snapshot_meta FOR +VALUES IN (2); +CREATE TABLE af_snapshot_meta_folder PARTITION OF af_snapshot_meta FOR +VALUES IN (3); +CREATE TABLE af_snapshot_meta_database_row PARTITION OF af_snapshot_meta FOR +VALUES IN (4); +CREATE TABLE af_snapshot_meta_user_awareness PARTITION OF af_snapshot_meta FOR +VALUES IN (5); + +-- create af_snapshot_state table +CREATE TABLE IF NOT EXISTS af_snapshot_state( + snapshot_id UUID NOT NULL DEFAULT uuid_generate_v4(), + workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE, + oid TEXT NOT NULL, + doc_state BYTEA NOT NULL, + doc_state_version INTEGER NOT NULL, + deps_snapshot_id UUID, + partition_key INTEGER NOT NULL, + created_at BIGINT NOT NULL, + PRIMARY KEY (snapshot_id, partition_key) +) PARTITION BY LIST (partition_key); +CREATE TABLE af_snapshot_state_document PARTITION OF af_snapshot_state FOR +VALUES IN (0); +CREATE TABLE af_snapshot_state_database PARTITION OF af_snapshot_state FOR +VALUES IN (1); +CREATE TABLE af_snapshot_state_workspace_database PARTITION OF af_snapshot_state FOR +VALUES IN (2); +CREATE TABLE af_snapshot_state_folder PARTITION OF af_snapshot_state FOR +VALUES IN (3); +CREATE TABLE af_snapshot_state_database_row PARTITION OF af_snapshot_state FOR +VALUES IN (4); +CREATE TABLE af_snapshot_state_user_awareness PARTITION OF af_snapshot_state FOR +VALUES IN (5); + +-- Index for af_snapshot_state table +CREATE INDEX IF NOT EXISTS idx_snapshot_state_oid_created ON af_snapshot_state (oid, created_at DESC); diff --git a/configs/migrations/20240510024506_chat_message.sql b/configs/migrations/20240510024506_chat_message.sql new file mode 100644 index 0000000..a0d0f26 --- /dev/null +++ b/configs/migrations/20240510024506_chat_message.sql @@ -0,0 +1,27 @@ +-- Add migration script here +-- Create table for chat documents +CREATE TABLE af_chat +( + chat_id UUID PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + name TEXT NOT NULL DEFAULT '', + rag_ids JSONB NOT NULL DEFAULT '[]', + workspace_id UUID NOT NULL, + FOREIGN KEY (workspace_id) REFERENCES af_workspace (workspace_id) ON DELETE CASCADE +); + +-- Create table for chat messages +CREATE TABLE af_chat_messages +( + message_id BIGSERIAL PRIMARY KEY, + author JSONB NOT NULL, + chat_id UUID NOT NULL, + content TEXT NOT NULL, + deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + edited_at TIMESTAMP DEFAULT NULL, + FOREIGN KEY (chat_id) REFERENCES af_chat (chat_id) ON DELETE CASCADE +); + +CREATE INDEX idx_chat_messages_chat_id_created_at ON af_chat_messages (message_id ASC, created_at ASC); diff --git a/configs/migrations/20240529054858_workspace_add_token_usage.sql b/configs/migrations/20240529054858_workspace_add_token_usage.sql new file mode 100644 index 0000000..82fd5ce --- /dev/null +++ b/configs/migrations/20240529054858_workspace_add_token_usage.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE af_workspace ADD COLUMN search_token_usage BIGINT NOT NULL DEFAULT 0; +ALTER TABLE af_workspace ADD COLUMN index_token_usage BIGINT NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/configs/migrations/20240531031836_chat_message_meta.sql b/configs/migrations/20240531031836_chat_message_meta.sql new file mode 100644 index 0000000..a214588 --- /dev/null +++ b/configs/migrations/20240531031836_chat_message_meta.sql @@ -0,0 +1,7 @@ +-- Add migration script here +ALTER TABLE af_chat + ADD COLUMN meta_data JSONB DEFAULT '{}' NOT NULL; + +ALTER TABLE af_chat_messages + ADD COLUMN meta_data JSONB DEFAULT '{}' NOT NULL, + ADD COLUMN reply_message_id BIGINT; diff --git a/configs/migrations/20240604090043_add_workspace_settings.sql b/configs/migrations/20240604090043_add_workspace_settings.sql new file mode 100644 index 0000000..dadd1c6 --- /dev/null +++ b/configs/migrations/20240604090043_add_workspace_settings.sql @@ -0,0 +1,2 @@ +-- Add migration script here +ALTER TABLE af_workspace ADD COLUMN settings JSONB; \ No newline at end of file diff --git a/configs/migrations/20240613112820_publish_collab.sql b/configs/migrations/20240613112820_publish_collab.sql new file mode 100644 index 0000000..3f6dd26 --- /dev/null +++ b/configs/migrations/20240613112820_publish_collab.sql @@ -0,0 +1,30 @@ +-- stores the published view of a workspace by a user of workspace +CREATE TABLE IF NOT EXISTS af_published_collab ( + doc_name TEXT NOT NULL, + published_by BIGINT NOT NULL REFERENCES af_user(uid) ON DELETE CASCADE, + workspace_id UUID NOT NULL REFERENCES af_workspace(workspace_id) ON DELETE CASCADE, + metadata JSONB NOT NULL, + blob BYTEA NOT NULL DEFAULT '', + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + + PRIMARY KEY (workspace_id, doc_name) +); + +-- trigger to update updated_at column +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER af_published_collab_update_updated_at +BEFORE UPDATE ON af_published_collab +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); + +-- every workspace have a prefix for published view +ALTER TABLE af_workspace ADD COLUMN publish_namespace TEXT UNIQUE; +CREATE INDEX IF NOT EXISTS publish_namespace_idx ON af_workspace(publish_namespace); diff --git a/configs/migrations/20240614171931_collab_embeddings.sql b/configs/migrations/20240614171931_collab_embeddings.sql new file mode 100644 index 0000000..7377990 --- /dev/null +++ b/configs/migrations/20240614171931_collab_embeddings.sql @@ -0,0 +1,24 @@ +DO $$ +BEGIN + -- Add migration script here + CREATE EXTENSION IF NOT EXISTS vector; + + -- create table to store collab embeddings + CREATE TABLE IF NOT EXISTS af_collab_embeddings + ( + fragment_id TEXT NOT NULL PRIMARY KEY, + oid TEXT NOT NULL, + partition_key INTEGER NOT NULL, + content_type INTEGER NOT NULL, + indexed_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (NOW()), + content TEXT, + embedding VECTOR(1536), + FOREIGN KEY (oid, partition_key) REFERENCES af_collab (oid, partition_key) ON DELETE CASCADE + ); + + CREATE INDEX IF NOT EXISTS af_collab_embeddings_similarity_idx ON af_collab_embeddings USING hnsw (embedding vector_cosine_ops); + +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'could not create "vector" extension, ignoring this migration'; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/configs/migrations/20240617135926_af_workspace_foreign_key_indices.sql b/configs/migrations/20240617135926_af_workspace_foreign_key_indices.sql new file mode 100644 index 0000000..0ef41f4 --- /dev/null +++ b/configs/migrations/20240617135926_af_workspace_foreign_key_indices.sql @@ -0,0 +1,7 @@ +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_blob_metadata ON af_blob_metadata (workspace_id); +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_chat ON af_chat (workspace_id); +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_collab_snapshot ON af_collab_snapshot (workspace_id); +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_collab ON af_collab (workspace_id); +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_snapshot_meta ON af_snapshot_meta (workspace_id); +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_snapshot_state ON af_snapshot_state (workspace_id); +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_workspace_member ON af_workspace_member (workspace_id); diff --git a/configs/migrations/20240618035048_af_workspace_ai_usage.sql b/configs/migrations/20240618035048_af_workspace_ai_usage.sql new file mode 100644 index 0000000..9738a1d --- /dev/null +++ b/configs/migrations/20240618035048_af_workspace_ai_usage.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS af_workspace_ai_usage ( + created_at DATE NOT NULL, -- day level of granularity + workspace_id UUID NOT NULL, -- workspace id for which the usage is being recorded + search_requests INT, -- number of search requests made + search_tokens_consumed BIGINT, -- number of tokens consumed for search requests + index_tokens_consumed BIGINT, -- number of tokens consumed for indexing documents + PRIMARY KEY (created_at, workspace_id) +); + +-- migrate token usage data from af_workspace to af_workspace_ai_usage +INSERT INTO af_workspace_ai_usage (created_at, workspace_id, search_tokens_consumed, index_tokens_consumed) +SELECT + now()::date as created_at, + workspace_id, + search_token_usage as search_tokens_consumed, + index_token_usage as index_tokens_consumed +FROM af_workspace +WHERE search_token_usage IS NOT NULL + OR index_token_usage IS NOT NULL; + +-- drop the redundant columns from af_workspace +ALTER TABLE af_workspace DROP COLUMN IF EXISTS search_token_usage; +ALTER TABLE af_workspace DROP COLUMN IF EXISTS index_token_usage; \ No newline at end of file diff --git a/configs/migrations/20240618173348_publish_collab_2.sql b/configs/migrations/20240618173348_publish_collab_2.sql new file mode 100644 index 0000000..ad1564f --- /dev/null +++ b/configs/migrations/20240618173348_publish_collab_2.sql @@ -0,0 +1,6 @@ +ALTER TABLE af_published_collab ADD COLUMN view_id UUID NOT NULL DEFAULT gen_random_uuid(); +ALTER TABLE af_published_collab DROP CONSTRAINT af_published_collab_pkey; +ALTER TABLE af_published_collab ADD PRIMARY KEY (workspace_id, view_id); + +CREATE INDEX IF NOT EXISTS idx_workspace_id_on_af_published_collab ON af_published_collab (workspace_id); +CREATE INDEX IF NOT EXISTS idx_published_by_on_af_published_collab ON af_published_collab (published_by); diff --git a/configs/migrations/20240621105148_publish_collab_3.sql b/configs/migrations/20240621105148_publish_collab_3.sql new file mode 100644 index 0000000..68fe2d8 --- /dev/null +++ b/configs/migrations/20240621105148_publish_collab_3.sql @@ -0,0 +1 @@ +ALTER TABLE af_published_collab RENAME COLUMN doc_name TO publish_name; diff --git a/configs/migrations/20240626184736_publish_collab_4.sql b/configs/migrations/20240626184736_publish_collab_4.sql new file mode 100644 index 0000000..f2a5b62 --- /dev/null +++ b/configs/migrations/20240626184736_publish_collab_4.sql @@ -0,0 +1,6 @@ +-- Add a unique constraint on publish_name +ALTER TABLE public.af_published_collab +ADD CONSTRAINT unique_publish_name UNIQUE (publish_name); + +-- Add an index on publish_name +CREATE INDEX idx_publish_name ON public.af_published_collab (publish_name); diff --git a/configs/migrations/20240627525836_publish_collab_5.sql b/configs/migrations/20240627525836_publish_collab_5.sql new file mode 100644 index 0000000..605ab2f --- /dev/null +++ b/configs/migrations/20240627525836_publish_collab_5.sql @@ -0,0 +1,7 @@ +-- Drop the existing unique constraint on publish_name +ALTER TABLE public.af_published_collab +DROP CONSTRAINT unique_publish_name; + +-- Add a new unique constraint for the combination of publish_name and workspace_id +ALTER TABLE public.af_published_collab +ADD CONSTRAINT unique_workspace_id_publish_name UNIQUE (workspace_id, publish_name); diff --git a/configs/migrations/20240629035230_publish_collab_6.sql b/configs/migrations/20240629035230_publish_collab_6.sql new file mode 100644 index 0000000..7a6ae45 --- /dev/null +++ b/configs/migrations/20240629035230_publish_collab_6.sql @@ -0,0 +1,9 @@ +-- Update existing null values to ensure no nulls are present before adding NOT NULL constraint +UPDATE public.af_workspace +SET publish_namespace = uuid_generate_v4()::text +WHERE publish_namespace IS NULL; + +-- Alter the column to set NOT NULL constraint and a default value +ALTER TABLE public.af_workspace +ALTER COLUMN publish_namespace SET NOT NULL, +ALTER COLUMN publish_namespace SET DEFAULT uuid_generate_v4()::text; diff --git a/configs/migrations/20240630010030_workspace_member_foreign_key.sql b/configs/migrations/20240630010030_workspace_member_foreign_key.sql new file mode 100644 index 0000000..9ff4892 --- /dev/null +++ b/configs/migrations/20240630010030_workspace_member_foreign_key.sql @@ -0,0 +1,3 @@ +ALTER TABLE public.af_workspace_member +ADD CONSTRAINT af_workspace_member_uid_fkey +FOREIGN KEY (uid) REFERENCES af_user(uid) ON DELETE CASCADE; diff --git a/configs/migrations/before/20230312043000_supabase_auth.sql b/configs/migrations/before/20230312043000_supabase_auth.sql new file mode 100644 index 0000000..70a44b0 --- /dev/null +++ b/configs/migrations/before/20230312043000_supabase_auth.sql @@ -0,0 +1,37 @@ +-- Add migration script here +-- Create the anon and authenticated roles if they don't exist +CREATE OR REPLACE FUNCTION create_roles(roles text []) RETURNS void LANGUAGE plpgsql AS $$ +DECLARE role_name text; +BEGIN FOREACH role_name IN ARRAY roles LOOP IF NOT EXISTS ( + SELECT 1 + FROM pg_roles + WHERE rolname = role_name +) THEN EXECUTE 'CREATE ROLE ' || role_name; +END IF; +END LOOP; +END; +$$; +SELECT create_roles(ARRAY ['anon', 'authenticated']); + +-- Create supabase_admin user if it does not exist +DO $$ BEGIN IF NOT EXISTS ( + SELECT + FROM pg_catalog.pg_roles + WHERE rolname = 'supabase_admin' +) THEN CREATE USER supabase_admin LOGIN CREATEROLE CREATEDB REPLICATION BYPASSRLS; +END IF; +END $$; +-- Create supabase_auth_admin user if it does not exist +DO $$ BEGIN IF NOT EXISTS ( + SELECT + FROM pg_catalog.pg_roles + WHERE rolname = 'supabase_auth_admin' +) THEN CREATE USER supabase_auth_admin BYPASSRLS NOINHERIT CREATEROLE LOGIN NOREPLICATION PASSWORD 'root'; +END IF; +END $$; +-- Create auth schema if it does not exist +CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_auth_admin; +-- Grant permissions +GRANT CREATE ON DATABASE postgres TO supabase_auth_admin; +-- Set search_path for supabase_auth_admin +ALTER USER supabase_auth_admin SET search_path = 'auth'; diff --git a/configs/nginx/nginx.conf b/configs/nginx/nginx.conf new file mode 100644 index 0000000..20f82f0 --- /dev/null +++ b/configs/nginx/nginx.conf @@ -0,0 +1,187 @@ +# Minimal nginx configuration for AppFlowy-Cloud +# Self Hosted AppFlowy Cloud user should alter this file to suit their needs + +events { + worker_connections 1024; +} + +http { + # docker dns resolver + resolver 127.0.0.11 valid=10s; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 8080; + + # https://github.com/nginxinc/nginx-prometheus-exporter + location = /stub_status { + stub_status; + } + } + + + server { + ssl_certificate /etc/nginx/ssl/certificate.crt; + ssl_certificate_key /etc/nginx/ssl/private_key.key; + + listen 80; + listen 443 ssl; + client_max_body_size 10M; + + underscores_in_headers on; + + # GoTrue + location /gotrue/ { + set $gotrue gotrue; + proxy_pass http://$gotrue:9999; + + rewrite ^/gotrue(/.*)$ $1 break; + + # Allow headers like redirect_to to be handed over to the gotrue + # for correct redirecting + proxy_set_header Host $http_host; + proxy_pass_request_headers on; + } + + # WebSocket + location /ws { + set $appflowy_cloud appflowy_cloud; + proxy_pass http://$appflowy_cloud:8000; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 86400; + } + + # AppFlowy-Cloud + # created a separate location block for handling CORS preflight (OPTIONS) requests specifically for the /api endpoint. + location = /api/options { + if ($http_origin ~* (http://127.0.0.1:8000)) { + add_header 'Access-Control-Allow-Origin' $http_origin; + } + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; + add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Accept, Client-Version'; + add_header 'Access-Control-Max-Age' 3600; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + + location /api/chat { + set $appflowy_cloud appflowy_cloud; + proxy_pass http://$appflowy_cloud:8000; + + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding on; + proxy_buffering off; + proxy_cache off; + + proxy_read_timeout 600s; + proxy_connect_timeout 600s; + proxy_send_timeout 600s; + } + + location /api { + set $appflowy_cloud appflowy_cloud; + proxy_pass http://$appflowy_cloud:8000; + + proxy_set_header X-Request-Id $request_id; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Set CORS headers for other requests + if ($http_origin ~* (http://127.0.0.1:8000)) { + add_header 'Access-Control-Allow-Origin' $http_origin always; + } + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Accept, Client-Version' always; + add_header 'Access-Control-Max-Age' 3600 always; + + location ~* ^/api/workspace/([a-zA-Z0-9_-]+)/publish$ { + set $appflowy_cloud appflowy_cloud; + proxy_pass http://$appflowy_cloud:8000; + proxy_request_buffering off; + client_max_body_size 256M; + } + } + + # AppFlowy AI + location /ai { + set $ai ai; + proxy_pass http://$ai:5001; + proxy_set_header Host $host; + proxy_pass_request_headers on; + } + + # Minio Web UI + # Derive from: https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html + # Optional Module, comment this section if you are did not deploy minio in docker-compose.yml + location /minio/ { + set $minio minio; + proxy_pass http://$minio:9001; + + rewrite ^/minio/(.*) /$1 break; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-NginX-Proxy true; + + ## This is necessary to pass the correct IP to be hashed + real_ip_header X-Real-IP; + + proxy_connect_timeout 300; + + ## To support websockets in MinIO versions released after January 2023 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + # Some environments may encounter CORS errors (Kubernetes + Nginx Ingress) + # Uncomment the following line to set the Origin request to an empty string + # proxy_set_header Origin ''; + + chunked_transfer_encoding off; + } + + # PgAdmin + # Optional Module, comment this section if you are did not deploy pgadmin in docker-compose.yml + location /pgadmin/ { + set $pgadmin pgadmin; + proxy_pass http://$pgadmin:80; + + proxy_set_header X-Script-Name /pgadmin; + proxy_set_header X-Scheme $scheme; + proxy_set_header Host $host; + proxy_redirect off; + } + + # Portainer + # Optional Module, comment this section if you are did not deploy portainer in docker-compose.yml + location /portainer/ { + set $portainer portainer; + proxy_pass http://$portainer:9000; + + rewrite ^/portainer/(.*) /$1 break; + } + + # Admin Frontend + # Optional Module, comment this section if you are did not deploy admin_frontend in docker-compose.yml + location / { + set $admin_frontend admin_frontend; + proxy_pass http://$admin_frontend:3000; + + proxy_set_header X-Scheme $scheme; + proxy_set_header Host $host; + } + } + +} diff --git a/configs/nginx/ssl/certificate.crt b/configs/nginx/ssl/certificate.crt new file mode 100755 index 0000000..10514d9 --- /dev/null +++ b/configs/nginx/ssl/certificate.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFRDCCAywCCQDXwkFioxoJ2TANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJU3Vubnl2YWxlMRYwFAYD +VQQKDA1BcHBGbG93eSxJbmMuMRQwEgYDVQQDDAthcHBmbG93eS5pbzAeFw0yMzAz +MTUwMDUxNDVaFw0yNDAzMTQwMDUxNDVaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApDYWxpZm9ybmlhMRIwEAYDVQQHDAlTdW5ueXZhbGUxFjAUBgNVBAoMDUFwcEZs +b3d5LEluYy4xFDASBgNVBAMMC2FwcGZsb3d5LmlvMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA09v/ouq4r7+oLOWqVscYpW5QRLR5O6OYZprocIARAtWA +qBkywhPku/SZq27dtPD7Pi3soSPkMhYDFALai4idgELCFxxkTuHWNm3J+Y8PcMq2 +RX325/pQVpOMTkChqaUzh93ynYqv89x3lT9z4saknBde/WO2yOJ6sfED9w+ezYgm +34LV5Z0cofQTDEiTX58KV3MmG5hRMdBwCaDg1jUb3jKr5lBrF4+EHbAN5PWH282V +JdyOTvZp/CF5TcnAMONkYENjURpnSXJes34ufYHkmr0eDa+2pfc3TI5wlB3tPQyN +p+B5TsDCDofv0Zme5Ur42TWcwsG0WRvtDw2KZ98wBtGaIv8UEQjXipNQVzeCv5Yv +zeykjdDhOJ/OZFzsm2vtl55t52M43xYMo3QPmjHMiVYz9KVvPqrVo+O5PI7B7uwY +JxWQIJosUa6AeKfkCAQd8mlNqYylqV8Utqs8b4zee8Vf7hzaCYNKxyDvqsd3yDwE +umh5zVuxJitLchDFT4mv1v3yLHocusV2lwjfEk66R/o5BBDdviycxeAM5Q1lyi7M +RywHAt/eVQNaBq0HSa2vbIm5yTZNQYwuhnNgv035hf8vpu+tFOGepTqgy/CrYpPm +VnsClJVrmxd4LfUZ0aZODiKCVx6psfeBvTXu1r7/SjsmbvYE4ubfM66optyFc3cC +AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAfhSUTP91rBP+8zvsoxomKdeClVFURczr +HO/VuXVmBoKLASFqFcT84usRhb5T6XB7o2GcCYSo0VFJw99UM6nLsZ7c0MKaAjT6 +/9VeyLtDfhCDkffGGxpeYhme+0PY8TXIU5aO0ZhQwzXUOiC7t3Ac3AzHTPGrGqOK +aAGhMo7V2bQXcNR6NFhsUlJOtVE59MxL1K5Ug1oMn/H+NUF6/st+KzohruQJSSWG +GcbqVor0zZbFJSvRJ5P3ngw2cg2SIj9w6RwUWMp+a5kOx10fOYrQEHGyTHFlVDy0 +yOCU4eVO3EVTm7Se1XVwmG3kNKQaLFJf1voMuYD2sFbZ0nhGJDSZOcGUrchUXPQB +C9MwU52OeNm5VwE/41wLoFvOkJ/I/Ak7vccl1YJXpefa6qjNOFm5X0jA7D4egDfC +IVs5m30qa6Birx0xS6RUuuvxLJyNzgLSzsC1eFwjR2uwIUrGpYo3YI4+bMxp2Wnk +6qtm5G8D1giWg6z0RLw+GSj4QfcJEBP+zyiH/MdB5te9kXVYLKyS+DGgTTatLi1l +MBBK5b7dvfwo08J/sksK+mPHHBsV9TPAkqMp9vuZw25pRAEnvjEOUPfi3X9EZDw7 +A3LElN9KDks3IioioAOm0vPvHrlfziSljt5IMkuZLT3lUe6B3cs69caLO0S/ZDTz +yqGwLIqHYos= +-----END CERTIFICATE----- diff --git a/configs/nginx/ssl/private_key.key b/configs/nginx/ssl/private_key.key new file mode 100755 index 0000000..d403fc7 --- /dev/null +++ b/configs/nginx/ssl/private_key.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +Oi+igzFlcelWDZ/eQ8CpDkSpAqtwwx97 +-----END PRIVATE KEY----- diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..234187f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,171 @@ +# Essential services for AppFlowy Cloud + +version: '3' +services: + nginx: + container_name: appflowy_nginx #ADDED + restart: on-failure + image: nginx:1.24.0 #CHANGED + ports: + - 80:80 # Disable this if you are using TLS + - 443:443 + volumes: + - ./configs/nginx/nginx.conf:/etc/nginx/nginx.conf #CHANGED + - ./configs/nginx/ssl/certificate.crt:/etc/nginx/ssl/certificate.crt #CHANGED + - ./configs/nginx/ssl/private_key.key:/etc/nginx/ssl/private_key.key #CHANGED + + # You do not need this if you have configured to use your own s3 file storage + minio: + container_name: appflowy_minio #ADDED + restart: on-failure + image: minio/minio:RELEASE.2024-07-16T23-46-41Z #CHANGED not necessary + environment: + - MINIO_BROWSER_REDIRECT_URL=http://localhost/minio + - MINIO_ROOT_USER=${APPFLOWY_S3_ACCESS_KEY:-minioadmin} + - MINIO_ROOT_PASSWORD=${APPFLOWY_S3_SECRET_KEY:-minioadmin} + command: server /data --console-address ":9001" + volumes: + - ./data/minio_data:/data #CHANGED + + postgres: + container_name: appflowy_postgres #ADDED + restart: on-failure + image: pgvector/pgvector:0.7.2-pg16 #ADDED +# build: +# context: ./postgres +# dockerfile: postgres.Dockerfile + environment: + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_DB=${POSTGRES_DB:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} + - POSTGRES_HOST=${POSTGRES_HOST:-postgres} + volumes: + - ./configs/migrations/before:/docker-entrypoint-initdb.d #CHANGED + - ./data/postgres_data:/var/lib/postgresql/data #CHANGED + + redis: + container_name: appflowy_redis #ADDED + restart: on-failure + image: redis:7.2.5 #CHANGED + + gotrue: + container_name: appflowy_gotrue #ADDED + restart: on-failure +# build: +# context: . +# dockerfile: docker/gotrue.Dockerfile + image: appflowyinc/gotrue:0.5.32 #CHANGED + environment: + # There are a lot of options to configure GoTrue. You can reference the example config: + # https://github.com/supabase/gotrue/blob/master/example.env + - GOTRUE_SITE_URL=appflowy-flutter:// # redirected to AppFlowy application + - URI_ALLOW_LIST=* # adjust restrict if necessary + - GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret + - GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP} + - GOTRUE_DB_DRIVER=postgres + - API_EXTERNAL_URL=${API_EXTERNAL_URL} + - DATABASE_URL=${GOTRUE_DATABASE_URL} + - PORT=9999 + - GOTRUE_SMTP_HOST=${GOTRUE_SMTP_HOST} # e.g. smtp.gmail.com + - GOTRUE_SMTP_PORT=${GOTRUE_SMTP_PORT} # e.g. 465 + - GOTRUE_SMTP_USER=${GOTRUE_SMTP_USER} # email sender, e.g. noreply@appflowy.io + - GOTRUE_SMTP_PASS=${GOTRUE_SMTP_PASS} # email password + - GOTRUE_MAILER_URLPATHS_CONFIRMATION=/gotrue/verify + - GOTRUE_MAILER_URLPATHS_INVITE=/gotrue/verify + - GOTRUE_MAILER_URLPATHS_RECOVERY=/gotrue/verify + - GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=/gotrue/verify + - GOTRUE_SMTP_ADMIN_EMAIL=${GOTRUE_SMTP_ADMIN_EMAIL} # email with admin privileges e.g. internal@appflowy.io + - GOTRUE_SMTP_MAX_FREQUENCY=${GOTRUE_SMTP_MAX_FREQUENCY:-1ns} # set to 1ns for running tests + - GOTRUE_RATE_LIMIT_EMAIL_SENT=${GOTRUE_RATE_LIMIT_EMAIL_SENT:-100} # number of email sendable per minute + - GOTRUE_MAILER_AUTOCONFIRM=${GOTRUE_MAILER_AUTOCONFIRM:-false} # change this to true to skip email confirmation + # Google OAuth config + - GOTRUE_EXTERNAL_GOOGLE_ENABLED=${GOTRUE_EXTERNAL_GOOGLE_ENABLED} + - GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=${GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID} + - GOTRUE_EXTERNAL_GOOGLE_SECRET=${GOTRUE_EXTERNAL_GOOGLE_SECRET} + - GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=${GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI} + # GITHUB OAuth config + - GOTRUE_EXTERNAL_GITHUB_ENABLED=${GOTRUE_EXTERNAL_GITHUB_ENABLED} + - GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=${GOTRUE_EXTERNAL_GITHUB_CLIENT_ID} + - GOTRUE_EXTERNAL_GITHUB_SECRET=${GOTRUE_EXTERNAL_GITHUB_SECRET} + - GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=${GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI} + # Discord OAuth config + - GOTRUE_EXTERNAL_DISCORD_ENABLED=${GOTRUE_EXTERNAL_DISCORD_ENABLED} + - GOTRUE_EXTERNAL_DISCORD_CLIENT_ID=${GOTRUE_EXTERNAL_DISCORD_CLIENT_ID} + - GOTRUE_EXTERNAL_DISCORD_SECRET=${GOTRUE_EXTERNAL_DISCORD_SECRET} + - GOTRUE_EXTERNAL_DISCORD_REDIRECT_URI=${GOTRUE_EXTERNAL_DISCORD_REDIRECT_URI} + + appflowy_cloud: + container_name: appflowy_cloud #ADDED + restart: on-failure + environment: + - RUST_LOG=${RUST_LOG:-info} + - APPFLOWY_ENVIRONMENT=production + - APPFLOWY_DATABASE_URL=${APPFLOWY_DATABASE_URL} + - APPFLOWY_REDIS_URI=redis://redis:6379 + - APPFLOWY_GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} + - APPFLOWY_GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP} + - APPFLOWY_GOTRUE_BASE_URL=${APPFLOWY_GOTRUE_BASE_URL} + - APPFLOWY_GOTRUE_EXT_URL=${API_EXTERNAL_URL} + - APPFLOWY_GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL} + - APPFLOWY_GOTRUE_ADMIN_PASSWORD=${GOTRUE_ADMIN_PASSWORD} + - APPFLOWY_S3_USE_MINIO=${APPFLOWY_S3_USE_MINIO} + - APPFLOWY_S3_MINIO_URL=${APPFLOWY_S3_MINIO_URL} + - APPFLOWY_S3_ACCESS_KEY=${APPFLOWY_S3_ACCESS_KEY} + - APPFLOWY_S3_SECRET_KEY=${APPFLOWY_S3_SECRET_KEY} + - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET} + - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION} + - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST} + - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT} + - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME} + - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD} + - APPFLOWY_ACCESS_CONTROL=${APPFLOWY_ACCESS_CONTROL} + - APPFLOWY_DATABASE_MAX_CONNECTIONS=${APPFLOWY_DATABASE_MAX_CONNECTIONS} + - APPFLOWY_AI_SERVER_HOST=${APPFLOWY_AI_SERVER_HOST} + - APPFLOWY_AI_SERVER_PORT=${APPFLOWY_AI_SERVER_PORT} + - APPFLOWY_OPENAI_API_KEY=${APPFLOWY_OPENAI_API_KEY} +# build: +# context: . +# dockerfile: Dockerfile +# args: +# FEATURES: "" + image: appflowyinc/appflowy_cloud:0.5.32 #CHANGED + + admin_frontend: + container_name: appflowy_admin #ADDED + restart: on-failure +# build: +# context: . +# dockerfile: ./admin_frontend/Dockerfile + image: appflowyinc/admin_frontend:0.5.32 #CHANGED + environment: + - RUST_LOG=${RUST_LOG:-info} + - ADMIN_FRONTEND_REDIS_URL=${ADMIN_FRONTEND_REDIS_URL:-redis://redis:6379} + - ADMIN_FRONTEND_GOTRUE_URL=${ADMIN_FRONTEND_GOTRUE_URL:-http://gotrue:9999} + - ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=${ADMIN_FRONTEND_APPFLOWY_CLOUD_URL:-http://appflowy_cloud:8000} + +# ai: +# container_name: appflowy_ai #ADDED +# restart: on-failure +# image: appflowyinc/appflowy_ai:0.5.16 #CHANGED +# environment: +# - OPENAI_API_KEY=${APPFLOWY_AI_OPENAI_API_KEY} +# - APPFLOWY_AI_SERVER_PORT=${APPFLOWY_AI_SERVER_PORT} +# - APPFLOWY_AI_DATABASE_URL=${APPFLOWY_AI_DATABASE_URL} + + appflowy_history: + container_name: appflowy_history #ADDED + restart: on-failure + image: appflowyinc/appflowy_history:0.5.32 #CHANGED +# build: +# context: . +# dockerfile: ./services/appflowy-history/Dockerfile + environment: + - RUST_LOG=${RUST_LOG:-info} + - APPFLOWY_HISTORY_REDIS_URL=redis://redis:6379 + - APPFLOWY_HISTORY_ENVIRONMENT=production + - APPFLOWY_HISTORY_DATABASE_URL=${APPFLOWY_HISTORY_DATABASE_URL} + +#volumes: +# postgres_data: +# minio_data: +