Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@workspace/backend",
"license": "MIT",
"type": "module",
"exports": {
"./supabase/types": "./types.ts",
"./supabase/utils": "./utils.ts"
},
"scripts": {
"dev": "npx supabase start && npx supabase functions serve"
}
}
8 changes: 4 additions & 4 deletions apps/backend/supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ project_id = "backend"
[api]
enabled = true
# Port to use for the API URL.
port = 94321
port = 14321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
# endpoints. `public` and `graphql_public` schemas are included by default.
schemas = ["public", "graphql_public"]
Expand All @@ -23,17 +23,17 @@ enabled = false

[db]
# Port to use for the local database URL.
port = 94322
port = 14322
# Port used by db diff command to initialize the shadow database.
shadow_port = 94320
shadow_port = 14320
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
# server_version;` on the remote database to check.
major_version = 17

[db.pooler]
enabled = false
# Port to use for the local connection pooler.
port = 94329
port = 14329
# Specifies when a server connection can be reused by other clients.
# Configure one of the supported pooler modes: `transaction`, `session`.
pool_mode = "transaction"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, full_name)
values (new.id, new.raw_user_meta_data->>'full_name');
return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();

create type "public"."profile_repositories_statuses" as enum ('PENDING', 'ACCEPTED');

create table "public"."profile_repositories" (
"profile" uuid not null default gen_random_uuid(),
"repository" uuid not null default gen_random_uuid(),
"status" profile_repositories_statuses not null default 'PENDING'::profile_repositories_statuses,
"created_at" timestamp with time zone not null default now()
);

create policy "Enable users to view their own data only"
on "public"."profile_repositories"
as permissive
for select
to authenticated
using ((( SELECT auth.uid() AS uid) = profile));

alter table "public"."profile_repositories" enable row level security;

create table "public"."profiles" (
"id" uuid not null default auth.uid(),
"full_name" text not null,
"created_at" timestamp with time zone not null default now()
);

alter table "public"."profiles" enable row level security;

create table "public"."repositories" (
"id" uuid not null default gen_random_uuid(),
"name" text not null,
"created_at" timestamp with time zone not null default now()
);

alter table "public"."repositories" enable row level security;

CREATE UNIQUE INDEX profile_repositories_pkey ON public.profile_repositories USING btree (profile, repository);

CREATE UNIQUE INDEX profiles_pkey ON public.profiles USING btree (id);

CREATE UNIQUE INDEX repositories_pkey ON public.repositories USING btree (id);

alter table "public"."profile_repositories" add constraint "profile_repositories_pkey" PRIMARY KEY using index "profile_repositories_pkey";

alter table "public"."profiles" add constraint "profiles_pkey" PRIMARY KEY using index "profiles_pkey";

alter table "public"."repositories" add constraint "repositories_pkey" PRIMARY KEY using index "repositories_pkey";

alter table "public"."profile_repositories" add constraint "profile_repositories_profile_fkey" FOREIGN KEY (profile) REFERENCES profiles(id) not valid;

alter table "public"."profile_repositories" validate constraint "profile_repositories_profile_fkey";

alter table "public"."profile_repositories" add constraint "profile_repositories_repository_fkey" FOREIGN KEY (repository) REFERENCES repositories(id) not valid;

alter table "public"."profile_repositories" validate constraint "profile_repositories_repository_fkey";

alter table "public"."profiles" add constraint "profiles_id_fkey" FOREIGN KEY (id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE SET DEFAULT not valid;

alter table "public"."profiles" validate constraint "profiles_id_fkey";

create policy "Enable write for all auth"
on "public"."repositories"
as permissive
for insert
to authenticated
with check (true);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
CREATE OR REPLACE FUNCTION public.create_repository(
project_name text
)
RETURNS repositories
LANGUAGE plpgsql
STRICT
VOLATILE
SECURITY DEFINER
AS $func$
DECLARE
v_repo repositories%ROWTYPE;
BEGIN
-- 1) Create the repository
INSERT INTO repositories (name)
VALUES (project_name)
RETURNING * INTO v_repo;

-- 2) Link it to the profile
INSERT INTO profile_repositories (profile, repository, status)
VALUES (auth.uid(), v_repo.id, 'ACCEPTED');

-- 3) Return the inserted repo row
RETURN v_repo;
END
$func$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
create type "public"."project_types" as enum ('SUPABASE', 'VERCEL');

