Based on the thorough review of the existing documentation, I'll now create comprehensive documentation for building a Golang backend for the Clinic Management System. I'll incorporate the multi-tenant schema design where each clinic has its own schema, add the new owner role, and include the subscription/pricing model based on user counts. Here's my comprehensive rewrite: Clinic Management System Backend Documentation (Golang) This documentation provides comprehensive information for implementing the Clinic Management System API using Go (Golang). It outlines the required endpoints, authentication mechanisms, database structure, and security considerations. Table of Contents API Overview Authentication & Security Database Schema Implementation Guidelines Testing Guidelines Getting Started The Clinic Management System is a comprehensive platform for managing medical clinics. It provides functionality for: Clinic management and profile maintenance Clinic owners, managers, doctors, and secretary management Patient records and appointment scheduling Payment processing AI-powered consultations Multi-tenant architecture with isolated schemas per clinic Subscription management based on staff counts and patient limits API Overview The Clinic Management System API follows RESTful principles and is organized into logical modules. This document provides an overview of the API structure and response format conventions. API Base URL The base URL for all API endpoints is: Development: http://localhost:4499/api Production: https://[production-domain]/api Response Format All API endpoints return responses in a consistent JSON format: { "success": boolean, "message": "string (optional)", "data": object | array (optional) } Success Response { "success": true, "message": "Operation completed successfully", "data": { // Response data here } } Error Response { "success": false, "message": "Error description", "errors": [ // Optional detailed errors ] } Pagination Format For endpoints returning lists, pagination is implemented using the following format: { "success": true, "data": { "data": [], // Array of items "total": 100, // Total number of items "page": 1, // Current page "limit": 10, // Items per page "totalPages": 10 // Total number of pages } } API Modules The API is structured into the following modules: Authentication Clinic Management Doctor Management Patient Management Appointment Management Payment Processing AI Consultation Notifications File Management Analytics Trial & Subscription Public Endpoints Authentication & Security Authentication Flow Registration & Email Verification User registers with email and password System sends a verification email with a token User clicks the verification link Email is marked as verified, and account becomes active Login & Session Management User provides credentials (email/password or OAuth) System validates credentials and generates tokens: Access Token: Short-lived JWT (15-60 minutes) Refresh Token: Long-lived, stored as HTTP-only cookie Access token is used for API authentication When access token expires, refresh token is used to obtain a new one Upon login, user selects which account to use if they have multiple roles (e.g., doctor in one clinic, patient in another) Token Implementation Access Token Type: JWT (JSON Web Token) Expiration: 15-60 minutes (configurable) Storage: Client-side (memory, not in localStorage) Contents: User ID User roles Clinic ID (if applicable) Schema name (for multi-tenant isolation) Issued time Expiration time Refresh Token Type: Random secure string or JWT Expiration: 7-30 days (configurable) Storage: HTTP-only, secure cookie Additional Protection: Binding to user's IP address or device fingerprint One-time use (rotation on each refresh) Stored in database with revocation capability Security Measures Password Handling Storage: Passwords must be hashed using bcrypt with appropriate work factor (12+) Requirements: Minimum 8 characters At least one uppercase letter At least one lowercase letter At least one number At least one special character CSRF Protection CSRF tokens required for all state-changing operations Token verification on server side Stateless CSRF using double-submit cookie pattern Rate Limiting Implement rate limiting for sensitive endpoints: Login: 5 attempts per minute per IP Registration: 3 attempts per hour per IP Password reset: 3 attempts per hour per user/IP API endpoints: Tiered rate limits based on endpoint sensitivity Role-Based Access Control (RBAC) Implement the following roles with appropriate permissions: admin: System administrators owner: Clinic owners (highest clinic-level privilege) clinic_manager: Clinic managers doctor: Medical professionals secretary: Administrative staff patient: Registered patients Detailed permissions matrix: | Resource | admin | owner | clinic_manager | doctor | secretary | patient | |----------|-------|-------|----------------|--------|-----------|---------| | System settings | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Clinic management | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | Doctor management | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | Patient records | ✅ | ✅ | ✅ | ✅ | ✅ | 🔶* | | Appointments | ✅ | ✅ | ✅ | ✅ | ✅ | 🔶* | | Billing/payments | ✅ | ✅ | ✅ | ❌ | ✅ | 🔶* | | Staff management | ✅ | ✅ | 🔶** | ❌ | ❌ | ❌ | | Subscription | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | *🔶: Patients can only access their own records **🔶: Clinic managers can manage staff except owners Audit Logging Implement comprehensive audit logging for security events: Authentication events (login, logout, token refresh) Access to sensitive data (medical records, patient information) Changes to user roles or permissions System configuration changes Log format: { "eventId": "uuid", "eventType": "LOGIN_SUCCESS", "timestamp": "ISO-8601 timestamp", "userId": "user-uuid", "userRole": "role", "ipAddress": "127.0.0.1", "userAgent": "Browser/OS info", "resourceId": "resource-uuid", "resourceType": "patient_record", "action": "read", "details": { "additionalInfo": "Event-specific details" } } API Security Best Practices Use HTTPS for all communications Implement proper CORS configuration Set secure HTTP headers: Strict-Transport-Security Content-Security-Policy X-Content-Type-Options X-Frame-Options X-XSS-Protection Validate all input data Return appropriate HTTP status codes Implement request timeouts Use parameterized queries to prevent SQL injection Handling Sensitive Data Apply encryption at rest for sensitive data: Patient medical records Personal identifiable information Payment information Use field-level encryption for highly sensitive information Implement data access logging and auditing Apply data masking for sensitive fields in logs and non-essential displays Database Schema Multi-Tenant Architecture The system implements a schema-per-tenant model in PostgreSQL: Each clinic has its own dedicated PostgreSQL schema Schema names follow a pattern: clinic_{clinic_id} Public schema contains shared tables and authentication data Clinic-specific data resides in the clinic's schema Each request includes the clinic context for proper schema selection Core Tables Public Schema -- Public schema (shared across clinics) CREATE TABLE public.clinics ( id UUID PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, location TEXT NOT NULL, city_id UUID REFERENCES public.cities(id), latitude DECIMAL(10, 8), longitude DECIMAL(11, 8), schema_name VARCHAR(63) UNIQUE NOT NULL, subscription_plan_id UUID REFERENCES public.subscription_plans(id), max_doctors INTEGER NOT NULL DEFAULT 1, max_staff INTEGER NOT NULL DEFAULT 2, max_patients_per_month INTEGER NOT NULL DEFAULT 100, is_active BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE public.users ( id UUID PRIMARY KEY, email VARCHAR(255) NOT NULL, password_hash VARCHAR(255), first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, phone VARCHAR(20), email_verified BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_login_at TIMESTAMP WITH TIME ZONE, google_id VARCHAR(255) UNIQUE, profile_image_url TEXT ); CREATE TABLE public.roles ( id UUID PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description TEXT ); CREATE TABLE public.user_clinic_roles ( id UUID PRIMARY KEY, user_id UUID REFERENCES public.users(id) ON DELETE CASCADE, clinic_id UUID REFERENCES public.clinics(id) ON DELETE CASCADE, role_id UUID REFERENCES public.roles(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, clinic_id, role_id) ); CREATE TABLE public.subscription_plans ( id UUID PRIMARY KEY, name VARCHAR(100) NOT NULL, price_monthly DECIMAL(10, 2) NOT NULL, price_annually DECIMAL(10, 2) NOT NULL, base_doctors INTEGER NOT NULL DEFAULT 1, base_staff INTEGER NOT NULL DEFAULT 2, base_patients_per_month INTEGER NOT NULL DEFAULT 100, free_ai_consults INTEGER DEFAULT 0, additional_doctor_price_monthly DECIMAL(10, 2) DEFAULT 0, additional_doctor_price_annually DECIMAL(10, 2) DEFAULT 0, additional_staff_price_monthly DECIMAL(10, 2) DEFAULT 0, additional_staff_price_annually DECIMAL(10, 2) DEFAULT 0, additional_patients_price_monthly DECIMAL(10, 2) DEFAULT 0, additional_patients_price_annually DECIMAL(10, 2) DEFAULT 0, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE public.clinic_subscriptions ( id UUID PRIMARY KEY, clinic_id UUID REFERENCES public.clinics(id) ON DELETE CASCADE, plan_id UUID REFERENCES public.subscription_plans(id), billing_period VARCHAR(20) CHECK (billing_period IN ('monthly', 'annually')), start_date DATE NOT NULL, end_date DATE NOT NULL, status VARCHAR(20) CHECK (status IN ('active', 'expired', 'cancelled')), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE public.refresh_tokens ( id UUID PRIMARY KEY, user_id UUID REFERENCES public.users(id) ON DELETE CASCADE, token_hash VARCHAR(255) NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), revoked BOOLEAN DEFAULT FALSE, revoked_at TIMESTAMP WITH TIME ZONE ); CREATE TABLE public.security_events ( id UUID PRIMARY KEY, event_type VARCHAR(50) NOT NULL, user_id UUID REFERENCES public.users(id) ON DELETE SET NULL, clinic_id UUID REFERENCES public.clinics(id) ON DELETE SET NULL, ip_address VARCHAR(45), user_agent TEXT, message TEXT, data JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Reference data tables CREATE TABLE public.cities ( id UUID PRIMARY KEY, name_en VARCHAR(100) NOT NULL, name_ar VARCHAR(100) NOT NULL, province_en VARCHAR(100) NOT NULL, province_ar VARCHAR(100) NOT NULL, country_en VARCHAR(100) NOT NULL, country_ar VARCHAR(100) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE public.specializations ( id UUID PRIMARY KEY, name_en VARCHAR(100) NOT NULL, name_ar VARCHAR(100) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); Clinic-Specific Schema (per tenant) -- Each clinic gets its own schema CREATE SCHEMA clinic_{id}; -- Tables in clinic_{id} schema CREATE TABLE clinic_{id}.doctors ( id UUID PRIMARY KEY, user_id UUID NOT NULL, specialization_id UUID, average_rating DECIMAL(3, 2) DEFAULT 0, total_ratings INTEGER DEFAULT 0, ai_consult_quota INTEGER DEFAULT 0, ai_consult_used INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE clinic_{id}.doctor_availability ( id UUID PRIMARY KEY, doctor_id UUID REFERENCES clinic_{id}.doctors(id) ON DELETE CASCADE, day_of_week INTEGER NOT NULL CHECK (day_of_week BETWEEN 0 AND 6), start_time TIME NOT NULL, end_time TIME NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), CONSTRAINT valid_time_range CHECK (start_time < end_time) ); CREATE TABLE clinic_{id}.patients ( id UUID PRIMARY KEY, user_id UUID NOT NULL, date_of_birth DATE, gender VARCHAR(20) CHECK (gender IN ('male', 'female', 'other')), medical_history TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE clinic_{id}.appointments ( id UUID PRIMARY KEY, patient_id UUID REFERENCES clinic_{id}.patients(id) ON DELETE CASCADE, doctor_id UUID REFERENCES clinic_{id}.doctors(id) ON DELETE CASCADE, start_time TIMESTAMP WITH TIME ZONE NOT NULL, end_time TIMESTAMP WITH TIME ZONE NOT NULL, status VARCHAR(20) CHECK (status IN ('pending', 'accepted', 'rejected', 'completed', 'cancelled', 'no_show')), notes TEXT, created_by UUID NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), CONSTRAINT valid_appointment_time CHECK (start_time < end_time) ); CREATE TABLE clinic_{id}.invoices ( id UUID PRIMARY KEY, patient_id UUID REFERENCES clinic_{id}.patients(id) ON DELETE SET NULL, appointment_id UUID REFERENCES clinic_{id}.appointments(id), amount DECIMAL(10, 2) NOT NULL, status VARCHAR(20) CHECK (status IN ('unpaid', 'paid', 'partially_paid', 'cancelled')), due_date DATE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE clinic_{id}.payment_methods ( id UUID PRIMARY KEY, name VARCHAR(100) NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE clinic_{id}.payments ( id UUID PRIMARY KEY, invoice_id UUID REFERENCES clinic_{id}.invoices(id) ON DELETE CASCADE, payment_method_id UUID REFERENCES clinic_{id}.payment_methods(id), amount DECIMAL(10, 2) NOT NULL, status VARCHAR(20) CHECK (status IN ('pending', 'completed', 'failed')), transaction_id VARCHAR(255), created_by UUID NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE clinic_{id}.patient_files ( id UUID PRIMARY KEY, patient_id UUID REFERENCES clinic_{id}.patients(id) ON DELETE CASCADE, filename VARCHAR(255) NOT NULL, file_path TEXT NOT NULL, file_type VARCHAR(100) NOT NULL, file_size INTEGER NOT NULL, uploaded_by UUID NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE clinic_{id}.ai_consultations ( id UUID PRIMARY KEY, doctor_id UUID REFERENCES clinic_{id}.doctors(id) ON DELETE CASCADE, question TEXT NOT NULL, response TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), completed_at TIMESTAMP WITH TIME ZONE ); CREATE TABLE clinic_{id}.notifications ( id UUID PRIMARY KEY, user_id UUID NOT NULL, title VARCHAR(255) NOT NULL, message TEXT NOT NULL, is_read BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); Data Relationships Users and Roles Each user can have multiple roles across different clinics The owner role is automatically assigned during clinic registration A user's email is unique within the system, but can have different roles in different clinics Upon login, if a user has multiple roles, they must choose which account to use Clinic Subscription System Each clinic has a subscription plan that defines limits: Maximum number of doctors Maximum number of staff (managers + secretaries) Maximum patients per month When adding new staff members, the system checks against these limits If a limit is reached, the system notifies the owner and offers to upgrade The owner can approve the upgrade, which changes the subscription automatically Implementation Guidelines Technology Stack Recommended Go Stack Language: Go 1.18+ with modules API Framework: Echo, Gin, or Fiber Database: PostgreSQL 13+ ORM/Query Builder: GORM, SQLx, or pgx Authentication: JWT with Go packages Validation: validator.v10 or ozzo-validation Testing: Go testing package with testify Documentation: Swagger/OpenAPI with swaggo Logging: zap or zerolog Configuration: viper Key Packages github.com/labstack/echo/v4 or github.com/gin-gonic/gin: API framework github.com/jackc/pgx/v4 or github.com/jmoiron/sqlx: Database access github.com/golang-jwt/jwt/v4: JWT implementation golang.org/x/crypto/bcrypt: Password hashing github.com/go-playground/validator/v10: Request validation go.uber.org/zap or github.com/rs/zerolog: Logging github.com/spf13/viper: Configuration github.com/google/uuid: UUID generation github.com/stretchr/testify: Testing utilities github.com/swaggo/swag: API documentation Project Structure / ├── cmd/ │ └── api/ │ └── main.go # Application entry point │ ├── internal/ │ ├── api/ # API layer │ │ ├── handlers/ # Request handlers │ │ │ ├── auth.go │ │ │ ├── clinic.go │ │ │ └── ... │ │ ├── middleware/ # Custom middleware │ │ │ ├── auth.go │ │ │ ├── clinic.go │ │ │ └── ... │ │ └── routes/ # Route definitions │ │ └── api.go │ │ │ ├── config/ # Configuration │ │ └── config.go │ │ │ ├── domain/ # Business domain │ │ ├── models/ # Domain models │ │ │ ├── user.go │ │ │ ├── clinic.go │ │ │ └── ... │ │ └── services/ # Business logic │ │ ├── auth_service.go │ │ ├── clinic_service.go │ │ └── ... │ │ │ ├── db/ # Database access │ │ ├── migrations/ # SQL migrations │ │ ├── repositories/ # Data access │ │ │ ├── user_repo.go │ │ │ ├── clinic_repo.go │ │ │ └── ... │ │ ├── sqlc/ # Generated SQL code (if using sqlc) │ │ └── db.go # Database connection │ │ │ └── utils/ # Utility functions │ ├── security.go │ ├── validator.go │ └── ... │ ├── pkg/ # Public packages │ ├── logger/ │ ├── validator/ │ └── ... │ ├── docs/ # Documentation ├── scripts/ # Utility scripts └── tests/ # Tests Coding Standards General Guidelines Follow Go's official style guide and idiomatic Go practices Use meaningful variable and function names Write clear comments following Go conventions Handle errors properly with context Implement proper logging Write tests for all functionality Keep functions short and focused API Design Follow RESTful principles Use consistent response formats Implement proper validation Include appropriate HTTP status codes Document with OpenAPI/Swagger Version the API (/api/v1/...) Authentication Implementation // Example JWT middleware in Go package middleware import ( "fmt" "net/http" "strings" "github.com/golang-jwt/jwt/v4" "github.com/labstack/echo/v4" "github.com/your-org/clinic-system/internal/config" ) type UserClaims struct { ID string `json:"id"` Roles []string `json:"roles"` ClinicID string `json:"clinicId,omitempty"` Schema string `json:"schema,omitempty"` jwt.RegisteredClaims } func JWTAuth(config *config.Config) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { authHeader := c.Request().Header.Get("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { return c.JSON(http.StatusUnauthorized, map[string]interface{}{ "success": false, "message": "Unauthorized", }) } tokenString := strings.TrimPrefix(authHeader, "Bearer ") token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(config.JWT.Secret), nil }) if err != nil || !token.Valid { return c.JSON(http.StatusUnauthorized, map[string]interface{}{ "success": false, "message": "Invalid token", }) } claims, ok := token.Claims.(*UserClaims) if !ok { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to parse token claims", }) } c.Set("user", claims) return next(c) } } } // Role-based authorization func RoleGuard(roles ...string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { userClaims, ok := c.Get("user").(*UserClaims) if !ok { return c.JSON(http.StatusUnauthorized, map[string]interface{}{ "success": false, "message": "Unauthorized", }) } hasRole := false for _, userRole := range userClaims.Roles { for _, requiredRole := range roles { if userRole == requiredRole { hasRole = true break } } if hasRole { break } } if !hasRole { return c.JSON(http.StatusForbidden, map[string]interface{}{ "success": false, "message": "Forbidden", }) } return next(c) } } } Database Access With GORM: // Example repository pattern with GORM package repositories import ( "context" "errors" "github.com/google/uuid" "github.com/your-org/clinic-system/internal/domain/models" "gorm.io/gorm" ) type UserRepository struct { db *gorm.DB } func NewUserRepository(db *gorm.DB) *UserRepository { return &UserRepository{ db: db, } } func (r *UserRepository) FindByID(ctx context.Context, id string) (*models.User, error) { var user models.User result := r.db.WithContext(ctx). Preload("Roles"). First(&user, "id = ?", id) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, nil } if result.Error != nil { return nil, result.Error } return &user, nil } func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) { var user models.User result := r.db.WithContext(ctx). Preload("Roles"). First(&user, "email = ?", email) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, nil } if result.Error != nil { return nil, result.Error } return &user, nil } func (r *UserRepository) Create(ctx context.Context, user *models.User) error { if user.ID == "" { user.ID = uuid.New().String() } return r.db.WithContext(ctx).Create(user).Error } func (r *UserRepository) AddRole(ctx context.Context, userID, clinicID, roleID string) error { userRole := models.UserClinicRole{ ID: uuid.New().String(), UserID: userID, ClinicID: clinicID, RoleID: roleID, } return r.db.WithContext(ctx).Create(&userRole).Error } // Additional methods Multi-Tenant Schema Management // Middleware for tenant context management package middleware import ( "net/http" "github.com/labstack/echo/v4" "github.com/your-org/clinic-system/internal/config" "github.com/your-org/clinic-system/internal/db" ) func SetTenantContext() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { userClaims, ok := c.Get("user").(*UserClaims) if !ok || userClaims.ClinicID == "" { return next(c) } // Set schema name for database operations schemaName := "clinic_" + userClaims.ClinicID ctx := db.WithSchema(c.Request().Context(), schemaName) c.SetRequest(c.Request().WithContext(ctx)) return next(c) } } } // Database schema context management package db import ( "context" ) type schemaKey struct{} func WithSchema(ctx context.Context, schema string) context.Context { return context.WithValue(ctx, schemaKey{}, schema) } func GetSchema(ctx context.Context) string { if schema, ok := ctx.Value(schemaKey{}).(string); ok { return schema } return "public" } // Usage in database operations func (db *Database) WithSchema(ctx context.Context) *gorm.DB { schema := GetSchema(ctx) return db.DB.WithContext(ctx).Exec("SET search_path TO " + schema) } Error Handling Implement a centralized error handling mechanism: package api import ( "errors" "net/http" "github.com/labstack/echo/v4" "github.com/your-org/clinic-system/pkg/logger" ) type ApiError struct { StatusCode int Message string Err error } func (e *ApiError) Error() string { if e.Err != nil { return e.Err.Error() } return e.Message } func NewApiError(statusCode int, message string, err error) *ApiError { return &ApiError{ StatusCode: statusCode, Message: message, Err: err, } } func ErrorHandler(logger *logger.Logger) echo.HTTPErrorHandler { return func(err error, c echo.Context) { var apiErr *ApiError if errors.As(err, &apiErr) { logger.Error("API Error", "error", apiErr.Error(), "status", apiErr.StatusCode, "path", c.Path(), "method", c.Request().Method, ) c.JSON(apiErr.StatusCode, map[string]interface{}{ "success": false, "message": apiErr.Message, }) return } if he, ok := err.(*echo.HTTPError); ok { logger.Error("HTTP Error", "error", he.Message, "status", he.Code, "path", c.Path(), "method", c.Request().Method, ) c.JSON(he.Code, map[string]interface{}{ "success": false, "message": he.Message, }) return } logger.Error("Unhandled Error", "error", err.Error(), "path", c.Path(), "method", c.Request().Method, ) c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Internal server error", }) } } Validation Using validator package: package handlers import ( "net/http" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" ) type LoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` } type CustomValidator struct { validator *validator.Validate } func (cv *CustomValidator) Validate(i interface{}) error { return cv.validator.Struct(i) } func LoginHandler(c echo.Context) error { req := new(LoginRequest) if err := c.Bind(req); err != nil { return c.JSON(http.StatusBadRequest, map[string]interface{}{ "success": false, "message": "Invalid request format", }) } if err := c.Validate(req); err != nil { return c.JSON(http.StatusBadRequest, map[string]interface{}{ "success": false, "message": "Validation failed", "errors": FormatValidationErrors(err), }) } // Process login... return c.JSON(http.StatusOK, map[string]interface{}{ "success": true, "message": "Login successful", }) } func FormatValidationErrors(err error) []map[string]string { var errors []map[string]string for _, err := range err.(validator.ValidationErrors) { errors = append(errors, map[string]string{ "field": err.Field(), "message": getValidationErrorMessage(err), }) } return errors } func getValidationErrorMessage(err validator.FieldError) string { switch err.Tag() { case "required": return "This field is required" case "email": return "Invalid email format" case "min": return "Should be at least " + err.Param() + " characters long" default: return "Invalid value" } } Logging Implement structured logging: package logger import ( "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type Logger struct { *zap.SugaredLogger } func NewLogger(environment string) (*Logger, error) { var config zap.Config if environment == "production" { config = zap.NewProductionConfig() } else { config = zap.NewDevelopmentConfig() } config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder config.OutputPaths = []string{"stdout"} config.ErrorOutputPaths = []string{"stderr"} logger, err := config.Build() if err != nil { return nil, err } sugar := logger.Sugar() return &Logger{sugar}, nil } func (l *Logger) WithFields(fields map[string]interface{}) *Logger { args := make([]interface{}, 0, len(fields)*2) for k, v := range fields { args = append(args, k, v) } return &Logger{l.With(args...)} } API Documentation Use swaggo for Swagger/OpenAPI documentation: package main import ( "github.com/labstack/echo/v4" echoSwagger "github.com/swaggo/echo-swagger" _ "github.com/your-org/clinic-system/docs" // Generated by swag ) // @title Clinic Management System API // @version 1.0 // @description API documentation for the Clinic Management System // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.email support@example.com // @license.name MIT // @license.url https://opensource.org/licenses/MIT // @host localhost:4499 // @BasePath /api // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization func main() { e := echo.New() e.GET("/swagger/*", echoSwagger.WrapHandler) // Setup routes, middleware etc. e.Logger.Fatal(e.Start(":4499")) } File Upload Implement secure file upload handling: package handlers import ( "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "strings" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/your-org/clinic-system/internal/config" ) var ( allowedTypes = map[string]bool{ "image/jpeg": true, "image/png": true, "application/pdf": true, } maxFileSize = 5 * 1024 * 1024 // 5MB ) func UploadFileHandler(c echo.Context) error { // Get file from request file, err := c.FormFile("file") if err != nil { return c.JSON(http.StatusBadRequest, map[string]interface{}{ "success": false, "message": "No file uploaded", }) } // Validate file size if file.Size > int64(maxFileSize) { return c.JSON(http.StatusBadRequest, map[string]interface{}{ "success": false, "message": fmt.Sprintf("File too large (max %dMB)", maxFileSize/1024/1024), }) } // Open the uploaded file src, err := file.Open() if err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to read uploaded file", }) } defer src.Close() // Read mime type for validation buffer := make([]byte, 512) _, err = src.Read(buffer) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to read file content", }) } // Reset file pointer _, err = src.Seek(0, 0) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to process file", }) } // Get content type contentType := http.DetectContentType(buffer) // Validate content type if !allowedTypes[contentType] { return c.JSON(http.StatusBadRequest, map[string]interface{}{ "success": false, "message": "File type not supported", }) } // Generate unique filename fileExt := filepath.Ext(file.Filename) newFilename := uuid.New().String() + fileExt // Get upload directory from config uploadDir := config.Get().Upload.Directory // Create directory if it doesn't exist if err := os.MkdirAll(uploadDir, 0755); err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to create upload directory", }) } // Create destination file dst, err := os.Create(filepath.Join(uploadDir, newFilename)) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to create destination file", }) } defer dst.Close() // Copy file content if _, err = io.Copy(dst, src); err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{ "success": false, "message": "Failed to save file", }) } // Return success response return c.JSON(http.StatusOK, map[string]interface{}{ "success": true, "message": "File uploaded successfully", "data": map[string]interface{}{ "filename": newFilename, "originalName": file.Filename, "size": file.Size, "type": contentType, }, }) } Performance Considerations Database Optimization: Use appropriate indexes Optimize queries Use connection pooling Consider caching for frequently accessed data API Performance: Implement pagination Use projection to return only needed fields Use compression middleware Consider using Redis for caching Memory Management: Use memory efficiently with proper types Implement connection pooling Use streaming for large responses Properly handle file uploads Go-Specific Optimizations: Use goroutines appropriately Implement proper concurrency patterns Use sync.Pool for frequent allocations Profile and optimize hot code paths Scaling: Design for horizontal scaling Use stateless authentication Consider message queues for background tasks Security Best Practices Beyond the security measures outlined in the Security document: Dependency Management: Regularly update dependencies Use go mod tidy to clean up dependencies Use security scanning tools like gosec Input Sanitization: Sanitize all user inputs Use parameterized queries Validate file uploads Logging and Monitoring: Avoid logging sensitive information Implement centralized logging Set up alerts for suspicious activities Container Security (if applicable): Use minimal base images Don't run containers as root Scan container images for vulnerabilities Deployment Considerations Environment Configuration: Use environment variables for configuration Never hardcode secrets Use different configurations for development, testing, and production Containerization: Use Docker for consistent environments Implement health checks Optimize container builds CI/CD: Implement automated testing Use linting and code quality checks Automate deployment process Monitoring: Implement application performance monitoring Set up health check endpoints Monitor database performance Testing Guidelines Testing Strategy We recommend implementing a comprehensive testing strategy that includes: Unit Testing: Test individual functions or methods in isolation Integration Testing: Test interactions between components API Testing: Test the API endpoints Performance Testing: Test system performance under load Security Testing: Test for vulnerabilities Unit Testing package services import ( "context" "testing" "time" "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/your-org/clinic-system/internal/domain/models" "github.com/your-org/clinic-system/internal/domain/repositories" "golang.org/x/crypto/bcrypt" ) // Mock user repository type MockUserRepository struct { mock.Mock } func (m *MockUserRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) { args := m.Called(ctx, email) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*models.User), args.Error(1) } // More mock methods... func TestAuthService_Login(t *testing.T) { // Setup userRepo := new(MockUserRepository) tokenService := new(MockTokenService) authService := NewAuthService(userRepo, tokenService) ctx := context.Background() // Test case: successful login t.Run("SuccessfulLogin", func(t *testing.T) { // Arrange email := "test@example.com" password := "password123" hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) user := &models.User{ ID: "user-id", Email: email, PasswordHash: string(hashedPassword), Roles: []models.Role{ {Name: "clinic_manager"}, }, } tokens := models.TokenPair{ AccessToken: "access-token", RefreshToken: "refresh-token", } userRepo.On("FindByEmail", ctx, email).Return(user, nil) tokenService.On("GenerateTokens", mock.Anything, mock.Anything).Return(tokens, nil) // Act result, err := authService.Login(ctx, email, password) // Assert assert.NoError(t, err) assert.Equal(t, tokens, result) userRepo.AssertExpectations(t) tokenService.AssertExpectations(t) }) // Test case: user not found t.Run("UserNotFound", func(t *testing.T) { // Arrange email := "nonexistent@example.com" password := "password123" userRepo.On("FindByEmail", ctx, email).Return(nil, nil) // Act result, err := authService.Login(ctx, email, password) // Assert assert.Error(t, err) assert.Equal(t, models.TokenPair{}, result) userRepo.AssertExpectations(t) }) // More test cases... } API Testing package api_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/your-org/clinic-system/internal/api/handlers" "github.com/your-org/clinic-system/internal/domain/models" "github.com/your-org/clinic-system/internal/domain/services" ) func TestLoginHandler(t *testing.T) { // Setup e := echo.New() // Test case: successful login t.Run("SuccessfulLogin", func(t *testing.T) { // Arrange mockAuthService := new(services.MockAuthService) authHandler := handlers.NewAuthHandler(mockAuthService) tokens := models.TokenPair{ AccessToken: "access-token", RefreshToken: "refresh-token", } loginRequest := map[string]string{ "email": "test@example.com", "password": "password123", } mockAuthService.On("Login", mock.Anything, loginRequest["email"], loginRequest["password"]).Return(tokens, nil) reqBody, _ := json.Marshal(loginRequest) req := httptest.NewRequest(http.MethodPost, "/api/auth/login", bytes.NewReader(reqBody)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // Act err := authHandler.Login(c) // Assert assert.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response map[string]interface{} _ = json.Unmarshal(rec.Body.Bytes(), &response) assert.True(t, response["success"].(bool)) assert.Equal(t, tokens.AccessToken, response["data"].(map[string]interface{})["accessToken"]) mockAuthService.AssertExpectations(t) }) // More test cases... }