diff options
Diffstat (limited to 'shrine')
| -rw-r--r-- | shrine/controllers/auth.go | 4 | ||||
| -rw-r--r-- | shrine/controllers/responses.go | 4 | ||||
| -rw-r--r-- | shrine/controllers/stats.go | 30 | ||||
| -rw-r--r-- | shrine/models/user.go | 9 | ||||
| -rw-r--r-- | shrine/repositories/user.go | 28 | ||||
| -rw-r--r-- | shrine/router/auth.go | 1 | ||||
| -rw-r--r-- | shrine/router/stats.go | 13 | ||||
| -rw-r--r-- | shrine/types/response.go | 14 | ||||
| -rw-r--r-- | shrine/utils/auth/auth.go | 6 | ||||
| -rw-r--r-- | shrine/utils/shortcuts/response.go | 3 |
10 files changed, 112 insertions, 0 deletions
diff --git a/shrine/controllers/auth.go b/shrine/controllers/auth.go index fc14d2a..ff81ebe 100644 --- a/shrine/controllers/auth.go +++ b/shrine/controllers/auth.go @@ -180,4 +180,8 @@ func LogoutController(context *fiber.Ctx) error { func MeController(context *fiber.Ctx) error { user := auth.GetUser(context) return Success(context, user.ToResponse()) +} + +func HeartbeatController(context *fiber.Ctx) error { + return NoContent(context) }
\ No newline at end of file diff --git a/shrine/controllers/responses.go b/shrine/controllers/responses.go index b3fa824..0e9e515 100644 --- a/shrine/controllers/responses.go +++ b/shrine/controllers/responses.go @@ -50,3 +50,7 @@ func Success(context *fiber.Ctx, data any) error { func Created(context *fiber.Ctx, data any) error { return shortcuts.Response(context, data).As(fiber.StatusCreated) } + +func NoContent(context *fiber.Ctx) error { + return shortcuts.Response(context, nil).As(fiber.StatusNoContent) +} diff --git a/shrine/controllers/stats.go b/shrine/controllers/stats.go new file mode 100644 index 0000000..33aa991 --- /dev/null +++ b/shrine/controllers/stats.go @@ -0,0 +1,30 @@ +package controllers + +import ( + "shrine/repositories" + "shrine/types" + + "github.com/gofiber/fiber/v2" +) + +func StatsController(context *fiber.Ctx) error { + newest := repositories.NewestCitizens(5) + online := repositories.OnlineCitizens(10) + + newestSummaries := make([]types.CitizenSummary, len(newest)) + for i, u := range newest { + newestSummaries[i] = u.ToSummary() + } + + onlineSummaries := make([]types.CitizenSummary, len(online)) + for i, u := range online { + onlineSummaries[i] = u.ToSummary() + } + + return Success(context, types.StatsResponse{ + Citizens: repositories.CountCitizens(), + Online: repositories.CountOnline(), + NewestCitizens: newestSummaries, + OnlineCitizens: onlineSummaries, + }) +}
\ No newline at end of file diff --git a/shrine/models/user.go b/shrine/models/user.go index 96a181f..c0e1e23 100644 --- a/shrine/models/user.go +++ b/shrine/models/user.go @@ -122,6 +122,15 @@ func (user *User) ToResponse() types.UserResponse { } } +func (user *User) ToSummary() types.CitizenSummary { + return types.CitizenSummary{ + ID: user.ID, + Username: user.Username, + DisplayName: user.DisplayName, + AvatarURL: user.AvatarURL, + } +} + func (user *User) BeforeCreate(tx *gorm.DB) error { _, bypassUsername := tx.Get("bypass_username_validation") diff --git a/shrine/repositories/user.go b/shrine/repositories/user.go index 1161257..d47cb33 100644 --- a/shrine/repositories/user.go +++ b/shrine/repositories/user.go @@ -40,4 +40,32 @@ func FindUserByVerification(hash string, verificationType enums.VerificationType var user models.User err := database.DB.Where("verification_hash = ? AND verification_type = ? AND verification_expiry > ?", hash, verificationType, time.Now()).First(&user).Error return &user, err +} + +func UpdateLastSeen(user *models.User) { + database.DB.Model(user).Update("last_seen_at", user.LastSeenAt) +} + +func CountCitizens() int64 { + var count int64 + database.DB.Model(&models.User{}).Where("email_verified = ?", true).Count(&count) + return count +} + +func CountOnline() int64 { + var count int64 + database.DB.Model(&models.User{}).Where("last_seen_at > ?", time.Now().Add(-5*time.Minute)).Count(&count) + return count +} + +func NewestCitizens(limit int) []models.User { + var users []models.User + database.DB.Where("email_verified = ?", true).Order("created_at desc").Limit(limit).Find(&users) + return users +} + +func OnlineCitizens(limit int) []models.User { + var users []models.User + database.DB.Where("last_seen_at > ?", time.Now().Add(-5*time.Minute)).Order("last_seen_at desc").Limit(limit).Find(&users) + return users }
\ No newline at end of file diff --git a/shrine/router/auth.go b/shrine/router/auth.go index 5ad5fee..0c625fe 100644 --- a/shrine/router/auth.go +++ b/shrine/router/auth.go @@ -16,4 +16,5 @@ func init() { urls.Path(types.POST, "/reactivate", controllers.ResendActivationController, "reactivate") urls.Path(types.POST, "/logout", auth.RequireAuthentication(controllers.LogoutController), "logout") urls.Path(types.GET, "/me", auth.RequireAuthentication(controllers.MeController), "me") + urls.Path(types.POST, "/heartbeat", auth.RequireAuthentication(controllers.HeartbeatController), "heartbeat") }
\ No newline at end of file diff --git a/shrine/router/stats.go b/shrine/router/stats.go new file mode 100644 index 0000000..8c165f9 --- /dev/null +++ b/shrine/router/stats.go @@ -0,0 +1,13 @@ +package router + +import ( + "shrine/controllers" + "shrine/types" + "shrine/utils/urls" +) + +func init() { + urls.SetNamespace("stats") + + urls.Path(types.GET, "/", controllers.StatsController, "index") +}
\ No newline at end of file diff --git a/shrine/types/response.go b/shrine/types/response.go index 7e15ecc..f13b8dd 100644 --- a/shrine/types/response.go +++ b/shrine/types/response.go @@ -31,3 +31,17 @@ type AuthResponse struct { Token string `json:"token"` User UserResponse `json:"user"` } + +type CitizenSummary struct { + ID uint `json:"id"` + Username string `json:"username"` + DisplayName string `json:"display_name"` + AvatarURL string `json:"avatar_url"` +} + +type StatsResponse struct { + Citizens int64 `json:"citizens"` + Online int64 `json:"online"` + NewestCitizens []CitizenSummary `json:"newest_citizens"` + OnlineCitizens []CitizenSummary `json:"online_citizens"` +} diff --git a/shrine/utils/auth/auth.go b/shrine/utils/auth/auth.go index 488ba91..3199918 100644 --- a/shrine/utils/auth/auth.go +++ b/shrine/utils/auth/auth.go @@ -53,6 +53,12 @@ func IsAuthenticated(context *fiber.Ctx) bool { return false } + now := time.Now() + if token.User.LastSeenAt == nil || time.Since(*token.User.LastSeenAt) > time.Minute { + token.User.LastSeenAt = &now + repositories.UpdateLastSeen(&token.User) + } + context.Locals(userKey, &token.User) context.Locals(tokenHashKey, tokenHash) return true diff --git a/shrine/utils/shortcuts/response.go b/shrine/utils/shortcuts/response.go index e90f16b..70332ab 100644 --- a/shrine/utils/shortcuts/response.go +++ b/shrine/utils/shortcuts/response.go @@ -12,5 +12,8 @@ func Response(ctx *fiber.Ctx, data any) *response { func (r *response) As(status int) error { r.status = status + if r.data == nil { + return r.ctx.SendStatus(status) + } return r.ctx.Status(status).JSON(r.data) } |
