Add event query api

Signed-off-by: Asai Neko <sugar@sne.moe>
This commit is contained in:
2025-12-28 01:05:47 +08:00
parent b6003544c8
commit b3fe91444d
8 changed files with 79 additions and 43 deletions

View File

@@ -28,5 +28,5 @@ SECRETS_TURNSTILE_SECRET=0x4AAAAAACI5pgVONOZ0rzyAYsdUcoOBF8w
TTL_MAGIC_LINK_TTL=10m TTL_MAGIC_LINK_TTL=10m
TTL_ACCESS_TTL=15s TTL_ACCESS_TTL=15s
TTL_REFRESH_TTL=7d TTL_REFRESH_TTL=168h
TTL_CHECKIN_COSE_TTL=10m TTL_CHECKIN_COSE_TTL=10m

View File

@@ -51,7 +51,7 @@ type secrets struct {
type ttl struct { type ttl struct {
MagicLinkTTL string `yaml:"magic_link_ttl"` MagicLinkTTL string `yaml:"magic_link_ttl"`
JwtTTL string `yaml:"jwt_ttl"` AccessTTL string `yaml:"access_ttl"`
RefreshTTL string `yaml:"refresh_ttl"` RefreshTTL string `yaml:"refresh_ttl"`
CheckinCodeTTL string `yaml:"checkin_code_ttl"` CheckinCodeTTL string `yaml:"checkin_code_ttl"`
} }

View File

@@ -4,19 +4,16 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"nixcn-cms/data" "nixcn-cms/data"
"time" "time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/redis/go-redis/v9"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type Token struct { type Token struct {
UserID uuid.UUID
Application string Application string
} }
@@ -26,9 +23,9 @@ type JwtClaims struct {
} }
// Generate jwt clames // Generate jwt clames
func (self *Token) NewClaims() JwtClaims { func (self *Token) NewClaims(userId uuid.UUID) JwtClaims {
return JwtClaims{ return JwtClaims{
UserID: self.UserID, UserID: userId,
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.access_ttl"))), ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.access_ttl"))),
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),
@@ -38,8 +35,8 @@ func (self *Token) NewClaims() JwtClaims {
} }
// Generate access token // Generate access token
func (self *Token) GenerateAccessToken() (string, error) { func (self *Token) GenerateAccessToken(userId uuid.UUID) (string, error) {
claims := self.NewClaims() claims := self.NewClaims(userId)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secret := viper.GetString("secrets.jwt_secret") secret := viper.GetString("secrets.jwt_secret")
signedToken, err := token.SignedString([]byte(secret)) signedToken, err := token.SignedString([]byte(secret))
@@ -59,9 +56,9 @@ func (self *Token) GenerateRefreshToken() (string, error) {
} }
// Issue both access and refresh token // Issue both access and refresh token
func (self *Token) IssueTokens() (string, string, error) { func (self *Token) IssueTokens(userId uuid.UUID) (string, string, error) {
// Gen atk // Gen atk
access, err := self.GenerateAccessToken() access, err := self.GenerateAccessToken(userId)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@@ -80,14 +77,14 @@ func (self *Token) IssueTokens() (string, string, error) {
if err := data.Redis.Set( if err := data.Redis.Set(
ctx, ctx,
"refresh:"+refresh, "refresh:"+refresh,
self.UserID.String(), userId.String(),
ttl, ttl,
).Err(); err != nil { ).Err(); err != nil {
return "", "", err return "", "", err
} }
// user -> refresh tokens // user -> refresh tokens
userSetKey := "user:" + self.UserID.String() + ":refresh_tokens" userSetKey := "user:" + userId.String() + ":refresh_tokens"
if err := data.Redis.SAdd( if err := data.Redis.SAdd(
ctx, ctx,
@@ -109,49 +106,52 @@ func (self *Token) RefreshAccessToken(refreshToken string) (string, error) {
ctx := context.Background() ctx := context.Background()
key := "refresh:" + refreshToken key := "refresh:" + refreshToken
userIDStr, err := data.Redis.Get(ctx, key).Result() userIdStr, err := data.Redis.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
return "", errors.New("invalid refresh token")
}
return "", err
}
userID, err := uuid.Parse(userIDStr)
if err != nil { if err != nil {
return "", err return "", err
} }
self.UserID = userID userId, err := uuid.Parse(userIdStr)
if err != nil {
return "", err
}
// Generate access token // Generate access token
return self.GenerateAccessToken() return self.GenerateAccessToken(userId)
} }
func (self *Token) RenewRefreshToken(refreshToken string) (string, error) { func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
err := self.RevokeRefreshToken(refreshToken) ctx := context.Background()
ttl := viper.GetDuration("ttl.refresh_ttl")
key := "refresh:" + refreshToken
userIdStr, err := data.Redis.Get(ctx, key).Result()
if err != nil { if err != nil {
return "", err return "", err
} }
refresh, err := self.GenerateRefreshToken() refresh, err := self.GenerateRefreshToken()
if err != nil {
return "", err
}
// Store to redis err = self.RevokeRefreshToken(refreshToken)
ctx := context.Background() if err != nil {
ttl := viper.GetDuration("ttl.refresh_ttl") return "", err
}
// refresh -> user // refresh -> user
if err := data.Redis.Set( if err := data.Redis.Set(
ctx, ctx,
"refresh:"+refresh, "refresh:"+refresh,
self.UserID.String(), userIdStr,
ttl, ttl,
).Err(); err != nil { ).Err(); err != nil {
return "", err return "", err
} }
// user -> refresh tokens // user -> refresh tokens
userSetKey := "user:" + self.UserID.String() + ":refresh_tokens" userSetKey := "user:" + userIdStr + ":refresh_tokens"
if err := data.Redis.SAdd( if err := data.Redis.SAdd(
ctx, ctx,

View File

@@ -99,10 +99,9 @@ func VerifyMagicLink(c *gin.Context) {
// Generate jwt // Generate jwt
JwtTool := cryptography.Token{ JwtTool := cryptography.Token{
UserID: user.UserId,
Application: viper.GetString("server.application"), Application: viper.GetString("server.application"),
} }
accessToken, refreshToken, err := JwtTool.IssueTokens() accessToken, refreshToken, err := JwtTool.IssueTokens(user.UserId)
if err != nil { if err != nil {
c.JSON(500, gin.H{ c.JSON(500, gin.H{
"status": "error generating tokens", "status": "error generating tokens",

View File

@@ -13,7 +13,7 @@ func Refresh(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "invalid request"}) c.JSON(400, gin.H{"status": "invalid request"})
return return
} }
@@ -23,18 +23,13 @@ func Refresh(c *gin.Context) {
access, err := JwtTool.RefreshAccessToken(req.RefreshToken) access, err := JwtTool.RefreshAccessToken(req.RefreshToken)
if err != nil { if err != nil {
c.JSON(401, gin.H{"error": "invalid refresh token"}) c.JSON(401, gin.H{"status": "invalid refresh token"})
return return
} }
err = JwtTool.RevokeRefreshToken(req.RefreshToken) refresh, err := JwtTool.RenewRefreshToken(req.RefreshToken)
if err != nil { if err != nil {
c.JSON(500, gin.H{"status": "cannot revoke refresh token"}) c.JSON(500, gin.H{"statis": "error renew refresh token"})
}
refresh, err := JwtTool.GenerateRefreshToken()
if err != nil {
c.JSON(401, gin.H{"status": "cannot generate new refresh token"})
} }
c.JSON(200, gin.H{ c.JSON(200, gin.H{

View File

@@ -1,7 +1,13 @@
package event package event
import "github.com/gin-gonic/gin" import (
"nixcn-cms/middleware"
"github.com/gin-gonic/gin"
)
func Handler(r *gin.RouterGroup) { func Handler(r *gin.RouterGroup) {
r.Use(middleware.JWTAuth())
r.GET("/info", Info) r.GET("/info", Info)
r.GET("/query", Query)
} }

36
service/event/query.go Normal file
View File

@@ -0,0 +1,36 @@
package event
import (
"nixcn-cms/data"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func Query(c *gin.Context) {
userId, ok := c.Get("user_id")
if !ok {
c.JSON(400, gin.H{"status": "could not found user_id"})
return
}
eventId, ok := c.GetQuery("event_id")
if !ok {
c.JSON(400, gin.H{"status": "could not found event_id"})
return
}
data := new(data.User)
err := data.GetByUserId(userId.(uuid.UUID))
if err != nil {
c.JSON(404, gin.H{"status": "cannot found user"})
return
}
if data.Checkin[eventId] == nil {
c.JSON(404, gin.H{"status": "cannot found user checked in"})
return
}
c.JSON(200, gin.H{
"checkin_time": data.Checkin[eventId],
})
}

View File

@@ -9,7 +9,7 @@ import (
func Handler(r *gin.RouterGroup) { func Handler(r *gin.RouterGroup) {
r.Use(middleware.JWTAuth()) r.Use(middleware.JWTAuth())
r.GET("/info", Info) r.GET("/info", Info)
r.POST("/checkin", Checkin) r.GET("/checkin", Checkin)
r.POST("/checkin/submit", CheckinSubmit) r.POST("/checkin/submit", CheckinSubmit)
r.PATCH("/update", Update) r.PATCH("/update", Update)
r.GET("/list", List) r.GET("/list", List)