Files
Completed/internal/model/model.go
T
SansanandClaude Sonnet 5 384c4a7217 Restrict platform-list edits to admins
Anyone authenticated can still read the shared platform list, but only an
admin may add, rename or delete from it.

- users.is_admin boolean, defaulting to false. The very first account ever
  registered becomes an admin automatically (CreateUser's INSERT uses
  NOT EXISTS (SELECT 1 FROM users) for is_admin), so there's always at
  least one without manual setup; promoting anyone beyond that is a manual
  UPDATE for now (documented in the migration's comment on the column).
- GetSessionAndUser, GetUserByEmail and GetUserByID now also select
  is_admin, and it flows through to model.User (and so to
  /api/auth/{register,login,me}'s response) and to the auth middleware's
  authedUser.
- New requireAdminMiddleware + addAdminAuth, layered on top of the existing
  auth middleware. Wired onto create/update/delete-platform; list/get stay
  on plain addAuth, since reading the list only requires being logged in.

Verified end to end: the first registered user comes back is_admin:true,
the second false; the non-admin gets 403 on create/update/delete-platform
but 200 on list; the admin can do all of it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-03 11:39:21 +02:00

60 lines
1.9 KiB
Go

// Package model holds the plain data types shared between the API and its
// clients. It has no dependency on Huma, Echo or the database, so clients
// can import it without pulling in the server.
package model
// User is the public view of an account; it never carries the password hash.
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Bio string `json:"bio,omitempty"`
IsAdmin bool `json:"is_admin"`
}
type RegisterInput struct {
Email string `json:"email" format:"email"`
Password string `json:"password" minLength:"8"`
Name string `json:"name" minLength:"1"`
}
type LoginInput struct {
Email string `json:"email" format:"email"`
Password string `json:"password"`
}
// AuthResponse is returned by register and login. Token is also set as an
// httpOnly cookie, but is included in the body so non-browser clients (the
// TUI, the Fyne GUI) can store and send it themselves.
type AuthResponse struct {
Token string `json:"token"`
User User `json:"user"`
}
type Platform struct {
ID string `json:"id"`
Name string `json:"name"`
}
type PlatformInput struct {
Name string `json:"name" minLength:"1" doc:"Name of the platform"`
}
type Game struct {
ID string `json:"id"`
Name string `json:"name"`
PlatformID string `json:"platform_id"`
PlatformName string `json:"platform_name"`
Score int32 `json:"score"`
ReleaseDate string `json:"release_date" format:"date" doc:"YYYY-MM-DD"`
Finished string `json:"finished,omitempty" format:"date" doc:"YYYY-MM-DD (optional)"`
}
type GameInput struct {
Name string `json:"name" minLength:"1"`
PlatformID string `json:"platform_id"`
Score int32 `json:"score" minimum:"0" maximum:"100"`
ReleaseDate string `json:"release_date" format:"date" doc:"YYYY-MM-DD"`
Finished string `json:"finished,omitempty" format:"date" doc:"YYYY-MM-DD (optional)"`
}