Complete rewrite of Django REST API to Go with: - Gin web framework for HTTP routing - GORM for database operations - GoAdmin for admin panel - Gorush integration for push notifications - Redis for caching and job queues Features implemented: - User authentication (login, register, logout, password reset) - Residence management (CRUD, sharing, share codes) - Task management (CRUD, kanban board, completions) - Contractor management (CRUD, specialties) - Document management (CRUD, warranties) - Notifications (preferences, push notifications) - Subscription management (tiers, limits) Infrastructure: - Docker Compose for local development - Database migrations and seed data - Admin panel for data management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/treytartt/mycrib-api/internal/config"
|
|
)
|
|
|
|
// CacheService provides Redis caching functionality
|
|
type CacheService struct {
|
|
client *redis.Client
|
|
}
|
|
|
|
var cacheInstance *CacheService
|
|
|
|
// NewCacheService creates a new cache service
|
|
func NewCacheService(cfg *config.RedisConfig) (*CacheService, error) {
|
|
opt, err := redis.ParseURL(cfg.URL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse Redis URL: %w", err)
|
|
}
|
|
|
|
if cfg.Password != "" {
|
|
opt.Password = cfg.Password
|
|
}
|
|
if cfg.DB != 0 {
|
|
opt.DB = cfg.DB
|
|
}
|
|
|
|
client := redis.NewClient(opt)
|
|
|
|
// Test connection
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := client.Ping(ctx).Err(); err != nil {
|
|
return nil, fmt.Errorf("failed to connect to Redis: %w", err)
|
|
}
|
|
|
|
log.Info().
|
|
Str("url", cfg.URL).
|
|
Int("db", opt.DB).
|
|
Msg("Connected to Redis")
|
|
|
|
cacheInstance = &CacheService{client: client}
|
|
return cacheInstance, nil
|
|
}
|
|
|
|
// GetCache returns the cache service instance
|
|
func GetCache() *CacheService {
|
|
return cacheInstance
|
|
}
|
|
|
|
// Client returns the underlying Redis client
|
|
func (c *CacheService) Client() *redis.Client {
|
|
return c.client
|
|
}
|
|
|
|
// Set stores a value with expiration
|
|
func (c *CacheService) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal value: %w", err)
|
|
}
|
|
|
|
return c.client.Set(ctx, key, data, expiration).Err()
|
|
}
|
|
|
|
// Get retrieves a value by key
|
|
func (c *CacheService) Get(ctx context.Context, key string, dest interface{}) error {
|
|
data, err := c.client.Get(ctx, key).Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return json.Unmarshal(data, dest)
|
|
}
|
|
|
|
// GetString retrieves a string value by key
|
|
func (c *CacheService) GetString(ctx context.Context, key string) (string, error) {
|
|
return c.client.Get(ctx, key).Result()
|
|
}
|
|
|
|
// SetString stores a string value with expiration
|
|
func (c *CacheService) SetString(ctx context.Context, key string, value string, expiration time.Duration) error {
|
|
return c.client.Set(ctx, key, value, expiration).Err()
|
|
}
|
|
|
|
// Delete removes a key
|
|
func (c *CacheService) Delete(ctx context.Context, keys ...string) error {
|
|
return c.client.Del(ctx, keys...).Err()
|
|
}
|
|
|
|
// Exists checks if a key exists
|
|
func (c *CacheService) Exists(ctx context.Context, keys ...string) (int64, error) {
|
|
return c.client.Exists(ctx, keys...).Result()
|
|
}
|
|
|
|
// Close closes the Redis connection
|
|
func (c *CacheService) Close() error {
|
|
if c.client != nil {
|
|
return c.client.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Auth token cache helpers
|
|
const (
|
|
AuthTokenPrefix = "auth_token_"
|
|
TokenCacheTTL = 5 * time.Minute
|
|
)
|
|
|
|
// CacheAuthToken caches a user ID for a token
|
|
func (c *CacheService) CacheAuthToken(ctx context.Context, token string, userID uint) error {
|
|
key := AuthTokenPrefix + token
|
|
return c.SetString(ctx, key, fmt.Sprintf("%d", userID), TokenCacheTTL)
|
|
}
|
|
|
|
// GetCachedAuthToken gets a cached user ID for a token
|
|
func (c *CacheService) GetCachedAuthToken(ctx context.Context, token string) (uint, error) {
|
|
key := AuthTokenPrefix + token
|
|
val, err := c.GetString(ctx, key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var userID uint
|
|
_, err = fmt.Sscanf(val, "%d", &userID)
|
|
return userID, err
|
|
}
|
|
|
|
// InvalidateAuthToken removes a cached token
|
|
func (c *CacheService) InvalidateAuthToken(ctx context.Context, token string) error {
|
|
key := AuthTokenPrefix + token
|
|
return c.Delete(ctx, key)
|
|
}
|
|
|
|
// Static data cache helpers
|
|
const (
|
|
StaticDataKey = "static_data"
|
|
StaticDataTTL = 1 * time.Hour
|
|
)
|
|
|
|
// CacheStaticData caches static lookup data
|
|
func (c *CacheService) CacheStaticData(ctx context.Context, data interface{}) error {
|
|
return c.Set(ctx, StaticDataKey, data, StaticDataTTL)
|
|
}
|
|
|
|
// GetCachedStaticData retrieves cached static data
|
|
func (c *CacheService) GetCachedStaticData(ctx context.Context, dest interface{}) error {
|
|
return c.Get(ctx, StaticDataKey, dest)
|
|
}
|
|
|
|
// InvalidateStaticData removes cached static data
|
|
func (c *CacheService) InvalidateStaticData(ctx context.Context) error {
|
|
return c.Delete(ctx, StaticDataKey)
|
|
}
|