create table "public"."projects" (
"id" uuid not null default gen_random_uuid (),
"name" text not null,
"repository" uuid not null,
"url" text null,
"type" public.project_types not null,
"created_at" timestamp with time zone not null default now(),
constraint projects_pkey primary key (id),
constraint projects_repository_fkey foreign KEY (repository) references repositories (id)
) TABLESPACE pg_default;

alter table "public"."projects" enable row level security;

create policy "Enable insert for repo user"
on "public"."projects"
as permissive
for insert
to authenticated
with check ((EXISTS ( SELECT 1
FROM repositories
WHERE (repositories.id = projects.repository))));

create policy "Enabled read for repo member"
on "public"."projects"
as permissive
for select
to authenticated
using ((EXISTS ( SELECT 1
FROM repositories
WHERE (repositories.id = projects.repository))));

create policy "Enable read for users"
on "public"."repositories"
as permissive
for select
to authenticated
using ((EXISTS ( SELECT 1
FROM profile_repositories
WHERE (profile_repositories.repository = repositories.id))));
39 changes: 15 additions & 24 deletions apps/frontend/app/page.tsx → apps/frontend/app/(app)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { createClient } from "@/utils/supabase/server";
import Image from "next/image";
import { redirect } from "next/navigation";

export default async function Home() {

const supabase = await createClient();
const {data: user} = await supabase.auth.getUser();
console.log('User', user)
const {data, error} = await supabase.from('profile_repositories').select('*').eq('profile', user.user?.id);

if (data?.length === 0){
redirect('/wizard');
}

export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
Expand Down Expand Up @@ -35,29 +47,8 @@ export default function Home() {
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>


</div>
</main>
</div>
Expand Down
22 changes: 22 additions & 0 deletions apps/frontend/app/(app)/repository/[id]/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use server'

import { createClient } from "@/utils/supabase/server";

export async function createProjectAction(formData: FormData){
const repositoryId = formData.get('repository') as string;
const name = formData.get('name') as string;
const projectType = formData.get('type') as string;

console.log(repositoryId, name, projectType);

const supabase = await createClient();
const {data, error} = await supabase.from('projects').insert({
name,
repository: repositoryId,
type: projectType,
});
if (error){
console.error(error.message);
throw error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AddProjectDialog } from "@/components/add-project-dialog";
import SupabaseIcon from "@/components/supabase-icon";
import VercelIcon from "@/components/vercel-icon";
import { createClient } from "@/utils/supabase/server";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@repo/ui/src/components/card";
import { Folder } from "lucide-react";


export async function ProjectsCard({ repositoryId }: { repositoryId: string }) {
const supabase = await createClient();
const { data: projects, error } = await supabase.from('projects').select('*').eq('repository', repositoryId);
if (error){
console.error(error.message);
throw error;
}

return (
<Card>
<CardHeader>
<div className="flex items-center gap-2 flex flex-row">
<Folder className="h-5 w-5 text-muted-foreground" />
<CardTitle className="flex-1">Projects</CardTitle>
<AddProjectDialog repositoryId={repositoryId} />
</div>
<CardDescription>
{projects.length} project{projects.length !== 1 ? 's' : ''} in this repository
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{projects.map((project) => (
<div key={project.id} className="flex items-center gap-3 p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors">
{project.type === 'SUPABASE' ? <SupabaseIcon /> : <VercelIcon />}
<span className="font-medium">{project.name}</span>
</div>
))}
</div>
</CardContent>
</Card>
);
}

49 changes: 49 additions & 0 deletions apps/frontend/app/(app)/repository/[id]/components/users-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@repo/ui/src/components/card";
import { Users } from "lucide-react";

// Mock data - replace with actual data when available
const mockUsers = [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' },
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com' },
];

export async function UsersCard({ repositoryId }: { repositoryId: string }) {
// TODO: Fetch users from database
// const supabase = await createClient();
// const { data: users } = await supabase.from('repository_users').select('*, users(*)').eq('repository_id', repositoryId);

const users = mockUsers;

return (
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-muted-foreground" />
<CardTitle>Users</CardTitle>
</div>
<CardDescription>
{users.length} user{users.length !== 1 ? 's' : ''} with access
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{users.map((user) => (
<div key={user.id} className="flex items-center gap-3 p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors">
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<span className="text-xs font-semibold text-primary">
{user.name.split(' ').map(n => n[0]).join('')}
</span>
</div>
<div className="flex-1 min-w-0">
<p className="font-medium truncate">{user.name}</p>
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}

Loading