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>
60 lines
1.9 KiB
Go
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)"`
|
|
}
|