@ekkos/cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/LocalSessionStore.d.ts +129 -0
- package/dist/cache/LocalSessionStore.js +688 -0
- package/dist/cache/capture.d.ts +26 -0
- package/dist/cache/capture.js +461 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.js +23 -0
- package/dist/cache/types.d.ts +147 -0
- package/dist/cache/types.js +40 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +478 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +829 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.js +658 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +109 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +157 -0
- package/dist/deploy/agents.d.ts +15 -0
- package/dist/deploy/agents.js +72 -0
- package/dist/deploy/hooks.d.ts +16 -0
- package/dist/deploy/hooks.js +121 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +24 -0
- package/dist/deploy/instructions.d.ts +12 -0
- package/dist/deploy/instructions.js +36 -0
- package/dist/deploy/mcp.d.ts +19 -0
- package/dist/deploy/mcp.js +109 -0
- package/dist/deploy/plugins.d.ts +19 -0
- package/dist/deploy/plugins.js +62 -0
- package/dist/deploy/settings.d.ts +8 -0
- package/dist/deploy/settings.js +84 -0
- package/dist/deploy/skills.d.ts +19 -0
- package/dist/deploy/skills.js +60 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -0
- package/dist/restore/RestoreOrchestrator.d.ts +48 -0
- package/dist/restore/RestoreOrchestrator.js +481 -0
- package/dist/restore/index.d.ts +4 -0
- package/dist/restore/index.js +20 -0
- package/dist/utils/platform.d.ts +29 -0
- package/dist/utils/platform.js +65 -0
- package/dist/utils/session-words.json +119 -0
- package/dist/utils/state.d.ts +57 -0
- package/dist/utils/state.js +186 -0
- package/dist/utils/templates.d.ts +24 -0
- package/dist/utils/templates.js +118 -0
- package/package.json +48 -0
- package/templates/CLAUDE.md +287 -0
- package/templates/README.md +378 -0
- package/templates/agents/README.md +182 -0
- package/templates/agents/code-reviewer.md +166 -0
- package/templates/agents/debug-detective.md +169 -0
- package/templates/agents/ekkOS_Vercel.md +99 -0
- package/templates/agents/extension-manager.md +229 -0
- package/templates/agents/git-companion.md +185 -0
- package/templates/agents/github-test-agent.md +321 -0
- package/templates/agents/railway-manager.md +179 -0
- package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
- package/templates/claude-plugins/README.md +587 -0
- package/templates/claude-plugins/agents/code-reviewer.json +14 -0
- package/templates/claude-plugins/agents/debug-detective.json +15 -0
- package/templates/claude-plugins/agents/git-companion.json +14 -0
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
- package/templates/claude-plugins-admin/README.md +446 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
- package/templates/commands/continue.md +47 -0
- package/templates/cursor-hooks/after-agent-response.sh +117 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
- package/templates/cursor-hooks/hooks.json +20 -0
- package/templates/cursor-hooks/lib/contract.sh +320 -0
- package/templates/cursor-hooks/stop.sh +75 -0
- package/templates/cursor-rules/ekkos-memory.md +187 -0
- package/templates/hooks/assistant-response.sh +96 -0
- package/templates/hooks/hooks.json +28 -0
- package/templates/hooks/lib/contract.sh +320 -0
- package/templates/hooks/lib/state.sh +158 -0
- package/templates/hooks/session-start.ps1 +41 -0
- package/templates/hooks/session-start.sh +318 -0
- package/templates/hooks/stop.ps1 +16 -0
- package/templates/hooks/stop.sh +989 -0
- package/templates/hooks/user-prompt-submit.ps1 +174 -0
- package/templates/hooks/user-prompt-submit.sh +587 -0
- package/templates/hooks-node/lib/state.js +187 -0
- package/templates/hooks-node/stop.js +416 -0
- package/templates/hooks-node/user-prompt-submit.js +337 -0
- package/templates/plan-template.md +306 -0
- package/templates/rules/00-hooks-contract.mdc +89 -0
- package/templates/rules/30-ekkos-core.mdc +188 -0
- package/templates/rules/31-ekkos-messages.mdc +78 -0
- package/templates/skills/continue/SKILL.md +169 -0
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
- package/templates/skills/ekkOS_Learn/Skill.md +265 -0
- package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
- package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
- package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
- package/templates/skills/ekkOS_Safety/Skill.md +265 -0
- package/templates/skills/ekkOS_Schema/Skill.md +251 -0
- package/templates/skills/ekkOS_Summary/Skill.md +257 -0
- package/templates/skills/ekkOS_Vault/Skill.md +287 -0
- package/templates/skills/permissions/Skill.md +322 -0
- package/templates/spec-template.md +159 -0
- package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
- package/templates/windsurf-hooks/hooks.json +10 -0
- package/templates/windsurf-hooks/lib/contract.sh +320 -0
- package/templates/windsurf-rules/ekkos-memory.md +129 -0
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
# Backend Agent
|
|
2
|
+
|
|
3
|
+
**ADMIN ONLY** - AI Backend Developer specialized in API development, database schema design, Supabase, PostgreSQL, Row Level Security, and server-side logic.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Backend Agent is your API and database expert. It:
|
|
8
|
+
- Designs database schemas with proper indexes and RLS policies
|
|
9
|
+
- Builds REST and GraphQL APIs
|
|
10
|
+
- Implements authentication and authorization
|
|
11
|
+
- Optimizes database queries
|
|
12
|
+
- Handles data migrations
|
|
13
|
+
- Ensures data integrity and security
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### `/backend schema`
|
|
18
|
+
|
|
19
|
+
Design and implement database schema.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
/backend schema "Entity or feature description"
|
|
23
|
+
|
|
24
|
+
# Examples
|
|
25
|
+
/backend schema "Teams with members and roles"
|
|
26
|
+
/backend schema "Notification system with preferences"
|
|
27
|
+
/backend schema "Usage tracking and billing"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**What happens:**
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
🔧 Backend Agent: Designing "Teams Schema"
|
|
34
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
35
|
+
|
|
36
|
+
📊 Schema Analysis
|
|
37
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
38
|
+
|
|
39
|
+
Searching existing schemas...
|
|
40
|
+
✓ Found users table (auth.users reference)
|
|
41
|
+
✓ Found organizations table (similar pattern)
|
|
42
|
+
✓ Checked RLS patterns
|
|
43
|
+
|
|
44
|
+
Requirements:
|
|
45
|
+
• Teams table (uuid, name, owner, created_at)
|
|
46
|
+
• Team members (many-to-many via junction)
|
|
47
|
+
• Role-based access (owner, admin, member)
|
|
48
|
+
• Invitation system
|
|
49
|
+
• Soft deletes
|
|
50
|
+
|
|
51
|
+
Security:
|
|
52
|
+
• RLS policies for data isolation
|
|
53
|
+
• Owner/admin permissions
|
|
54
|
+
• Audit logging
|
|
55
|
+
|
|
56
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
57
|
+
📝 Creating Migration
|
|
58
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
59
|
+
|
|
60
|
+
Creating supabase/migrations/20250107000000_create_teams.sql...
|
|
61
|
+
|
|
62
|
+
```sql
|
|
63
|
+
-- Create teams table
|
|
64
|
+
CREATE TABLE IF NOT EXISTS public.teams (
|
|
65
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
66
|
+
name TEXT NOT NULL CHECK (char_length(name) >= 3 AND char_length(name) <= 50),
|
|
67
|
+
slug TEXT NOT NULL UNIQUE,
|
|
68
|
+
owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
69
|
+
avatar_url TEXT,
|
|
70
|
+
description TEXT,
|
|
71
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
72
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
73
|
+
deleted_at TIMESTAMPTZ
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
-- Create team_members junction table
|
|
77
|
+
CREATE TABLE IF NOT EXISTS public.team_members (
|
|
78
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
79
|
+
team_id UUID NOT NULL REFERENCES public.teams(id) ON DELETE CASCADE,
|
|
80
|
+
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
81
|
+
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
|
|
82
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
83
|
+
invited_by UUID REFERENCES auth.users(id),
|
|
84
|
+
|
|
85
|
+
-- Ensure one membership per user per team
|
|
86
|
+
UNIQUE(team_id, user_id)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
-- Create team_invitations table
|
|
90
|
+
CREATE TABLE IF NOT EXISTS public.team_invitations (
|
|
91
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
92
|
+
team_id UUID NOT NULL REFERENCES public.teams(id) ON DELETE CASCADE,
|
|
93
|
+
email TEXT NOT NULL,
|
|
94
|
+
role TEXT NOT NULL CHECK (role IN ('admin', 'member')),
|
|
95
|
+
invited_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
96
|
+
token TEXT NOT NULL UNIQUE DEFAULT encode(gen_random_bytes(32), 'hex'),
|
|
97
|
+
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
|
|
98
|
+
accepted_at TIMESTAMPTZ,
|
|
99
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
100
|
+
|
|
101
|
+
-- Prevent duplicate pending invitations
|
|
102
|
+
UNIQUE(team_id, email) WHERE accepted_at IS NULL
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
-- Indexes for performance
|
|
106
|
+
CREATE INDEX idx_teams_owner_id ON public.teams(owner_id) WHERE deleted_at IS NULL;
|
|
107
|
+
CREATE INDEX idx_teams_slug ON public.teams(slug) WHERE deleted_at IS NULL;
|
|
108
|
+
CREATE INDEX idx_teams_created_at ON public.teams(created_at DESC) WHERE deleted_at IS NULL;
|
|
109
|
+
|
|
110
|
+
CREATE INDEX idx_team_members_team_id ON public.team_members(team_id);
|
|
111
|
+
CREATE INDEX idx_team_members_user_id ON public.team_members(user_id);
|
|
112
|
+
|
|
113
|
+
CREATE INDEX idx_team_invitations_team_id ON public.team_invitations(team_id);
|
|
114
|
+
CREATE INDEX idx_team_invitations_email ON public.team_invitations(email) WHERE accepted_at IS NULL;
|
|
115
|
+
CREATE INDEX idx_team_invitations_token ON public.team_invitations(token) WHERE accepted_at IS NULL;
|
|
116
|
+
CREATE INDEX idx_team_invitations_expires_at ON public.team_invitations(expires_at) WHERE accepted_at IS NULL;
|
|
117
|
+
|
|
118
|
+
-- Trigger for updated_at
|
|
119
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
120
|
+
RETURNS TRIGGER AS $$
|
|
121
|
+
BEGIN
|
|
122
|
+
NEW.updated_at = NOW();
|
|
123
|
+
RETURN NEW;
|
|
124
|
+
END;
|
|
125
|
+
$$ LANGUAGE plpgsql;
|
|
126
|
+
|
|
127
|
+
CREATE TRIGGER update_teams_updated_at
|
|
128
|
+
BEFORE UPDATE ON public.teams
|
|
129
|
+
FOR EACH ROW
|
|
130
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
131
|
+
|
|
132
|
+
-- Row Level Security Policies
|
|
133
|
+
ALTER TABLE public.teams ENABLE ROW LEVEL SECURITY;
|
|
134
|
+
ALTER TABLE public.team_members ENABLE ROW LEVEL SECURITY;
|
|
135
|
+
ALTER TABLE public.team_invitations ENABLE ROW LEVEL SECURITY;
|
|
136
|
+
|
|
137
|
+
-- Teams policies
|
|
138
|
+
CREATE POLICY "Users can view teams they belong to"
|
|
139
|
+
ON public.teams
|
|
140
|
+
FOR SELECT
|
|
141
|
+
USING (
|
|
142
|
+
deleted_at IS NULL AND (
|
|
143
|
+
owner_id = auth.uid() OR
|
|
144
|
+
id IN (
|
|
145
|
+
SELECT team_id FROM public.team_members WHERE user_id = auth.uid()
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
CREATE POLICY "Users can create teams"
|
|
151
|
+
ON public.teams
|
|
152
|
+
FOR INSERT
|
|
153
|
+
WITH CHECK (owner_id = auth.uid());
|
|
154
|
+
|
|
155
|
+
CREATE POLICY "Team owners can update their teams"
|
|
156
|
+
ON public.teams
|
|
157
|
+
FOR UPDATE
|
|
158
|
+
USING (owner_id = auth.uid() AND deleted_at IS NULL)
|
|
159
|
+
WITH CHECK (owner_id = auth.uid());
|
|
160
|
+
|
|
161
|
+
CREATE POLICY "Team owners can soft delete their teams"
|
|
162
|
+
ON public.teams
|
|
163
|
+
FOR UPDATE
|
|
164
|
+
USING (owner_id = auth.uid())
|
|
165
|
+
WITH CHECK (deleted_at IS NOT NULL);
|
|
166
|
+
|
|
167
|
+
-- Team members policies
|
|
168
|
+
CREATE POLICY "Users can view team members of teams they belong to"
|
|
169
|
+
ON public.team_members
|
|
170
|
+
FOR SELECT
|
|
171
|
+
USING (
|
|
172
|
+
team_id IN (
|
|
173
|
+
SELECT id FROM public.teams WHERE
|
|
174
|
+
deleted_at IS NULL AND (
|
|
175
|
+
owner_id = auth.uid() OR
|
|
176
|
+
id IN (SELECT team_id FROM public.team_members WHERE user_id = auth.uid())
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
CREATE POLICY "Team admins can add members"
|
|
182
|
+
ON public.team_members
|
|
183
|
+
FOR INSERT
|
|
184
|
+
WITH CHECK (
|
|
185
|
+
team_id IN (
|
|
186
|
+
SELECT teams.id FROM public.teams
|
|
187
|
+
LEFT JOIN public.team_members ON teams.id = team_members.team_id
|
|
188
|
+
WHERE teams.deleted_at IS NULL AND (
|
|
189
|
+
teams.owner_id = auth.uid() OR
|
|
190
|
+
(team_members.user_id = auth.uid() AND team_members.role IN ('owner', 'admin'))
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
CREATE POLICY "Team admins can remove members"
|
|
196
|
+
ON public.team_members
|
|
197
|
+
FOR DELETE
|
|
198
|
+
USING (
|
|
199
|
+
team_id IN (
|
|
200
|
+
SELECT teams.id FROM public.teams
|
|
201
|
+
LEFT JOIN public.team_members ON teams.id = team_members.team_id
|
|
202
|
+
WHERE teams.deleted_at IS NULL AND (
|
|
203
|
+
teams.owner_id = auth.uid() OR
|
|
204
|
+
(team_members.user_id = auth.uid() AND team_members.role IN ('owner', 'admin'))
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
-- Team invitations policies
|
|
210
|
+
CREATE POLICY "Users can view invitations to teams they admin"
|
|
211
|
+
ON public.team_invitations
|
|
212
|
+
FOR SELECT
|
|
213
|
+
USING (
|
|
214
|
+
team_id IN (
|
|
215
|
+
SELECT teams.id FROM public.teams
|
|
216
|
+
LEFT JOIN public.team_members ON teams.id = team_members.team_id
|
|
217
|
+
WHERE teams.deleted_at IS NULL AND (
|
|
218
|
+
teams.owner_id = auth.uid() OR
|
|
219
|
+
(team_members.user_id = auth.uid() AND team_members.role IN ('owner', 'admin'))
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
CREATE POLICY "Team admins can create invitations"
|
|
225
|
+
ON public.team_invitations
|
|
226
|
+
FOR INSERT
|
|
227
|
+
WITH CHECK (
|
|
228
|
+
team_id IN (
|
|
229
|
+
SELECT teams.id FROM public.teams
|
|
230
|
+
LEFT JOIN public.team_members ON teams.id = team_members.team_id
|
|
231
|
+
WHERE teams.deleted_at IS NULL AND (
|
|
232
|
+
teams.owner_id = auth.uid() OR
|
|
233
|
+
(team_members.user_id = auth.uid() AND team_members.role IN ('owner', 'admin'))
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
COMMENT ON TABLE public.teams IS 'Teams for collaboration';
|
|
239
|
+
COMMENT ON TABLE public.team_members IS 'Team membership and roles';
|
|
240
|
+
COMMENT ON TABLE public.team_invitations IS 'Pending team invitations';
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
244
|
+
✅ Schema Complete
|
|
245
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
246
|
+
|
|
247
|
+
Tables Created: 3
|
|
248
|
+
✓ teams (9 columns, 3 indexes)
|
|
249
|
+
✓ team_members (7 columns, 2 indexes)
|
|
250
|
+
✓ team_invitations (10 columns, 4 indexes)
|
|
251
|
+
|
|
252
|
+
Security:
|
|
253
|
+
✓ RLS enabled on all tables
|
|
254
|
+
✓ 10 policies for data isolation
|
|
255
|
+
✓ Owner/admin permission checks
|
|
256
|
+
✓ Soft delete support
|
|
257
|
+
|
|
258
|
+
Performance:
|
|
259
|
+
✓ Indexes on foreign keys
|
|
260
|
+
✓ Indexes on query columns (email, token, expires_at)
|
|
261
|
+
✓ Partial indexes for soft deletes
|
|
262
|
+
✓ Composite unique constraints
|
|
263
|
+
|
|
264
|
+
Data Integrity:
|
|
265
|
+
✓ CHECK constraints on text length
|
|
266
|
+
✓ UNIQUE constraints prevent duplicates
|
|
267
|
+
✓ Foreign keys with CASCADE
|
|
268
|
+
✓ Non-null constraints where needed
|
|
269
|
+
|
|
270
|
+
Next Steps:
|
|
271
|
+
• Test migration: supabase db reset
|
|
272
|
+
• Generate types: supabase gen types typescript
|
|
273
|
+
• Build API: /backend api "Teams CRUD"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `/backend api`
|
|
277
|
+
|
|
278
|
+
Build REST API endpoints.
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
/backend api "API description"
|
|
282
|
+
|
|
283
|
+
# Examples
|
|
284
|
+
/backend api "Teams CRUD operations"
|
|
285
|
+
/backend api "Pattern search with filters"
|
|
286
|
+
/backend api "User profile update"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**What happens:**
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
🔧 Backend Agent: Building "Teams CRUD API"
|
|
293
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
294
|
+
|
|
295
|
+
📊 API Planning
|
|
296
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
297
|
+
|
|
298
|
+
Checking database schema...
|
|
299
|
+
✓ Found teams table schema
|
|
300
|
+
✓ Found RLS policies
|
|
301
|
+
✓ Generated TypeScript types
|
|
302
|
+
|
|
303
|
+
Endpoints to create:
|
|
304
|
+
• POST /api/teams - Create team
|
|
305
|
+
• GET /api/teams - List user's teams
|
|
306
|
+
• GET /api/teams/:id - Get team details
|
|
307
|
+
• PATCH /api/teams/:id - Update team
|
|
308
|
+
• DELETE /api/teams/:id - Soft delete team
|
|
309
|
+
• POST /api/teams/:id/invite - Invite member
|
|
310
|
+
• POST /api/teams/:id/accept - Accept invitation
|
|
311
|
+
|
|
312
|
+
Stack:
|
|
313
|
+
• Next.js 14 Route Handlers
|
|
314
|
+
• Supabase Client (server-side)
|
|
315
|
+
• Zod validation
|
|
316
|
+
• TypeScript strict mode
|
|
317
|
+
|
|
318
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
319
|
+
📝 Implementation
|
|
320
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
321
|
+
|
|
322
|
+
Creating apps/web/app/api/teams/route.ts...
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
326
|
+
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
|
|
327
|
+
import { cookies } from 'next/headers';
|
|
328
|
+
import { z } from 'zod';
|
|
329
|
+
import { Database } from '@/types/supabase';
|
|
330
|
+
|
|
331
|
+
// Validation schemas
|
|
332
|
+
const createTeamSchema = z.object({
|
|
333
|
+
name: z.string()
|
|
334
|
+
.min(3, 'Team name must be at least 3 characters')
|
|
335
|
+
.max(50, 'Team name must be less than 50 characters')
|
|
336
|
+
.regex(/^[a-zA-Z0-9\s-]+$/, 'Team name can only contain letters, numbers, spaces, and hyphens'),
|
|
337
|
+
description: z.string().max(500).optional(),
|
|
338
|
+
avatar_url: z.string().url().optional(),
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
type CreateTeamInput = z.infer<typeof createTeamSchema>;
|
|
342
|
+
|
|
343
|
+
// POST /api/teams - Create a new team
|
|
344
|
+
export async function POST(request: NextRequest) {
|
|
345
|
+
try {
|
|
346
|
+
const supabase = createRouteHandlerClient<Database>({ cookies });
|
|
347
|
+
|
|
348
|
+
// Verify authentication
|
|
349
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
350
|
+
if (authError || !user) {
|
|
351
|
+
return NextResponse.json(
|
|
352
|
+
{ error: 'Unauthorized' },
|
|
353
|
+
{ status: 401 }
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Parse and validate request body
|
|
358
|
+
const body = await request.json();
|
|
359
|
+
const validationResult = createTeamSchema.safeParse(body);
|
|
360
|
+
|
|
361
|
+
if (!validationResult.success) {
|
|
362
|
+
return NextResponse.json(
|
|
363
|
+
{ error: 'Validation failed', details: validationResult.error.errors },
|
|
364
|
+
{ status: 400 }
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const data: CreateTeamInput = validationResult.data;
|
|
369
|
+
|
|
370
|
+
// Generate URL-safe slug from name
|
|
371
|
+
const slug = data.name
|
|
372
|
+
.toLowerCase()
|
|
373
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
374
|
+
.replace(/^-|-$/g, '');
|
|
375
|
+
|
|
376
|
+
// Create team
|
|
377
|
+
const { data: team, error: createError } = await supabase
|
|
378
|
+
.from('teams')
|
|
379
|
+
.insert({
|
|
380
|
+
name: data.name,
|
|
381
|
+
slug,
|
|
382
|
+
owner_id: user.id,
|
|
383
|
+
description: data.description,
|
|
384
|
+
avatar_url: data.avatar_url,
|
|
385
|
+
})
|
|
386
|
+
.select()
|
|
387
|
+
.single();
|
|
388
|
+
|
|
389
|
+
if (createError) {
|
|
390
|
+
// Handle duplicate slug
|
|
391
|
+
if (createError.code === '23505') {
|
|
392
|
+
return NextResponse.json(
|
|
393
|
+
{ error: 'A team with this name already exists' },
|
|
394
|
+
{ status: 409 }
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.error('Failed to create team:', createError);
|
|
399
|
+
return NextResponse.json(
|
|
400
|
+
{ error: 'Failed to create team' },
|
|
401
|
+
{ status: 500 }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Add owner as member
|
|
406
|
+
const { error: memberError } = await supabase
|
|
407
|
+
.from('team_members')
|
|
408
|
+
.insert({
|
|
409
|
+
team_id: team.id,
|
|
410
|
+
user_id: user.id,
|
|
411
|
+
role: 'owner',
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
if (memberError) {
|
|
415
|
+
console.error('Failed to add owner as member:', memberError);
|
|
416
|
+
// Team created but owner not added - should rarely happen
|
|
417
|
+
// RLS will still allow owner access via owner_id
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return NextResponse.json(team, { status: 201 });
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.error('Unexpected error in POST /api/teams:', error);
|
|
423
|
+
return NextResponse.json(
|
|
424
|
+
{ error: 'Internal server error' },
|
|
425
|
+
{ status: 500 }
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// GET /api/teams - List user's teams
|
|
431
|
+
export async function GET(request: NextRequest) {
|
|
432
|
+
try {
|
|
433
|
+
const supabase = createRouteHandlerClient<Database>({ cookies });
|
|
434
|
+
|
|
435
|
+
// Verify authentication
|
|
436
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
437
|
+
if (authError || !user) {
|
|
438
|
+
return NextResponse.json(
|
|
439
|
+
{ error: 'Unauthorized' },
|
|
440
|
+
{ status: 401 }
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Parse query parameters
|
|
445
|
+
const { searchParams } = new URL(request.url);
|
|
446
|
+
const search = searchParams.get('search') || '';
|
|
447
|
+
const offset = parseInt(searchParams.get('offset') || '0', 10);
|
|
448
|
+
const limit = Math.min(parseInt(searchParams.get('limit') || '12', 10), 100);
|
|
449
|
+
|
|
450
|
+
// Build query with RLS automatically filtering by user
|
|
451
|
+
let query = supabase
|
|
452
|
+
.from('teams')
|
|
453
|
+
.select(`
|
|
454
|
+
id,
|
|
455
|
+
name,
|
|
456
|
+
slug,
|
|
457
|
+
avatar_url,
|
|
458
|
+
description,
|
|
459
|
+
created_at,
|
|
460
|
+
owner_id,
|
|
461
|
+
team_members!inner(user_id, role)
|
|
462
|
+
`)
|
|
463
|
+
.order('created_at', { ascending: false })
|
|
464
|
+
.range(offset, offset + limit - 1);
|
|
465
|
+
|
|
466
|
+
// Add search filter if provided
|
|
467
|
+
if (search) {
|
|
468
|
+
query = query.ilike('name', `%${search}%`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const { data: teams, error: queryError } = await query;
|
|
472
|
+
|
|
473
|
+
if (queryError) {
|
|
474
|
+
console.error('Failed to fetch teams:', queryError);
|
|
475
|
+
return NextResponse.json(
|
|
476
|
+
{ error: 'Failed to fetch teams' },
|
|
477
|
+
{ status: 500 }
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Format response with member count and user's role
|
|
482
|
+
const formattedTeams = teams.map(team => ({
|
|
483
|
+
id: team.id,
|
|
484
|
+
name: team.name,
|
|
485
|
+
slug: team.slug,
|
|
486
|
+
avatar_url: team.avatar_url,
|
|
487
|
+
description: team.description,
|
|
488
|
+
created_at: team.created_at,
|
|
489
|
+
member_count: team.team_members.length,
|
|
490
|
+
role: team.team_members.find(m => m.user_id === user.id)?.role || 'member',
|
|
491
|
+
is_owner: team.owner_id === user.id,
|
|
492
|
+
}));
|
|
493
|
+
|
|
494
|
+
return NextResponse.json(formattedTeams);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.error('Unexpected error in GET /api/teams:', error);
|
|
497
|
+
return NextResponse.json(
|
|
498
|
+
{ error: 'Internal server error' },
|
|
499
|
+
{ status: 500 }
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Creating apps/web/app/api/teams/[id]/route.ts...
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
509
|
+
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
|
|
510
|
+
import { cookies } from 'next/headers';
|
|
511
|
+
import { z } from 'zod';
|
|
512
|
+
import { Database } from '@/types/supabase';
|
|
513
|
+
|
|
514
|
+
const updateTeamSchema = z.object({
|
|
515
|
+
name: z.string().min(3).max(50).optional(),
|
|
516
|
+
description: z.string().max(500).optional(),
|
|
517
|
+
avatar_url: z.string().url().optional(),
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// GET /api/teams/:id - Get team details
|
|
521
|
+
export async function GET(
|
|
522
|
+
request: NextRequest,
|
|
523
|
+
{ params }: { params: { id: string } }
|
|
524
|
+
) {
|
|
525
|
+
try {
|
|
526
|
+
const supabase = createRouteHandlerClient<Database>({ cookies });
|
|
527
|
+
|
|
528
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
529
|
+
if (authError || !user) {
|
|
530
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const { data: team, error } = await supabase
|
|
534
|
+
.from('teams')
|
|
535
|
+
.select(`
|
|
536
|
+
*,
|
|
537
|
+
team_members!inner(
|
|
538
|
+
id,
|
|
539
|
+
user_id,
|
|
540
|
+
role,
|
|
541
|
+
joined_at,
|
|
542
|
+
profiles:user_id(username, avatar_url)
|
|
543
|
+
)
|
|
544
|
+
`)
|
|
545
|
+
.eq('id', params.id)
|
|
546
|
+
.single();
|
|
547
|
+
|
|
548
|
+
if (error || !team) {
|
|
549
|
+
return NextResponse.json({ error: 'Team not found' }, { status: 404 });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return NextResponse.json(team);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error('Error in GET /api/teams/:id:', error);
|
|
555
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// PATCH /api/teams/:id - Update team
|
|
560
|
+
export async function PATCH(
|
|
561
|
+
request: NextRequest,
|
|
562
|
+
{ params }: { params: { id: string } }
|
|
563
|
+
) {
|
|
564
|
+
try {
|
|
565
|
+
const supabase = createRouteHandlerClient<Database>({ cookies });
|
|
566
|
+
|
|
567
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
568
|
+
if (authError || !user) {
|
|
569
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Parse and validate
|
|
573
|
+
const body = await request.json();
|
|
574
|
+
const validationResult = updateTeamSchema.safeParse(body);
|
|
575
|
+
|
|
576
|
+
if (!validationResult.success) {
|
|
577
|
+
return NextResponse.json(
|
|
578
|
+
{ error: 'Validation failed', details: validationResult.error.errors },
|
|
579
|
+
{ status: 400 }
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Update team (RLS ensures only owner can update)
|
|
584
|
+
const { data: team, error } = await supabase
|
|
585
|
+
.from('teams')
|
|
586
|
+
.update(validationResult.data)
|
|
587
|
+
.eq('id', params.id)
|
|
588
|
+
.select()
|
|
589
|
+
.single();
|
|
590
|
+
|
|
591
|
+
if (error) {
|
|
592
|
+
if (error.code === 'PGRST116') {
|
|
593
|
+
return NextResponse.json({ error: 'Team not found or access denied' }, { status: 404 });
|
|
594
|
+
}
|
|
595
|
+
console.error('Failed to update team:', error);
|
|
596
|
+
return NextResponse.json({ error: 'Failed to update team' }, { status: 500 });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return NextResponse.json(team);
|
|
600
|
+
} catch (error) {
|
|
601
|
+
console.error('Error in PATCH /api/teams/:id:', error);
|
|
602
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// DELETE /api/teams/:id - Soft delete team
|
|
607
|
+
export async function DELETE(
|
|
608
|
+
request: NextRequest,
|
|
609
|
+
{ params }: { params: { id: string } }
|
|
610
|
+
) {
|
|
611
|
+
try {
|
|
612
|
+
const supabase = createRouteHandlerClient<Database>({ cookies });
|
|
613
|
+
|
|
614
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
615
|
+
if (authError || !user) {
|
|
616
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Soft delete (RLS ensures only owner can delete)
|
|
620
|
+
const { error } = await supabase
|
|
621
|
+
.from('teams')
|
|
622
|
+
.update({ deleted_at: new Date().toISOString() })
|
|
623
|
+
.eq('id', params.id)
|
|
624
|
+
.is('deleted_at', null);
|
|
625
|
+
|
|
626
|
+
if (error) {
|
|
627
|
+
console.error('Failed to delete team:', error);
|
|
628
|
+
return NextResponse.json({ error: 'Failed to delete team' }, { status: 500 });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return NextResponse.json({ success: true }, { status: 200 });
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.error('Error in DELETE /api/teams/:id:', error);
|
|
634
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
640
|
+
✅ API Complete
|
|
641
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
642
|
+
|
|
643
|
+
Endpoints Created: 5
|
|
644
|
+
✓ POST /api/teams (create team)
|
|
645
|
+
✓ GET /api/teams (list teams with pagination)
|
|
646
|
+
✓ GET /api/teams/:id (get team details)
|
|
647
|
+
✓ PATCH /api/teams/:id (update team)
|
|
648
|
+
✓ DELETE /api/teams/:id (soft delete)
|
|
649
|
+
|
|
650
|
+
Security:
|
|
651
|
+
✓ Authentication required on all endpoints
|
|
652
|
+
✓ RLS policies enforce data isolation
|
|
653
|
+
✓ Input validation with Zod
|
|
654
|
+
✓ SQL injection protected (Supabase client)
|
|
655
|
+
|
|
656
|
+
Features:
|
|
657
|
+
✓ Pagination support (offset/limit)
|
|
658
|
+
✓ Search functionality
|
|
659
|
+
✓ Error handling (400, 401, 404, 409, 500)
|
|
660
|
+
✓ TypeScript types from database
|
|
661
|
+
✓ Proper status codes
|
|
662
|
+
|
|
663
|
+
Performance:
|
|
664
|
+
✓ Single database queries (no N+1)
|
|
665
|
+
✓ Index-optimized queries
|
|
666
|
+
✓ Efficient joins with select()
|
|
667
|
+
|
|
668
|
+
Next Steps:
|
|
669
|
+
• Add rate limiting: /backend fix "Add rate limiting"
|
|
670
|
+
• Add audit logging: /backend fix "Add audit logs"
|
|
671
|
+
• Write tests: /qa write "Teams API"
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### `/backend fix`
|
|
675
|
+
|
|
676
|
+
Fix backend issues or optimize existing code.
|
|
677
|
+
|
|
678
|
+
```bash
|
|
679
|
+
/backend fix "Issue description"
|
|
680
|
+
|
|
681
|
+
# Examples
|
|
682
|
+
/backend fix "Add rate limiting to Teams API"
|
|
683
|
+
/backend fix "Optimize pattern search query"
|
|
684
|
+
/backend fix "Fix N+1 query in dashboard"
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### `/backend optimize`
|
|
688
|
+
|
|
689
|
+
Optimize database queries and performance.
|
|
690
|
+
|
|
691
|
+
```bash
|
|
692
|
+
/backend optimize "Query or endpoint"
|
|
693
|
+
|
|
694
|
+
# Examples
|
|
695
|
+
/backend optimize "Pattern search"
|
|
696
|
+
/backend optimize "/api/dashboard/stats"
|
|
697
|
+
/backend optimize "Slow RLS policies"
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## MCP Tools Used
|
|
701
|
+
|
|
702
|
+
The Backend Agent uses:
|
|
703
|
+
|
|
704
|
+
- `ekkOS_Search` - Find similar API patterns and schemas
|
|
705
|
+
- `ekkOS_IndexSchema` - Index database schemas
|
|
706
|
+
- `ekkOS_GetSchema` - Get table schemas for queries
|
|
707
|
+
- `ekkOS_Codebase` - Search existing API implementations
|
|
708
|
+
- `ekkOS_Forge` - Save new API patterns
|
|
709
|
+
- `supabase_list_tables` - List database tables
|
|
710
|
+
- `supabase_execute_sql` - Execute SQL queries
|
|
711
|
+
- `supabase_apply_migration` - Apply database migrations
|
|
712
|
+
- `supabase_generate_typescript_types` - Generate types
|
|
713
|
+
- `Read` - Read existing code
|
|
714
|
+
- `Edit` - Update code
|
|
715
|
+
- `Write` - Create new files
|
|
716
|
+
- `Bash` - Run Supabase CLI commands
|
|
717
|
+
|
|
718
|
+
## Best Practices
|
|
719
|
+
|
|
720
|
+
### Always Use RLS
|
|
721
|
+
|
|
722
|
+
```sql
|
|
723
|
+
-- ✅ Good: RLS enabled with proper policies
|
|
724
|
+
ALTER TABLE public.teams ENABLE ROW LEVEL SECURITY;
|
|
725
|
+
|
|
726
|
+
CREATE POLICY "Users can view their teams"
|
|
727
|
+
ON public.teams
|
|
728
|
+
FOR SELECT
|
|
729
|
+
USING (owner_id = auth.uid());
|
|
730
|
+
|
|
731
|
+
-- ❌ Bad: No RLS (security risk!)
|
|
732
|
+
CREATE TABLE public.teams (...);
|
|
733
|
+
-- Missing: ALTER TABLE ... ENABLE ROW LEVEL SECURITY
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Validate All Inputs
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
// ✅ Good: Zod validation
|
|
740
|
+
const schema = z.object({
|
|
741
|
+
name: z.string().min(3).max(50),
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
const result = schema.safeParse(body);
|
|
745
|
+
if (!result.success) {
|
|
746
|
+
return NextResponse.json({ error: 'Invalid input' }, { status: 400 });
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ❌ Bad: No validation
|
|
750
|
+
const { name } = await request.json();
|
|
751
|
+
// Direct use without validation - security risk!
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Use Proper Indexes
|
|
755
|
+
|
|
756
|
+
```sql
|
|
757
|
+
-- ✅ Good: Indexes on query columns
|
|
758
|
+
CREATE INDEX idx_teams_owner_id ON teams(owner_id);
|
|
759
|
+
CREATE INDEX idx_teams_created_at ON teams(created_at DESC);
|
|
760
|
+
|
|
761
|
+
-- ❌ Bad: No indexes on queried columns
|
|
762
|
+
-- Will cause slow queries at scale
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
## Troubleshooting
|
|
766
|
+
|
|
767
|
+
### RLS Policy Blocking Queries
|
|
768
|
+
|
|
769
|
+
**Problem:** Query returns empty results unexpectedly
|
|
770
|
+
**Check:** RLS policies with `EXPLAIN` in Supabase
|
|
771
|
+
**Fix:** Agent reviews and adjusts policies
|
|
772
|
+
|
|
773
|
+
### Slow Queries
|
|
774
|
+
|
|
775
|
+
**Problem:** API endpoint timing out
|
|
776
|
+
**Check:** `EXPLAIN ANALYZE` on query
|
|
777
|
+
**Fix:** Agent adds indexes or optimizes query
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
## Summary
|
|
782
|
+
|
|
783
|
+
The Backend Agent is your API and database expert that:
|
|
784
|
+
|
|
785
|
+
✅ **Schemas** - Database design with RLS and indexes
|
|
786
|
+
✅ **APIs** - REST endpoints with validation
|
|
787
|
+
✅ **Security** - RLS policies and auth checks
|
|
788
|
+
✅ **Optimization** - Query performance tuning
|
|
789
|
+
|
|
790
|
+
**Ship secure, scalable APIs.**
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
/backend schema "Your feature here"
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
---
|
|
797
|
+
|
|
798
|
+
**Build secure. Scale smart. Ship fast.** 🔧
|