fix: deployment issues
Brijesh Wawdhane ops@brijesh.dev
Wed, 25 Dec 2024 15:36:23 +0530
9 files changed,
503 insertions(+),
456 deletions(-)
A
.dockerignore
@@ -0,0 +1,33 @@
+# Binaries for programs and plugins +**/*.exe +**/*.exe~ +**/*.dll +**/*.so +**/*.dylib + +# Test binary, built with "go test -c" +**/*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +**/*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +**/go.work +**/tmp + +# IDE specific files +**/.vscode +**/.idea + +# .env file +**/.env + +# Project build +**/main +**/*templ.go + +# OS X generated file +**/.DS_Store
M
Dockerfile
→
Dockerfile
@@ -12,8 +12,8 @@
FROM alpine:3.20.1 AS prod WORKDIR /app COPY --from=build /app/main /app/main -# COPY temp_file.der /app/temp_file.der -# COPY sf-class2-root.crt /app/sf-class2-root.crt -# COPY cassandra_truststore.jks /app/cassandra_truststore.jks + +COPY ./sf-class2-root.crt /app/sf-class2-root.crt + EXPOSE ${PORT} CMD ["./main"]
M
deployment.yaml
→
deployment.yaml
@@ -16,7 +16,7 @@ app: argus-core
spec: containers: - name: argus-core - image: brijeshwawdhane/argus-core:0.1.0-alpha.5 + image: brijeshwawdhane/argus-core:0.1.0-alpha.7 ports: - containerPort: 8080 envFrom:
M
internal/database/database.go
→
internal/database/database.go
@@ -2,79 +2,13 @@ package database
import ( "fmt" - "io" "log" - "net/http" "os" - "strings" - "time" "github.com/gocql/gocql" _ "github.com/joho/godotenv/autoload" - "golang.org/x/exp/rand" ) -type User struct { - ID gocql.UUID `json:"id"` - Email string `json:"email"` - PasswordHash string `json:"-"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type Application struct { - ID gocql.UUID `json:"id"` - UserID gocql.UUID `json:"user_id"` - Name string `json:"name"` - Description string `json:"description"` - KeyHash string `json:"-"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type Log struct { - ApplicationID gocql.UUID `json:"application_id"` - LogID gocql.UUID `json:"log_id"` - UserID gocql.UUID `json:"user_id"` - Timestamp time.Time `json:"timestamp"` - Level string `json:"level"` - Message string `json:"message"` -} - -type LogsFilter struct { - ApplicationID gocql.UUID - PageSize int - Cursor *time.Time - LogLevel string - StartTime *time.Time - EndTime *time.Time -} - -type Service interface { - Health() map[string]string - Close() error - - CreateUser(email, passwordHash string) (*User, error) - GetUserByEmail(email string) (*User, error) - GetUserByID(id gocql.UUID) (*User, error) - - CreateApplication(userID gocql.UUID, name, description, keyHash string) (*Application, error) - GetApplication(id gocql.UUID) (*Application, error) - ListApplications(userID gocql.UUID) ([]Application, error) - UpdateApplication(id gocql.UUID, name, description string) (*Application, error) - DeleteApplication(id gocql.UUID) error - RegenerateApplicationKey(appID gocql.UUID, newKeyHash string) error - - BatchInsertLogs(logs []Log) error - GetRecentLogs(filter LogsFilter) ([]Log, error) - ValidateApplicationKey(keyHash string) (*Application, error) // Helper method for API key validation - GenerateDummyLogs(applicationID gocql.UUID) (int, error) -} - -type service struct { - session *gocql.Session -} - func New() Service { // Load environment variables cassandraHost := os.Getenv("CASSANDRA_HOST")@@ -82,31 +16,17 @@ cassandraUsername := os.Getenv("CASSANDRA_USERNAME")
cassandraPassword := os.Getenv("CASSANDRA_PASSWORD") cassandraKeyspace := os.Getenv("CASSANDRA_KEYSPACE") - // Download certificate - resp, err := http.Get("https://certs.secureserver.net/repository/sf-class2-root.crt") - if err != nil { - log.Fatal("Failed to download certificate:", err) - } - defer resp.Body.Close() - - tempCertFile, err := os.CreateTemp("", "cassandra-cert-*.crt") - if err != nil { - log.Fatal("Failed to create temp cert file:", err) - } - defer os.Remove(tempCertFile.Name()) - - if _, err := io.Copy(tempCertFile, resp.Body); err != nil { - log.Fatal("Failed to write certificate to file:", err) - } + certFileName := "sf-class2-root.crt" cluster := gocql.NewCluster(cassandraHost) cluster.Port = 9142 + cluster.SslOpts = nil cluster.Authenticator = gocql.PasswordAuthenticator{ Username: cassandraUsername, Password: cassandraPassword, } cluster.SslOpts = &gocql.SslOptions{ - CaPath: tempCertFile.Name(), + CaPath: certFileName, EnableHostVerification: false, } cluster.Consistency = gocql.LocalQuorum@@ -138,372 +58,3 @@ func (s *service) Close() error {
s.session.Close() return nil } - -// User operations -func (s *service) CreateUser(email, passwordHash string) (*User, error) { - user := &User{ - ID: gocql.TimeUUID(), - Email: email, - PasswordHash: passwordHash, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - if err := s.session.Query(` - INSERT INTO users (id, email, password_hash, created_at, updated_at) - VALUES (?, ?, ?, ?, ?)`, - user.ID, user.Email, user.PasswordHash, user.CreatedAt, user.UpdatedAt, - ).Exec(); err != nil { - return nil, fmt.Errorf("error creating user: %w", err) - } - - return user, nil -} - -func (s *service) GetUserByEmail(email string) (*User, error) { - var user User - if err := s.session.Query(` - SELECT id, email, password_hash, created_at, updated_at - FROM users WHERE email = ? ALLOW FILTERING`, - email, - ).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt, &user.UpdatedAt); err != nil { - return nil, fmt.Errorf("user not found: %w", err) - } - return &user, nil -} - -func (s *service) GetUserByID(id gocql.UUID) (*User, error) { - var user User - if err := s.session.Query(` - SELECT id, email, password_hash, created_at, updated_at - FROM users WHERE id = ?`, - id, - ).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt, &user.UpdatedAt); err != nil { - return nil, fmt.Errorf("user not found: %w", err) - } - return &user, nil -} - -func (s *service) CreateApplication(userID gocql.UUID, name, description, keyHash string) (*Application, error) { - app := &Application{ - ID: gocql.TimeUUID(), - UserID: userID, - Name: name, - Description: description, - KeyHash: keyHash, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - if err := s.session.Query(` - INSERT INTO applications (id, user_id, name, description, key_hash, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?)`, - app.ID, app.UserID, app.Name, app.Description, app.KeyHash, app.CreatedAt, app.UpdatedAt, - ).Exec(); err != nil { - return nil, fmt.Errorf("error creating application: %w", err) - } - - return app, nil -} - -func (s *service) GetApplication(id gocql.UUID) (*Application, error) { - var app Application - if err := s.session.Query(` - SELECT id, user_id, name, description, key_hash, created_at, updated_at - FROM applications WHERE id = ?`, - id, - ).Scan( - &app.ID, &app.UserID, &app.Name, &app.Description, - &app.KeyHash, &app.CreatedAt, &app.UpdatedAt, - ); err != nil { - return nil, fmt.Errorf("application not found: %w", err) - } - return &app, nil -} - -func (s *service) ListApplications(userID gocql.UUID) ([]Application, error) { - iter := s.session.Query(` - SELECT id, user_id, name, description, key_hash, created_at, updated_at - FROM applications WHERE user_id = ? ALLOW FILTERING`, - userID, - ).Iter() - - var apps []Application - var app Application - for iter.Scan( - &app.ID, &app.UserID, &app.Name, &app.Description, - &app.KeyHash, &app.CreatedAt, &app.UpdatedAt, - ) { - apps = append(apps, app) - } - - if err := iter.Close(); err != nil { - return nil, fmt.Errorf("error listing applications: %w", err) - } - - return apps, nil -} - -func (s *service) UpdateApplication(id gocql.UUID, name, description string) (*Application, error) { - now := time.Now() - if err := s.session.Query(` - UPDATE applications - SET name = ?, - description = ?, - updated_at = ? - WHERE id = ?`, - name, description, now, id, - ).Exec(); err != nil { - return nil, fmt.Errorf("error updating application: %w", err) - } - - return s.GetApplication(id) -} - -func (s *service) DeleteApplication(id gocql.UUID) error { - if err := s.session.Query(` - DELETE FROM applications WHERE id = ?`, - id, - ).Exec(); err != nil { - return fmt.Errorf("error deleting application: %w", err) - } - - return nil -} - -func (s *service) RegenerateApplicationKey(appID gocql.UUID, newKeyHash string) error { - now := time.Now() - if err := s.session.Query(` - UPDATE applications - SET key_hash = ?, - updated_at = ? - WHERE id = ?`, - newKeyHash, now, appID, - ).Exec(); err != nil { - return fmt.Errorf("error regenerating application key: %w", err) - } - return nil -} - -// BatchInsertLogs inserts multiple logs in a batch operation -func (s *service) BatchInsertLogs(logs []Log) error { - batch := s.session.NewBatch(gocql.LoggedBatch) - - for _, log := range logs { - batch.Query(` - INSERT INTO logs ( - application_id, - timestamp, - log_id, - user_id, - log_level, - message - ) VALUES (?, ?, ?, ?, ?, ?)`, - log.ApplicationID, - log.Timestamp, - log.LogID, - log.UserID, - log.Level, - log.Message, - ) - } - - if err := s.session.ExecuteBatch(batch); err != nil { - return fmt.Errorf("failed to batch insert logs: %w", err) - } - - return nil -} - -func (s *service) GetRecentLogs(filter LogsFilter) ([]Log, error) { - if filter.PageSize <= 0 || filter.PageSize > 100 { - filter.PageSize = 100 - } - - // Build the query dynamically based on filters - query := "SELECT application_id, timestamp, log_id, user_id, log_level, message FROM logs WHERE application_id = ?" - args := []interface{}{filter.ApplicationID} - - // Add timestamp conditions - if filter.Cursor != nil { - query += " AND timestamp < ?" - args = append(args, *filter.Cursor) - } - if filter.StartTime != nil { - query += " AND timestamp >= ?" - args = append(args, *filter.StartTime) - } - if filter.EndTime != nil { - query += " AND timestamp <= ?" - args = append(args, *filter.EndTime) - } - - // Add log level filter if specified - if filter.LogLevel != "" { - query += " AND log_level = ?" - args = append(args, filter.LogLevel) - } - - // Add ordering and limit - query += " ORDER BY timestamp DESC LIMIT ?" - args = append(args, filter.PageSize) - - // Execute query with ALLOW FILTERING - iter := s.session.Query(query+" ALLOW FILTERING", args...).Iter() - - var logs []Log - var log Log - for iter.Scan( - &log.ApplicationID, - &log.Timestamp, - &log.LogID, - &log.UserID, - &log.Level, - &log.Message, - ) { - logs = append(logs, log) - } - - if err := iter.Close(); err != nil { - return nil, fmt.Errorf("error fetching logs: %w", err) - } - - return logs, nil -} - -// ValidateApplicationKey validates an API key and returns the associated application -func (s *service) ValidateApplicationKey(keyHash string) (*Application, error) { - var app Application - - // Query the applications table using the key hash - if err := s.session.Query(` - SELECT id, user_id, name, description, key_hash, created_at, updated_at - FROM applications - WHERE key_hash = ? - ALLOW FILTERING`, - keyHash, - ).Scan( - &app.ID, - &app.UserID, - &app.Name, - &app.Description, - &app.KeyHash, - &app.CreatedAt, - &app.UpdatedAt, - ); err != nil { - if err == gocql.ErrNotFound { - return nil, fmt.Errorf("invalid API key") - } - return nil, fmt.Errorf("error validating API key: %w", err) - } - - return &app, nil -} - -func (s *service) GenerateDummyLogs(applicationID gocql.UUID) (int, error) { - // Get the application to ensure it exists and get its user ID - var app Application - if err := s.session.Query(` - SELECT id, user_id FROM applications WHERE id = ? - `, applicationID).Scan(&app.ID, &app.UserID); err != nil { - return 0, fmt.Errorf("application not found: %w", err) - } - - // Generate 30 logs spanning the last 5 minutes - now := time.Now() - startTime := now.Add(-5 * time.Minute) - - batch := s.session.NewBatch(gocql.UnloggedBatch) - - // Sample messages and levels for variety - messages := []string{ - "User authentication successful", - "Database connection established", - "API request processed successfully", - "Cache miss for key: %s", - "Background job completed in %dms", - "Memory usage: %d MB", - "Request failed with status code: %d", - "Rate limit exceeded for IP: %s", - "Configuration reload initiated", - "File upload completed: %s", - } - - levelWeights := map[string]int{ - "DEBUG": 15, - "INFO": 60, - "WARN": 15, - "ERROR": 8, - "FATAL": 2, - } - - // Random data for interpolation - sampleIPs := []string{"192.168.1.1", "10.0.0.1", "172.16.0.1", "8.8.8.8"} - sampleFiles := []string{"user.jpg", "data.csv", "config.json", "backup.zip"} - sampleKeys := []string{"user:1234", "session:5678", "settings:9012", "temp:3456"} - - for i := 0; i < 30; i++ { - // Calculate timestamp with even distribution over 5 minutes - progress := float64(i) / 30.0 - timestamp := startTime.Add(time.Duration(progress * 5 * float64(time.Minute))) - - // Select log level based on weights - rand.Seed(uint64(time.Now().UnixNano())) - r := rand.Intn(100) - var level string - sum := 0 - for l, weight := range levelWeights { - sum += weight - if r < sum { - level = l - break - } - } - - // Select and format message - msgTemplate := messages[rand.Intn(len(messages))] - var msg string - - switch { - case strings.Contains(msgTemplate, "IP:"): - msg = fmt.Sprintf(msgTemplate, sampleIPs[rand.Intn(len(sampleIPs))]) - case strings.Contains(msgTemplate, "File"): - msg = fmt.Sprintf(msgTemplate, sampleFiles[rand.Intn(len(sampleFiles))]) - case strings.Contains(msgTemplate, "key:"): - msg = fmt.Sprintf(msgTemplate, sampleKeys[rand.Intn(len(sampleKeys))]) - case strings.Contains(msgTemplate, "MB"): - msg = fmt.Sprintf(msgTemplate, rand.Intn(1000)) - case strings.Contains(msgTemplate, "ms"): - msg = fmt.Sprintf(msgTemplate, rand.Intn(500)) - case strings.Contains(msgTemplate, "status code:"): - codes := []int{400, 401, 403, 404, 500, 502, 503} - msg = fmt.Sprintf(msgTemplate, codes[rand.Intn(len(codes))]) - default: - msg = msgTemplate - } - - // Add to batch - batch.Query(` - INSERT INTO logs ( - application_id, - timestamp, - log_id, - user_id, - log_level, - message - ) VALUES (?, ?, ?, ?, ?, ?)`, - applicationID, - timestamp, - gocql.TimeUUID(), - app.UserID, - level, - msg, - ) - } - - if err := s.session.ExecuteBatch(batch); err != nil { - return 0, fmt.Errorf("failed to insert dummy logs: %w", err) - } - - return 30, nil -}
A
internal/database/ops_application.go
@@ -0,0 +1,109 @@
+package database + +import ( + "fmt" + "time" + + "github.com/gocql/gocql" +) + +func (s *service) CreateApplication(userID gocql.UUID, name, description, keyHash string) (*Application, error) { + app := &Application{ + ID: gocql.TimeUUID(), + UserID: userID, + Name: name, + Description: description, + KeyHash: keyHash, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if err := s.session.Query(` + INSERT INTO applications (id, user_id, name, description, key_hash, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + app.ID, app.UserID, app.Name, app.Description, app.KeyHash, app.CreatedAt, app.UpdatedAt, + ).Exec(); err != nil { + return nil, fmt.Errorf("error creating application: %w", err) + } + + return app, nil +} + +func (s *service) GetApplication(id gocql.UUID) (*Application, error) { + var app Application + if err := s.session.Query(` + SELECT id, user_id, name, description, key_hash, created_at, updated_at + FROM applications WHERE id = ?`, + id, + ).Scan( + &app.ID, &app.UserID, &app.Name, &app.Description, + &app.KeyHash, &app.CreatedAt, &app.UpdatedAt, + ); err != nil { + return nil, fmt.Errorf("application not found: %w", err) + } + return &app, nil +} + +func (s *service) ListApplications(userID gocql.UUID) ([]Application, error) { + iter := s.session.Query(` + SELECT id, user_id, name, description, key_hash, created_at, updated_at + FROM applications WHERE user_id = ? ALLOW FILTERING`, + userID, + ).Iter() + + var apps []Application + var app Application + for iter.Scan( + &app.ID, &app.UserID, &app.Name, &app.Description, + &app.KeyHash, &app.CreatedAt, &app.UpdatedAt, + ) { + apps = append(apps, app) + } + + if err := iter.Close(); err != nil { + return nil, fmt.Errorf("error listing applications: %w", err) + } + + return apps, nil +} + +func (s *service) UpdateApplication(id gocql.UUID, name, description string) (*Application, error) { + now := time.Now() + if err := s.session.Query(` + UPDATE applications + SET name = ?, + description = ?, + updated_at = ? + WHERE id = ?`, + name, description, now, id, + ).Exec(); err != nil { + return nil, fmt.Errorf("error updating application: %w", err) + } + + return s.GetApplication(id) +} + +func (s *service) DeleteApplication(id gocql.UUID) error { + if err := s.session.Query(` + DELETE FROM applications WHERE id = ?`, + id, + ).Exec(); err != nil { + return fmt.Errorf("error deleting application: %w", err) + } + + return nil +} + +func (s *service) RegenerateApplicationKey(appID gocql.UUID, newKeyHash string) error { + now := time.Now() + if err := s.session.Query(` + UPDATE applications + SET key_hash = ?, + updated_at = ? + WHERE id = ?`, + newKeyHash, now, appID, + ).Exec(); err != nil { + return fmt.Errorf("error regenerating application key: %w", err) + } + return nil +}
A
internal/database/ops_logs.go
@@ -0,0 +1,231 @@
+package database + +import ( + "fmt" + "strings" + "time" + + "github.com/gocql/gocql" + "golang.org/x/exp/rand" +) + +func (s *service) BatchInsertLogs(logs []Log) error { + batch := s.session.NewBatch(gocql.LoggedBatch) + + for _, log := range logs { + batch.Query(` + INSERT INTO logs ( + application_id, + timestamp, + log_id, + user_id, + log_level, + message + ) VALUES (?, ?, ?, ?, ?, ?)`, + log.ApplicationID, + log.Timestamp, + log.LogID, + log.UserID, + log.Level, + log.Message, + ) + } + + if err := s.session.ExecuteBatch(batch); err != nil { + return fmt.Errorf("failed to batch insert logs: %w", err) + } + + return nil +} + +func (s *service) GetRecentLogs(filter LogsFilter) ([]Log, error) { + if filter.PageSize <= 0 || filter.PageSize > 100 { + filter.PageSize = 100 + } + + // Build the query dynamically based on filters + query := "SELECT application_id, timestamp, log_id, user_id, log_level, message FROM logs WHERE application_id = ?" + args := []interface{}{filter.ApplicationID} + + // Add timestamp conditions + if filter.Cursor != nil { + query += " AND timestamp < ?" + args = append(args, *filter.Cursor) + } + if filter.StartTime != nil { + query += " AND timestamp >= ?" + args = append(args, *filter.StartTime) + } + if filter.EndTime != nil { + query += " AND timestamp <= ?" + args = append(args, *filter.EndTime) + } + + // Add log level filter if specified + if filter.LogLevel != "" { + query += " AND log_level = ?" + args = append(args, filter.LogLevel) + } + + // Add ordering and limit + query += " ORDER BY timestamp DESC LIMIT ?" + args = append(args, filter.PageSize) + + // Execute query with ALLOW FILTERING + iter := s.session.Query(query+" ALLOW FILTERING", args...).Iter() + + var logs []Log + var log Log + for iter.Scan( + &log.ApplicationID, + &log.Timestamp, + &log.LogID, + &log.UserID, + &log.Level, + &log.Message, + ) { + logs = append(logs, log) + } + + if err := iter.Close(); err != nil { + return nil, fmt.Errorf("error fetching logs: %w", err) + } + + return logs, nil +} + +func (s *service) ValidateApplicationKey(keyHash string) (*Application, error) { + var app Application + + // Query the applications table using the key hash + if err := s.session.Query(` + SELECT id, user_id, name, description, key_hash, created_at, updated_at + FROM applications + WHERE key_hash = ? + ALLOW FILTERING`, + keyHash, + ).Scan( + &app.ID, + &app.UserID, + &app.Name, + &app.Description, + &app.KeyHash, + &app.CreatedAt, + &app.UpdatedAt, + ); err != nil { + if err == gocql.ErrNotFound { + return nil, fmt.Errorf("invalid API key") + } + return nil, fmt.Errorf("error validating API key: %w", err) + } + + return &app, nil +} + +func (s *service) GenerateDummyLogs(applicationID gocql.UUID) (int, error) { + // Get the application to ensure it exists and get its user ID + var app Application + if err := s.session.Query(` + SELECT id, user_id FROM applications WHERE id = ? + `, applicationID).Scan(&app.ID, &app.UserID); err != nil { + return 0, fmt.Errorf("application not found: %w", err) + } + + // Generate 30 logs spanning the last 5 minutes + now := time.Now() + startTime := now.Add(-5 * time.Minute) + + batch := s.session.NewBatch(gocql.UnloggedBatch) + + // Sample messages and levels for variety + messages := []string{ + "User authentication successful", + "Database connection established", + "API request processed successfully", + "Cache miss for key: %s", + "Background job completed in %dms", + "Memory usage: %d MB", + "Request failed with status code: %d", + "Rate limit exceeded for IP: %s", + "Configuration reload initiated", + "File upload completed: %s", + } + + levelWeights := map[string]int{ + "DEBUG": 15, + "INFO": 60, + "WARN": 15, + "ERROR": 8, + "FATAL": 2, + } + + // Random data for interpolation + sampleIPs := []string{"192.168.1.1", "10.0.0.1", "172.16.0.1", "8.8.8.8"} + sampleFiles := []string{"user.jpg", "data.csv", "config.json", "backup.zip"} + sampleKeys := []string{"user:1234", "session:5678", "settings:9012", "temp:3456"} + + for i := 0; i < 30; i++ { + // Calculate timestamp with even distribution over 5 minutes + progress := float64(i) / 30.0 + timestamp := startTime.Add(time.Duration(progress * 5 * float64(time.Minute))) + + // Select log level based on weights + rand.Seed(uint64(time.Now().UnixNano())) + r := rand.Intn(100) + var level string + sum := 0 + for l, weight := range levelWeights { + sum += weight + if r < sum { + level = l + break + } + } + + // Select and format message + msgTemplate := messages[rand.Intn(len(messages))] + var msg string + + switch { + case strings.Contains(msgTemplate, "IP:"): + msg = fmt.Sprintf(msgTemplate, sampleIPs[rand.Intn(len(sampleIPs))]) + case strings.Contains(msgTemplate, "File"): + msg = fmt.Sprintf(msgTemplate, sampleFiles[rand.Intn(len(sampleFiles))]) + case strings.Contains(msgTemplate, "key:"): + msg = fmt.Sprintf(msgTemplate, sampleKeys[rand.Intn(len(sampleKeys))]) + case strings.Contains(msgTemplate, "MB"): + msg = fmt.Sprintf(msgTemplate, rand.Intn(1000)) + case strings.Contains(msgTemplate, "ms"): + msg = fmt.Sprintf(msgTemplate, rand.Intn(500)) + case strings.Contains(msgTemplate, "status code:"): + codes := []int{400, 401, 403, 404, 500, 502, 503} + msg = fmt.Sprintf(msgTemplate, codes[rand.Intn(len(codes))]) + default: + msg = msgTemplate + } + + // Add to batch + batch.Query(` + INSERT INTO logs ( + application_id, + timestamp, + log_id, + user_id, + log_level, + message + ) VALUES (?, ?, ?, ?, ?, ?)`, + applicationID, + timestamp, + gocql.TimeUUID(), + app.UserID, + level, + msg, + ) + } + + if err := s.session.ExecuteBatch(batch); err != nil { + return 0, fmt.Errorf("failed to insert dummy logs: %w", err) + } + + return 30, nil +}
A
internal/database/ops_user.go
@@ -0,0 +1,52 @@
+package database + +import ( + "fmt" + "time" + + "github.com/gocql/gocql" +) + +func (s *service) CreateUser(email, passwordHash string) (*User, error) { + user := &User{ + ID: gocql.TimeUUID(), + Email: email, + PasswordHash: passwordHash, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if err := s.session.Query(` + INSERT INTO users (id, email, password_hash, created_at, updated_at) + VALUES (?, ?, ?, ?, ?)`, + user.ID, user.Email, user.PasswordHash, user.CreatedAt, user.UpdatedAt, + ).Exec(); err != nil { + return nil, fmt.Errorf("error creating user: %w", err) + } + + return user, nil +} + +func (s *service) GetUserByEmail(email string) (*User, error) { + var user User + if err := s.session.Query(` + SELECT id, email, password_hash, created_at, updated_at + FROM users WHERE email = ? ALLOW FILTERING`, + email, + ).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt, &user.UpdatedAt); err != nil { + return nil, fmt.Errorf("user not found: %w", err) + } + return &user, nil +} + +func (s *service) GetUserByID(id gocql.UUID) (*User, error) { + var user User + if err := s.session.Query(` + SELECT id, email, password_hash, created_at, updated_at + FROM users WHERE id = ?`, + id, + ).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt, &user.UpdatedAt); err != nil { + return nil, fmt.Errorf("user not found: %w", err) + } + return &user, nil +}
A
internal/database/service.go
@@ -0,0 +1,28 @@
+package database + +import "github.com/gocql/gocql" + +type Service interface { + Health() map[string]string + Close() error + + CreateUser(email, passwordHash string) (*User, error) + GetUserByEmail(email string) (*User, error) + GetUserByID(id gocql.UUID) (*User, error) + + CreateApplication(userID gocql.UUID, name, description, keyHash string) (*Application, error) + GetApplication(id gocql.UUID) (*Application, error) + ListApplications(userID gocql.UUID) ([]Application, error) + UpdateApplication(id gocql.UUID, name, description string) (*Application, error) + DeleteApplication(id gocql.UUID) error + RegenerateApplicationKey(appID gocql.UUID, newKeyHash string) error + + BatchInsertLogs(logs []Log) error + GetRecentLogs(filter LogsFilter) ([]Log, error) + ValidateApplicationKey(keyHash string) (*Application, error) // Helper method for API key validation + GenerateDummyLogs(applicationID gocql.UUID) (int, error) +} + +type service struct { + session *gocql.Session +}
A
internal/database/types.go
@@ -0,0 +1,43 @@
+package database + +import ( + "time" + + "github.com/gocql/gocql" +) + +type User struct { + ID gocql.UUID `json:"id"` + Email string `json:"email"` + PasswordHash string `json:"-"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type Application struct { + ID gocql.UUID `json:"id"` + UserID gocql.UUID `json:"user_id"` + Name string `json:"name"` + Description string `json:"description"` + KeyHash string `json:"-"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type Log struct { + ApplicationID gocql.UUID `json:"application_id"` + LogID gocql.UUID `json:"log_id"` + UserID gocql.UUID `json:"user_id"` + Timestamp time.Time `json:"timestamp"` + Level string `json:"level"` + Message string `json:"message"` +} + +type LogsFilter struct { + ApplicationID gocql.UUID + PageSize int + Cursor *time.Time + LogLevel string + StartTime *time.Time + EndTime *time.Time +}