UPDATE: Auth module, User module
Some checks failed
Build and Release / release (push) Failing after 1m25s
Some checks failed
Build and Release / release (push) Failing after 1m25s
This commit is contained in:
83
pkg/cache/redis.go
vendored
83
pkg/cache/redis.go
vendored
@@ -5,18 +5,22 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"history-api/pkg/config"
|
||||
"history-api/pkg/constants"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Cache interface {
|
||||
Set(ctx context.Context, key string, value any, ttl time.Duration) error
|
||||
Get(ctx context.Context, key string, dest any) error
|
||||
Del(ctx context.Context, keys ...string) error
|
||||
DelByPattern(ctx context.Context, pattern string) error
|
||||
MGet(ctx context.Context, keys ...string) [][]byte
|
||||
MSet(ctx context.Context, pairs map[string]any, ttl time.Duration) error
|
||||
Set(ctx context.Context, key string, value any, ttl time.Duration) error
|
||||
Get(ctx context.Context, key string, dest any) error
|
||||
Del(ctx context.Context, keys ...string) error
|
||||
DelByPattern(ctx context.Context, pattern string) error
|
||||
MGet(ctx context.Context, keys ...string) [][]byte
|
||||
MSet(ctx context.Context, pairs map[string]any, ttl time.Duration) error
|
||||
Exists(ctx context.Context, key string) (bool, error)
|
||||
GetRawClient() *redis.Client
|
||||
PublishTask(ctx context.Context, streamName string, taskType constants.TaskType, payload any) error
|
||||
}
|
||||
|
||||
type RedisClient struct {
|
||||
@@ -49,33 +53,45 @@ func NewRedisClient() (Cache, error) {
|
||||
return &RedisClient{client: rdb}, nil
|
||||
}
|
||||
|
||||
func (r *RedisClient) GetRawClient() *redis.Client {
|
||||
return r.client
|
||||
}
|
||||
|
||||
func (r *RedisClient) Exists(ctx context.Context, key string) (bool, error) {
|
||||
count, err := r.client.Exists(ctx, key).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (r *RedisClient) Del(ctx context.Context, keys ...string) error {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.client.Del(ctx, keys...).Err()
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.client.Del(ctx, keys...).Err()
|
||||
}
|
||||
|
||||
func (r *RedisClient) DelByPattern(ctx context.Context, pattern string) error {
|
||||
var cursor uint64
|
||||
for {
|
||||
keys, nextCursor, err := r.client.Scan(ctx, cursor, pattern, 100).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error scanning keys with pattern %s: %v", pattern, err)
|
||||
}
|
||||
var cursor uint64
|
||||
for {
|
||||
keys, nextCursor, err := r.client.Scan(ctx, cursor, pattern, 1000).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error scanning keys with pattern %s: %v", pattern, err)
|
||||
}
|
||||
|
||||
if len(keys) > 0 {
|
||||
if err := r.client.Del(ctx, keys...).Err(); err != nil {
|
||||
return fmt.Errorf("error deleting keys during scan: %v", err)
|
||||
if len(keys) > 0 {
|
||||
if err := r.client.Unlink(ctx, keys...).Err(); err != nil {
|
||||
return fmt.Errorf("error unlinking keys during scan: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor = nextCursor
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
cursor = nextCursor
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RedisClient) Set(ctx context.Context, key string, value any, ttl time.Duration) error {
|
||||
@@ -121,6 +137,21 @@ func (r *RedisClient) MGet(ctx context.Context, keys ...string) [][]byte {
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *RedisClient) PublishTask(ctx context.Context, streamName string, taskType constants.TaskType, payload any) error {
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.client.XAdd(ctx, &redis.XAddArgs{
|
||||
Stream: streamName,
|
||||
Values: map[string]interface{}{
|
||||
"task_type": taskType.String(),
|
||||
"payload": string(payloadBytes),
|
||||
},
|
||||
}).Err()
|
||||
}
|
||||
|
||||
func GetMultiple[T any](ctx context.Context, c Cache, keys []string) ([]T, error) {
|
||||
raws := c.MGet(ctx, keys...)
|
||||
final := make([]T, 0)
|
||||
|
||||
@@ -34,3 +34,11 @@ func GetConfig(config string) (string, error) {
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetConfigWithDefault(config, defaultValue string) string {
|
||||
var data string = os.Getenv(config)
|
||||
if data == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package constant
|
||||
package constants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package constant
|
||||
package constants
|
||||
|
||||
type Role string
|
||||
|
||||
@@ -18,10 +18,15 @@ func (r Role) Compare(other Role) bool {
|
||||
return r == other
|
||||
}
|
||||
|
||||
func (r Role) IsValid() bool {
|
||||
return CheckValidRole(r)
|
||||
}
|
||||
|
||||
func CheckValidRole(r Role) bool {
|
||||
return r == ADMIN || r == MOD || r == HISTORIAN || r == USER || r == BANNED
|
||||
}
|
||||
|
||||
|
||||
func ParseRole(s string) (Role, bool) {
|
||||
r := Role(s)
|
||||
if CheckValidRole(r) {
|
||||
6
pkg/constants/sream.go
Normal file
6
pkg/constants/sream.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
StreamEmailName = "stream:email_tasks"
|
||||
GroupEmailName = "email_workers_group"
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package constant
|
||||
package constants
|
||||
|
||||
type StatusType int16
|
||||
|
||||
11
pkg/constants/task.go
Normal file
11
pkg/constants/task.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package constants
|
||||
|
||||
type TaskType string
|
||||
|
||||
const (
|
||||
TaskTypeSendEmailOTP TaskType = "SEND_EMAIL_OTP"
|
||||
)
|
||||
|
||||
func (t TaskType) String() string {
|
||||
return string(t)
|
||||
}
|
||||
13
pkg/constants/time.go
Normal file
13
pkg/constants/time.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TokenCooldownDuration = 1 * time.Minute
|
||||
TokenExpirationDuration = 20 * time.Minute
|
||||
NormalCacheDuration = 15 * time.Minute
|
||||
ListCacheDuration = 10 * time.Minute
|
||||
AccessTokenDuration = 15 * time.Minute
|
||||
RefreshTokenDuration = 7 * 24 * time.Hour
|
||||
TokenVerifiedDuration = 10 * time.Minute
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package constant
|
||||
package constants
|
||||
|
||||
type TokenType int16
|
||||
|
||||
@@ -24,6 +24,10 @@ func (t TokenType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (t TokenType) Value() int16 {
|
||||
return int16(t)
|
||||
}
|
||||
|
||||
func ParseTokenType(v int16) TokenType {
|
||||
switch v {
|
||||
case 1:
|
||||
@@ -37,4 +41,19 @@ func ParseTokenType(v int16) TokenType {
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func ParseTokenTypeFromString(s string) TokenType {
|
||||
switch s {
|
||||
case "PASSWORD_RESET":
|
||||
return TokenPasswordReset
|
||||
case "EMAIL_VERIFY":
|
||||
return TokenEmailVerify
|
||||
case "LOGIN_MAGIC_LINK":
|
||||
return TokenMagicLink
|
||||
case "REFRESH_TOKEN":
|
||||
return TokenRefreshToken
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package constant
|
||||
package constants
|
||||
|
||||
type VerifyType int16
|
||||
|
||||
@@ -13,6 +13,15 @@ func UUIDToString(v pgtype.UUID) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func StringToUUID(s string) (pgtype.UUID, error) {
|
||||
var pgId pgtype.UUID
|
||||
err := pgId.Scan(s)
|
||||
if err != nil {
|
||||
return pgtype.UUID{}, err
|
||||
}
|
||||
return pgId, nil
|
||||
}
|
||||
|
||||
func TextToString(v pgtype.Text) string {
|
||||
if v.Valid {
|
||||
return v.String
|
||||
|
||||
70
pkg/email/email.go
Normal file
70
pkg/email/email.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"history-api/assets"
|
||||
"history-api/pkg/config"
|
||||
"history-api/pkg/constants"
|
||||
"strings"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
func SendMailOTP(toEmail, otpCode string, tokenType constants.TokenType) error {
|
||||
userSmtp, err := config.GetConfig("SMTP_USER")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
passSmtp, err := config.GetConfig("SMTP_PASS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var subject string
|
||||
var templatePath string
|
||||
|
||||
switch tokenType {
|
||||
case constants.TokenPasswordReset:
|
||||
subject = "Your Password Reset Code"
|
||||
templatePath = "resources/password_reset.html"
|
||||
case constants.TokenEmailVerify:
|
||||
subject = "Verify your email address"
|
||||
templatePath = "resources/email_verify.html"
|
||||
default:
|
||||
return fmt.Errorf("invalid token type: %v", tokenType)
|
||||
}
|
||||
htmlTemplate, err := assets.GetFileContent(templatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read email template: %s", err)
|
||||
}
|
||||
|
||||
message := mail.NewMsg()
|
||||
if err := message.From(userSmtp); err != nil {
|
||||
return fmt.Errorf("failed to set From email address: %s", err)
|
||||
}
|
||||
if err := message.To(toEmail); err != nil {
|
||||
return fmt.Errorf("failed to set To email address: %s", err)
|
||||
}
|
||||
|
||||
finalHTML := strings.ReplaceAll(htmlTemplate, "{{OTP_CODE}}", otpCode)
|
||||
|
||||
message.Subject(subject)
|
||||
message.SetBodyString(mail.TypeTextHTML, finalHTML)
|
||||
client, err := mail.NewClient(
|
||||
"smtp.gmail.com",
|
||||
mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover),
|
||||
mail.WithTLSPortPolicy(mail.TLSMandatory),
|
||||
mail.WithUsername(userSmtp),
|
||||
mail.WithPassword(passSmtp),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create mail client: %s", err)
|
||||
}
|
||||
|
||||
err = client.DialAndSend(message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send mail: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user