2
0
Files
logikonline 9e6d1d63de
All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m10s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 5m5s
Build and Release / Lint (push) Successful in 5m20s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 2m54s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m29s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 8m14s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 8m19s
Build and Release / Build Binary (linux/arm64) (push) Successful in 8m27s
feat(blog): add comments, reactions, and guest verification
Implements threaded comment system with support for authenticated users and verified guests. Adds email verification flow for guest commenters with token-based sessions and 6-digit codes. Includes reaction system (like/love/laugh/etc) for posts and comments. Adds comment count to blog posts, user profile blog tab, and email notifications for comment verification. Implements nested reply support with parent-child relationships.
2026-02-01 22:22:18 -05:00

117 lines
3.1 KiB
Go

// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package blog
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"math/big"
"code.gitcaddy.com/server/v3/models/db"
"code.gitcaddy.com/server/v3/modules/timeutil"
)
// BlogGuestToken represents an email-verified anonymous commenter session.
type BlogGuestToken struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
Email string `xorm:"VARCHAR(255) NOT NULL"`
Name string `xorm:"VARCHAR(100) NOT NULL"`
Token string `xorm:"VARCHAR(64) UNIQUE NOT NULL"`
Code string `xorm:"VARCHAR(6) NOT NULL"`
Verified bool `xorm:"NOT NULL DEFAULT false"`
IP string `xorm:"VARCHAR(45)"`
ExpiresUnix timeutil.TimeStamp `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
func init() {
db.RegisterModel(new(BlogGuestToken))
}
// GenerateToken creates a random hex token.
func GenerateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
// GenerateVerificationCode creates a random 6-digit code.
func GenerateVerificationCode() (string, error) {
n, err := rand.Int(rand.Reader, big.NewInt(1000000))
if err != nil {
return "", err
}
return fmt.Sprintf("%06d", n.Int64()), nil
}
// CreateGuestToken inserts a new guest token.
func CreateGuestToken(ctx context.Context, t *BlogGuestToken) error {
_, err := db.GetEngine(ctx).Insert(t)
return err
}
// GetGuestTokenByToken returns a guest token by its token string.
func GetGuestTokenByToken(ctx context.Context, token string) (*BlogGuestToken, error) {
t := &BlogGuestToken{}
has, err := db.GetEngine(ctx).Where("token = ?", token).Get(t)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return t, nil
}
// GetGuestTokenByEmail returns an existing non-expired token for the given email.
func GetGuestTokenByEmail(ctx context.Context, email string) (*BlogGuestToken, error) {
t := &BlogGuestToken{}
has, err := db.GetEngine(ctx).
Where("email = ? AND expires_unix > ?", email, timeutil.TimeStampNow()).
OrderBy("created_unix DESC").
Get(t)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return t, nil
}
// VerifyGuestToken marks a token as verified if the code matches.
func VerifyGuestToken(ctx context.Context, token, code string) (*BlogGuestToken, error) {
t, err := GetGuestTokenByToken(ctx, token)
if err != nil {
return nil, err
}
if t == nil {
return nil, nil
}
if t.Code != code {
return nil, nil
}
if timeutil.TimeStampNow() > t.ExpiresUnix {
return nil, nil
}
t.Verified = true
if _, err := db.GetEngine(ctx).ID(t.ID).Cols("verified").Update(t); err != nil {
return nil, err
}
return t, nil
}
// CleanupExpiredGuestTokens removes expired guest tokens.
func CleanupExpiredGuestTokens(ctx context.Context) error {
_, err := db.GetEngine(ctx).Where("expires_unix < ?", timeutil.TimeStampNow()).Delete(new(BlogGuestToken))
return err
}