Add event service, caddy test domain

Signed-off-by: Asai Neko <sugar@sne.moe>
This commit is contained in:
2025-12-27 03:45:31 +08:00
parent 2b99d415de
commit afc62f311b
12 changed files with 145 additions and 12 deletions

View File

@@ -1,9 +1,31 @@
SERVER_APPLICATION=nixcn-cms
SERVER_ADDRESS=:8000
SERVER_EXTERNAL_URL=http://test.sne.moe
SERVER_DEBUG_MODE=true
SERVER_FILE_LOGGER=false
SERVER_JWT_SECRET=test
DATABASE_TYPE=postgres
DATABASE_HOST=127.0.0.1
DATABASE_NAME=postgres
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=postgres
CACHE_HOSTS=["127.0.0.1:6379"]
CACHE_MASTER=
CACHE_USERNAME=
CACHE_PASSWORD=
CACHE_DB=0
SEARCH_HOST=127.0.0.1
SEARCH_API_KEY=
EMAIL_RESEND_API_KEY=
EMAIL_FROM=
SECRETS_JWT_SECRET=something
SECRETS_TURNSTILE_SECRET=something
TTL_MAGIC_LINK_TTL=10m
TTL_JWT_TTL=15s
TTL_REFRESH_TTL=7d
TTL_CHECKIN_COSE_TTL=10m

View File

@@ -28,4 +28,5 @@ secrets:
ttl:
magic_link_ttl: 10m
jwt_ttl: 15s
refresh_ttl: 48h
refresh_ttl: 7d
checkin_code_ttl: 5m

View File

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

View File

@@ -1,11 +1,17 @@
package data
import (
"context"
"errors"
"fmt"
"math/rand"
"strings"
"time"
"github.com/go-viper/mapstructure/v2"
"github.com/google/uuid"
"github.com/meilisearch/meilisearch-go"
"github.com/spf13/viper"
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@@ -157,3 +163,62 @@ func (self *User) FastListUsers(limit, offset int64) (*[]UserSearchDoc, error) {
}
return &list, nil
}
func (self *User) GenCheckinCode(eventId uuid.UUID) (*string, error) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
randomNumber := rng.Intn(900000) + 100000
randNumString := fmt.Sprintf("%06d", randomNumber)
ctx := context.Background()
ttl := viper.GetDuration("ttl.checkin_code_ttl")
Redis.Set(
ctx,
"checkin_code:"+randNumString,
self.UserId.String()+":"+eventId.String(),
ttl,
)
return &randNumString, nil
}
func (self *User) VerifyCheckinCode(checkinCode string) (*uuid.UUID, error) {
ctx := context.Background()
result := Redis.Get(ctx, "checkin_code:"+checkinCode).String()
if result == "" {
return nil, errors.New("invalid or expired checkin code")
}
split := strings.Split(result, ":")
if len(split) < 2 {
return nil, errors.New("invalid checkin code format")
}
userId := split[0]
eventId := split[1]
var returnedUserId uuid.UUID
err := Database.Transaction(func(tx *gorm.DB) error {
checkinData := map[string]interface{}{
eventId: time.Now(),
}
if err := tx.Model(&User{}).Where("user_id = ?", userId).Updates(map[string]interface{}{
"checkin": checkinData,
}).Error; err != nil {
return err
}
parsedUserId, err := uuid.Parse(userId)
if err != nil {
return err
}
returnedUserId = parsedUserId
return nil
})
if err != nil {
return nil, err
}
return &returnedUserId, nil
}

View File

@@ -26,7 +26,8 @@
enable = true;
dataDir = "${config.env.DEVENV_STATE}/caddy";
config = ''
:8080 {
test.sne.moe {
tls internal
handle /api/* {
reverse_proxy 127.0.0.1:8000
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"nixcn-cms/data"
"time"
@@ -29,7 +30,7 @@ func (self *Token) NewClaims() JwtClaims {
return JwtClaims{
UserID: self.UserID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.jwt_ttl"))),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(viper.GetDuration("ttl.assess_ttl"))),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: self.Application,
},
@@ -41,7 +42,11 @@ func (self *Token) GenerateAccessToken() (string, error) {
claims := self.NewClaims()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secret := viper.GetString("secrets.jwt_secret")
return token.SignedString(secret)
signedToken, err := token.SignedString([]byte(secret))
if err != nil {
return "", fmt.Errorf("error signing token: %v", err)
}
return signedToken, nil
}
// Generate refresh token
@@ -50,7 +55,7 @@ func (self *Token) GenerateRefreshToken() (string, error) {
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
return base64.URLEncoding.EncodeToString(b), nil
}
// Issue both access and refresh token

View File

@@ -0,0 +1 @@
package event

View File

@@ -35,7 +35,6 @@ func Info(c *gin.Context) {
}
c.JSON(200, gin.H{
"event_id": event.EventId,
"name": event.Name,
"start_time": event.StartTime,
"end_time": event.EndTime,

View File

@@ -0,0 +1 @@
package event

View File

@@ -0,0 +1 @@
package event

View File

@@ -2,7 +2,6 @@ package user
import (
"nixcn-cms/data"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
@@ -35,7 +34,43 @@ func Checkin(c *gin.Context) {
})
return
}
data.UpdateCheckin(userId.(uuid.UUID), eventId, time.Now())
data.UserId = userId.(uuid.UUID)
code, err := data.GenCheckinCode(eventId)
if err != nil {
c.JSON(500, gin.H{
"status": "error generating code",
})
return
}
c.JSON(200, gin.H{
"checkin_code": code,
})
}
func CheckinSubmit(c *gin.Context) {
var req struct {
ChekinCode string `json:"checkin_code"`
}
c.ShouldBindJSON(&req)
data := new(data.User)
userId, err := data.VerifyCheckinCode(req.ChekinCode)
if err != nil {
c.JSON(400, gin.H{
"status": "error verify checkin code",
})
return
}
data.GetByUserId(*userId)
if data.PermissionLevel <= 20 {
c.JSON(403, gin.H{
"status": "access denied",
})
}
c.JSON(200, gin.H{
"status": "success",
})

View File

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