summaryrefslogtreecommitdiff
path: root/shrine
diff options
context:
space:
mode:
Diffstat (limited to 'shrine')
-rw-r--r--shrine/controllers/auth.go4
-rw-r--r--shrine/controllers/responses.go4
-rw-r--r--shrine/controllers/stats.go30
-rw-r--r--shrine/models/user.go9
-rw-r--r--shrine/repositories/user.go28
-rw-r--r--shrine/router/auth.go1
-rw-r--r--shrine/router/stats.go13
-rw-r--r--shrine/types/response.go14
-rw-r--r--shrine/utils/auth/auth.go6
-rw-r--r--shrine/utils/shortcuts/response.go3
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)
}