-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Summary
Implement the backend logic, storage, and API endpoints to allow hackers to manage groups (teams). Hackers should be able to create, join, view, update, leave, and delete teams.
This implementation must support multi-user team membership, enforce a maximum team size of 4, and automatically delete teamswhen empty.
The feature must follow existing patterns used in applications and schedule.
What to build
1. DB Migrations
Create a new migration file (e.g., 0000XX_create_teams.up.sql and .down.sql) inside cmd/migrate/migrations/.
Up Migration
CREATE TABLE teams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
code TEXT NOT NULL UNIQUE, -- Used for joining teams
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Join table for team membership (many-to-many)
CREATE TABLE team_members (
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (team_id, user_id)
);
-- Optional indexes
CREATE INDEX idx_team_members_user_id ON team_members(user_id);
CREATE INDEX idx_team_members_team_id ON team_members(team_id);Down: Drop the table.
DROP TABLE IF EXISTS team_members;
DROP TABLE IF EXISTS teams;2. Store Layer
Create internal/store/teams.go.
- Define the Team struct matching the database schema.
- Implement the TeamsStore struct with the following methods:
Create(ctx context.Context, team *Team, userID string) errorGetByID(ctx context.Context, id string) (*Team, error)GetByUserID(ctx context.Context, userID string) (*Team, error)AddMember(ctx context.Context, teamID, userID string) errorRemoveMember(ctx context.Context, teamID, userID string) errorGetMembers(ctx context.Context, teamID, string) ([]User, error)Update(ctx context.Context, team *Team) errorDelete(ctx context.Context, id string) error
Important Logic
-
When creating a team:
- Generate a random 6-character code
- Insert team
- Automatically add creator to team_members
-
Enforce team size limit (4 users max):
- Check member count before inserting into team_members
-
When removing a member:
- Delete membership row
- If team has 0 members → delete team
-
Members should only be able to add/remove themselves, not other users.
Update internal/store/storage.go:
- Add the Teams interface definition.
- Add Teams to the Storage struct.
- Initialize it in NewStorage.
Update internal/store/mock_store.go:
- Add Teams to NewMockStore
- Add a MockTeamStore interface
- Add the functions.
3. API Handlers
- Create cmd/api/teams.go.
- Implement the following handlers attached to the application struct:
- createTeamHandler: Create a new team, auto-add the creator, and fail if the user is already associated with a team.
- getTeamHandler: Fetch the team details for the currently authenticated user.
- joinTeamHandler: Join a team via invite code; fails if the code is invalid, the team is at capacity, or the user is already in a team.
- leaveTeamHandler: Remove the current user from their team and trigger a team deletion if they were the final member.
- updateTeamHandler: Update the team's name, restricted strictly to active members of that team.
- deleteTeamHandler: (Optional) Allow any member to manually dissolve the team, or rely on the leaveTeamHandler auto-delete logic.
- Include Swagger Documentation
- Implement the following handlers attached to the application struct:
4. Wire Routes
- Update [cmd/api/api.go]:
- Inside the AuthRequiredMiddleware team (likely near /applications), add the route team:
r.Route("/teams", func(r chi.Router) {
r.Post("/", app.createTeamHandler)
r.Get("/me", app.getTeamHandler)
r.Post("/join", app.joinTeamHandler)
r.Delete("/leave", app.leaveTeamHandler)
r.Put("/{teamID}", app.updateTeamHandler)
r.Delete("/{teamID}", app.deleteTeamHandler) // optional
})5. Testing
- Create cmd/api/teams_test.go.
Team Creation
✅ Successfully create team
❌ Fail if user already in team
Joining
✅ Join with valid code
❌ Invalid code
❌ id != caller's id
❌ Team full (>=4 members)
❌ Already in a team
Leaving
✅ Leave team successfully
✅ Last member leaves → team deleted
❌ id != caller's id
Updating
✅ Member can update team
❌ Non-member cannot update
Authorization
❌ Access team without membership
Notes
Do NOT add a team_id column directly to users
Use team_members as the source of truth
Ensure consistency with existing store + handler patterns
Generate team code in backend (not user-provided)
This design is subject to change. If you have alternative ideas or would like to implement this feature differently, please discuss with Caleb and update this issue accordingly. — Noel