From cf7f36688d388119fe2485602e96d5cb4d887b63 Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 1 Sep 2024 14:42:26 -0400 Subject: [PATCH 01/13] create primary migrations. add seed data --- ...40901155537_create_auth_users_triggers.sql | 23 ++++ .../20240901155538_create_users_table.sql | 46 +++++++ .../20240901165124_create_posts_table.sql | 60 +++++++++ apps/api/supabase/seed.sql | 122 ++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql create mode 100644 apps/api/supabase/migrations/20240901155538_create_users_table.sql create mode 100644 apps/api/supabase/migrations/20240901165124_create_posts_table.sql create mode 100644 apps/api/supabase/seed.sql diff --git a/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql b/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql new file mode 100644 index 00000000..12162c09 --- /dev/null +++ b/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql @@ -0,0 +1,23 @@ +create function public .handle_new_user() returns trigger language plpgsql security definer +set + search_path = '' as $$ begin + insert into + public .users (id, email, full_name, username) + values + ( + new .id, + new .email, + new .raw_user_meta_data ->> 'full_name', + new .raw_user_meta_data ->> 'username' + ); + +return new; + +end; + +$$; + +-- trigger the function every time a user is created +create trigger on_auth_user_created after +insert + on auth.users for each row execute function public .handle_new_user(); \ No newline at end of file diff --git a/apps/api/supabase/migrations/20240901155538_create_users_table.sql b/apps/api/supabase/migrations/20240901155538_create_users_table.sql new file mode 100644 index 00000000..c8d1fc96 --- /dev/null +++ b/apps/api/supabase/migrations/20240901155538_create_users_table.sql @@ -0,0 +1,46 @@ +-- create users table +create table public .users ( + id uuid primary key, + username text unique not null, + email text unique not null, + full_name text, + avatar_url text, + bio text, + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now(), + constraint fk_auth_user foreign key (id) references auth.users(id) on + delete + cascade +); + +-- create index on username for faster lookups +create index idx_users_username on public .users (username); + +-- enable row level security (rls) +alter table + public .users enable row level security; + +-- create a trigger to update the updated_at column +create +or replace function update_updated_at() returns trigger as $$ begin + new .updated_at = now(); + +return new; + +end; + +$$ language plpgsql; + +create trigger users_updated_at before +update + on public .users for each row execute function update_updated_at(); + +-- create a policy to allow users to read all profiles +create policy read_all_profiles on public .users for +select + using (true); + +-- create a policy to allow users to update their own profile +create policy update_own_profile on public .users for +update + using (auth.uid() = id); \ No newline at end of file diff --git a/apps/api/supabase/migrations/20240901165124_create_posts_table.sql b/apps/api/supabase/migrations/20240901165124_create_posts_table.sql new file mode 100644 index 00000000..2c8254ea --- /dev/null +++ b/apps/api/supabase/migrations/20240901165124_create_posts_table.sql @@ -0,0 +1,60 @@ +-- create posts table +create table posts ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null, + title text not null, + content text not null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +-- add foreign key constraint +alter table + posts +add + constraint fk_posts_user foreign key (user_id) references public .users(id) on +delete + cascade; + +-- create index for faster queries +create index idx_posts_user_id on posts(user_id); + +-- add rls policies +alter table + posts enable row level security; + +-- policy to allow anyone to read all posts +create policy "allow read access for all users" on posts for +select + using (true); + +-- policy to allow users to insert their own posts +create policy "allow insert for authenticated users" on posts for +insert + with check (auth.uid() = user_id); + +-- policy to allow users to update their own posts +create policy "allow update for post owners" on posts for +update + using (auth.uid() = user_id); + +-- policy to allow users to delete their own posts +create policy "allow delete for post owners" on posts for +delete + using (auth.uid() = user_id); + +-- function to update the updated_at timestamp +create +or replace function update_updated_at() returns trigger as $$ begin + new .updated_at = now(); + +return new; + +end; + +$$ language plpgsql; + +-- trigger to call the update_updated_at function +create trigger update_posts_updated_at before +update + on posts for each row execute function update_updated_at(); \ No newline at end of file diff --git a/apps/api/supabase/seed.sql b/apps/api/supabase/seed.sql new file mode 100644 index 00000000..65559367 --- /dev/null +++ b/apps/api/supabase/seed.sql @@ -0,0 +1,122 @@ +INSERT INTO + auth.users ( + instance_id, + id, + aud, + role, + email, + encrypted_password, + email_confirmed_at, + invited_at, + confirmation_token, + confirmation_sent_at, + recovery_token, + recovery_sent_at, + email_change_token_new, + email_change, + email_change_sent_at, + last_sign_in_at, + raw_app_meta_data, + raw_user_meta_data, + is_super_admin, + created_at, + updated_at, + phone, + phone_confirmed_at, + phone_change, + phone_change_token, + phone_change_sent_at, + email_change_token_current, + email_change_confirm_status, + banned_until, + reauthentication_token, + reauthentication_sent_at, + is_sso_user, + deleted_at, + is_anonymous + ) +VALUES + ( + '00000000-0000-0000-0000-000000000000', + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + 'authenticated', + 'authenticated', + 'user@example.com', + '$2a$10$nnqTShcTX48N6QWWjbPUee.wrGz1kGx/uq5lORviCm.fn04W1BeRe', + '2024-09-01 17:21:01.462788+00', + NULL, + '', + NULL, + '', + NULL, + '', + '', + NULL, + NULL, + '{"provider": "email", "providers": ["email"]}', + '{"username": "username", "full_name": "Test User"}', + NULL, + '2024-09-01 17:21:01.455486+00', + '2024-09-01 17:21:01.46295+00', + NULL, + NULL, + '', + '', + NULL, + '', + 0, + NULL, + '', + NULL, + false, + NULL, + false + ); + +INSERT INTO + auth.identities ( + provider_id, + user_id, + identity_data, + provider, + last_sign_in_at, + created_at, + updated_at, + id + ) +VALUES + ( + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + '{"sub": "aec53558-767e-4408-b4d6-1c1e6f17ffe5", "email": "user@example.com", "email_verified": false, "phone_verified": false}', + 'email', + '2024-09-01 17:21:01.459821+00', + '2024-09-01 17:21:01.459849+00', + '2024-09-01 17:21:01.459849+00', + 'c5e81668-437b-47c2-83e2-84b8566b3018' + ); + +-- Seed data for posts +INSERT INTO + posts (user_id, title, content) +VALUES + ( + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + 'React Server Components: A Game Changer', + 'React Server Components are revolutionizing how we build React applications. They allow for better performance and smaller bundle sizes by running components on the server. This new paradigm is especially powerful when combined with frameworks like Next.js 13+.' + ), + ( + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + 'The Rise of Bun: A New JavaScript Runtime', + 'Bun is gaining traction as a fast all-in-one JavaScript runtime. It aims to replace Node.js, npm, yarn, and more. With its focus on performance and developer experience, Bun is definitely worth keeping an eye on in 2024.' + ), + ( + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + 'TypeScript 5.0: What''s New and Exciting', + 'TypeScript 5.0 brings several new features and improvements, including decorators, const type parameters, and more. These enhancements continue to make TypeScript an essential tool for building robust JavaScript applications.' + ), + ( + 'aec53558-767e-4408-b4d6-1c1e6f17ffe5', + 'The State of JavaScript Frameworks in 2024', + 'While React remains dominant, frameworks like Svelte and Solid are gaining popularity for their performance and simplicity. Meanwhile, meta-frameworks like Next.js and Remix are becoming increasingly important in the React ecosystem.' + ); \ No newline at end of file From e0164d9efcc65265230f57747316949881c7a2d1 Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 1 Sep 2024 14:43:49 -0400 Subject: [PATCH 02/13] reflect schema name field, inferred from zod update schema --- apps/app/src/components/users.server.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/components/users.server.tsx b/apps/app/src/components/users.server.tsx index 75c005e8..b1df108d 100644 --- a/apps/app/src/components/users.server.tsx +++ b/apps/app/src/components/users.server.tsx @@ -6,7 +6,7 @@ export async function UsersServer() { return (
{data?.map((user) => ( -
{user.name}
+
{user.full_name}
))}
); From bbe58ab755f32a769f13bc6021f4c622f3b16159 Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 1 Sep 2024 14:44:15 -0400 Subject: [PATCH 03/13] sync types --- packages/supabase/src/types/db.ts | 498 +++++++++++++++++++++++++++++- 1 file changed, 497 insertions(+), 1 deletion(-) diff --git a/packages/supabase/src/types/db.ts b/packages/supabase/src/types/db.ts index 2066a59b..de2b38ef 100644 --- a/packages/supabase/src/types/db.ts +++ b/packages/supabase/src/types/db.ts @@ -1,3 +1,499 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[]; + export type Database = { - public: unknown; + public: { + Tables: { + posts: { + Row: { + content: string; + created_at: string; + id: string; + title: string; + updated_at: string; + user_id: string; + }; + Insert: { + content: string; + created_at?: string; + id?: string; + title: string; + updated_at?: string; + user_id: string; + }; + Update: { + content?: string; + created_at?: string; + id?: string; + title?: string; + updated_at?: string; + user_id?: string; + }; + Relationships: [ + { + foreignKeyName: "fk_posts_user"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "users"; + referencedColumns: ["id"]; + }, + ]; + }; + users: { + Row: { + avatar_url: string | null; + bio: string | null; + created_at: string | null; + email: string; + full_name: string | null; + id: string; + updated_at: string | null; + username: string; + }; + Insert: { + avatar_url?: string | null; + bio?: string | null; + created_at?: string | null; + email: string; + full_name?: string | null; + id: string; + updated_at?: string | null; + username: string; + }; + Update: { + avatar_url?: string | null; + bio?: string | null; + created_at?: string | null; + email?: string; + full_name?: string | null; + id?: string; + updated_at?: string | null; + username?: string; + }; + Relationships: [ + { + foreignKeyName: "fk_auth_user"; + columns: ["id"]; + isOneToOne: true; + referencedRelation: "users"; + referencedColumns: ["id"]; + }, + ]; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; + storage: { + Tables: { + buckets: { + Row: { + allowed_mime_types: string[] | null; + avif_autodetection: boolean | null; + created_at: string | null; + file_size_limit: number | null; + id: string; + name: string; + owner: string | null; + owner_id: string | null; + public: boolean | null; + updated_at: string | null; + }; + Insert: { + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id: string; + name: string; + owner?: string | null; + owner_id?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; + Update: { + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id?: string; + name?: string; + owner?: string | null; + owner_id?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; + Relationships: []; + }; + migrations: { + Row: { + executed_at: string | null; + hash: string; + id: number; + name: string; + }; + Insert: { + executed_at?: string | null; + hash: string; + id: number; + name: string; + }; + Update: { + executed_at?: string | null; + hash?: string; + id?: number; + name?: string; + }; + Relationships: []; + }; + objects: { + Row: { + bucket_id: string | null; + created_at: string | null; + id: string; + last_accessed_at: string | null; + metadata: Json | null; + name: string | null; + owner: string | null; + owner_id: string | null; + path_tokens: string[] | null; + updated_at: string | null; + user_metadata: Json | null; + version: string | null; + }; + Insert: { + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + owner_id?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + user_metadata?: Json | null; + version?: string | null; + }; + Update: { + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + owner_id?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + user_metadata?: Json | null; + version?: string | null; + }; + Relationships: [ + { + foreignKeyName: "objects_bucketId_fkey"; + columns: ["bucket_id"]; + isOneToOne: false; + referencedRelation: "buckets"; + referencedColumns: ["id"]; + }, + ]; + }; + s3_multipart_uploads: { + Row: { + bucket_id: string; + created_at: string; + id: string; + in_progress_size: number; + key: string; + owner_id: string | null; + upload_signature: string; + user_metadata: Json | null; + version: string; + }; + Insert: { + bucket_id: string; + created_at?: string; + id: string; + in_progress_size?: number; + key: string; + owner_id?: string | null; + upload_signature: string; + user_metadata?: Json | null; + version: string; + }; + Update: { + bucket_id?: string; + created_at?: string; + id?: string; + in_progress_size?: number; + key?: string; + owner_id?: string | null; + upload_signature?: string; + user_metadata?: Json | null; + version?: string; + }; + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_bucket_id_fkey"; + columns: ["bucket_id"]; + isOneToOne: false; + referencedRelation: "buckets"; + referencedColumns: ["id"]; + }, + ]; + }; + s3_multipart_uploads_parts: { + Row: { + bucket_id: string; + created_at: string; + etag: string; + id: string; + key: string; + owner_id: string | null; + part_number: number; + size: number; + upload_id: string; + version: string; + }; + Insert: { + bucket_id: string; + created_at?: string; + etag: string; + id?: string; + key: string; + owner_id?: string | null; + part_number: number; + size?: number; + upload_id: string; + version: string; + }; + Update: { + bucket_id?: string; + created_at?: string; + etag?: string; + id?: string; + key?: string; + owner_id?: string | null; + part_number?: number; + size?: number; + upload_id?: string; + version?: string; + }; + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_parts_bucket_id_fkey"; + columns: ["bucket_id"]; + isOneToOne: false; + referencedRelation: "buckets"; + referencedColumns: ["id"]; + }, + { + foreignKeyName: "s3_multipart_uploads_parts_upload_id_fkey"; + columns: ["upload_id"]; + isOneToOne: false; + referencedRelation: "s3_multipart_uploads"; + referencedColumns: ["id"]; + }, + ]; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + can_insert_object: { + Args: { + bucketid: string; + name: string; + owner: string; + metadata: Json; + }; + Returns: undefined; + }; + extension: { + Args: { + name: string; + }; + Returns: string; + }; + filename: { + Args: { + name: string; + }; + Returns: string; + }; + foldername: { + Args: { + name: string; + }; + Returns: string[]; + }; + get_size_by_bucket: { + Args: Record; + Returns: { + size: number; + bucket_id: string; + }[]; + }; + list_multipart_uploads_with_delimiter: { + Args: { + bucket_id: string; + prefix_param: string; + delimiter_param: string; + max_keys?: number; + next_key_token?: string; + next_upload_token?: string; + }; + Returns: { + key: string; + id: string; + created_at: string; + }[]; + }; + list_objects_with_delimiter: { + Args: { + bucket_id: string; + prefix_param: string; + delimiter_param: string; + max_keys?: number; + start_after?: string; + next_token?: string; + }; + Returns: { + name: string; + id: string; + metadata: Json; + updated_at: string; + }[]; + }; + operation: { + Args: Record; + Returns: string; + }; + search: { + Args: { + prefix: string; + bucketname: string; + limits?: number; + levels?: number; + offsets?: number; + search?: string; + sortcolumn?: string; + sortorder?: string; + }; + Returns: { + name: string; + id: string; + updated_at: string; + created_at: string; + last_accessed_at: string; + metadata: Json; + }[]; + }; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; }; + +type PublicSchema = Database[Extract]; + +export type Tables< + PublicTableNameOrOptions extends + | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & + Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R; + } + ? R + : never + : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & + PublicSchema["Views"]) + ? (PublicSchema["Tables"] & + PublicSchema["Views"])[PublicTableNameOrOptions] extends { + Row: infer R; + } + ? R + : never + : never; + +export type TablesInsert< + PublicTableNameOrOptions extends + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I; + } + ? I + : never + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Insert: infer I; + } + ? I + : never + : never; + +export type TablesUpdate< + PublicTableNameOrOptions extends + | keyof PublicSchema["Tables"] + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U; + } + ? U + : never + : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] + ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { + Update: infer U; + } + ? U + : never + : never; + +export type Enums< + PublicEnumNameOrOptions extends + | keyof PublicSchema["Enums"] + | { schema: keyof Database }, + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] + ? PublicSchema["Enums"][PublicEnumNameOrOptions] + : never; From a660a86fb24dd23655a50f60f313876bb5b5af9f Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 1 Sep 2024 14:44:29 -0400 Subject: [PATCH 04/13] add getPosts query --- packages/supabase/src/queries/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/supabase/src/queries/index.ts b/packages/supabase/src/queries/index.ts index 2cfcc96d..162753b9 100644 --- a/packages/supabase/src/queries/index.ts +++ b/packages/supabase/src/queries/index.ts @@ -28,3 +28,16 @@ export async function getUsers() { throw error; } } + +export async function getPosts() { + const supabase = createClient(); + + try { + const result = await supabase.from("posts").select("*"); + + return result; + } catch (error) { + logger.error(error); + throw error; + } +} From 5892a27a765a355bc5519023b2b9d8be171cad59 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:00:55 +0200 Subject: [PATCH 05/13] Update RLS --- .../migrations/20240901155538_create_users_table.sql | 6 +++--- .../migrations/20240901165124_create_posts_table.sql | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/api/supabase/migrations/20240901155538_create_users_table.sql b/apps/api/supabase/migrations/20240901155538_create_users_table.sql index c8d1fc96..cb2e76d6 100644 --- a/apps/api/supabase/migrations/20240901155538_create_users_table.sql +++ b/apps/api/supabase/migrations/20240901155538_create_users_table.sql @@ -35,10 +35,10 @@ create trigger users_updated_at before update on public .users for each row execute function update_updated_at(); --- create a policy to allow users to read all profiles -create policy read_all_profiles on public .users for +-- create a policy to allow users to read their own profile +create policy select_own_profile on public .users for select - using (true); + using (auth.uid() = id); -- create a policy to allow users to update their own profile create policy update_own_profile on public .users for diff --git a/apps/api/supabase/migrations/20240901165124_create_posts_table.sql b/apps/api/supabase/migrations/20240901165124_create_posts_table.sql index 2c8254ea..517cd06e 100644 --- a/apps/api/supabase/migrations/20240901165124_create_posts_table.sql +++ b/apps/api/supabase/migrations/20240901165124_create_posts_table.sql @@ -23,10 +23,11 @@ create index idx_posts_user_id on posts(user_id); alter table posts enable row level security; --- policy to allow anyone to read all posts -create policy "allow read access for all users" on posts for +-- policy to allow read access for all authenticated users +create policy "allow read access for all authenticated users" on posts for select - using (true); + to authenticated + using ( true ); -- policy to allow users to insert their own posts create policy "allow insert for authenticated users" on posts for From 8f4e3e517e56830214c26d48849a2847c547a920 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:27:48 +0200 Subject: [PATCH 06/13] Update migration and readme --- README.md | 4 ++ apps/api/package.json | 6 +- apps/api/supabase/migrations/.gitkeep | 0 ...40901155537_create_auth_users_triggers.sql | 34 +++++------- .../20240901155538_create_users_table.sql | 55 ++++++++----------- .../20240901165124_create_posts_table.sql | 6 +- .../app/[locale]/(dashboard)/posts/page.tsx | 15 +++++ .../[locale]/(dashboard)/settings/page.tsx | 19 ------- .../posts.loading.tsx} | 2 +- .../app/src/components/posts/posts.server.tsx | 13 +++++ apps/app/src/components/users.server.tsx | 13 ----- 11 files changed, 78 insertions(+), 89 deletions(-) delete mode 100644 apps/api/supabase/migrations/.gitkeep create mode 100644 apps/app/src/app/[locale]/(dashboard)/posts/page.tsx delete mode 100644 apps/app/src/app/[locale]/(dashboard)/settings/page.tsx rename apps/app/src/components/{users.loading.tsx => posts/posts.loading.tsx} (50%) create mode 100644 apps/app/src/components/posts/posts.server.tsx delete mode 100644 apps/app/src/components/users.server.tsx diff --git a/README.md b/README.md index e2a9c271..350c476a 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,10 @@ bun dev:web // starts the web app in development mode bun dev:app // starts the app in development mode bun dev:api // starts the api in development mode bun dev:email // starts the email app in development mode + +// Database +bun migrate // run migrations +bun seed // run seed ``` ## How to use diff --git a/apps/api/package.json b/apps/api/package.json index 481e4544..5a121b1d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -3,7 +3,11 @@ "private": true, "scripts": { "dev": "supabase start", - "login": "supabase login" + "login": "supabase login", + "lint": "supabase db lint", + "migrate": "supabase migration up", + "seed": "supabase db seed generate && supabase db seed run", + "reset": "supabase db reset" }, "dependencies": { "supabase": "^1.191.3" diff --git a/apps/api/supabase/migrations/.gitkeep b/apps/api/supabase/migrations/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql b/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql index 12162c09..b46bcef4 100644 --- a/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql +++ b/apps/api/supabase/migrations/20240901155537_create_auth_users_triggers.sql @@ -1,23 +1,19 @@ -create function public .handle_new_user() returns trigger language plpgsql security definer -set - search_path = '' as $$ begin - insert into - public .users (id, email, full_name, username) - values - ( - new .id, - new .email, - new .raw_user_meta_data ->> 'full_name', - new .raw_user_meta_data ->> 'username' - ); - -return new; - +create function public.handle_new_user() returns trigger +language plpgsql security definer set search_path = '' +as $$ +begin + insert into public.users (id, email, full_name) + values ( + new.id, + new.email, + new.raw_user_meta_data ->> 'full_name' + ); + return new; end; - $$; -- trigger the function every time a user is created -create trigger on_auth_user_created after -insert - on auth.users for each row execute function public .handle_new_user(); \ No newline at end of file +create trigger on_auth_user_created +after insert on auth.users +for each row +execute function public.handle_new_user(); \ No newline at end of file diff --git a/apps/api/supabase/migrations/20240901155538_create_users_table.sql b/apps/api/supabase/migrations/20240901155538_create_users_table.sql index cb2e76d6..237026b4 100644 --- a/apps/api/supabase/migrations/20240901155538_create_users_table.sql +++ b/apps/api/supabase/migrations/20240901155538_create_users_table.sql @@ -1,46 +1,35 @@ -- create users table -create table public .users ( - id uuid primary key, - username text unique not null, - email text unique not null, - full_name text, - avatar_url text, - bio text, - created_at timestamp with time zone default now(), - updated_at timestamp with time zone default now(), - constraint fk_auth_user foreign key (id) references auth.users(id) on - delete - cascade +create table public.users ( + id uuid primary key, + email text unique not null, + full_name text, + avatar_url text, + created_at timestamp with time zone default now(), + updated_at timestamp with time zone default now(), + constraint fk_auth_user foreign key (id) references auth.users(id) on delete cascade ); --- create index on username for faster lookups -create index idx_users_username on public .users (username); - -- enable row level security (rls) -alter table - public .users enable row level security; +alter table public.users enable row level security; -- create a trigger to update the updated_at column -create -or replace function update_updated_at() returns trigger as $$ begin - new .updated_at = now(); - -return new; - +create or replace function update_updated_at() +returns trigger as $$ +begin + new.updated_at = now(); + return new; end; - $$ language plpgsql; -create trigger users_updated_at before -update - on public .users for each row execute function update_updated_at(); +create trigger users_updated_at +before update on public.users +for each row +execute function update_updated_at(); -- create a policy to allow users to read their own profile -create policy select_own_profile on public .users for -select - using (auth.uid() = id); +create policy select_own_profile on public.users +for select using (auth.uid() = id); -- create a policy to allow users to update their own profile -create policy update_own_profile on public .users for -update - using (auth.uid() = id); \ No newline at end of file +create policy update_own_profile on public.users +for update using (auth.uid() = id); \ No newline at end of file diff --git a/apps/api/supabase/migrations/20240901165124_create_posts_table.sql b/apps/api/supabase/migrations/20240901165124_create_posts_table.sql index 517cd06e..00abcb03 100644 --- a/apps/api/supabase/migrations/20240901165124_create_posts_table.sql +++ b/apps/api/supabase/migrations/20240901165124_create_posts_table.sql @@ -12,7 +12,7 @@ create table posts ( alter table posts add - constraint fk_posts_user foreign key (user_id) references public .users(id) on + constraint fk_posts_user foreign key (user_id) references public.users(id) on delete cascade; @@ -27,7 +27,7 @@ alter table create policy "allow read access for all authenticated users" on posts for select to authenticated - using ( true ); + using (true); -- policy to allow users to insert their own posts create policy "allow insert for authenticated users" on posts for @@ -47,7 +47,7 @@ delete -- function to update the updated_at timestamp create or replace function update_updated_at() returns trigger as $$ begin - new .updated_at = now(); + new.updated_at = now(); return new; diff --git a/apps/app/src/app/[locale]/(dashboard)/posts/page.tsx b/apps/app/src/app/[locale]/(dashboard)/posts/page.tsx new file mode 100644 index 00000000..94d4a923 --- /dev/null +++ b/apps/app/src/app/[locale]/(dashboard)/posts/page.tsx @@ -0,0 +1,15 @@ +import { PostsLoading } from "@/components/posts/posts.loading"; +import { PostsServer } from "@/components/posts/posts.server"; +import { Suspense } from "react"; + +export const metadata = { + title: "Posts", +}; + +export default function Page() { + return ( + }> + + + ); +} diff --git a/apps/app/src/app/[locale]/(dashboard)/settings/page.tsx b/apps/app/src/app/[locale]/(dashboard)/settings/page.tsx deleted file mode 100644 index ab6488c0..00000000 --- a/apps/app/src/app/[locale]/(dashboard)/settings/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { UsersLoading } from "@/components/users.loading"; -import { UsersServer } from "@/components/users.server"; -import type { Metadata } from "next"; -import { Suspense } from "react"; - -export const metadata: Metadata = { - title: "Settings", - description: "Settings", -}; - -export default function Page() { - return ( -
- }> - - -
- ); -} diff --git a/apps/app/src/components/users.loading.tsx b/apps/app/src/components/posts/posts.loading.tsx similarity index 50% rename from apps/app/src/components/users.loading.tsx rename to apps/app/src/components/posts/posts.loading.tsx index 240a1644..995046ed 100644 --- a/apps/app/src/components/users.loading.tsx +++ b/apps/app/src/components/posts/posts.loading.tsx @@ -1,3 +1,3 @@ -export function UsersLoading() { +export function PostsLoading() { return
Loading...
; } diff --git a/apps/app/src/components/posts/posts.server.tsx b/apps/app/src/components/posts/posts.server.tsx new file mode 100644 index 00000000..cfe1696d --- /dev/null +++ b/apps/app/src/components/posts/posts.server.tsx @@ -0,0 +1,13 @@ +import { getPosts } from "@v1/supabase/queries"; + +export async function PostsServer() { + const { data } = await getPosts(); + + return ( +
+ {data?.map((post) => ( +
{post.title}
+ ))} +
+ ); +} diff --git a/apps/app/src/components/users.server.tsx b/apps/app/src/components/users.server.tsx deleted file mode 100644 index b1df108d..00000000 --- a/apps/app/src/components/users.server.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { getUsers } from "@v1/supabase/queries"; - -export async function UsersServer() { - const { data } = await getUsers(); - - return ( -
- {data?.map((user) => ( -
{user.full_name}
- ))} -
- ); -} From 01c5c4a497cf86dc55a0d441384cc5d37b727c1f Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:29:17 +0200 Subject: [PATCH 07/13] Add CI check --- .github/workflows/check.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 00000000..d61c7e3e --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,20 @@ +name: Check +on: + push: + paths: + - apps/website/** + - packages/** +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Install dependencies + run: bun install + - name: 🔦 Run linter + run: bun run lint + - name: 🪐 Check TypeScript + run: bun run typecheck \ No newline at end of file From 1cab1cfcd4740be5ba241737dc3a2f32687b16fa Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:31:59 +0200 Subject: [PATCH 08/13] Add CI check --- .github/workflows/check.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d61c7e3e..256ccbd2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,8 +2,7 @@ name: Check on: push: paths: - - apps/website/** - - packages/** + - '**' jobs: check: runs-on: ubuntu-latest From 09fd82360ff43b41df1028ba4a2abc6e0911a451 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:37:53 +0200 Subject: [PATCH 09/13] Don't lint db in CI --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 256ccbd2..949975d3 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,6 +14,6 @@ jobs: - name: Install dependencies run: bun install - name: 🔦 Run linter - run: bun run lint + run: bun run lint --filter=!@v1/api - name: 🪐 Check TypeScript run: bun run typecheck \ No newline at end of file From 9fa27db2fed79737ebdcb301c602ba59ba37d4f8 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:39:06 +0200 Subject: [PATCH 10/13] Don't lint db in CI --- .github/workflows/check.yml | 2 +- apps/api/package.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 949975d3..256ccbd2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,6 +14,6 @@ jobs: - name: Install dependencies run: bun install - name: 🔦 Run linter - run: bun run lint --filter=!@v1/api + run: bun run lint - name: 🪐 Check TypeScript run: bun run typecheck \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index 5a121b1d..83c3e5f0 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -4,7 +4,6 @@ "scripts": { "dev": "supabase start", "login": "supabase login", - "lint": "supabase db lint", "migrate": "supabase migration up", "seed": "supabase db seed generate && supabase db seed run", "reset": "supabase db reset" From b93747664ca5dd2d766a53f6aaf182dee1a8a430 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:43:48 +0200 Subject: [PATCH 11/13] Update dependencies --- bun.lockb | Bin 329488 -> 327624 bytes package.json | 2 +- packages/ui/package.json | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lockb b/bun.lockb index b809cac94b5ad4202669a6f0899b547f3314e13f..dde60c88e33a0058e259b0b8855f5001a6d18011 100755 GIT binary patch delta 46105 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 delta 46883 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 Date: Mon, 2 Sep 2024 09:49:37 +0200 Subject: [PATCH 12/13] Fix generation and types --- apps/api/package.json | 3 +- packages/supabase/package.json | 3 +- packages/supabase/src/mutations/index.ts | 3 +- packages/supabase/src/queries/index.ts | 14 - packages/supabase/src/types/db.ts | 321 ----------------------- 5 files changed, 5 insertions(+), 339 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 83c3e5f0..846760b3 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -6,7 +6,8 @@ "login": "supabase login", "migrate": "supabase migration up", "seed": "supabase db seed generate && supabase db seed run", - "reset": "supabase db reset" + "reset": "supabase db reset", + "generate": "supabase gen types --lang=typescript --local --schema public > ../../packages/supabase/src/types/db.ts" }, "dependencies": { "supabase": "^1.191.3" diff --git a/packages/supabase/package.json b/packages/supabase/package.json index 831f906f..77dd1f4e 100644 --- a/packages/supabase/package.json +++ b/packages/supabase/package.json @@ -6,8 +6,7 @@ "clean": "rm -rf .turbo node_modules", "lint": "biome check .", "format": "biome format --write .", - "typecheck": "tsc --noEmit", - "db:generate": "supabase gen types --lang=typescript --project-id $PROJECT_ID --schema public > src/types/db.ts" + "typecheck": "tsc --noEmit" }, "dependencies": { "@supabase/ssr": "^0.5.1", diff --git a/packages/supabase/src/mutations/index.ts b/packages/supabase/src/mutations/index.ts index e20c0736..6c56f070 100644 --- a/packages/supabase/src/mutations/index.ts +++ b/packages/supabase/src/mutations/index.ts @@ -1,7 +1,8 @@ import { logger } from "@v1/logger"; import { createClient } from "@v1/supabase/server"; +import type { Database, Tables } from "../types"; -export async function updateUser(userId: string, data: unknown) { +export async function updateUser(userId: string, data: Tables<"users">) { const supabase = createClient(); try { diff --git a/packages/supabase/src/queries/index.ts b/packages/supabase/src/queries/index.ts index 162753b9..13a38a3c 100644 --- a/packages/supabase/src/queries/index.ts +++ b/packages/supabase/src/queries/index.ts @@ -15,20 +15,6 @@ export async function getUser() { } } -export async function getUsers() { - const supabase = createClient(); - - try { - const result = await supabase.from("users").select("*"); - - return result; - } catch (error) { - logger.error(error); - - throw error; - } -} - export async function getPosts() { const supabase = createClient(); diff --git a/packages/supabase/src/types/db.ts b/packages/supabase/src/types/db.ts index de2b38ef..ab8242bc 100644 --- a/packages/supabase/src/types/db.ts +++ b/packages/supabase/src/types/db.ts @@ -47,33 +47,27 @@ export type Database = { users: { Row: { avatar_url: string | null; - bio: string | null; created_at: string | null; email: string; full_name: string | null; id: string; updated_at: string | null; - username: string; }; Insert: { avatar_url?: string | null; - bio?: string | null; created_at?: string | null; email: string; full_name?: string | null; id: string; updated_at?: string | null; - username: string; }; Update: { avatar_url?: string | null; - bio?: string | null; created_at?: string | null; email?: string; full_name?: string | null; id?: string; updated_at?: string | null; - username?: string; }; Relationships: [ { @@ -99,321 +93,6 @@ export type Database = { [_ in never]: never; }; }; - storage: { - Tables: { - buckets: { - Row: { - allowed_mime_types: string[] | null; - avif_autodetection: boolean | null; - created_at: string | null; - file_size_limit: number | null; - id: string; - name: string; - owner: string | null; - owner_id: string | null; - public: boolean | null; - updated_at: string | null; - }; - Insert: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id: string; - name: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; - Update: { - allowed_mime_types?: string[] | null; - avif_autodetection?: boolean | null; - created_at?: string | null; - file_size_limit?: number | null; - id?: string; - name?: string; - owner?: string | null; - owner_id?: string | null; - public?: boolean | null; - updated_at?: string | null; - }; - Relationships: []; - }; - migrations: { - Row: { - executed_at: string | null; - hash: string; - id: number; - name: string; - }; - Insert: { - executed_at?: string | null; - hash: string; - id: number; - name: string; - }; - Update: { - executed_at?: string | null; - hash?: string; - id?: number; - name?: string; - }; - Relationships: []; - }; - objects: { - Row: { - bucket_id: string | null; - created_at: string | null; - id: string; - last_accessed_at: string | null; - metadata: Json | null; - name: string | null; - owner: string | null; - owner_id: string | null; - path_tokens: string[] | null; - updated_at: string | null; - user_metadata: Json | null; - version: string | null; - }; - Insert: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - user_metadata?: Json | null; - version?: string | null; - }; - Update: { - bucket_id?: string | null; - created_at?: string | null; - id?: string; - last_accessed_at?: string | null; - metadata?: Json | null; - name?: string | null; - owner?: string | null; - owner_id?: string | null; - path_tokens?: string[] | null; - updated_at?: string | null; - user_metadata?: Json | null; - version?: string | null; - }; - Relationships: [ - { - foreignKeyName: "objects_bucketId_fkey"; - columns: ["bucket_id"]; - isOneToOne: false; - referencedRelation: "buckets"; - referencedColumns: ["id"]; - }, - ]; - }; - s3_multipart_uploads: { - Row: { - bucket_id: string; - created_at: string; - id: string; - in_progress_size: number; - key: string; - owner_id: string | null; - upload_signature: string; - user_metadata: Json | null; - version: string; - }; - Insert: { - bucket_id: string; - created_at?: string; - id: string; - in_progress_size?: number; - key: string; - owner_id?: string | null; - upload_signature: string; - user_metadata?: Json | null; - version: string; - }; - Update: { - bucket_id?: string; - created_at?: string; - id?: string; - in_progress_size?: number; - key?: string; - owner_id?: string | null; - upload_signature?: string; - user_metadata?: Json | null; - version?: string; - }; - Relationships: [ - { - foreignKeyName: "s3_multipart_uploads_bucket_id_fkey"; - columns: ["bucket_id"]; - isOneToOne: false; - referencedRelation: "buckets"; - referencedColumns: ["id"]; - }, - ]; - }; - s3_multipart_uploads_parts: { - Row: { - bucket_id: string; - created_at: string; - etag: string; - id: string; - key: string; - owner_id: string | null; - part_number: number; - size: number; - upload_id: string; - version: string; - }; - Insert: { - bucket_id: string; - created_at?: string; - etag: string; - id?: string; - key: string; - owner_id?: string | null; - part_number: number; - size?: number; - upload_id: string; - version: string; - }; - Update: { - bucket_id?: string; - created_at?: string; - etag?: string; - id?: string; - key?: string; - owner_id?: string | null; - part_number?: number; - size?: number; - upload_id?: string; - version?: string; - }; - Relationships: [ - { - foreignKeyName: "s3_multipart_uploads_parts_bucket_id_fkey"; - columns: ["bucket_id"]; - isOneToOne: false; - referencedRelation: "buckets"; - referencedColumns: ["id"]; - }, - { - foreignKeyName: "s3_multipart_uploads_parts_upload_id_fkey"; - columns: ["upload_id"]; - isOneToOne: false; - referencedRelation: "s3_multipart_uploads"; - referencedColumns: ["id"]; - }, - ]; - }; - }; - Views: { - [_ in never]: never; - }; - Functions: { - can_insert_object: { - Args: { - bucketid: string; - name: string; - owner: string; - metadata: Json; - }; - Returns: undefined; - }; - extension: { - Args: { - name: string; - }; - Returns: string; - }; - filename: { - Args: { - name: string; - }; - Returns: string; - }; - foldername: { - Args: { - name: string; - }; - Returns: string[]; - }; - get_size_by_bucket: { - Args: Record; - Returns: { - size: number; - bucket_id: string; - }[]; - }; - list_multipart_uploads_with_delimiter: { - Args: { - bucket_id: string; - prefix_param: string; - delimiter_param: string; - max_keys?: number; - next_key_token?: string; - next_upload_token?: string; - }; - Returns: { - key: string; - id: string; - created_at: string; - }[]; - }; - list_objects_with_delimiter: { - Args: { - bucket_id: string; - prefix_param: string; - delimiter_param: string; - max_keys?: number; - start_after?: string; - next_token?: string; - }; - Returns: { - name: string; - id: string; - metadata: Json; - updated_at: string; - }[]; - }; - operation: { - Args: Record; - Returns: string; - }; - search: { - Args: { - prefix: string; - bucketname: string; - limits?: number; - levels?: number; - offsets?: number; - search?: string; - sortcolumn?: string; - sortorder?: string; - }; - Returns: { - name: string; - id: string; - updated_at: string; - created_at: string; - last_accessed_at: string; - metadata: Json; - }[]; - }; - }; - Enums: { - [_ in never]: never; - }; - CompositeTypes: { - [_ in never]: never; - }; - }; }; type PublicSchema = Database[Extract]; From 4fd5e3f521ea04544dd65488e284a310a5f70f57 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 2 Sep 2024 09:54:29 +0200 Subject: [PATCH 13/13] Fix types --- .../user/{update-user-action.ts => update-post-action.ts} | 0 packages/supabase/src/mutations/index.ts | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename apps/app/src/actions/user/{update-user-action.ts => update-post-action.ts} (100%) diff --git a/apps/app/src/actions/user/update-user-action.ts b/apps/app/src/actions/user/update-post-action.ts similarity index 100% rename from apps/app/src/actions/user/update-user-action.ts rename to apps/app/src/actions/user/update-post-action.ts diff --git a/packages/supabase/src/mutations/index.ts b/packages/supabase/src/mutations/index.ts index 6c56f070..d09be30a 100644 --- a/packages/supabase/src/mutations/index.ts +++ b/packages/supabase/src/mutations/index.ts @@ -1,8 +1,8 @@ import { logger } from "@v1/logger"; import { createClient } from "@v1/supabase/server"; -import type { Database, Tables } from "../types"; +import type { Database, Tables, TablesUpdate } from "../types"; -export async function updateUser(userId: string, data: Tables<"users">) { +export async function updateUser(userId: string, data: TablesUpdate<"users">) { const supabase = createClient(); try {