fix: update lessons
This commit is contained in:
332
Les18-Supabase-Auth-RLS/Les18-Huiswerk.md
Normal file
332
Les18-Supabase-Auth-RLS/Les18-Huiswerk.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# Les 18 — Huiswerk
|
||||
## Multi-user Tasks app met RLS + social login + AUTH.md
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Deadline:** Voor eindopdracht-inlevering (4 weken)
|
||||
**Inleveren:** GitHub repo + `AUTH.md` in root + Vercel productie URL
|
||||
|
||||
---
|
||||
|
||||
## Doel
|
||||
|
||||
Bouw je auth-demo uit naar productie-niveau: tasks per user met RLS, GitHub social login, en deploy. Eindopdracht-ready.
|
||||
|
||||
---
|
||||
|
||||
## Onderdeel A — Tasks tabel + RLS (verplicht)
|
||||
|
||||
### A1 — Schema
|
||||
|
||||
In Supabase SQL Editor:
|
||||
|
||||
```sql
|
||||
create table tasks (
|
||||
id bigserial primary key,
|
||||
user_id uuid not null references auth.users(id) default auth.uid(),
|
||||
text text not null,
|
||||
done boolean default false,
|
||||
created_at timestamp default now()
|
||||
);
|
||||
|
||||
alter table tasks enable row level security;
|
||||
|
||||
create policy "select own tasks" on tasks
|
||||
for select using (auth.uid() = user_id);
|
||||
|
||||
create policy "insert own tasks" on tasks
|
||||
for insert with check (auth.uid() = user_id);
|
||||
|
||||
create policy "update own tasks" on tasks
|
||||
for update using (auth.uid() = user_id);
|
||||
|
||||
create policy "delete own tasks" on tasks
|
||||
for delete using (auth.uid() = user_id);
|
||||
```
|
||||
|
||||
### A2 — Tasks UI
|
||||
|
||||
`app/page.tsx` — extend met tasks lijst + form:
|
||||
|
||||
```tsx
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
async function addTask(formData: FormData) {
|
||||
"use server";
|
||||
const supabase = await createClient();
|
||||
await supabase.from("tasks").insert({ text: formData.get("text") as string });
|
||||
revalidatePath("/");
|
||||
}
|
||||
|
||||
async function toggleTask(id: number, done: boolean) {
|
||||
"use server";
|
||||
const supabase = await createClient();
|
||||
await supabase.from("tasks").update({ done: !done }).eq("id", id);
|
||||
revalidatePath("/");
|
||||
}
|
||||
|
||||
async function deleteTask(id: number) {
|
||||
"use server";
|
||||
const supabase = await createClient();
|
||||
await supabase.from("tasks").delete().eq("id", id);
|
||||
revalidatePath("/");
|
||||
}
|
||||
|
||||
export default async function Home() {
|
||||
const supabase = await createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) redirect("/login");
|
||||
|
||||
const { data: tasks } = await supabase
|
||||
.from("tasks")
|
||||
.select("*")
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
return (
|
||||
<main className="p-8 max-w-2xl mx-auto">
|
||||
<h1 className="text-2xl mb-4">Tasks van {user.email}</h1>
|
||||
|
||||
<form action={addTask} className="flex gap-2 mb-6">
|
||||
<input name="text" className="border p-2 flex-1" placeholder="Nieuwe taak..." />
|
||||
<button className="bg-blue-600 text-white px-4 py-2 rounded">+</button>
|
||||
</form>
|
||||
|
||||
<ul className="space-y-2">
|
||||
{tasks?.map((t) => (
|
||||
<li key={t.id} className="flex items-center gap-2 p-2 border rounded">
|
||||
<form action={toggleTask.bind(null, t.id, t.done)}>
|
||||
<button className={t.done ? "line-through" : ""}>{t.text}</button>
|
||||
</form>
|
||||
<form action={deleteTask.bind(null, t.id)} className="ml-auto">
|
||||
<button className="text-red-500">×</button>
|
||||
</form>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Eisen
|
||||
|
||||
- [ ] `tasks` tabel met RLS aan
|
||||
- [ ] 4 policies: select / insert / update / delete
|
||||
- [ ] UI: add, toggle, delete tasks werkt
|
||||
- [ ] In incognito met ander account: andere tasks zichtbaar (geen vermenging)
|
||||
|
||||
---
|
||||
|
||||
## Onderdeel B — Social login (GitHub) (verplicht)
|
||||
|
||||
### B1 — GitHub OAuth App
|
||||
|
||||
1. github.com → Settings → Developer settings → OAuth Apps → New
|
||||
2. Application name: jouw app
|
||||
3. Homepage URL: `http://localhost:3000` (dev) of `https://yourapp.vercel.app` (prod)
|
||||
4. Callback URL: `https://your-project.supabase.co/auth/v1/callback`
|
||||
5. Save, generate Client Secret
|
||||
|
||||
### B2 — Configure in Supabase
|
||||
|
||||
1. Dashboard → Auth → Providers → GitHub
|
||||
2. Enable + plak Client ID + Secret
|
||||
|
||||
### B3 — Button toevoegen
|
||||
|
||||
In `app/login/page.tsx`:
|
||||
|
||||
```tsx
|
||||
async function signInWithGitHub() {
|
||||
const supabase = createClient();
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: "github",
|
||||
options: { redirectTo: `${location.origin}/auth/callback` },
|
||||
});
|
||||
}
|
||||
|
||||
// In render:
|
||||
<button onClick={signInWithGitHub} className="border px-4 py-2 mt-3 rounded">
|
||||
Of: Login met GitHub
|
||||
</button>
|
||||
```
|
||||
|
||||
### Eisen
|
||||
|
||||
- [ ] GitHub OAuth App aangemaakt
|
||||
- [ ] Supabase configured
|
||||
- [ ] Klik op GitHub button → redirect → ingelogd
|
||||
- [ ] Tasks werken met GitHub-account
|
||||
|
||||
---
|
||||
|
||||
## Onderdeel C — Deploy naar Vercel (verplicht)
|
||||
|
||||
Combineert Les 14/15:
|
||||
|
||||
1. Push naar GitHub
|
||||
2. Vercel: import repo
|
||||
3. Add env vars: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
4. Deploy
|
||||
5. Update Supabase Auth → URL Configuration:
|
||||
- **Site URL:** `https://yourapp.vercel.app`
|
||||
- **Redirect URLs:** `https://yourapp.vercel.app/**`, `http://localhost:3000/**`
|
||||
6. Update GitHub OAuth App callback URL to productie
|
||||
|
||||
### Eisen
|
||||
|
||||
- [ ] App live op Vercel
|
||||
- [ ] Magic link werkt op productie URL
|
||||
- [ ] GitHub login werkt op productie URL
|
||||
- [ ] Tasks per user werken op productie
|
||||
|
||||
---
|
||||
|
||||
## Onderdeel D — `AUTH.md` documentatie (verplicht)
|
||||
|
||||
In repo-root.
|
||||
|
||||
### Sectie 1 — Auth methoden
|
||||
|
||||
Welke auth methoden ondersteund:
|
||||
- Magic link (default)
|
||||
- GitHub OAuth
|
||||
- (eventuele extra's)
|
||||
|
||||
### Sectie 2 — RLS policies
|
||||
|
||||
Lijst je policies + waarom:
|
||||
|
||||
```markdown
|
||||
**Table: tasks**
|
||||
- `select own tasks` — users zien alleen eigen tasks
|
||||
- `insert own tasks` — voorkomt inserten op andermans naam
|
||||
- `update own tasks` — alleen eigen toggle
|
||||
- `delete own tasks` — alleen eigen delete
|
||||
```
|
||||
|
||||
### Sectie 3 — Test van multi-user isolation
|
||||
|
||||
Bewijs dat RLS werkt:
|
||||
- Screenshot van Account A's tasks
|
||||
- Screenshot van Account B's tasks (incognito)
|
||||
- Bewijs: account A's tasks staan NIET in account B's lijst
|
||||
|
||||
### Sectie 4 — Deployment checklist
|
||||
|
||||
Wat je moest aanpassen voor productie:
|
||||
- Site URL in Supabase
|
||||
- Redirect URLs in Supabase
|
||||
- GitHub OAuth callback URL
|
||||
- Env vars in Vercel
|
||||
|
||||
### Sectie 5 — Eindopdracht-architectuur (~250 woorden)
|
||||
|
||||
Concrete plan voor je eigen eindopdracht:
|
||||
- App naam + doel
|
||||
- User-flows (signup, login, kernfeature)
|
||||
- Welke tabellen + RLS policies
|
||||
- AI feature(s) en hoe ze user-context gebruiken
|
||||
- Externe APIs (uit Les 17)
|
||||
- Tijd-plan (4-6 weken)
|
||||
|
||||
### Vorm
|
||||
|
||||
- Max 800 woorden
|
||||
- Screenshots zijn waardevol
|
||||
- Concrete plan, geen vaagheid
|
||||
|
||||
---
|
||||
|
||||
## Bonus (optioneel)
|
||||
|
||||
### Bonus 1 — Email + password naast magic link
|
||||
|
||||
Voeg klassieke signup/login toe. Combine met magic link.
|
||||
|
||||
### Bonus 2 — Profile pagina
|
||||
|
||||
`/profile` — user kan naam, avatar URL, etc. instellen. Aparte `profiles` tabel met RLS.
|
||||
|
||||
### Bonus 3 — Team feature
|
||||
|
||||
Multi-user binnen team. Aparte tabel `teams`, junction `team_members`. RLS policies die team-leden toelaten elkaars data te zien.
|
||||
|
||||
### Bonus 4 — AI integration met user context
|
||||
|
||||
Voeg een AI chat toe waar de user-tasks deel zijn van de context. AI weet wat jouw tasks zijn (door RLS), kan helpen prioriteren.
|
||||
|
||||
---
|
||||
|
||||
## Inleveren
|
||||
|
||||
1. **GitHub repo URL** in Brightspace
|
||||
2. **Vercel productie URL** in Brightspace
|
||||
3. **`AUTH.md`** in repo-root
|
||||
4. **Test-account credentials** voor docent (email of GitHub)
|
||||
5. **2-3 screenshots** van multi-user test
|
||||
|
||||
---
|
||||
|
||||
## Beoordeling
|
||||
|
||||
| Criterium | Punten |
|
||||
|-----------|--------|
|
||||
| A — Tasks + RLS werkt (4 policies) | 3 |
|
||||
| B — GitHub OAuth werkt | 2 |
|
||||
| C — Deployed op Vercel + auth werkt op prod | 2 |
|
||||
| D — AUTH.md compleet (5 secties) | 2 |
|
||||
| Multi-user isolation bewezen | 1 |
|
||||
| **Totaal** | **10** |
|
||||
|
||||
Voldoende = 6+. Bonus telt mee.
|
||||
|
||||
---
|
||||
|
||||
## Tijd-indicatie
|
||||
|
||||
| Onderdeel | Tijd |
|
||||
|-----------|------|
|
||||
| A — Tasks + RLS | 50 min |
|
||||
| B — GitHub OAuth | 25 min |
|
||||
| C — Deploy | 20 min |
|
||||
| D — AUTH.md | 35 min |
|
||||
| **Totaal** | **~2 uur 10 min** |
|
||||
|
||||
---
|
||||
|
||||
## Veelvoorkomende valkuilen
|
||||
|
||||
| Probleem | Oplossing |
|
||||
|----------|-----------|
|
||||
| Tasks van andere users zichtbaar | RLS niet aan, of policy `using (true)` |
|
||||
| Insert faalt met "row violates" | RLS check faalt — check `with check (auth.uid() = user_id)` + default |
|
||||
| OAuth redirect 404 | Site URL niet bijgewerkt voor productie |
|
||||
| Magic link werkt lokaal niet prod | Site URL in Supabase staat nog op localhost |
|
||||
| Cookies werken niet | Middleware draait niet — check matcher en path |
|
||||
| `auth.uid()` returnt null | User niet ingelogd — check session in Server Component |
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
- **Test isolation in incognito** — alleen daar ben je écht "another user"
|
||||
- **RLS error messages** kunnen vaag zijn — log policy + auth.uid() in Postgres function voor debug
|
||||
- **Productie env vars** — vergeet niet je Vercel env vars te zetten
|
||||
- **Eindopdracht-koppeling:** dit huiswerk is je startpunt — bouw eindopdracht hier op
|
||||
- **Veel succes met de eindopdracht!**
|
||||
|
||||
---
|
||||
|
||||
## Voor je begint aan de eindopdracht
|
||||
|
||||
Als afsluiting:
|
||||
|
||||
1. Plan je eindopdracht concreet (Sectie 5 van AUTH.md is de basis)
|
||||
2. Start een nieuwe repo van scratch óf bouw verder op deze
|
||||
3. Push klein en vaak — niet één big commit aan eind
|
||||
4. Vraag hulp tijdig — Slack, mail, office hours
|
||||
5. Plan tijd in van min 8-10 uur/week voor 4-6 weken
|
||||
|
||||
**Bedankt voor jullie inzet deze 18 lessen. Veel succes met de eindopdracht!**
|
||||
Reference in New Issue
Block a user