mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(server): initialize project skeleton with db and api setup
Establish the foundational structure for the ez-api server. Key changes include: - Set up main entry point with graceful shutdown and Gin router - Configure database connections for PostgreSQL (GORM) and Redis - Define core data models (User, Provider, Key, Model) - Implement configuration loading and basic key creation handler - Add Dockerfile for multi-stage builds and .gitignore
This commit is contained in:
42
internal/api/handler.go
Normal file
42
internal/api/handler.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/ez-api/ez-api/internal/model"
|
||||
"github.com/ez-api/ez-api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
db *gorm.DB
|
||||
sync *service.SyncService
|
||||
}
|
||||
|
||||
func NewHandler(db *gorm.DB, sync *service.SyncService) *Handler {
|
||||
return &Handler{db: db, sync: sync}
|
||||
}
|
||||
|
||||
func (h *Handler) CreateKey(c *gin.Context) {
|
||||
var key model.Key
|
||||
if err := c.ShouldBindJSON(&key); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Save to DB
|
||||
if err := h.db.Create(&key).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create key", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Sync to Redis
|
||||
if err := h.sync.SyncKey(&key); err != nil {
|
||||
// Note: In a real system, we might want to rollback DB or retry async
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync key to Redis", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, key)
|
||||
}
|
||||
58
internal/config/config.go
Normal file
58
internal/config/config.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Postgres PostgresConfig
|
||||
Redis RedisConfig
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
DSN string
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Addr string
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
return &Config{
|
||||
Server: ServerConfig{
|
||||
Port: getEnv("EZ_API_PORT", "8080"),
|
||||
},
|
||||
Postgres: PostgresConfig{
|
||||
DSN: getEnv("EZ_PG_DSN", "host=localhost user=postgres password=postgres dbname=ezapi port=5432 sslmode=disable"),
|
||||
},
|
||||
Redis: RedisConfig{
|
||||
Addr: getEnv("EZ_REDIS_ADDR", "localhost:6379"),
|
||||
Password: getEnv("EZ_REDIS_PASSWORD", ""),
|
||||
DB: getEnvInt("EZ_REDIS_DB", 0),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvInt(key string, fallback int) int {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
if i, err := strconv.Atoi(value); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
37
internal/model/models.go
Normal file
37
internal/model/models.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Username string `gorm:"uniqueIndex;not null"`
|
||||
Quota int64 `gorm:"default:0"`
|
||||
Role string `gorm:"default:'user'"` // admin, user
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"not null"`
|
||||
Type string `gorm:"not null"` // openai, anthropic, etc.
|
||||
BaseURL string
|
||||
APIKey string
|
||||
}
|
||||
|
||||
type Key struct {
|
||||
gorm.Model
|
||||
ProviderID *uint
|
||||
Provider *Provider
|
||||
KeySecret string `gorm:"not null"`
|
||||
Balance float64
|
||||
Status string `gorm:"default:'active'"` // active, suspended
|
||||
Weight int `gorm:"default:10"`
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"uniqueIndex;not null"`
|
||||
ContextWindow int
|
||||
CostPerToken float64
|
||||
}
|
||||
44
internal/service/sync.go
Normal file
44
internal/service/sync.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/ez-api/ez-api/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type SyncService struct {
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
func NewSyncService(rdb *redis.Client) *SyncService {
|
||||
return &SyncService{rdb: rdb}
|
||||
}
|
||||
|
||||
func (s *SyncService) SyncKey(key *model.Key) error {
|
||||
// Hash the token
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(key.KeySecret))
|
||||
tokenHash := hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
redisKey := fmt.Sprintf("auth:token:%s", tokenHash)
|
||||
|
||||
// Store in Redis
|
||||
// Using HSet to store multiple fields
|
||||
fields := map[string]interface{}{
|
||||
"status": key.Status,
|
||||
"balance": key.Balance,
|
||||
}
|
||||
if key.ProviderID != nil {
|
||||
fields["master_id"] = *key.ProviderID
|
||||
} else {
|
||||
fields["master_id"] = 0 // Default or handle as needed
|
||||
}
|
||||
|
||||
err := s.rdb.HSet(context.Background(), redisKey, fields).Err()
|
||||
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user