Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1,143 changes: 1,143 additions & 0 deletions DEVOPS_DEPLOYMENT_GUIDE.md

Large diffs are not rendered by default.

1,906 changes: 1,906 additions & 0 deletions DEVOPS_OPENSTACK_DEPLOYMENT_GUIDE.md

Large diffs are not rendered by default.

1,257 changes: 1,257 additions & 0 deletions PLATFORM_PRESENTATION.html

Large diffs are not rendered by default.

760 changes: 760 additions & 0 deletions PLATFORM_PRESENTATION.md

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions ab-testing-framework/cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"fmt"
"log"
"net/http"
"os"
"ab-testing-framework/internal/handlers"
"ab-testing-framework/internal/repository"
"ab-testing-framework/internal/service"

"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8096"
}
db, err := gorm.Open(sqlite.Open("abtest.db"), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
repo := repository.NewABTestRepository(db)
if err := repo.AutoMigrate(); err != nil {
log.Fatalf("Failed to run migrations: %v", err)
}
svc := service.NewABTestService(repo)
handler := handlers.NewABTestHandler(svc)
mux := http.NewServeMux()
handler.RegisterRoutes(mux)
addr := fmt.Sprintf(":%s", port)
log.Printf("ab-testing-framework starting on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
12 changes: 12 additions & 0 deletions ab-testing-framework/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module ab-testing-framework

go 1.21

require (
github.com/gin-gonic/gin v1.9.1
github.com/google/uuid v1.4.0
github.com/lib/pq v1.10.9
github.com/sirupsen/logrus v1.9.3
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5
)
152 changes: 152 additions & 0 deletions ab-testing-framework/internal/handlers/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package handlers

import (
"encoding/json"
"net/http"
"ab-testing-framework/internal/service"

"github.com/google/uuid"
)

type ABTestHandler struct {
svc *service.ABTestService
}

func NewABTestHandler(svc *service.ABTestService) *ABTestHandler {
return &ABTestHandler{svc: svc}
}

func (h *ABTestHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /api/v1/experiments", h.CreateExperiment)
mux.HandleFunc("GET /api/v1/experiments", h.ListExperiments)
mux.HandleFunc("GET /api/v1/experiments/{id}", h.GetExperiment)
mux.HandleFunc("POST /api/v1/experiments/{id}/variants", h.AddVariant)
mux.HandleFunc("GET /api/v1/experiments/{id}/variants", h.GetVariants)
mux.HandleFunc("POST /api/v1/experiments/{id}/start", h.StartExperiment)
mux.HandleFunc("POST /api/v1/experiments/{id}/stop", h.StopExperiment)
mux.HandleFunc("POST /api/v1/experiments/{id}/assign", h.AssignUser)
mux.HandleFunc("POST /api/v1/experiments/metrics", h.RecordMetric)
mux.HandleFunc("POST /api/v1/experiments/{id}/results", h.CalculateResults)
mux.HandleFunc("GET /api/v1/experiments/{id}/results", h.GetResults)
mux.HandleFunc("GET /health", h.HealthCheck)
mux.HandleFunc("GET /ready", h.ReadinessCheck)
}

func (h *ABTestHandler) CreateExperiment(w http.ResponseWriter, r *http.Request) {
var req service.CreateExperimentRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err.Error()); return
}
result, err := h.svc.CreateExperiment(r.Context(), req)
if err != nil { writeError(w, http.StatusUnprocessableEntity, err.Error()); return }
writeJSON(w, http.StatusCreated, result)
}

func (h *ABTestHandler) ListExperiments(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
module := r.URL.Query().Get("module")
results, err := h.svc.GetExperiments(r.Context(), status, module)
if err != nil { writeError(w, http.StatusInternalServerError, err.Error()); return }
writeJSON(w, http.StatusOK, results)
}

func (h *ABTestHandler) GetExperiment(w http.ResponseWriter, r *http.Request) {
id, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
result, err := h.svc.GetExperiment(r.Context(), id)
if err != nil { writeError(w, http.StatusNotFound, err.Error()); return }
writeJSON(w, http.StatusOK, result)
}

func (h *ABTestHandler) AddVariant(w http.ResponseWriter, r *http.Request) {
expID, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
var req service.AddVariantRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err.Error()); return
}
result, err := h.svc.AddVariant(r.Context(), expID, req)
if err != nil { writeError(w, http.StatusUnprocessableEntity, err.Error()); return }
writeJSON(w, http.StatusCreated, result)
}

func (h *ABTestHandler) GetVariants(w http.ResponseWriter, r *http.Request) {
expID, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
results, err := h.svc.GetVariants(r.Context(), expID)
if err != nil { writeError(w, http.StatusInternalServerError, err.Error()); return }
writeJSON(w, http.StatusOK, results)
}

func (h *ABTestHandler) StartExperiment(w http.ResponseWriter, r *http.Request) {
id, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
if err := h.svc.StartExperiment(r.Context(), id); err != nil {
writeError(w, http.StatusUnprocessableEntity, err.Error()); return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "running"})
}

func (h *ABTestHandler) StopExperiment(w http.ResponseWriter, r *http.Request) {
id, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
if err := h.svc.StopExperiment(r.Context(), id); err != nil {
writeError(w, http.StatusUnprocessableEntity, err.Error()); return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "completed"})
}

func (h *ABTestHandler) AssignUser(w http.ResponseWriter, r *http.Request) {
expID, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
var req struct { UserID string `json:"user_id"` }
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err.Error()); return
}
result, err := h.svc.AssignUser(r.Context(), expID, req.UserID)
if err != nil { writeError(w, http.StatusUnprocessableEntity, err.Error()); return }
writeJSON(w, http.StatusOK, result)
}

func (h *ABTestHandler) RecordMetric(w http.ResponseWriter, r *http.Request) {
var req service.RecordMetricRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err.Error()); return
}
if err := h.svc.RecordMetric(r.Context(), req); err != nil {
writeError(w, http.StatusUnprocessableEntity, err.Error()); return
}
writeJSON(w, http.StatusCreated, map[string]string{"status": "recorded"})
}

func (h *ABTestHandler) CalculateResults(w http.ResponseWriter, r *http.Request) {
expID, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
results, err := h.svc.CalculateResults(r.Context(), expID)
if err != nil { writeError(w, http.StatusUnprocessableEntity, err.Error()); return }
writeJSON(w, http.StatusCreated, results)
}

func (h *ABTestHandler) GetResults(w http.ResponseWriter, r *http.Request) {
expID, err := uuid.Parse(r.PathValue("id"))
if err != nil { writeError(w, http.StatusBadRequest, "invalid experiment ID"); return }
results, err := h.svc.GetResults(r.Context(), expID)
if err != nil { writeError(w, http.StatusInternalServerError, err.Error()); return }
writeJSON(w, http.StatusOK, results)
}

func (h *ABTestHandler) HealthCheck(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "healthy", "service": "ab-testing-framework"})
}

func (h *ABTestHandler) ReadinessCheck(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ready", "service": "ab-testing-framework"})
}

func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json"); w.WriteHeader(status); json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, msg string) {
writeJSON(w, status, map[string]string{"error": msg})
}
Loading