forked from nixcn/nixcn-cms
Add event service, caddy test domain
Signed-off-by: Asai Neko <sugar@sne.moe>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -28,4 +28,5 @@ secrets:
|
||||
ttl:
|
||||
magic_link_ttl: 10m
|
||||
jwt_ttl: 15s
|
||||
refresh_ttl: 48h
|
||||
refresh_ttl: 7d
|
||||
checkin_code_ttl: 5m
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
65
data/user.go
65
data/user.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package event
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package event
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package event
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user