added players

This commit is contained in:
2026-02-16 21:31:02 +11:00
parent a2841a1982
commit 85fcf104b9
17 changed files with 168 additions and 39 deletions

View File

@@ -7,15 +7,18 @@ import (
)
type AuditMeta struct {
r *http.Request
u *User
ipAddress string
userAgent string
u *User
}
func NewAudit(r *http.Request, u *User) *AuditMeta {
if u == nil {
u = CurrentUser(r.Context())
}
return &AuditMeta{r, u}
func NewAudit(ipAdd, agent string, user *User) *AuditMeta {
return &AuditMeta{ipAdd, agent, user}
}
func NewAuditFromRequest(r *http.Request) *AuditMeta {
u := CurrentUser(r.Context())
return &AuditMeta{r.RemoteAddr, r.UserAgent(), u}
}
// AuditInfo contains metadata for audit logging
@@ -45,7 +48,10 @@ func extractTableName[T any]() string {
if bunTag != "" {
// Parse tag: "table:seasons,alias:s" -> "seasons"
for part := range strings.SplitSeq(bunTag, ",") {
part, _ := strings.CutPrefix(part, "table:")
part, match := strings.CutPrefix(part, "table:")
if match {
return part
}
return part
}
}
@@ -56,6 +62,38 @@ func extractTableName[T any]() string {
return strings.ToLower(t.Name()) + "s"
}
// extractTableName gets the bun table alias from a model type using reflection
// Example: Season with `bun:"table:seasons,alias:s"` returns "s"
func extractTableAlias[T any]() string {
var model T
t := reflect.TypeOf(model)
// Handle pointer types
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
// Look for bun.BaseModel field with table tag
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Name() == "BaseModel" {
bunTag := field.Tag.Get("bun")
if bunTag != "" {
// Parse tag: "table:seasons,alias:s" -> "seasons"
for part := range strings.SplitSeq(bunTag, ",") {
part, match := strings.CutPrefix(part, "alias:")
if match {
return part
}
}
}
}
}
// Fallback: use struct name in lowercase + "s"
return strings.ToLower(t.Name()) + "s"
}
// extractResourceType converts a table name to singular resource type
// Example: "seasons" -> "season", "users" -> "user"
func extractResourceType(tableName string) string {

View File

@@ -49,9 +49,6 @@ func log(
if meta.u == nil {
return errors.New("user cannot be nil for audit logging")
}
if meta.r == nil {
return errors.New("request cannot be nil for audit logging")
}
// Convert resourceID to string
var resourceIDStr *string
@@ -70,18 +67,14 @@ func log(
detailsJSON = jsonBytes
}
// Extract IP and User-Agent from request
ipAddress := meta.r.RemoteAddr
userAgent := meta.r.UserAgent()
log := &AuditLog{
UserID: meta.u.ID,
Action: info.Action,
ResourceType: info.ResourceType,
ResourceID: resourceIDStr,
Details: detailsJSON,
IPAddress: ipAddress,
UserAgent: userAgent,
IPAddress: meta.ipAddress,
UserAgent: meta.userAgent,
Result: result,
ErrorMessage: errorMessage,
CreatedAt: time.Now().Unix(),

View File

@@ -37,6 +37,10 @@ func (g *fieldgetter[T]) Get(ctx context.Context) (*T, error) {
return g.get(ctx)
}
func (g *fieldgetter[T]) String() string {
return g.q.String()
}
func (g *fieldgetter[T]) Relation(name string, apply ...func(*bun.SelectQuery) *bun.SelectQuery) *fieldgetter[T] {
g.q = g.q.Relation(name, apply...)
return g
@@ -66,5 +70,6 @@ func GetByID[T any](
tx bun.Tx,
id int,
) *fieldgetter[T] {
return GetByField[T](tx, "id", id)
prefix := extractTableAlias[T]()
return GetByField[T](tx, prefix+".id", id)
}

View File

@@ -0,0 +1,37 @@
package migrations
import (
"context"
"git.haelnorr.com/h/oslstats/internal/db"
"github.com/uptrace/bun"
)
func init() {
Migrations.MustRegister(
// UP migration
func(ctx context.Context, conn *bun.DB) error {
// Add your migration code here
_, err := conn.NewCreateTable().
Model((*db.Player)(nil)).
IfNotExists().
Exec(ctx)
if err != nil {
return err
}
return nil
},
// DOWN migration
func(ctx context.Context, conn *bun.DB) error {
// Add your rollback code here
_, err := conn.NewDropTable().
Model((*db.Player)(nil)).
IfExists().
Exec(ctx)
if err != nil {
return err
}
return nil
},
)
}

54
internal/db/player.go Normal file
View File

@@ -0,0 +1,54 @@
package db
import (
"context"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
type Player struct {
bun.BaseModel `bun:"table:players,alias:p"`
ID int `bun:"id,pk,autoincrement" json:"id"`
SlapID *string `bun:"slap_id,unique" json:"slap_id"`
DiscordID string `bun:"discord_id,unique,notnull" json:"discord_id"`
UserID *int `bun:"user_id,unique" json:"user_id"`
User *User `bun:"rel:belongs-to,join:user_id=id" json:"-"`
}
func NewPlayer(ctx context.Context, tx bun.Tx, discordID string, audit *AuditMeta) (*Player, error) {
player := &Player{DiscordID: discordID}
user, err := GetUserByDiscordID(ctx, tx, discordID)
if err != nil && !IsBadRequest(err) {
return nil, errors.Wrap(err, "GetUserByDiscordID")
}
if user != nil {
player.UserID = &user.ID
}
err = Insert(tx, player).
WithAudit(audit, nil).Exec(ctx)
if err != nil {
return nil, errors.Wrap(err, "Insert")
}
return player, nil
}
func GetPlayer(ctx context.Context, tx bun.Tx, playerID int) (*Player, error) {
return GetByID[Player](tx, playerID).Relation("User").Get(ctx)
}
func UpdatePlayerSlapID(ctx context.Context, tx bun.Tx, playerID int, slapID string, audit *AuditMeta) error {
player, err := GetPlayer(ctx, tx, playerID)
if err != nil {
return errors.Wrap(err, "GetPlayer")
}
player.SlapID = &slapID
err = UpdateByID(tx, player.ID, player).Column("slap_id").
WithAudit(audit, nil).Exec(ctx)
if err != nil {
return errors.Wrap(err, "UpdateByID")
}
return nil
}

View File

@@ -33,6 +33,7 @@ func (db *DB) RegisterModels() []any {
(*Permission)(nil),
(*AuditLog)(nil),
(*Fixture)(nil),
(*Player)(nil),
}
db.RegisterModel(models...)
return models

View File

@@ -20,7 +20,8 @@ type User struct {
CreatedAt int64 `bun:"created_at" json:"created_at"`
DiscordID string `bun:"discord_id,unique" json:"discord_id"`
Roles []*Role `bun:"m2m:user_roles,join:User=Role" json:"-"`
Roles []*Role `bun:"m2m:user_roles,join:User=Role" json:"-"`
Player *Player `bun:"rel:has-one,join:id=user_id"`
}
func (u *User) GetID() int {