From ce5bcd9b429b8e55581cdc6c0c49ffee7519eff8 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 18:26:02 -0400 Subject: [PATCH] Clearing files --- .gitea/workflows/workflow.yaml | 134 -- .gitignore | 6 - Dockerfile | 54 - Makefile | 21 - README.md | 21 - cmd/api/main.go | 161 --- cmd/api/main_test.go | 129 -- docker-compose.yaml | 112 -- docs/docs.go | 1282 ----------------- docs/swagger.json | 1258 ---------------- docs/swagger.yaml | 810 ----------- go.mod | 48 - go.sum | 122 -- internal/app/app.go | 9 - internal/config/config.go | 168 --- internal/db/connection.go | 136 -- internal/handler/contact.go | 246 ---- internal/handler/contact_test.go | 123 -- internal/handler/draftMessage.go | 15 - internal/handler/endpoint/endpoint.go | 29 - internal/handler/helper.go | 46 - internal/handler/helper_test.go | 111 -- internal/handler/instant_message.go | 189 --- internal/handler/message.go | 180 --- internal/handler/message_event_response.go | 174 --- .../handler/message_event_response_test.go | 162 --- internal/handler/message_test.go | 74 - internal/handler/schedule_message_status.go | 169 --- .../handler/schedule_message_status_test.go | 75 - internal/handler/scheduled_message.go | 242 ---- internal/handler/scheduled_message_event.go | 247 ---- .../handler/scheduled_message_event_test.go | 182 --- internal/handler/scheduled_message_test.go | 141 -- internal/middleware/auth.go | 47 - internal/middleware/middleware.go | 22 - internal/model/messageDraft.go | 6 - internal/services/jwt_service.go | 49 - internal/store/contact_store.go | 182 --- .../store/message_event_response_store.go | 61 - internal/store/message_store.go | 95 -- internal/store/mock/contact_store.go | 146 -- .../mock/message_event_response_store.go | 81 -- .../mock/scheduled_message_event_store.go | 159 -- .../store/mock/scheduled_message_store.go | 141 -- internal/store/mock/store.go | 101 -- .../store/scheduled_message_event_store.go | 113 -- internal/store/scheduled_message_store.go | 127 -- internal/version/version.go | 17 - 48 files changed, 8223 deletions(-) delete mode 100644 .gitea/workflows/workflow.yaml delete mode 100644 .gitignore delete mode 100644 Dockerfile delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 cmd/api/main.go delete mode 100644 cmd/api/main_test.go delete mode 100644 docker-compose.yaml delete mode 100644 docs/docs.go delete mode 100644 docs/swagger.json delete mode 100644 docs/swagger.yaml delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 internal/app/app.go delete mode 100644 internal/config/config.go delete mode 100644 internal/db/connection.go delete mode 100644 internal/handler/contact.go delete mode 100644 internal/handler/contact_test.go delete mode 100644 internal/handler/draftMessage.go delete mode 100644 internal/handler/endpoint/endpoint.go delete mode 100644 internal/handler/helper.go delete mode 100644 internal/handler/helper_test.go delete mode 100644 internal/handler/instant_message.go delete mode 100644 internal/handler/message.go delete mode 100644 internal/handler/message_event_response.go delete mode 100644 internal/handler/message_event_response_test.go delete mode 100644 internal/handler/message_test.go delete mode 100644 internal/handler/schedule_message_status.go delete mode 100644 internal/handler/schedule_message_status_test.go delete mode 100644 internal/handler/scheduled_message.go delete mode 100644 internal/handler/scheduled_message_event.go delete mode 100644 internal/handler/scheduled_message_event_test.go delete mode 100644 internal/handler/scheduled_message_test.go delete mode 100644 internal/middleware/auth.go delete mode 100644 internal/middleware/middleware.go delete mode 100644 internal/model/messageDraft.go delete mode 100644 internal/services/jwt_service.go delete mode 100644 internal/store/contact_store.go delete mode 100644 internal/store/message_event_response_store.go delete mode 100644 internal/store/message_store.go delete mode 100644 internal/store/mock/contact_store.go delete mode 100644 internal/store/mock/message_event_response_store.go delete mode 100644 internal/store/mock/scheduled_message_event_store.go delete mode 100644 internal/store/mock/scheduled_message_store.go delete mode 100644 internal/store/mock/store.go delete mode 100644 internal/store/scheduled_message_event_store.go delete mode 100644 internal/store/scheduled_message_store.go delete mode 100644 internal/version/version.go diff --git a/.gitea/workflows/workflow.yaml b/.gitea/workflows/workflow.yaml deleted file mode 100644 index 2c1eef7..0000000 --- a/.gitea/workflows/workflow.yaml +++ /dev/null @@ -1,134 +0,0 @@ -name: Go - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-24.04 # You can change this to macos-latest or windows-latest if needed - - steps: - - uses: actions/checkout@v5 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: '1.26.2' - - - name: Build - run: | - echo "Initializing config" - mkdir -p ~/.ssh - echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender_models_deploy_key - chmod 600 ~/.ssh/textsender_models_deploy_key - ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts - - eval $(ssh-agent -s) - ssh-add -v ~/.ssh/textsender_models_deploy_key - - go env -w GOPRIVATE='${{ secrets.GIT_HOST_ROOT }}' - - echo "Creating local .gitconfig" - touch ~/.gitconfig - cat > ~/.gitconfig << "EOF" - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = https://${{ secrets.GIT_HOST_ROOT }} - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = http://${{ secrets.GIT_HOST_ROOT }} - EOF - - echo "Building binary" - make build - - echo "Binary built" - file textsender-api - - test: - name: Test - runs-on: ubuntu-24.04 - services: - postgres: - image: postgres:18.4-alpine - env: - POSTGRES_USER: ${{ secrets.DB_TEST_USER }} - POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }} - POSTGRES_DB: ${{ secrets.DB_TEST_NAME }} - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: '1.26.2' - - - name: Install PostgreSQL client - run: sudo apt update && sudo apt-get install -y postgresql-client - - - name: Wait for PostgreSQL to be ready - run: | - for i in {1..30}; do - if pg_isready -h postgres -p 5432; then - echo "PostgreSQL is ready" - exit 0 - fi - echo "Waiting for PostgreSQL... Attempt $i" - sleep 2 - done - echo "PostgreSQL did not start in time" - exit 1 - - - name: Run tests - env: - DB_NAME: ${{ secrets.DB_TEST_NAME }} - DB_USER: ${{ secrets.DB_TEST_USER }} - DB_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }} - DB_HOST: postgres - DB_PORT: 5432 - DB_SSLMODE: disable - SECRET_KEY: ${{ secrets.SECRET_KEY }} - run: | - echo "JWT_SECRET=$SECRET_KEY" > .env - echo "DB_NAME=$DB_NAME" >> .env - echo "DB_USER=$DB_USER" >> .env - echo "DB_PASSWORD=$DB_PASSWORD" >> .env - echo "DB_HOST=$DB_HOST" >> .env - echo "DB_PORT=$DB_PORT" >> .env - echo "DB_SSLMODE=$DB_SSLMODE" >> .env - echo "TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U" >> .env - echo "TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T" >> .env - echo "TWILIO_AUTH_TOKEN=f4a1f2b0b79ea3735078c2d8ee9684e1" >> .env - echo "TWILIO_PHONE_NUMBER=10123456789" >> .env - echo "ALLOWED_ORIGINS=http://localhost:5173" >> .env - - echo "Initializing config" - mkdir -p ~/.ssh - echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender_models_deploy_key - chmod 600 ~/.ssh/textsender_models_deploy_key - ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts - - eval $(ssh-agent -s) - ssh-add -v ~/.ssh/textsender_models_deploy_key - - go env -w GOPRIVATE='${{ secrets.GIT_HOST_ROOT }}' - - echo "Creating local .gitconfig" - touch ~/.gitconfig - cat > ~/.gitconfig << "EOF" - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = https://${{ secrets.GIT_HOST_ROOT }} - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = http://${{ secrets.GIT_HOST_ROOT }} - EOF - - go test -v ./... diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 80799e6..0000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/textsender-api -/vendor/ - -.env -.env.local -.env.docker diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ad58f9d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# Multi-stage Dockerfile for Go application -FROM golang:1.26.2 AS builder - -WORKDIR /app - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - openssh-client git - -RUN mkdir -p -m 0700 ~/.ssh && \ - ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts - -# Configure Git to use SSH for GitHub -RUN git config --global url."git@git.kundeng.us:".insteadOf "https://git.kundeng.us/" && \ - git config --global url."git@git.kundeng.us:".insteadOf "http://git.kundeng.us/" - -# Set up the Go environment for private modules -ENV GOPRIVATE=git.kundeng.us - - -# Copy go mod and sum files -COPY go.mod go.sum ./ - -RUN --mount=type=ssh \ - go mod download - -# Copy source code -COPY ./cmd ./cmd -COPY ./internal ./internal -COPY ./Makefile . -COPY ./.env . -COPY ./migrations ./migrations -COPY ./docs ./docs - -# Build the application -RUN CGO_ENABLED=0 GOOS=linux make build - -# Runtime stage -FROM alpine:latest AS production - -RUN apk --no-cache add ca-certificates - -WORKDIR /root/ - -# Copy the pre-built binary file from the previous stage -COPY --from=builder /app/textsender-api . -COPY --from=builder /app/.env ./ -COPY --from=builder /app/migrations ./migrations - -# Expose port -EXPOSE 8080 - -# Command to run the executable -CMD ["./textsender-api"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 296dfe1..0000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -VERSION ?= $(shell git describe --tags 2>/dev/null || echo "dev") -COMMIT ?= $(shell git rev-parse --short HEAD) -BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) -GO_VERSION ?= $(shell go version | awk '{print $$3}') - -.PHONY: build -build: - go build -ldflags="\ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.Version=$(VERSION)' \ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.BuildTime=$(BUILD_TIME)' \ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.Commit=$(COMMIT)' \ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.GoVersion=$(GO_VERSION)'" \ - -o textsender-api cmd/api/main.go - -.PHONY: install -install: - go install -ldflags="\ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.Version=$(VERSION)' \ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.BuildTime=$(BUILD_TIME)' \ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.Commit=$(COMMIT)' \ - -X 'git.kundeng.us/phoenix/textsender-api/internal/version.GoVersion=$(GO_VERSION)'" diff --git a/README.md b/README.md deleted file mode 100644 index 13a8c36..0000000 --- a/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# TextSender-API -A software system to process Text messaging queueing - - -## Getting started - - -### Building -```SHELL -make build -``` - - -Generate API documentation -``` -go install github.com/swaggo/swag/cmd/swag@latest - -swag init --generalInfo main.go --dir ./cmd/api,./internal/handler --output docs/ --parseDependency --parseInternal -``` - -The API documentation can be viewed from `http://localhost:8080/swagger/index.html`. diff --git a/cmd/api/main.go b/cmd/api/main.go deleted file mode 100644 index f0f1fa6..0000000 --- a/cmd/api/main.go +++ /dev/null @@ -1,161 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" - "github.com/swaggo/http-swagger/v2" - - _ "git.kundeng.us/phoenix/textsender-api/docs" - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/config" - database "git.kundeng.us/phoenix/textsender-api/internal/db" - "git.kundeng.us/phoenix/textsender-api/internal/handler" - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - mdlware "git.kundeng.us/phoenix/textsender-api/internal/middleware" - "git.kundeng.us/phoenix/textsender-api/internal/services" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -// @title textsender-api -// @version 1.0 -// @description Core API to send text messages - -// @host localhost:8080 -// @BasePath /api/v1 - -// @securityDefinitions.apikey BearerAuth -// @in header -// @name Authorization -// @description JWT Bearer Token -func main() { - cfg, tCfg, err := config.Load() - if err != nil { - log.Fatal("Error:", err) - } else if cfg == nil { - log.Fatal("Error initializing config") - } else if cfg.JWTSecret == "" { - log.Fatal("Error: JWTSecret not initialized") - } - - apiApp := app.App{TwilioConfig: tCfg} - - db, err := database.NewDatabase(cfg.GetDBConnString()) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } - defer db.Close() - - ctx := context.Background() - - if cfg.ResetDB { - log.Println("Resetting database") - if err := db.ResetDatabase(ctx); err != nil { - log.Fatal("Failed to reset database:", err) - } - log.Println("Database reset completed. Exiting.") - return - } else { - if exists, err := database.TableExists(ctx, db.Pool, "contacts"); err == nil && !exists { - log.Println("Resetting database") - if err = db.ResetDatabase(ctx); err != nil { - log.Fatal("Error:", err) - } - } else { - log.Fatal("Error:", err) - } - } - - jwtService := services.NewJWTService(cfg.JWTSecret) - - contactStore := store.NewContactStore(db.Pool) - messageStore := store.NewMessageStore(db.Pool) - schStore := store.NewScheduledMessageStore(db.Pool) - schMsgEventStore := store.NewScheduledMessageEventStore(db.Pool) - merStore := store.NewMessageEventResponseStore(db.Pool) - - contactHandler := handler.NewContactHandler(&apiApp, contactStore) - messageHandler := handler.NewMessageHandler(&apiApp, messageStore) - scheduledMessageHandler := handler.NewScheduledMessageHandler(&apiApp, schStore) - scheduledMessageEventHandler := handler.NewScheduledMessageEventHandler(&apiApp, schMsgEventStore, schStore) - scheduledMessageStatusHandler := handler.NewScheduledMessageStatusHandler(&apiApp, schMsgEventStore, schStore) - eventHandler := handler.NewEventResponseHandler(&apiApp, merStore) - instantMessageHandler := handler.NewSendInstantMessageHandler(&apiApp, merStore, contactStore, messageStore) - - router := chi.NewRouter() - - // Configure CORS - router.Use(cors.Handler(cors.Options{ - AllowedOrigins: cfg.AllowedOrigins, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, - AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, - ExposedHeaders: []string{"Link", "X-Total-Count"}, - AllowCredentials: true, - MaxAge: 300, // 5 minutes - })) - router.Use(middleware.Logger) - router.Use(middleware.Recoverer) - router.Use(middleware.Timeout(60 * time.Second)) - router.Use(mdlware.JSONContentType) - - router.Method("POST", endpoint.ADD_CONTACT_ENDPOINT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.AddContact))) - router.Method("GET", endpoint.GET_CONTACT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.GetContact))) - router.Method("POST", endpoint.ADD_MESSAGE, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(messageHandler.AddMessage))) - router.Method("GET", endpoint.GET_MESSAGE, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(messageHandler.GetMessage))) - router.Method("POST", endpoint.ScheduleMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.AddScheduledMessage))) - router.Method("POST", endpoint.AddEventToScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.AddScheduledMessageEvent))) - router.Method("GET", endpoint.GetScheduledMessageEventEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.GetScheduledMessageEvent))) - router.Method("DELETE", endpoint.DeleteScheduledMessageEventEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.DeleteScheduledMessageEvent))) - router.Method("PATCH", endpoint.UpdateScheduledMessageStatusEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageStatusHandler.UpdateStatus))) - router.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage))) - router.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage))) - router.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse))) - router.Method("GET", endpoint.FetchMessageEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.Fetch))) - router.Method("POST", endpoint.SendInstantMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(instantMessageHandler.Send))) - router.Method("PATCH", endpoint.Update_Names_Endpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.UpdateName))) - - router.Method("GET", "/swagger/*", httpSwagger.Handler( - httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.PORT)), - )) - - // Start server - server := &http.Server{ - Addr: ":" + cfg.ServerPort, - Handler: router, - ReadTimeout: 15 * time.Second, - WriteTimeout: 15 * time.Second, - IdleTimeout: 60 * time.Second, - } - - // Graceful shutdown - go func() { - log.Printf("Server starting on port %s", cfg.ServerPort) - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("Server failed to start: %v", err) - } - }() - - // Wait for interrupt signal - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutting down server...") - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - log.Fatalf("Server forced to shutdown: %v", err) - } - - log.Println("Server exited") -} diff --git a/cmd/api/main_test.go b/cmd/api/main_test.go deleted file mode 100644 index c6b0c3d..0000000 --- a/cmd/api/main_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - "context" - "flag" - "log" - "net/http" - "os" - "path" - "testing" - - "github.com/go-chi/chi/v5" - "github.com/joho/godotenv" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/config" - "git.kundeng.us/phoenix/textsender-api/internal/db" - "git.kundeng.us/phoenix/textsender-api/internal/handler" - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - mdlware "git.kundeng.us/phoenix/textsender-api/internal/middleware" - "git.kundeng.us/phoenix/textsender-api/internal/services" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -var testRouter *chi.Mux - -func TestMain(m *testing.M) { - cfg, apiApp := load() - - database, err := db.NewDatabase(cfg.GetDBConnString()) - if err != nil { - log.Println(err.Error()) - log.Fatal("Failed to initialize database") - } - defer database.Close() - - ctx := context.Background() - - if cfg.ResetDB { - if err = database.ResetDatabase(ctx); err != nil { - log.Println(err.Error()) - log.Fatalln("Failed to initialize database") - } - } else { - if exists, err := db.TableExists(ctx, database.Pool, "contacts "); err == nil && !exists { - log.Println("Resetting database") - err = database.ResetDatabase(ctx) - if err != nil { - log.Println("Error:", err) - } - } else { - log.Println("Error:", err) - } - } - - jwtService := services.NewJWTService(cfg.JWTSecret) - - contactStore := store.NewContactStore(database.Pool) - messageStore := store.NewMessageStore(db.Pool) - schStore := store.NewScheduledMessageStore(db.Pool) - schMsgEventStore := store.NewScheduledMessageEventStore(db.Pool) - merStore := store.NewMessageEventResponseStore(db.Pool) - - contactHandler := handler.NewContactHandler(apiApp, contactStore) - messageHandler := handler.NewMessageHandler(apiApp, messageStore) - scheduledMessageHandler := handler.NewScheduledMessageHandler(apiApp, schStore) - scheduledMessageEventHandler := handler.NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schStore) - scheduledMessageStatusHandler := handler.NewScheduledMessageStatusHandler(apiApp, schMsgEventStore, schStore) - eventHandler := handler.NewEventResponseHandler(apiApp, merStore) - instantMessageHandler := handler.NewSendInstantMessageHandler(apiApp, merStore, contactStore, messageStore) - - testRouter = chi.NewRouter() - testRouter.Handle(endpoint.ADD_CONTACT_ENDPOINT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.AddContact))) - testRouter.Handle(endpoint.GET_CONTACT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.GetContact))) - testRouter.Handle(endpoint.ADD_MESSAGE, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(messageHandler.AddMessage))) - testRouter.Handle(endpoint.GET_MESSAGE, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(messageHandler.GetMessage))) - testRouter.Handle(endpoint.ScheduleMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.AddScheduledMessage))) - testRouter.Method("GET", endpoint.GetScheduledMessageEventEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.GetScheduledMessageEvent))) - testRouter.Method("DELETE", endpoint.DeleteScheduledMessageEventEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.DeleteScheduledMessageEvent))) - testRouter.Method("PATCH", endpoint.UpdateScheduledMessageStatusEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageStatusHandler.UpdateStatus))) - testRouter.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage))) - testRouter.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage))) - testRouter.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse))) - testRouter.Method("GET", endpoint.FetchMessageEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.Fetch))) - testRouter.Method("POST", endpoint.SendInstantMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(instantMessageHandler.Send))) - testRouter.Method("PATCH", endpoint.Update_Names_Endpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.UpdateName))) - - code := m.Run() - os.Exit(code) -} - -func load() (*config.Config, *app.App) { - resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit") - port := flag.String("port", config.PORT, "Server port") - flag.Parse() - - cwd, _ := os.Getwd() - envPath := path.Join(cwd, ".env") - - err := godotenv.Load(envPath) - if err != nil { - envPath = path.Join(cwd, "../..", ".env") - if err := godotenv.Load(envPath); err != nil { - panic("Error loading .env file: " + err.Error()) - } - } - - unpackedConnString := config.UnpackDBConnString() - dbConnString := unpackedConnString.Parse() - - if tCfg, err := config.TwilioConfig(); err != nil { - log.Fatal("Error: ", err) - return nil, nil - } else { - return &config.Config{ - DBConnString: dbConnString, - ServerPort: *port, - ResetDB: *resetDb, - }, &app.App{TwilioConfig: tCfg} - } -} - -func resetTestDB(t *testing.T) { - t.Helper() - _, err := db.Pool.Exec(context.Background(), "DELETE FROM contacts") - if err != nil { - t.Fatalf("Failed to reset test database: %v", err) - } -} diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index b1b641c..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,112 +0,0 @@ -version: '3.8' # Use a recent version - -services: - api: - build: # Tells docker-compose to build the Dockerfile in the current directory - context: . - ssh: ["default"] # Uses host's SSH agent - container_name: textsender_api # Optional: Give the container a specific name - ports: - # Map host port 8000 to container port 3000 (adjust as needed) - - "8080:8080" - env_file: - - .env - depends_on: - main_db: - condition: service_healthy # Wait for the DB to be healthy before starting the app - networks: - - textsender-network - restart: unless-stopped # Optional: Restart policy - - auth_api: - build: - context: ../textsender-auth - ssh: ["default"] # Uses host's SSH agent - dockerfile: Dockerfile - container_name: textsender_auth # Optional: Give the container a specific name - ports: - # Map host port 8000 to container port 3000 (adjust as needed) - - "9080:9080" - env_file: - - ../textsender-auth/.env - depends_on: - auth_db: - condition: service_healthy # Wait for the DB to be healthy before starting the app - networks: - - textsender-network - restart: unless-stopped # Optional: Restart policy - - catapult: - build: - context: ../catapult - ssh: ["default"] - dockerfile: Dockerfile - container_name: catapult - env_file: - - ../catapult/.env - depends_on: - - auth_api - - api - networks: - - textsender-network - restart: unless-stopped - - # PostgreSQL Database Service - main_db: - image: postgres:18.4-alpine # Use an official Postgres image (Alpine variant is smaller) - container_name: textsender_db # Optional: Give the container a specific name - environment: - # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender} - POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_db} - volumes: - # Persist database data using a named volume - - postgres_data:/var/lib/postgresql - ports: - - "5432:5432" - healthcheck: - # Checks if Postgres is ready to accept connections - test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 10s - networks: - - textsender-network - restart: always # Optional: Restart policy - - auth_db: - image: postgres:18.4-alpine # Use an official Postgres image (Alpine variant is smaller) - container_name: textsender_auth_db # Optional: Give the container a specific name - environment: - # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender_auth} - POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_auth_db} - volumes: - # Persist database data using a named volume - - postgres_data_auth:/var/lib/postgresql - ports: - - "5433:5432" - healthcheck: - # Checks if Postgres is ready to accept connections - test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 10s - networks: - - textsender-network - restart: always # Optional: Restart policy - -# Define the named volume for data persistence -volumes: - postgres_data: - driver: local # Use the default local driver - postgres_data_auth: - driver: local # Use the default local driver - -networks: - textsender-network: - driver: bridge diff --git a/docs/docs.go b/docs/docs.go deleted file mode 100644 index 64e9ff5..0000000 --- a/docs/docs.go +++ /dev/null @@ -1,1282 +0,0 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT -package docs - -import "github.com/swaggo/swag" - -const docTemplate = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "contact": {}, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/contact": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Get a contact record to have a recipient to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contacts" - ], - "summary": "Get contact", - "parameters": [ - { - "type": "string", - "description": "Contact Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetContactResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetContactResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetContactResponse" - } - } - } - } - }, - "/contact/new": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a contact record to have a recipient to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contacts" - ], - "summary": "Add contact", - "parameters": [ - { - "description": "Data to add contact", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddContact" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddContactResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddContactResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddContactResponse" - } - } - } - } - }, - "/contact/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the first, last, or nickname of a Contact (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contacts" - ], - "summary": "Update names of Contact", - "parameters": [ - { - "description": "Data to update contact's names", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.UpdateNameRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - } - } - } - }, - "/instant/message": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Send a message without the constraints of scheduled messages (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "messages" - ], - "summary": "Send instant message", - "parameters": [ - { - "description": "Send an instnt message", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageRequest" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageResponse" - } - } - } - } - }, - "/message": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Get a message record to have a recipient to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "messages" - ], - "summary": "Get message", - "parameters": [ - { - "type": "string", - "description": "Message Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetMessageResponse" - } - } - } - } - }, - "/message/new": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a message record to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "messages" - ], - "summary": "Add message", - "parameters": [ - { - "description": "Data to add message", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddMessage" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddMessageResponse" - } - } - } - } - }, - "/schedule/message": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Get a scheduled message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled messages" - ], - "summary": "Get scheduled message", - "parameters": [ - { - "type": "string", - "description": "Contact Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageResponse" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Adds a scheduled message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled messages" - ], - "summary": "Add scheduled message", - "parameters": [ - { - "description": "Data to add scheduled message", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddScheduledMessage" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageResponse" - } - } - } - } - }, - "/schedule/message/event": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Gets a scheduled message event (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled message events" - ], - "summary": "Get scheduled message event", - "parameters": [ - { - "type": "string", - "description": "Contact Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Scheduled Message Id", - "name": "scheduled_message_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageEventResponse" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a scheduled message event (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled message events" - ], - "summary": "Add scheduled message event", - "parameters": [ - { - "description": "Data to add scheduled message event", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddScheduledMessageEvent" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageEventResponse" - } - } - } - } - }, - "/schedule/message/event/response": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Fetches a MessageEventResponse given a user id (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "message" - ], - "summary": "Fetcht MessageEventResponse", - "parameters": [ - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse" - } - } - } - } - }, - "/schedule/message/event/response/record": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Saves the result of sending a text message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "message" - ], - "summary": "Record MessageEventResponse", - "parameters": [ - { - "description": "Data to add MessageEventResponse", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RecordEventRequest" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.RecordEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.RecordEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.RecordEventResponse" - } - } - } - } - }, - "/schedule/message/event/{id}": { - "delete": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Deletes a scheduled message event (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled message events" - ], - "summary": "Delete scheduled message event", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "scheduled message event Id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.DeleteScheduledMessageEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.DeleteScheduledMessageEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.DeleteScheduledMessageEventResponse" - } - } - } - } - }, - "/schedule/message/fetch": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Fetches a scheduled message that is available to be processed (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled messages" - ], - "summary": "Fetch scheduled message", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.FetchNextMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.FetchNextMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.FetchNextMessageResponse" - } - } - } - } - }, - "/schedule/message/status/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the status of a scheduled message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "status" - ], - "summary": "Update status", - "parameters": [ - { - "description": "Used to update scheduled message status", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestScheduledMessageStatus" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - } - } - } - } - }, - "definitions": { - "contact.Contact": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "event.MessageEventResponse": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "response": { - "type": "object", - "additionalProperties": {} - }, - "scheduled_message_event_id": { - "type": "string" - }, - "sent": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.AddContactResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/contact.Contact" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.AddMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/message.Message" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.AddScheduledMessageEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessageEvent" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.AddScheduledMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.DeleteScheduledMessageEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessageEvent" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.FetchNextMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetContactResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/contact.Contact" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetMessageEventResponseFetchedResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/event.MessageEventResponse" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/message.Message" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetScheduledMessageEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessageEvent" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetScheduledMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RecordEventRequest": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "response": { - "type": "object", - "additionalProperties": {} - }, - "scheduled_message_event_id": { - "type": "string" - }, - "sent": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RecordEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/event.MessageEventResponse" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RequestAddContact": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RequestAddMessage": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RequestAddScheduledMessage": { - "type": "object", - "properties": { - "scheduled": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RequestAddScheduledMessageEvent": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "scheduled_message_id": { - "type": "string" - } - } - }, - "handler.RequestScheduledMessageStatus": { - "type": "object", - "properties": { - "scheduled_message_id": { - "type": "string" - }, - "status": { - "type": "string" - } - } - }, - "handler.ScheduledMessageChange": { - "type": "object", - "properties": { - "old_status": { - "type": "string" - }, - "scheduled_message": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - } - }, - "handler.ScheduledMessageStatusResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.ScheduledMessageChange" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.SendInstantMessageRequest": { - "type": "object", - "properties": { - "contact_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "message_id": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.SendInstantMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/event.MessageEventResponse" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.UpdateNameRequest": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.UpdateNameResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/contact.Contact" - } - }, - "message": { - "type": "string" - } - } - }, - "message.Message": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "id": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "scheduling.ScheduledMessage": { - "type": "object", - "properties": { - "created": { - "type": "string" - }, - "id": { - "type": "string" - }, - "scheduled": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "scheduling.ScheduledMessageEvent": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "created": { - "type": "string" - }, - "id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "scheduled_message_id": { - "type": "string" - } - } - } - }, - "securityDefinitions": { - "BearerAuth": { - "description": "JWT Bearer Token", - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -}` - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = &swag.Spec{ - Version: "1.0", - Host: "localhost:8080", - BasePath: "/api/v1", - Schemes: []string{}, - Title: "textsender-api", - Description: "Core API to send text messages", - InfoInstanceName: "swagger", - SwaggerTemplate: docTemplate, - LeftDelim: "{{", - RightDelim: "}}", -} - -func init() { - swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) -} diff --git a/docs/swagger.json b/docs/swagger.json deleted file mode 100644 index c08fe8b..0000000 --- a/docs/swagger.json +++ /dev/null @@ -1,1258 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "Core API to send text messages", - "title": "textsender-api", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:8080", - "basePath": "/api/v1", - "paths": { - "/contact": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Get a contact record to have a recipient to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contacts" - ], - "summary": "Get contact", - "parameters": [ - { - "type": "string", - "description": "Contact Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetContactResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetContactResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetContactResponse" - } - } - } - } - }, - "/contact/new": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a contact record to have a recipient to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contacts" - ], - "summary": "Add contact", - "parameters": [ - { - "description": "Data to add contact", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddContact" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddContactResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddContactResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddContactResponse" - } - } - } - } - }, - "/contact/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the first, last, or nickname of a Contact (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "contacts" - ], - "summary": "Update names of Contact", - "parameters": [ - { - "description": "Data to update contact's names", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.UpdateNameRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - } - } - } - }, - "/instant/message": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Send a message without the constraints of scheduled messages (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "messages" - ], - "summary": "Send instant message", - "parameters": [ - { - "description": "Send an instnt message", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageRequest" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.SendInstantMessageResponse" - } - } - } - } - }, - "/message": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Get a message record to have a recipient to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "messages" - ], - "summary": "Get message", - "parameters": [ - { - "type": "string", - "description": "Message Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetMessageResponse" - } - } - } - } - }, - "/message/new": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a message record to send a text to (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "messages" - ], - "summary": "Add message", - "parameters": [ - { - "description": "Data to add message", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddMessage" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddMessageResponse" - } - } - } - } - }, - "/schedule/message": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Get a scheduled message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled messages" - ], - "summary": "Get scheduled message", - "parameters": [ - { - "type": "string", - "description": "Contact Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageResponse" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Adds a scheduled message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled messages" - ], - "summary": "Add scheduled message", - "parameters": [ - { - "description": "Data to add scheduled message", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddScheduledMessage" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageResponse" - } - } - } - } - }, - "/schedule/message/event": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Gets a scheduled message event (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled message events" - ], - "summary": "Get scheduled message event", - "parameters": [ - { - "type": "string", - "description": "Contact Id", - "name": "id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Scheduled Message Id", - "name": "scheduled_message_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetScheduledMessageEventResponse" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add a scheduled message event (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled message events" - ], - "summary": "Add scheduled message event", - "parameters": [ - { - "description": "Data to add scheduled message event", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestAddScheduledMessageEvent" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.AddScheduledMessageEventResponse" - } - } - } - } - }, - "/schedule/message/event/response": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Fetches a MessageEventResponse given a user id (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "message" - ], - "summary": "Fetcht MessageEventResponse", - "parameters": [ - { - "type": "string", - "description": "User Id", - "name": "user_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.GetMessageEventResponseFetchedResponse" - } - } - } - } - }, - "/schedule/message/event/response/record": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Saves the result of sending a text message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "message" - ], - "summary": "Record MessageEventResponse", - "parameters": [ - { - "description": "Data to add MessageEventResponse", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RecordEventRequest" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.RecordEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.RecordEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.RecordEventResponse" - } - } - } - } - }, - "/schedule/message/event/{id}": { - "delete": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Deletes a scheduled message event (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled message events" - ], - "summary": "Delete scheduled message event", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "scheduled message event Id", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.DeleteScheduledMessageEventResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.DeleteScheduledMessageEventResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.DeleteScheduledMessageEventResponse" - } - } - } - } - }, - "/schedule/message/fetch": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Fetches a scheduled message that is available to be processed (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "scheduled messages" - ], - "summary": "Fetch scheduled message", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.FetchNextMessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.FetchNextMessageResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.FetchNextMessageResponse" - } - } - } - } - }, - "/schedule/message/status/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the status of a scheduled message (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "status" - ], - "summary": "Update status", - "parameters": [ - { - "description": "Used to update scheduled message status", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RequestScheduledMessageStatus" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.ScheduledMessageStatusResponse" - } - } - } - } - } - }, - "definitions": { - "contact.Contact": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "event.MessageEventResponse": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "response": { - "type": "object", - "additionalProperties": {} - }, - "scheduled_message_event_id": { - "type": "string" - }, - "sent": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.AddContactResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/contact.Contact" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.AddMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/message.Message" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.AddScheduledMessageEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessageEvent" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.AddScheduledMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.DeleteScheduledMessageEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessageEvent" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.FetchNextMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetContactResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/contact.Contact" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetMessageEventResponseFetchedResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/event.MessageEventResponse" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/message.Message" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetScheduledMessageEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessageEvent" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.GetScheduledMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RecordEventRequest": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "response": { - "type": "object", - "additionalProperties": {} - }, - "scheduled_message_event_id": { - "type": "string" - }, - "sent": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RecordEventResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/event.MessageEventResponse" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RequestAddContact": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RequestAddMessage": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RequestAddScheduledMessage": { - "type": "object", - "properties": { - "scheduled": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.RequestAddScheduledMessageEvent": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "scheduled_message_id": { - "type": "string" - } - } - }, - "handler.RequestScheduledMessageStatus": { - "type": "object", - "properties": { - "scheduled_message_id": { - "type": "string" - }, - "status": { - "type": "string" - } - } - }, - "handler.ScheduledMessageChange": { - "type": "object", - "properties": { - "old_status": { - "type": "string" - }, - "scheduled_message": { - "$ref": "#/definitions/scheduling.ScheduledMessage" - } - } - }, - "handler.ScheduledMessageStatusResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.ScheduledMessageChange" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.SendInstantMessageRequest": { - "type": "object", - "properties": { - "contact_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "message_id": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.SendInstantMessageResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/event.MessageEventResponse" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.UpdateNameRequest": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "nickname": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.UpdateNameResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/contact.Contact" - } - }, - "message": { - "type": "string" - } - } - }, - "message.Message": { - "type": "object", - "properties": { - "content": { - "type": "string" - }, - "id": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "scheduling.ScheduledMessage": { - "type": "object", - "properties": { - "created": { - "type": "string" - }, - "id": { - "type": "string" - }, - "scheduled": { - "type": "string" - }, - "status": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "scheduling.ScheduledMessageEvent": { - "type": "object", - "properties": { - "contact_id": { - "type": "string" - }, - "created": { - "type": "string" - }, - "id": { - "type": "string" - }, - "message_id": { - "type": "string" - }, - "scheduled_message_id": { - "type": "string" - } - } - } - }, - "securityDefinitions": { - "BearerAuth": { - "description": "JWT Bearer Token", - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml deleted file mode 100644 index 53252df..0000000 --- a/docs/swagger.yaml +++ /dev/null @@ -1,810 +0,0 @@ -basePath: /api/v1 -definitions: - contact.Contact: - properties: - first_name: - type: string - id: - type: string - last_name: - type: string - nickname: - type: string - phone_number: - type: string - user_id: - type: string - type: object - event.MessageEventResponse: - properties: - contact_id: - type: string - id: - type: string - message_id: - type: string - response: - additionalProperties: {} - type: object - scheduled_message_event_id: - type: string - sent: - type: string - status: - type: string - user_id: - type: string - type: object - handler.AddContactResponse: - properties: - data: - items: - $ref: '#/definitions/contact.Contact' - type: array - message: - type: string - type: object - handler.AddMessageResponse: - properties: - data: - items: - $ref: '#/definitions/message.Message' - type: array - message: - type: string - type: object - handler.AddScheduledMessageEventResponse: - properties: - data: - items: - $ref: '#/definitions/scheduling.ScheduledMessageEvent' - type: array - message: - type: string - type: object - handler.AddScheduledMessageResponse: - properties: - data: - items: - $ref: '#/definitions/scheduling.ScheduledMessage' - type: array - message: - type: string - type: object - handler.DeleteScheduledMessageEventResponse: - properties: - data: - items: - $ref: '#/definitions/scheduling.ScheduledMessageEvent' - type: array - message: - type: string - type: object - handler.FetchNextMessageResponse: - properties: - data: - items: - $ref: '#/definitions/scheduling.ScheduledMessage' - type: array - message: - type: string - type: object - handler.GetContactResponse: - properties: - data: - items: - $ref: '#/definitions/contact.Contact' - type: array - message: - type: string - type: object - handler.GetMessageEventResponseFetchedResponse: - properties: - data: - items: - $ref: '#/definitions/event.MessageEventResponse' - type: array - message: - type: string - type: object - handler.GetMessageResponse: - properties: - data: - items: - $ref: '#/definitions/message.Message' - type: array - message: - type: string - type: object - handler.GetScheduledMessageEventResponse: - properties: - data: - items: - $ref: '#/definitions/scheduling.ScheduledMessageEvent' - type: array - message: - type: string - type: object - handler.GetScheduledMessageResponse: - properties: - data: - items: - $ref: '#/definitions/scheduling.ScheduledMessage' - type: array - message: - type: string - type: object - handler.RecordEventRequest: - properties: - contact_id: - type: string - message_id: - type: string - response: - additionalProperties: {} - type: object - scheduled_message_event_id: - type: string - sent: - type: string - status: - type: string - user_id: - type: string - type: object - handler.RecordEventResponse: - properties: - data: - items: - $ref: '#/definitions/event.MessageEventResponse' - type: array - message: - type: string - type: object - handler.RequestAddContact: - properties: - first_name: - type: string - last_name: - type: string - nickname: - type: string - phone_number: - type: string - user_id: - type: string - type: object - handler.RequestAddMessage: - properties: - content: - type: string - user_id: - type: string - type: object - handler.RequestAddScheduledMessage: - properties: - scheduled: - type: string - status: - type: string - user_id: - type: string - type: object - handler.RequestAddScheduledMessageEvent: - properties: - contact_id: - type: string - message_id: - type: string - scheduled_message_id: - type: string - type: object - handler.RequestScheduledMessageStatus: - properties: - scheduled_message_id: - type: string - status: - type: string - type: object - handler.ScheduledMessageChange: - properties: - old_status: - type: string - scheduled_message: - $ref: '#/definitions/scheduling.ScheduledMessage' - type: object - handler.ScheduledMessageStatusResponse: - properties: - data: - items: - $ref: '#/definitions/handler.ScheduledMessageChange' - type: array - message: - type: string - type: object - handler.SendInstantMessageRequest: - properties: - contact_ids: - items: - type: string - type: array - message_id: - type: string - user_id: - type: string - type: object - handler.SendInstantMessageResponse: - properties: - data: - items: - $ref: '#/definitions/event.MessageEventResponse' - type: array - message: - type: string - type: object - handler.UpdateNameRequest: - properties: - contact_id: - type: string - first_name: - type: string - last_name: - type: string - nickname: - type: string - user_id: - type: string - type: object - handler.UpdateNameResponse: - properties: - data: - items: - $ref: '#/definitions/contact.Contact' - type: array - message: - type: string - type: object - message.Message: - properties: - content: - type: string - id: - type: string - user_id: - type: string - type: object - scheduling.ScheduledMessage: - properties: - created: - type: string - id: - type: string - scheduled: - type: string - status: - type: string - user_id: - type: string - type: object - scheduling.ScheduledMessageEvent: - properties: - contact_id: - type: string - created: - type: string - id: - type: string - message_id: - type: string - scheduled_message_id: - type: string - type: object -host: localhost:8080 -info: - contact: {} - description: Core API to send text messages - title: textsender-api - version: "1.0" -paths: - /contact: - get: - consumes: - - application/json - description: Get a contact record to have a recipient to send a text to (requires - JWT) - parameters: - - description: Contact Id - in: path - name: id - required: true - type: string - - description: User Id - in: path - name: user_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.GetContactResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.GetContactResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.GetContactResponse' - security: - - BearerAuth: [] - summary: Get contact - tags: - - contacts - /contact/new: - post: - consumes: - - application/json - description: Add a contact record to have a recipient to send a text to (requires - JWT) - parameters: - - description: Data to add contact - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RequestAddContact' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.AddContactResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.AddContactResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.AddContactResponse' - security: - - BearerAuth: [] - summary: Add contact - tags: - - contacts - /contact/update: - patch: - consumes: - - application/json - description: Update the first, last, or nickname of a Contact (requires JWT) - parameters: - - description: Data to update contact's names - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.UpdateNameRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - security: - - BearerAuth: [] - summary: Update names of Contact - tags: - - contacts - /instant/message: - post: - consumes: - - application/json - description: Send a message without the constraints of scheduled messages (requires - JWT) - parameters: - - description: Send an instnt message - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.SendInstantMessageRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.SendInstantMessageResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.SendInstantMessageResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.SendInstantMessageResponse' - security: - - BearerAuth: [] - summary: Send instant message - tags: - - messages - /message: - get: - consumes: - - application/json - description: Get a message record to have a recipient to send a text to (requires - JWT) - parameters: - - description: Message Id - in: path - name: id - required: true - type: string - - description: User Id - in: path - name: user_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.GetMessageResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.GetMessageResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.GetMessageResponse' - security: - - BearerAuth: [] - summary: Get message - tags: - - messages - /message/new: - post: - consumes: - - application/json - description: Add a message record to send a text to (requires JWT) - parameters: - - description: Data to add message - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RequestAddMessage' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.AddMessageResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.AddMessageResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.AddMessageResponse' - security: - - BearerAuth: [] - summary: Add message - tags: - - messages - /schedule/message: - get: - consumes: - - application/json - description: Get a scheduled message (requires JWT) - parameters: - - description: Contact Id - in: path - name: id - required: true - type: string - - description: User Id - in: path - name: user_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.GetScheduledMessageResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.GetScheduledMessageResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.GetScheduledMessageResponse' - security: - - BearerAuth: [] - summary: Get scheduled message - tags: - - scheduled messages - post: - consumes: - - application/json - description: Adds a scheduled message (requires JWT) - parameters: - - description: Data to add scheduled message - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RequestAddScheduledMessage' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.AddScheduledMessageResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.AddScheduledMessageResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.AddScheduledMessageResponse' - security: - - BearerAuth: [] - summary: Add scheduled message - tags: - - scheduled messages - /schedule/message/event: - get: - consumes: - - application/json - description: Gets a scheduled message event (requires JWT) - parameters: - - description: Contact Id - in: path - name: id - required: true - type: string - - description: Scheduled Message Id - in: path - name: scheduled_message_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.GetScheduledMessageEventResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.GetScheduledMessageEventResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.GetScheduledMessageEventResponse' - security: - - BearerAuth: [] - summary: Get scheduled message event - tags: - - scheduled message events - post: - consumes: - - application/json - description: Add a scheduled message event (requires JWT) - parameters: - - description: Data to add scheduled message event - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RequestAddScheduledMessageEvent' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.AddScheduledMessageEventResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.AddScheduledMessageEventResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.AddScheduledMessageEventResponse' - security: - - BearerAuth: [] - summary: Add scheduled message event - tags: - - scheduled message events - /schedule/message/event/{id}: - delete: - consumes: - - application/json - description: Deletes a scheduled message event (requires JWT) - parameters: - - description: scheduled message event Id - format: uuid - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.DeleteScheduledMessageEventResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.DeleteScheduledMessageEventResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.DeleteScheduledMessageEventResponse' - security: - - BearerAuth: [] - summary: Delete scheduled message event - tags: - - scheduled message events - /schedule/message/event/response: - get: - consumes: - - application/json - description: Fetches a MessageEventResponse given a user id (requires JWT) - parameters: - - description: User Id - in: path - name: user_id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.GetMessageEventResponseFetchedResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.GetMessageEventResponseFetchedResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.GetMessageEventResponseFetchedResponse' - security: - - BearerAuth: [] - summary: Fetcht MessageEventResponse - tags: - - message - /schedule/message/event/response/record: - post: - consumes: - - application/json - description: Saves the result of sending a text message (requires JWT) - parameters: - - description: Data to add MessageEventResponse - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RecordEventRequest' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.RecordEventResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.RecordEventResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.RecordEventResponse' - security: - - BearerAuth: [] - summary: Record MessageEventResponse - tags: - - message - /schedule/message/fetch: - get: - consumes: - - application/json - description: Fetches a scheduled message that is available to be processed (requires - JWT) - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.FetchNextMessageResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.FetchNextMessageResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.FetchNextMessageResponse' - security: - - BearerAuth: [] - summary: Fetch scheduled message - tags: - - scheduled messages - /schedule/message/status/update: - patch: - consumes: - - application/json - description: Update the status of a scheduled message (requires JWT) - parameters: - - description: Used to update scheduled message status - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RequestScheduledMessageStatus' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/handler.ScheduledMessageStatusResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.ScheduledMessageStatusResponse' - "404": - description: Not Found - schema: - $ref: '#/definitions/handler.ScheduledMessageStatusResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.ScheduledMessageStatusResponse' - security: - - BearerAuth: [] - summary: Update status - tags: - - status -securityDefinitions: - BearerAuth: - description: JWT Bearer Token - in: header - name: Authorization - type: apiKey -swagger: "2.0" diff --git a/go.mod b/go.mod deleted file mode 100644 index 696ac94..0000000 --- a/go.mod +++ /dev/null @@ -1,48 +0,0 @@ -module git.kundeng.us/phoenix/textsender-api - -go 1.26.2 - -require ( - git.kundeng.us/phoenix/swoosh v0.2.1 - git.kundeng.us/phoenix/textsender-models v0.2.1 - github.com/go-chi/chi/v5 v5.2.5 - github.com/go-chi/cors v1.2.2 - github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.9.2 - github.com/joho/godotenv v1.5.1 - github.com/stretchr/testify v1.11.1 - github.com/swaggo/http-swagger/v2 v2.0.2 - github.com/swaggo/swag v1.16.6 -) - -require ( - github.com/KyleBanks/depth v1.2.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.5 // indirect - github.com/go-openapi/spec v0.22.4 // indirect - github.com/go-openapi/swag/conv v0.26.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect - github.com/go-openapi/swag/jsonutils v0.26.0 // indirect - github.com/go-openapi/swag/loading v0.26.0 // indirect - github.com/go-openapi/swag/stringutils v0.26.0 // indirect - github.com/go-openapi/swag/typeutils v0.26.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.0 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/swaggo/files/v2 v2.0.2 // indirect - github.com/twilio/twilio-go v1.30.5 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.44.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index c090c0f..0000000 --- a/go.sum +++ /dev/null @@ -1,122 +0,0 @@ -git.kundeng.us/phoenix/swoosh v0.2.1 h1:mJ/zXBOqzi0YTrSm5kxvahzp2WH7lx4R3iIO0s9VFFI= -git.kundeng.us/phoenix/swoosh v0.2.1/go.mod h1:81XNzmTmDPuDPmXMUmKTLErNSKKBM+hsW5U60Lm78ww= -git.kundeng.us/phoenix/textsender-models v0.2.1 h1:21br4NF58aUFuCx8laKxC5RvZMl4GsSIaMX4bvf5plw= -git.kundeng.us/phoenix/textsender-models v0.2.1/go.mod h1:nu5QWy9o+spx/t9NFipaGmF5qiBJS/0QhxyCjoi3Z3E= -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= -github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= -github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= -github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= -github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= -github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= -github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= -github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q= -github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU= -github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0= -github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= -github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= -github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= -github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= -github.com/twilio/twilio-go v1.30.5 h1:hi6+2kMte29zrFBsw7VSSNtbF30GPMGz/4LRIUgXng8= -github.com/twilio/twilio-go v1.30.5/go.mod h1:QbitvbvtkV77Jn4BABAKVmxabYSjMyQG4tHey9gfPqg= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/app/app.go b/internal/app/app.go deleted file mode 100644 index 9948d51..0000000 --- a/internal/app/app.go +++ /dev/null @@ -1,9 +0,0 @@ -package app - -import ( - auxcfg "git.kundeng.us/phoenix/textsender-models/tx0/config/auxiliary" -) - -type App struct { - TwilioConfig *auxcfg.TwilioConfig -} diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index d23f4dc..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,168 +0,0 @@ -package config - -import ( - "flag" - "fmt" - "os" - "strconv" - "strings" - - auxcfg "git.kundeng.us/phoenix/textsender-models/tx0/config/auxiliary" - "github.com/joho/godotenv" - - "git.kundeng.us/phoenix/textsender-api/internal/version" -) - -type Config struct { - DBConnString string - ServerPort string - ResetDB bool - JWTSecret string `env:"JWT_SECRET" required:"true"` - TwilioConfig *auxcfg.TwilioConfig - AllowedOrigins []string -} - -type ConnectionInfo struct { - Username string - Password string - Database string - Host string - Port int - SslMode string -} - -const PORT = "8080" -const APP_NAME = "textsender-api" - -func PortString() string { - portMessage := fmt.Sprintf(":%s", PORT) - - return portMessage -} - -func (ci ConnectionInfo) Parse() string { - return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", ci.Username, ci.Password, ci.Host, ci.Port, ci.Database, ci.SslMode) -} - -func PrintName() { - fmt.Println(APP_NAME) - fmt.Println(version.String()) -} - -func Load() (*Config, *auxcfg.TwilioConfig, error) { - versionFlag := flag.Bool("version", false, "Print version information") - resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit") - port := flag.String("port", PORT, "Server port") - flag.Parse() - - if *versionFlag { - fmt.Println(version.String()) - os.Exit(-1) - } - - if err := godotenv.Load(); err != nil { - return nil, nil, fmt.Errorf("Error loading .env file") - } - - unpackedConnString := UnpackDBConnString() - dbConnString := unpackedConnString.Parse() - - if cfg, err := TwilioConfig(); err != nil { - return nil, nil, err - } else { - allowedOrigins := unpackAllowedOrigins() - return &Config{ - DBConnString: dbConnString, - ServerPort: *port, - ResetDB: *resetDb, - JWTSecret: os.Getenv("JWT_SECRET"), - AllowedOrigins: allowedOrigins, - }, cfg, nil - } -} - -func UnpackDBConnString() (connInfo ConnectionInfo) { - username := os.Getenv("DB_USER") - password := os.Getenv("DB_PASSWORD") - host := os.Getenv("DB_HOST") - port := os.Getenv("DB_PORT") - database := os.Getenv("DB_NAME") - sslMode := os.Getenv("DB_SSLMODE") - - if username != "" { - connInfo.Username = username - } else { - connInfo.Username = "user" - } - - if password != "" { - connInfo.Password = password - } else { - connInfo.Password = "password" - } - - if host != "" { - connInfo.Host = host - } else { - connInfo.Host = "localhost" - } - - if port != "" { - num, err := strconv.Atoi(port) - if err != nil { - return - } - connInfo.Port = num - } else { - connInfo.Port = 5432 - } - - if database != "" { - connInfo.Database = database - } else { - connInfo.Database = "textsender_db" - } - - if sslMode != "" { - connInfo.SslMode = sslMode - } else { - connInfo.SslMode = "disable" - } - - return -} - -func unpackAllowedOrigins() []string { - allowedOriginsRaw := os.Getenv("ALLOWED_ORIGINS") - allowedOriginsSplit := strings.Split(allowedOriginsRaw, ",") - return allowedOriginsSplit -} - -func TwilioConfig() (*auxcfg.TwilioConfig, error) { - authSid := os.Getenv("TWILIO_AUTH_SID") - serviceSid := os.Getenv("TWILIO_SERVICE_SID") - authToken := os.Getenv("TWILIO_AUTH_TOKEN") - phoneNumber := os.Getenv("TWILIO_PHONE_NUMBER") - - if len(authSid) == 0 { - return nil, fmt.Errorf("Twilio config account sid not found") - } else if len(serviceSid) == 0 { - return nil, fmt.Errorf("Twilio config service sid not found") - } else if len(authToken) == 0 { - return nil, fmt.Errorf("Twilio config auth token not found") - } else if len(phoneNumber) == 0 { - return nil, fmt.Errorf("Twilio config phone number not found") - } else { - var cfg auxcfg.TwilioConfig - cfg.AccountSID = authSid - cfg.ServiceSID = serviceSid - cfg.AuthToken = authToken - cfg.Number = phoneNumber - - return &cfg, nil - } -} - -func (c *Config) GetDBConnString() string { - return c.DBConnString -} diff --git a/internal/db/connection.go b/internal/db/connection.go deleted file mode 100644 index feab2a3..0000000 --- a/internal/db/connection.go +++ /dev/null @@ -1,136 +0,0 @@ -// db/connection.go -package db - -import ( - "context" - "fmt" - "log" - "os" - "path" - "strings" - "time" - - "github.com/jackc/pgx/v5/pgxpool" -) - -var Pool *pgxpool.Pool - -type Database struct { - Pool *pgxpool.Pool -} - -func NewDatabase(connString string) (*Database, error) { - ctx := context.Background() - // Parse the connection string and create pool configuration - config, err := pgxpool.ParseConfig(connString) - if err != nil { - return nil, fmt.Errorf("unable to parse DSN: %v", err) - } - - // Configure connection pool settings - config.MaxConns = 25 - config.MinConns = 5 - config.MaxConnLifetime = time.Hour - config.MaxConnIdleTime = 30 * time.Minute - config.HealthCheckPeriod = time.Minute - - // Configure connection timeouts - config.ConnConfig.ConnectTimeout = 10 * time.Second - config.ConnConfig.RuntimeParams["timezone"] = "UTC" - - // Create the connection pool - pool, err := pgxpool.NewWithConfig(ctx, config) - if err != nil { - return nil, fmt.Errorf("unable to create connection pool: %v", err) - } - - // Test the connection with a short timeout - if err := pool.Ping(ctx); err != nil { - pool.Close() - return nil, fmt.Errorf("unable to ping database: %v", err) - } - - log.Printf("Successfully connected to database") - return &Database{Pool: pool}, nil -} - -func TableExists(ctx context.Context, conn *pgxpool.Pool, tableName string) (bool, error) { - var exists bool - - query := ` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = $1 - ); - ` - - err := conn.QueryRow(ctx, query, tableName).Scan(&exists) - if err != nil { - return false, fmt.Errorf("error checking if table exists: %w", err) - } - - return exists, nil -} - -func (db *Database) Close() { - if db.Pool != nil { - db.Pool.Close() - log.Println("Database connection pool closed") - } -} - -// HealthCheck verifies the database connection is still alive -func (db *Database) HealthCheck(ctx context.Context) error { - return db.Pool.Ping(ctx) -} - -// GetPoolStats returns connection pool statistics -func (db *Database) GetPoolStats() string { - stats := db.Pool.Stat() - return fmt.Sprintf("TotalConns: %d, IdleConns: %d, AcquiredConns: %d", - stats.TotalConns(), stats.IdleConns(), stats.AcquiredConns()) -} - -func (db *Database) ResetDatabase(ctx context.Context) error { - tx, err := db.Pool.Begin(ctx) - if err != nil { - return fmt.Errorf("Transaction unable to begin: %v", err) - } - defer tx.Rollback(ctx) - - schemaContent, err := os.ReadFile("migrations/schema.sql") - if err != nil { - log.Println("Default migrations not found. Checking different directory") - cwd, _ := os.Getwd() - migrationsPath := path.Join(cwd, "../..", "migrations/schema.sql") - schemaContent, err = os.ReadFile(migrationsPath) - if err != nil { - return fmt.Errorf("error reading schema file: %v", err) - } - } - - for _, stmt := range strings.Split(string(schemaContent), ";") { - stmt = strings.TrimSpace(stmt) - if stmt == "" { - continue - } - - if strings.HasPrefix(stmt, "--") { - continue - } - - _, err := tx.Exec(ctx, stmt) - if err != nil { - return fmt.Errorf("error executing SQL statement: %v\nStatement: %s", err, stmt) - } - } - - if err := tx.Commit(ctx); err != nil { - return fmt.Errorf("unable to commit transaction: %v", err) - } - - log.Println("Database schema applied successfully") - - return nil -} diff --git a/internal/handler/contact.go b/internal/handler/contact.go deleted file mode 100644 index 52a0095..0000000 --- a/internal/handler/contact.go +++ /dev/null @@ -1,246 +0,0 @@ -package handler - -import ( - "fmt" - "log" - "net/http" - - "git.kundeng.us/phoenix/textsender-models/tx0/contact" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -type ContactHandler struct { - App *app.App - ContactStore store.ContactStore -} - -func NewContactHandler(apiApp *app.App, str store.ContactStore) *ContactHandler { - return &ContactHandler{App: apiApp, ContactStore: str} -} - -type RequestAddContact struct { - PhoneNumber string `json:"phone_number"` - UserId uuid.UUID `json:"user_id"` - Firstname *string `json:"first_name,omitempty"` - Lastname *string `json:"last_name,omitempty"` - Nickname *string `json:"nickname,omitempty"` -} - -type AddContactResponse struct { - Message string `json:"message"` - Data []contact.Contact `json:"data"` -} - -// AddContact godoc -// @Summary Add contact -// @Description Add a contact record to have a recipient to send a text to (requires JWT) -// @Tags contacts -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RequestAddContact true "Data to add contact" -// @Success 201 {object} AddContactResponse -// @Failure 400 {object} AddContactResponse -// @Failure 500 {object} AddContactResponse -// @Router /contact/new [post] -func (c *ContactHandler) AddContact(w http.ResponseWriter, r *http.Request) { - var req RequestAddContact - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - newContact := contact.Contact{PhoneNumber: req.PhoneNumber, UserId: &req.UserId, Firstname: req.Firstname, Lastname: req.Lastname, Nickname: req.Nickname} - - var statusCode int - var resp AddContactResponse - - ctx := r.Context() - - if exists, err := c.ContactStore.ContactExists(ctx, newContact.PhoneNumber, *newContact.UserId); err != nil { - log.Printf("Error: %v", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if exists { - statusCode = http.StatusBadRequest - resp.Message = "Cannot create contact" - } else { - if err := c.ContactStore.CreateContact(ctx, &newContact); err != nil { - log.Printf("Error: %v", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusCreated - resp.Message = "Contact created" - resp.Data = append(resp.Data, newContact) - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -type GetContactResponse struct { - Message string `json:"message"` - Data []contact.Contact `json:"data"` -} - -// GetContact godoc -// @Summary Get contact -// @Description Get a contact record to have a recipient to send a text to (requires JWT) -// @Tags contacts -// @Accept json -// @Produce json -// @Param id path string true "Contact Id" -// @Param user_id path string true "User Id" -// @Security BearerAuth -// @Success 200 {object} GetContactResponse -// @Failure 400 {object} GetContactResponse -// @Failure 500 {object} GetContactResponse -// @Router /contact [get] -func (c *ContactHandler) GetContact(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - fmt.Println("Method:", r.Method) - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - // One or the other - var id, userId uuid.UUID - queryParams := r.URL.Query() - - // Check if parameter exists - if _, exists := queryParams["id"]; !exists { - if _, exists := queryParams["user_id"]; !exists { - fmt.Fprintf(w, "Name parameter not provided") - http.Error(w, "Query params", http.StatusBadRequest) - return - } else { - var err error - userId, err = uuid.Parse(queryParams.Get("user_id")) - if err != nil { - fmt.Fprintf(w, "Name parameter exists: %s", userId) - } - } - } else { - var err error - idTmp := queryParams.Get("id") - id, err = uuid.Parse(idTmp) - if err != nil { - fmt.Println("Error:", err) - http.Error(w, "Error parsing Id", http.StatusBadRequest) - return - } - } - - var statusCode int - var resp GetContactResponse - - ctx := r.Context() - - if id != uuid.Nil { - fmt.Println("Checking with Id") - if con, err := c.ContactStore.GetContactByID(ctx, id); err == nil { - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, *con) - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } else if userId != uuid.Nil { - fmt.Println("Checking with User Id") - if contacts, err := c.ContactStore.GetAllContacts(ctx); err == nil { - for _, con := range contacts { - if *con.UserId == userId { - resp.Data = append(resp.Data, *con) - } - } - - if len(resp.Data) > 0 { - statusCode = http.StatusOK - resp.Message = "Successful" - } else { - statusCode = http.StatusNotFound - resp.Message = "Contact not found" - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Invalid query parameter" - } - - RespondWithJSON(w, statusCode, &resp) -} - -type UpdateNameRequest struct { - Nickname *string `json:"nickname,omitempty"` - Firstname *string `json:"first_name,omitempty"` - Lastname *string `json:"last_name,omitempty"` - ContactId uuid.UUID `json:"contact_id"` - UserId uuid.UUID `json:"user_id"` -} - -type UpdateNameResponse struct { - Message string `json:"message"` - Data []contact.Contact `json:"data"` -} - -// UpdateName godoc -// @Summary Update names of Contact -// @Description Update the first, last, or nickname of a Contact (requires JWT) -// @Tags contacts -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body UpdateNameRequest true "Data to update contact's names" -// @Success 200 {object} UpdateNameResponse -// @Failure 400 {object} UpdateNameResponse -// @Failure 500 {object} UpdateNameResponse -// @Router /contact/update [patch] -func (c *ContactHandler) UpdateName(w http.ResponseWriter, r *http.Request) { - var req UpdateNameRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp UpdateNameResponse - - if req.UserId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "User Id not provided" - } else if req.ContactId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Contact Id not provided" - } else if req.Firstname == nil && req.Lastname == nil && req.Nickname == nil { - statusCode = http.StatusBadRequest - resp.Message = "No name provided" - } else { - ctx := r.Context() - if con, err := c.ContactStore.GetContactByID(ctx, req.ContactId); err != nil { - resp.Message = err.Error() - statusCode = http.StatusInternalServerError - } else { - if affectedRows, err := c.ContactStore.UpdateNames(ctx, con, req.Firstname, req.Lastname, req.Nickname); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - log.Println("Updated", affectedRows, "rows") - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, *con) - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} diff --git a/internal/handler/contact_test.go b/internal/handler/contact_test.go deleted file mode 100644 index 218f08d..0000000 --- a/internal/handler/contact_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "git.kundeng.us/phoenix/textsender-models/tx0/contact" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-api/internal/store/mock" -) - -type Request struct { - PhoneNumber string - UserId uuid.UUID -} - -func TestCreateContactWithMock(t *testing.T) { - mockstore := mock.NewMockContactStore() - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - - handler := NewContactHandler(apiApp, mockstore) - - testUserId := uuid.New() - testBody := Request{PhoneNumber: "+12335403383", UserId: testUserId} - jsonValue, _ := json.Marshal(testBody) - - req, _ := http.NewRequest("POST", endpoint.ADD_CONTACT_ENDPOINT, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.AddContact(rr, req) - - assert.Equal(t, http.StatusCreated, rr.Code) - - var response AddContactResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error Creating contact %v", err) - - assert.NotEmpty(t, response.Data, "No Contact created") - - assert.NotNil(t, response.Data[0].Id, "Id should not be nil") -} - -func TestGetContactWithMock(t *testing.T) { - mockstore := mock.NewMockContactStore() - testUserId := uuid.New() - - testCon := contact.Contact{PhoneNumber: "+12335403383", UserId: &testUserId} - ctx := t.Context() - if err := mockstore.CreateContact(ctx, &testCon); err != nil { - assert.NoError(t, err, "Error creating contact") - return - } - - url := fmt.Sprintf("%s?user_id=%s", endpoint.GET_CONTACT, testCon.UserId) - req, _ := http.NewRequest("GET", url, nil) - rr := httptest.NewRecorder() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - contactHandler := NewContactHandler(apiApp, mockstore) - contactHandler.GetContact(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response GetContactResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error getting contact %v", err) - - assert.NotEmpty(t, response.Data, "No Contact retrieved") - - assert.NotNil(t, response.Data[0].Id, "Id should not be nil") -} - -func TestUpdateContactNamesWithMock(t *testing.T) { - mockstore := mock.NewMockContactStore() - testUserId := uuid.New() - - testCon := contact.Contact{PhoneNumber: "+12335403383", UserId: &testUserId} - ctx := t.Context() - var firstname, lastname, nickname string - firstname = "Bob" - lastname = "De-Buildor" - nickname = "With The Tool" - if err := mockstore.CreateContact(ctx, &testCon); err != nil { - assert.NoError(t, err, "Error creating contact") - return - } - - var requestBody UpdateNameRequest - requestBody.Firstname = &firstname - requestBody.Lastname = &lastname - requestBody.Nickname = &nickname - requestBody.ContactId = *testCon.Id - requestBody.UserId = testUserId - jsonValue, _ := json.Marshal(requestBody) - req, _ := http.NewRequest("PATCH", endpoint.Update_Names_Endpoint, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - contactHandler := NewContactHandler(apiApp, mockstore) - contactHandler.UpdateName(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response UpdateNameResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error updating names of contact %v", err) - - assert.NotEmpty(t, response.Data, "No Contact updated") - - assert.NotNil(t, response.Data[0].Firstname, "Firstname should not be nil") - assert.NotNil(t, response.Data[0].Lastname, "Lastname should not be nil") - assert.NotNil(t, response.Data[0].Nickname, "Nickname should not be nil") -} diff --git a/internal/handler/draftMessage.go b/internal/handler/draftMessage.go deleted file mode 100644 index 9d76e83..0000000 --- a/internal/handler/draftMessage.go +++ /dev/null @@ -1,15 +0,0 @@ -package handler - -import "net/http" - -import "git.kundeng.us/phoenix/textsender-api/internal/model" - -// TODO: Remove file -func DraftMessageHandler(w http.ResponseWriter, r *http.Request) { - da := model.MessageItem{ - Id: 1, - Message: "The Word", - } - - RespondWithJSON(w, http.StatusOK, da) -} diff --git a/internal/handler/endpoint/endpoint.go b/internal/handler/endpoint/endpoint.go deleted file mode 100644 index cefaa9e..0000000 --- a/internal/handler/endpoint/endpoint.go +++ /dev/null @@ -1,29 +0,0 @@ -package endpoint - -const ( - MESSAGE_DRAFT_ENDPOINT = "/api/v1/message/draft" - - ADD_MESSAGE = "/api/v1/message/new" - GET_MESSAGE = "/api/v1/message" - - GET_CONTACT = "/api/v1/contact" - ADD_CONTACT_ENDPOINT = "/api/v1/contact/new" - Update_Names_Endpoint = "/api/v1/contact/update" - - ScheduleMessageEndpoint = "/api/v1/schedule/message" - GetScheduledMessageEndpoint = "/api/v1/schedule/message" - - AddEventToScheduledMessageEndpoint = "/api/v1/schedule/message/event" - GetScheduledMessageEventEndpoint = "/api/v1/schedule/message/event" - DeleteScheduledMessageEventEndpoint = "/api/v1/schedule/message/event/{id}" - UpdateScheduledMessageStatusEndpoint = "/api/v1/schedule/message/status/update" - - FetchNextScheduledMessageEndpoint = "/api/v1/schedule/message/fetch" - - // TODO: Tweak endpoint verbage for MessageEventResponse endpoint - // TODO: Make RecordEventResponse more consistent with MessageEventResponse - RecordEventResponse = "/api/v1/schedule/message/event/response/record" - FetchMessageEventResponse = "/api/v1/schedule/message/event/response" - - SendInstantMessageEndpoint = "/api/v1/instant/message" -) diff --git a/internal/handler/helper.go b/internal/handler/helper.go deleted file mode 100644 index adb68b2..0000000 --- a/internal/handler/helper.go +++ /dev/null @@ -1,46 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "log" - "net/http" -) - -func ExtractFromRequest(r *http.Request, reqItem interface{}) error { - err := json.NewDecoder(r.Body).Decode(&reqItem) - if err != nil { - return err - } else { - return nil - } -} - -func InvalidJson(err error) string { - return fmt.Sprintf("Invalid JSON: %s", err.Error()) -} - -// Helper function to send JSON responses -// TODO: Change type of data to any -func RespondWithJSON(w http.ResponseWriter, statusCode int, data interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - if err := json.NewEncoder(w).Encode(data); err != nil { - log.Printf("Error encoding JSON: %v", err) - } -} - -// Gets the query parameter from the URL -func ParseQueryParams(r *http.Request, query string) (*string, error) { - queryParams := r.URL.Query() - if _, exists := queryParams[query]; exists { - value := queryParams.Get(query) - if len(value) == 0 { - return nil, fmt.Errorf("Value of query parameter is empty") - } else { - return &value, nil - } - } else { - return nil, fmt.Errorf("Could not find query %s", query) - } -} diff --git a/internal/handler/helper_test.go b/internal/handler/helper_test.go deleted file mode 100644 index 81ca2ea..0000000 --- a/internal/handler/helper_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package handler - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path" - "testing" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/contact" - "git.kundeng.us/phoenix/textsender-models/tx0/message" - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/joho/godotenv" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/config" - "git.kundeng.us/phoenix/textsender-api/internal/db" -) - -func GetApp() (*app.App, error) { - cwd, _ := os.Getwd() - envPath := path.Join(cwd, ".env") - - err := godotenv.Load(envPath) - if err != nil { - envPath = path.Join(cwd, "../..", ".env") - if err := godotenv.Load(envPath); err != nil { - panic("Error loading .env file: " + err.Error()) - } - } - - if tCfg, err := config.TwilioConfig(); err != nil { - return nil, fmt.Errorf("Error: %v", err) - } else { - return &app.App{TwilioConfig: tCfg}, nil - } -} - -func ResetTestDB(t *testing.T, tableName string) { - t.Helper() - _, err := db.Pool.Exec(context.Background(), fmt.Sprintf("DELETE FROM %s", tableName)) - if err != nil { - t.Fatalf("Failed to reset test database: %v", err) - } -} - -func ContactTest(id uuid.UUID, userId uuid.UUID) contact.Contact { - if id == uuid.Nil { - id = uuid.New() - return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId} - } else { - return contact.Contact{Id: &id, PhoneNumber: "+10123456789", UserId: &userId} - } -} - -func MessageTest(id uuid.UUID, userId uuid.UUID) message.Message { - if id == uuid.Nil { - id = uuid.New() - return message.Message{Id: &id, Content: "Oh how the mighty have fallen", UserId: &userId} - } else { - return message.Message{Id: &id, Content: "Oh how the mighty have fallen", UserId: &userId} - } -} - -func ScheduledMessageTest(id uuid.UUID, userId uuid.UUID, now time.Time) scheduling.ScheduledMessage { - if id == uuid.Nil { - return scheduling.ScheduledMessage{Id: uuid.New(), UserId: userId, Scheduled: now.Add(20 * time.Minute), Status: scheduling.Pending} - } else { - return scheduling.ScheduledMessage{Id: id, UserId: userId, Scheduled: now.Add(20 * time.Minute), Status: scheduling.Pending} - } -} - -func ScheduledMessageEventTest(messageId, contactId, scheduledMessageId uuid.UUID) scheduling.ScheduledMessageEvent { - return scheduling.ScheduledMessageEvent{MessageId: messageId, ContactId: contactId, ScheduledMessageId: scheduledMessageId} -} - -func MERResponseInString() string { - return `{ - "body": "Whoknows?", - "num_segments": "0", - "direction": "outbound-api", - "from": "+12243026041", - "to": "+16303831708", - "date_updated": "Sat,29Nov202519:06:59+0000", - "uri": "/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SM1193a529e7f7a840667cd1e0f13ea95a.json", - "account_sid": "ACefa1ef516314c9d1a68cbd657de49277", - "num_media": "0", - "status": "scheduled", - "messaging_service_sid": "MG803f3676706b92eb02e18dd820c447f2", - "sid": "SM1193a529e7f7a840667cd1e0f13ea95a", - "date_created": "Sat,29Nov202519:06:59+0000", - "api_version": "2010-04-01", - "subresource_uris": { - "media": "/2010-04-01/Accounts/ACefa1ef516314c9d1a68cbd657de49277/Messages/SM1193a529e7f7a840667cd1e0f13ea95a/Media.json" - } -}` -} - -func ConvertStringToMapOfAny(response string) (map[string]any, error) { - bytes := []byte(response) - var merResponse map[string]any - err := json.Unmarshal(bytes, &merResponse) - if err != nil { - return nil, err - } else { - return merResponse, nil - } -} diff --git a/internal/handler/instant_message.go b/internal/handler/instant_message.go deleted file mode 100644 index 46ad663..0000000 --- a/internal/handler/instant_message.go +++ /dev/null @@ -1,189 +0,0 @@ -package handler - -import ( - "context" - "fmt" - "log" - "net/http" - "strings" - "time" - - "git.kundeng.us/phoenix/swoosh/swoop/send" - "git.kundeng.us/phoenix/textsender-models/tx0/contact" - "git.kundeng.us/phoenix/textsender-models/tx0/message" - "git.kundeng.us/phoenix/textsender-models/tx0/message/event" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -const Contact_Limit = 400 - -type SendInstantMessageHandler struct { - App *app.App - MERStore store.MessageEventResponseStore - ContactStore store.ContactStore - MessageStore store.MessageStore -} - -func NewSendInstantMessageHandler(apiApp *app.App, str store.MessageEventResponseStore, cStore store.ContactStore, mStore store.MessageStore) *SendInstantMessageHandler { - return &SendInstantMessageHandler{App: apiApp, MERStore: str, ContactStore: cStore, MessageStore: mStore} -} - -type SendInstantMessageRequest struct { - ContactIds []uuid.UUID `json:"contact_ids"` - MessageId uuid.UUID `json:"message_id"` - UserId uuid.UUID `json:"user_id"` -} - -type SendInstantMessageResponse struct { - Message string `json:"message"` - Data []*event.MessageEventResponse `json:"data"` -} - -// Sent godoc -// @Summary Send instant message -// @Description Send a message without the constraints of scheduled messages (requires JWT) -// @Tags messages -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body SendInstantMessageRequest true "Send an instnt message" -// @Success 201 {object} SendInstantMessageResponse -// @Failure 400 {object} SendInstantMessageResponse -// @Failure 500 {object} SendInstantMessageResponse -// @Router /instant/message [post] -func (s *SendInstantMessageHandler) Send(w http.ResponseWriter, r *http.Request) { - var req SendInstantMessageRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, InvalidJson(err), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp SendInstantMessageResponse - - if req.UserId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "User Id is nil" - } else if req.MessageId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Message Id is nil" - } else if len(req.ContactIds) == 0 { - statusCode = http.StatusBadRequest - resp.Message = "No contacts provided" - } else { - if len(req.ContactIds) > Contact_Limit { - statusCode = http.StatusBadRequest - resp.Message = "Too many contacts" - } else { - ctx := r.Context() - if msg, contacts, err := s.verify(ctx, &req); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if msg == nil { - statusCode = http.StatusBadRequest - resp.Message = "Message is empty" - } else if len(contacts) == 0 { - statusCode = http.StatusBadRequest - resp.Message = "Contacts not found" - } else { - if s.App != nil { - msgSender := send.MessageSender{Config: s.App.TwilioConfig} - for _, c := range contacts { - if twilioResp, twilioRespObject, err := msgSender.Send(*msg, *c, nil); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - dateStr := strings.TrimSpace(*twilioResp.DateUpdated) - if parsedTime, err := time.Parse(time.RFC1123Z, dateStr); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - mer := event.MessageEventResponse{} - mer.Response = twilioRespObject - mer.UserId = req.UserId - mer.Sent = parsedTime - mer.Status = event.Message_Event_Response_Status_Instant - mer.ContactId = c.Id - mer.MessageId = msg.Id - if err := s.MERStore.Create(ctx, &mer); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, &mer) - } - } - } - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = "Config not properly configured" - } - } - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -func (s *SendInstantMessageHandler) verify(ctx context.Context, req *SendInstantMessageRequest) (*message.Message, []*contact.Contact, error) { - if msg, err := s.verifyMessage(ctx, req.MessageId); err != nil { - log.Println("Huh?") - return nil, nil, fmt.Errorf("Error:%v", err) - } else { - log.Println("Okay") - if msg != nil { - if contacts, err := s.verifyContacts(ctx, req.ContactIds); err != nil { - return nil, nil, fmt.Errorf("Error:%v", err) - } else { - if len(contacts) == 0 { - return msg, nil, nil - } else { - return msg, contacts, nil - } - } - } else { - log.Println("How can this be?") - return nil, nil, fmt.Errorf("Could not verify message") - } - } -} - -func (s *SendInstantMessageHandler) verifyMessage(ctx context.Context, messageId uuid.UUID) (*message.Message, error) { - if msg, err := s.MessageStore.GetMessageByID(ctx, messageId); err != nil { - return nil, fmt.Errorf("Error getting message:%v", err) - } else { - if msg == nil { - log.Println("Message is nil") - return nil, nil - } else { - return msg, nil - } - } -} - -func (s *SendInstantMessageHandler) verifyContacts(ctx context.Context, contactIds []uuid.UUID) ([]*contact.Contact, error) { - var contacts []*contact.Contact - - for _, contactId := range contactIds { - if c, err := s.ContactStore.GetContactByID(ctx, contactId); err != nil { - return nil, fmt.Errorf("Error getting contacts:%v", err) - } else { - if c != nil { - contacts = append(contacts, c) - } - } - } - - if len(contacts) == 0 { - return nil, nil - } else { - return contacts, nil - } -} diff --git a/internal/handler/message.go b/internal/handler/message.go deleted file mode 100644 index 0d2cd06..0000000 --- a/internal/handler/message.go +++ /dev/null @@ -1,180 +0,0 @@ -package handler - -import ( - "fmt" - "log" - "net/http" - - "git.kundeng.us/phoenix/textsender-models/tx0/message" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -type MessageHandler struct { - App *app.App - MessageStore store.MessageStore -} - -func NewMessageHandler(apiApp *app.App, str store.MessageStore) *MessageHandler { - return &MessageHandler{App: apiApp, MessageStore: str} -} - -const Message_Limit = 200 - -type RequestAddMessage struct { - Content string `json:"content"` - UserId uuid.UUID `json:"user_id"` -} - -type AddMessageResponse struct { - Message string `json:"message"` - Data []message.Message `json:"data"` -} - -// AddMessage godoc -// @Summary Add message -// @Description Add a message record to send a text to (requires JWT) -// @Tags messages -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RequestAddMessage true "Data to add message" -// @Success 201 {object} AddMessageResponse -// @Failure 400 {object} AddMessageResponse -// @Failure 500 {object} AddMessageResponse -// @Router /message/new [post] -func (m *MessageHandler) AddMessage(w http.ResponseWriter, r *http.Request) { - var req RequestAddMessage - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp AddMessageResponse - newMessage := message.Message{Content: req.Content, UserId: &req.UserId} - - if isMessageValid(&newMessage) { - ctx := r.Context() - - if err := m.MessageStore.CreateMessage(ctx, &newMessage); err != nil { - fmt.Println("Error:", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusCreated - resp.Message = "Message created" - resp.Data = append(resp.Data, newMessage) - } - } else { - statusCode = http.StatusBadRequest - resp.Message = fmt.Sprintf("Message length exceeds maximum limit. Current size: %d Limit: %d", len(newMessage.Content), Message_Limit) - } - - RespondWithJSON(w, statusCode, &resp) -} - -func isMessageValid(msg *message.Message) bool { - if len(msg.Content) <= Message_Limit { - return true - } else { - return false - } -} - -type GetMessageResponse struct { - Message string `json:"message"` - Data []message.Message `json:"data"` -} - -// GetMessage godoc -// @Summary Get message -// @Description Get a message record to have a recipient to send a text to (requires JWT) -// @Tags messages -// @Accept json -// @Produce json -// @Param id path string true "Message Id" -// @Param user_id path string true "User Id" -// @Security BearerAuth -// @Success 200 {object} GetMessageResponse -// @Failure 400 {object} GetMessageResponse -// @Failure 500 {object} GetMessageResponse -// @Router /message [get] -func (c *MessageHandler) GetMessage(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - var id, userId uuid.UUID - queryParams := r.URL.Query() - - // Check if parameter exists - if _, exists := queryParams["id"]; !exists { - if _, exists := queryParams["user_id"]; !exists { - fmt.Fprintf(w, "Name parameter not provided") - http.Error(w, "Query params", http.StatusBadRequest) - return - } else { - var err error - userId, err = uuid.Parse(queryParams.Get("user_id")) - if err != nil { - fmt.Fprintf(w, "Name parameter exists: %s", userId) - } - } - } else { - var err error - idTmp := queryParams.Get("id") - id, err = uuid.Parse(idTmp) - if err != nil { - fmt.Println("Error:", err) - http.Error(w, "Error parsing Id", http.StatusBadRequest) - return - } - } - - var statusCode int - var resp GetMessageResponse - - ctx := r.Context() - - if id != uuid.Nil { - log.Println("Checking with Id") - if con, err := c.MessageStore.GetMessageByID(ctx, id); err == nil { - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, *con) - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } else if userId != uuid.Nil { - log.Println("Checking with User Id") - if messages, err := c.MessageStore.GetAllMessages(ctx); err == nil { - log.Println("Amount of messages:", len(messages)) - for _, msg := range messages { - if *msg.UserId == userId { - resp.Data = append(resp.Data, *msg) - } - } - - if len(resp.Data) > 0 { - statusCode = http.StatusOK - resp.Message = "Successful" - } else { - statusCode = http.StatusNotFound - resp.Message = "Message not found" - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Invalid query parameter" - } - - RespondWithJSON(w, statusCode, &resp) -} diff --git a/internal/handler/message_event_response.go b/internal/handler/message_event_response.go deleted file mode 100644 index 7dc00ac..0000000 --- a/internal/handler/message_event_response.go +++ /dev/null @@ -1,174 +0,0 @@ -package handler - -import ( - "log" - "net/http" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/event" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -type EventResponseHandler struct { - App *app.App - MessageEventResponseStore store.MessageEventResponseStore -} - -func NewEventResponseHandler(apiApp *app.App, str store.MessageEventResponseStore) *EventResponseHandler { - return &EventResponseHandler{App: apiApp, MessageEventResponseStore: str} -} - -type RecordEventRequest struct { - ScheduledMessageEventId uuid.UUID `json:"scheduled_message_event_id"` - Response map[string]any `json:"response"` - UserId uuid.UUID `json:"user_id"` - Sent *time.Time `json:"sent,omitempty"` - Status string `json:"status"` - ContactId *uuid.UUID `json:"contact_id"` - MessageId *uuid.UUID `json:"message_id"` -} - -type RecordEventResponse struct { - Message string `json:"message"` - Data []*event.MessageEventResponse `json:"data"` -} - -// RecordMessageEventResponse godoc -// @Summary Record MessageEventResponse -// @Description Saves the result of sending a text message (requires JWT) -// @Tags message -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RecordEventRequest true "Data to add MessageEventResponse" -// @Success 201 {object} RecordEventResponse -// @Failure 400 {object} RecordEventResponse -// @Failure 500 {object} RecordEventResponse -// @Router /schedule/message/event/response/record [post] -func (e *EventResponseHandler) RecordResponse(w http.ResponseWriter, r *http.Request) { - var req RecordEventRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var rp event.MessageEventResponse - var resp RecordEventResponse - - if req.Sent == nil { - statusCode = http.StatusBadRequest - resp.Message = "Sent time is empty" - } else if req.UserId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "User Id is empty" - } else if len(req.Response) == 0 { - statusCode = http.StatusBadRequest - resp.Message = "Response is empty" - } else if req.ScheduledMessageEventId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Scheduled message event Id is empty" - } else { - log.Println("Starting process to record Message Event Response") - rp.ScheduledMessageEventId = req.ScheduledMessageEventId - rp.Response = req.Response - rp.UserId = req.UserId - rp.Sent = *req.Sent - - switch req.Status { - case event.Message_Event_Response_Status_Instant: - { - log.Println("Status:", req.Status) - rp.Status = req.Status - rp.MessageId = req.MessageId - rp.ContactId = req.ContactId - } - case event.Message_Event_Response_Status_Scheduled: - { - log.Println("Status:", req.Status) - rp.Status = req.Status - } - default: - { - statusCode = http.StatusBadRequest - resp.Message = "Status is invalid" - } - } - - ctx := r.Context() - if err := e.MessageEventResponseStore.Create(ctx, &rp); err != nil { - log.Println("Error:", err) - resp.Message = err.Error() - statusCode = http.StatusInternalServerError - } else { - log.Println("Created Message Event Response") - statusCode = http.StatusCreated - resp.Message = "Successful" - resp.Data = append(resp.Data, &rp) - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -type GetMessageEventResponseFetchedResponse struct { - Message string `json:"message"` - Data []*event.MessageEventResponse `json:"data"` -} - -// FetchtMessageEventResponse godoc -// @Summary Fetcht MessageEventResponse -// @Description Fetches a MessageEventResponse given a user id (requires JWT) -// @Tags message -// @Accept json -// @Produce json -// @Param user_id path string true "User Id" -// @Security BearerAuth -// @Success 200 {object} GetMessageEventResponseFetchedResponse -// @Failure 400 {object} GetMessageEventResponseFetchedResponse -// @Failure 500 {object} GetMessageEventResponseFetchedResponse -// @Router /schedule/message/event/response [get] -func (e *EventResponseHandler) Fetch(w http.ResponseWriter, r *http.Request) { - queryParams := r.URL.Query() - var userId uuid.UUID - - if _, exists := queryParams["user_id"]; !exists { - http.Error(w, "Query params", http.StatusBadRequest) - return - } else { - var err error - userId, err = uuid.Parse(queryParams.Get("user_id")) - if err != nil { - http.Error(w, "Query params", http.StatusBadRequest) - return - } - } - - var statusCode int - var resp GetMessageEventResponseFetchedResponse - - if userId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "User Id is nil" - } else { - ctx := r.Context() - if sentMessages, err := e.MessageEventResponseStore.GetWithUserId(ctx, userId); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if len(sentMessages) == 0 { - statusCode = http.StatusNotFound - resp.Message = "Not MessageEventResponses found" - } else { - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = sentMessages - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} diff --git a/internal/handler/message_event_response_test.go b/internal/handler/message_event_response_test.go deleted file mode 100644 index 0792ffa..0000000 --- a/internal/handler/message_event_response_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - evnt "git.kundeng.us/phoenix/textsender-models/tx0/message/event" - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-api/internal/store/mock" -) - -func TestRecordMessageEventResponseWithMock(t *testing.T) { - now := time.Now() - - mockStore := mock.NewMockScheduledMessageEventStore() - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgStore := mock.NewMockScheduledMessageStore() - merStore := mock.NewMockMessageEventResponseStore() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewEventResponseHandler(apiApp, merStore) - - contactId := uuid.New() - messageId := uuid.New() - scheduledMessageId := uuid.New() - testUserId := uuid.New() - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - event := scheduling.ScheduledMessageEvent{} - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } - - event.MessageId = *msg.Id - event.ContactId = *con.Id - event.ScheduledMessageId = schMsg.Id - if err := mockStore.CreateScheduledMessageEvent(ctx, &event); err != nil { - assert.NoError(t, err, "Error creating scheduled message event: %v", err) - } - - sent := now.Add(30 * time.Minute) - var merResponse map[string]any - merResponse, err = ConvertStringToMapOfAny(MERResponseInString()) - assert.NoError(t, err, "Error Converting to map[string]any") - - testReq := RecordEventRequest{ScheduledMessageEventId: event.Id, Response: merResponse, UserId: schMsg.UserId, Sent: &sent, Status: evnt.Message_Event_Response_Status_Scheduled} - jsonValue, _ := json.Marshal(testReq) - - req, _ := http.NewRequest("POST", endpoint.RecordEventResponse, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.RecordResponse(rr, req) - - assert.Equal(t, http.StatusCreated, rr.Code) - - var response RecordEventResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error parsing message event response:%v", err) - - assert.NotEmpty(t, response.Data, "No message event response created") - - var msgEventResp evnt.MessageEventResponse - msgEventResp = *response.Data[0] - - assert.NotEmpty(t, msgEventResp.Response, "The response of the message event response is empty") - assert.Equal(t, msgEventResp.Response, testReq.Response, "Responses do not match") -} - -func TestGetMessageEventResponse(t *testing.T) { - now := time.Now() - - mockStore := mock.NewMockScheduledMessageEventStore() - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgStore := mock.NewMockScheduledMessageStore() - merStore := mock.NewMockMessageEventResponseStore() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewEventResponseHandler(apiApp, merStore) - - contactId := uuid.New() - messageId := uuid.New() - scheduledMessageId := uuid.New() - testUserId := uuid.New() - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } - - event := scheduling.ScheduledMessageEvent{} - event.MessageId = *msg.Id - event.ContactId = *con.Id - event.ScheduledMessageId = schMsg.Id - sent := now.Add(30 * time.Minute) - if err := mockStore.CreateScheduledMessageEvent(ctx, &event); err != nil { - assert.NoError(t, err, "Error creating scheduled message event: %v", err) - } else { - m := evnt.MessageEventResponse{} - m.ScheduledMessageEventId = event.Id - m.UserId = schMsg.UserId - m.Status = evnt.Message_Event_Response_Status_Scheduled - m.Sent = sent - var merResponse map[string]any - merResponse, err = ConvertStringToMapOfAny(MERResponseInString()) - assert.NoError(t, err, "Error Converting to map[string]any") - m.Response = merResponse - - if err := merStore.Create(ctx, &m); err != nil { - assert.NoError(t, err, "Error creating MessageEventResponse: %v", err) - } - } - - url := fmt.Sprintf("%s?user_id=%s", endpoint.FetchMessageEventResponse, testUserId) - req, _ := http.NewRequest("GET", url, nil) - rr := httptest.NewRecorder() - - handler.Fetch(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response GetMessageEventResponseFetchedResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error parsing message event response: %v", err) - - assert.NotEmpty(t, response.Data, "No message event response found") - - var msgEventResp evnt.MessageEventResponse - msgEventResp = *response.Data[0] - - assert.NotEmpty(t, msgEventResp.Response, "The response of the message event response is empty") -} diff --git a/internal/handler/message_test.go b/internal/handler/message_test.go deleted file mode 100644 index 1781073..0000000 --- a/internal/handler/message_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "git.kundeng.us/phoenix/textsender-models/tx0/message" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-api/internal/store/mock" -) - -func TestCreateMessageWithMock(t *testing.T) { - mockStore := mock.NewMockMessageStore() - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewMessageHandler(apiApp, mockStore) - - testUserId := uuid.New() - testBody := RequestAddMessage{Content: "Who could tell?", UserId: testUserId} - jsonValue, _ := json.Marshal(testBody) - - req, _ := http.NewRequest("POST", endpoint.ADD_MESSAGE, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.AddMessage(rr, req) - - assert.Equal(t, http.StatusCreated, rr.Code) - - var response AddMessageResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error Creating message %v", err) - - assert.NotEmpty(t, response.Data, "No Message created") - - assert.NotNil(t, response.Data[0].Id, "Id should not be nil") -} - -func TestGetMessageWithMock(t *testing.T) { - mockstore := mock.NewMockMessageStore() - testUserId := uuid.New() - - testCon := message.Message{Content: "Who is the one that benefits?", UserId: &testUserId} - ctx := t.Context() - if err := mockstore.CreateMessage(ctx, &testCon); err != nil { - assert.NoError(t, err, "Error creating message") - return - } - - url := fmt.Sprintf("%s?user_id=%s", endpoint.GET_MESSAGE, testCon.UserId) - req, _ := http.NewRequest("GET", url, nil) - rr := httptest.NewRecorder() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - messageHandler := NewMessageHandler(apiApp, mockstore) - messageHandler.GetMessage(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response GetMessageResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error getting message %v", err) - - assert.NotEmpty(t, response.Data, "No Message retrieved") - - assert.NotNil(t, response.Data[0].Id, "Id should not be nil") -} diff --git a/internal/handler/schedule_message_status.go b/internal/handler/schedule_message_status.go deleted file mode 100644 index f935d75..0000000 --- a/internal/handler/schedule_message_status.go +++ /dev/null @@ -1,169 +0,0 @@ -package handler - -import ( - "net/http" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -type ScheduledMessageStatusHandler struct { - App *app.App - ScheduledMessageEventStore store.ScheduledMessageEventStore - ScheduledMessageStore store.ScheduledMessageStore -} - -func NewScheduledMessageStatusHandler(apiApp *app.App, str store.ScheduledMessageEventStore, schStore store.ScheduledMessageStore) *ScheduledMessageStatusHandler { - return &ScheduledMessageStatusHandler{App: apiApp, ScheduledMessageEventStore: str, ScheduledMessageStore: schStore} -} - -type RequestScheduledMessageStatus struct { - ScheduledMessageId uuid.UUID `json:"scheduled_message_id"` - Status string `json:"status"` -} - -type ScheduledMessageChange struct { - OldStatus string `json:"old_status"` - ScheduledMessage scheduling.ScheduledMessage `json:"scheduled_message"` -} - -type ScheduledMessageStatusResponse struct { - Message string `json:"message"` - Data []ScheduledMessageChange `json:"data"` -} - -// UpdateStatus godoc -// @Summary Update status -// @Description Update the status of a scheduled message (requires JWT) -// @Tags status -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RequestScheduledMessageStatus true "Used to update scheduled message status" -// @Success 201 {object} ScheduledMessageStatusResponse -// @Failure 400 {object} ScheduledMessageStatusResponse -// @Failure 404 {object} ScheduledMessageStatusResponse -// @Failure 500 {object} ScheduledMessageStatusResponse -// @Router /schedule/message/status/update [patch] -func (s *ScheduledMessageStatusHandler) UpdateStatus(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPatch { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - var req RequestScheduledMessageStatus - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp ScheduledMessageStatusResponse - - if req.ScheduledMessageId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Scheduled messaged Id is nil" - } else if len(req.Status) == 0 { - statusCode = http.StatusBadRequest - resp.Message = "No status provided" - } else { - ctx := r.Context() - - if schMsg, err := s.ScheduledMessageStore.Get(ctx, req.ScheduledMessageId); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if schMsg.Status == scheduling.Done { - statusCode = http.StatusNotFound - resp.Message = "Scheduled message has already completed" - } else { - switch req.Status { - case scheduling.Pending: - { - if schMsg.Status == scheduling.Processing { - statusCode = http.StatusBadRequest - resp.Message = "Message is currently processing" - } else { - if returnedStatus, err := s.ScheduledMessageStore.UpdateStatus(ctx, schMsg.Id, req.Status); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - chg := initScheduledMessageChange(schMsg, schMsg.Status, *returnedStatus) - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, chg) - } - } - } - case scheduling.Ready: - { - if events, err := s.ScheduledMessageEventStore.GetWithScheduleMessageId(ctx, schMsg.Id); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - oldStatus := schMsg.Status - if schMsg.Status != scheduling.Processing && schMsg.Status != scheduling.Ready { - // Update status - if len(events) > 0 { - if returnedStatus, err := s.ScheduledMessageStore.UpdateStatus(ctx, schMsg.Id, req.Status); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - chg := initScheduledMessageChange(schMsg, oldStatus, *returnedStatus) - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, chg) - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = "Not enough scheduled message events" - } - } else { - if schMsg.Status == scheduling.Processing { - statusCode = http.StatusBadRequest - resp.Message = "Scheduled messages are processing" - } else { - statusCode = http.StatusNotModified - resp.Message = "Status is already set" - } - } - } - } - case scheduling.Processing, scheduling.Done: - { - if events, err := s.ScheduledMessageEventStore.GetWithScheduleMessageId(ctx, schMsg.Id); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if len(events) > 0 { - if returnedStatus, err := s.ScheduledMessageStore.UpdateStatus(ctx, schMsg.Id, req.Status); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - chg := initScheduledMessageChange(schMsg, schMsg.Status, *returnedStatus) - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, chg) - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = "Not enough scheduled message events" - } - } - } - } - } - } - - } - - RespondWithJSON(w, statusCode, &resp) -} - -func initScheduledMessageChange(schMsg *scheduling.ScheduledMessage, oldStatus, newStatus string) ScheduledMessageChange { - schMsg.Status = newStatus - return ScheduledMessageChange{OldStatus: oldStatus, ScheduledMessage: *schMsg} -} diff --git a/internal/handler/schedule_message_status_test.go b/internal/handler/schedule_message_status_test.go deleted file mode 100644 index d868620..0000000 --- a/internal/handler/schedule_message_status_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-api/internal/store/mock" -) - -func TestUpdateScheduledMessageStatusWithMock(t *testing.T) { - now := time.Now() - - schMsgEventStore := mock.NewMockScheduledMessageEventStore() - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgStore := mock.NewMockScheduledMessageStore() - - contactId := uuid.New() - messageId := uuid.New() - scheduledMessageId := uuid.New() - testUserId := uuid.New() - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id) - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } else if err = schMsgEventStore.CreateScheduledMessageEvent(ctx, &event); err != nil { - assert.NoError(t, err, "Error creating scheduled message event: %v", err) - } - - testReq := RequestScheduledMessageStatus{} - testReq.Status = scheduling.Ready - testReq.ScheduledMessageId = schMsg.Id - jsonValue, _ := json.Marshal(testReq) - jsonBody := strings.NewReader(string(jsonValue)) - - req, _ := http.NewRequest("PATCH", endpoint.UpdateScheduledMessageStatusEndpoint, jsonBody) - rr := httptest.NewRecorder() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageStatusHandler(apiApp, schMsgEventStore, schMsgStore) - handler.UpdateStatus(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response ScheduledMessageStatusResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error creating event %v", err) - - assert.NotEmpty(t, response.Data, "Data should not be empty") - - changes := response.Data[0] - assert.NotEqual(t, changes.OldStatus, changes.ScheduledMessage.Status, "The status should not match") - assert.Equal(t, scheduling.Pending, changes.OldStatus, "The Old status does not match Old %s New %s", changes.OldStatus, scheduling.Pending) - assert.Equal(t, scheduling.Ready, changes.ScheduledMessage.Status, "Status has not been updated") -} diff --git a/internal/handler/scheduled_message.go b/internal/handler/scheduled_message.go deleted file mode 100644 index 8af8ee2..0000000 --- a/internal/handler/scheduled_message.go +++ /dev/null @@ -1,242 +0,0 @@ -package handler - -import ( - "fmt" - "net/http" - "strings" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -type ScheduledMessageHandler struct { - App *app.App - ScheduledMessageStore store.ScheduledMessageStore -} - -func NewScheduledMessageHandler(apiApp *app.App, str store.ScheduledMessageStore) *ScheduledMessageHandler { - return &ScheduledMessageHandler{App: apiApp, ScheduledMessageStore: str} -} - -type RequestAddScheduledMessage struct { - Scheduled time.Time `json:"scheduled"` - Status string `json:"status"` - UserId uuid.UUID `json:"user_id"` -} - -type AddScheduledMessageResponse struct { - Message string `json:"message"` - Data []scheduling.ScheduledMessage `json:"data"` -} - -// AddScheduledMessage godoc -// @Summary Add scheduled message -// @Description Adds a scheduled message (requires JWT) -// @Tags scheduled messages -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RequestAddScheduledMessage true "Data to add scheduled message" -// @Success 201 {object} AddScheduledMessageResponse -// @Failure 400 {object} AddScheduledMessageResponse -// @Failure 500 {object} AddScheduledMessageResponse -// @Router /schedule/message [post] -func (s *ScheduledMessageHandler) AddScheduledMessage(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - var req RequestAddScheduledMessage - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - scheduledMessage := scheduling.ScheduledMessage{Scheduled: req.Scheduled, Status: req.Status, UserId: req.UserId} - - var statusCode int - var resp AddScheduledMessageResponse - - if scheduledMessage.UserId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "No UserId" - } else { - ctx := r.Context() - - if validStatus, err := IsStatusValid(&scheduledMessage); err == nil { - if validStatus { - if valid, err := isScheduledTimeValid(&scheduledMessage); err == nil && valid { - if err = s.ScheduledMessageStore.CreateScheduledMessage(ctx, &scheduledMessage); err == nil { - statusCode = http.StatusCreated - resp.Data = append(resp.Data, scheduledMessage) - resp.Message = "Successful" - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Invalid scheduled time" - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Invalid status" - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -type GetScheduledMessageResponse struct { - Message string `json:"message"` - Data []scheduling.ScheduledMessage `json:"data"` -} - -// GetScheduledMessage godoc -// @Summary Get scheduled message -// @Description Get a scheduled message (requires JWT) -// @Tags scheduled messages -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param id path string true "Contact Id" -// @Param user_id path string true "User Id" -// @Success 200 {object} GetScheduledMessageResponse -// @Failure 400 {object} GetScheduledMessageResponse -// @Failure 500 {object} GetScheduledMessageResponse -// @Router /schedule/message [get] -func (s *ScheduledMessageHandler) GetScheduledMessage(w http.ResponseWriter, r *http.Request) { - var id, userId uuid.UUID - - if idParam, err := ParseQueryParams(r, "id"); err == nil { - var err error - id, err = uuid.Parse(*idParam) - if err != nil { - http.Error(w, "Error parsing Id", http.StatusBadRequest) - return - } - } - if userIdParam, err := ParseQueryParams(r, "user_id"); err == nil { - userId, err = uuid.Parse(*userIdParam) - if err != nil { - http.Error(w, "Error parsing Id", http.StatusBadRequest) - return - } - } - - var resp GetScheduledMessageResponse - var statusCode int - ctx := r.Context() - - if userId == uuid.Nil && id == uuid.Nil { - http.Error(w, "Invalid query parameters", http.StatusBadRequest) - return - } else if userId != uuid.Nil && id != uuid.Nil { - if schMsgs, err := s.ScheduledMessageStore.GetWithUserId(ctx, userId); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - for _, schMsg := range schMsgs { - if schMsg.Id == id { - resp.Data = append(resp.Data, *schMsg) - } - } - statusCode = http.StatusOK - resp.Message = "Successful" - } - } else if id != uuid.Nil { - if schMsg, err := s.ScheduledMessageStore.Get(ctx, id); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, *schMsg) - } - } else { - if schMsgs, err := s.ScheduledMessageStore.GetWithUserId(ctx, userId); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusOK - resp.Message = "Successful" - for _, schMsg := range schMsgs { - resp.Data = append(resp.Data, *schMsg) - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -type FetchNextMessageResponse struct { - Message string `json:"message"` - Data []scheduling.ScheduledMessage `json:"data"` -} - -// FetchNextMessage godoc -// @Summary Fetch scheduled message -// @Description Fetches a scheduled message that is available to be processed (requires JWT) -// @Tags scheduled messages -// @Accept json -// @Produce json -// @Security BearerAuth -// @Success 200 {object} FetchNextMessageResponse -// @Failure 400 {object} FetchNextMessageResponse -// @Failure 500 {object} FetchNextMessageResponse -// @Router /schedule/message/fetch [get] -func (s *ScheduledMessageHandler) FetchNextMessage(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - var resp FetchNextMessageResponse - var statusCode int - - if schMsg, err := s.ScheduledMessageStore.FetchNextScheduledMessage(ctx); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if schMsg != nil { - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, *schMsg) - } else { - statusCode = http.StatusNotFound - resp.Message = "No item" - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -func isScheduledTimeValid(schMsg *scheduling.ScheduledMessage) (bool, error) { - now := time.Now() - timeCutOff := now.Add(-5 * time.Minute) - futureCutOff := now.Add((24 * 7) * time.Hour) - - if schMsg.Scheduled.After(timeCutOff) { - if schMsg.Scheduled.Before(futureCutOff) { - return true, nil - } else { - return false, fmt.Errorf("Scheduled Time is too far in the future") - } - } else { - return false, nil - } -} - -func IsStatusValid(schMsg *scheduling.ScheduledMessage) (bool, error) { - schMsg.Status = strings.ToUpper(schMsg.Status) - if schMsg.Status == scheduling.Pending { - return true, nil - } else { - return false, fmt.Errorf("Status is not valid for creating scheduled message %s", schMsg.Status) - } -} diff --git a/internal/handler/scheduled_message_event.go b/internal/handler/scheduled_message_event.go deleted file mode 100644 index 9c4e17e..0000000 --- a/internal/handler/scheduled_message_event.go +++ /dev/null @@ -1,247 +0,0 @@ -package handler - -import ( - "net/http" - "strings" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/go-chi/chi/v5" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-api/internal/app" - "git.kundeng.us/phoenix/textsender-api/internal/store" -) - -type ScheduledMessageEventHandler struct { - App *app.App - ScheduledMessageEventStore store.ScheduledMessageEventStore - ScheduledMessageStore store.ScheduledMessageStore -} - -func NewScheduledMessageEventHandler(apiApp *app.App, str store.ScheduledMessageEventStore, schStore store.ScheduledMessageStore) *ScheduledMessageEventHandler { - return &ScheduledMessageEventHandler{App: apiApp, ScheduledMessageEventStore: str, ScheduledMessageStore: schStore} -} - -type RequestAddScheduledMessageEvent struct { - ContactId uuid.UUID `json:"contact_id"` - MessageId uuid.UUID `json:"message_id"` - ScheduledMessageId uuid.UUID `json:"scheduled_message_id"` -} - -type AddScheduledMessageEventResponse struct { - Message string `json:"message"` - Data []scheduling.ScheduledMessageEvent `json:"data"` -} - -// AddScheduledMessageEvent godoc -// @Summary Add scheduled message event -// @Description Add a scheduled message event (requires JWT) -// @Tags scheduled message events -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RequestAddScheduledMessageEvent true "Data to add scheduled message event" -// @Success 201 {object} AddScheduledMessageEventResponse -// @Failure 400 {object} AddScheduledMessageEventResponse -// @Failure 500 {object} AddScheduledMessageEventResponse -// @Router /schedule/message/event [post] -func (s *ScheduledMessageEventHandler) AddScheduledMessageEvent(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - var req RequestAddScheduledMessageEvent - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - event := scheduling.ScheduledMessageEvent{ContactId: req.ContactId, MessageId: req.MessageId, ScheduledMessageId: req.ScheduledMessageId} - - var statusCode int - var resp AddScheduledMessageEventResponse - - if event.ContactId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Contact Id is nil" - } else if event.MessageId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Message Id is nil" - } else if event.ScheduledMessageId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Scheduled Message Id is nil" - } else { - ctx := r.Context() - - if schMsg, err := s.ScheduledMessageStore.Get(ctx, event.ScheduledMessageId); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if schMsg.Status == scheduling.Pending { - if exists, err := s.ScheduledMessageEventStore.Exists(ctx, &event); err == nil { - if exists { - statusCode = http.StatusBadRequest - resp.Message = "Event already present" - } else { - if err = s.ScheduledMessageEventStore.CreateScheduledMessageEvent(ctx, &event); err == nil { - statusCode = http.StatusCreated - resp.Message = "Successful" - resp.Data = append(resp.Data, event) - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } - } else { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Scheduled message cannot be modified due to the status" - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -type GetScheduledMessageEventResponse struct { - Message string `json:"message"` - Data []scheduling.ScheduledMessageEvent `json:"data"` -} - -// GetScheduledMessageEvent godoc -// @Summary Get scheduled message event -// @Description Gets a scheduled message event (requires JWT) -// @Tags scheduled message events -// @Accept json -// @Produce json -// @Param id path string true "Contact Id" -// @Param scheduled_message_id path string true "Scheduled Message Id" -// @Security BearerAuth -// @Success 200 {object} GetScheduledMessageEventResponse -// @Failure 400 {object} GetScheduledMessageEventResponse -// @Failure 500 {object} GetScheduledMessageEventResponse -// @Router /schedule/message/event [get] -func (s *ScheduledMessageEventHandler) GetScheduledMessageEvent(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Metnot allowed", http.StatusMethodNotAllowed) - return - } - - var id, scheduledMessageId uuid.UUID - if idParam, err := ParseQueryParams(r, "id"); err == nil { - id, err = uuid.Parse(*idParam) - } - if scheduledMessageIdParam, err := ParseQueryParams(r, "scheduled_message_id"); err == nil { - scheduledMessageId, err = uuid.Parse(*scheduledMessageIdParam) - } - - var statusCode int - var resp GetScheduledMessageEventResponse - ctx := r.Context() - - if id == uuid.Nil && scheduledMessageId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "Query parameters missing" - } else if id != uuid.Nil { - if event, err := s.ScheduledMessageEventStore.Get(ctx, id); err != nil { - resp.Message = err.Error() - statusCode = http.StatusInternalServerError - } else { - if event != nil { - resp.Message = "Successful" - statusCode = http.StatusOK - resp.Data = append(resp.Data, *event) - } else { - statusCode = http.StatusNotFound - resp.Message = "Scheduled message event not found" - } - } - } else { - if events, err := s.ScheduledMessageEventStore.GetWithScheduleMessageId(ctx, scheduledMessageId); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusOK - resp.Message = "Successful" - for _, event := range events { - resp.Data = append(resp.Data, *event) - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} - -type DeleteScheduledMessageEventResponse struct { - Message string `json:"message"` - Data []scheduling.ScheduledMessageEvent `json:"data"` -} - -// DeleteScheduledMessageEvent godoc -// @Summary Delete scheduled message event -// @Description Deletes a scheduled message event (requires JWT) -// @Tags scheduled message events -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param id path string true "scheduled message event Id" Format(uuid) -// @Success 201 {object} DeleteScheduledMessageEventResponse -// @Failure 400 {object} DeleteScheduledMessageEventResponse -// @Failure 500 {object} DeleteScheduledMessageEventResponse -// @Router /schedule/message/event/{id} [delete] -func (s *ScheduledMessageEventHandler) DeleteScheduledMessageEvent(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodDelete { - http.Error(w, "Metnot allowed", http.StatusMethodNotAllowed) - return - } - - id := chi.URLParam(r, "id") - if len(id) == 0 { - pathParts := strings.Split(r.URL.Path, "/") - if len(pathParts) < 7 { - http.Error(w, "Id not provided", http.StatusBadRequest) - return - } else { - id = pathParts[6] - } - } - - var resp DeleteScheduledMessageEventResponse - var statusCode int - - if parsedId, err := uuid.Parse(id); err != nil { - resp.Message = err.Error() - statusCode = http.StatusBadRequest - } else { - ctx := r.Context() - if event, err := s.ScheduledMessageEventStore.Get(ctx, parsedId); err != nil { - resp.Message = err.Error() - statusCode = http.StatusInternalServerError - } else { - if schMsg, err := s.ScheduledMessageStore.Get(ctx, event.ScheduledMessageId); err != nil { - resp.Message = err.Error() - statusCode = http.StatusInternalServerError - } else { - if schMsg.Status == scheduling.Pending { - if err = s.ScheduledMessageEventStore.Delete(ctx, event.Id); err != nil { - resp.Message = err.Error() - statusCode = http.StatusInternalServerError - } else { - resp.Message = "Successful" - resp.Data = append(resp.Data, *event) - statusCode = http.StatusOK - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Invalid status" - } - } - } - } - - RespondWithJSON(w, statusCode, &resp) -} diff --git a/internal/handler/scheduled_message_event_test.go b/internal/handler/scheduled_message_event_test.go deleted file mode 100644 index 2889cc8..0000000 --- a/internal/handler/scheduled_message_event_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-api/internal/store/mock" -) - -var ( - contactId = uuid.New() - messageId = uuid.New() - scheduledMessageId = uuid.New() - testUserId = uuid.New() -) - -type CreateScheduledMessageEventRequest struct { - ContactId uuid.UUID `json:"contact_id"` - MessageId uuid.UUID `json:"message_id"` - ScheduledMessageId uuid.UUID `json:"scheduled_message_id"` -} - -func TestCreateScheduledMessageEventWithMock(t *testing.T) { - now := time.Now() - - mockStore := mock.NewMockScheduledMessageEventStore() - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgStore := mock.NewMockScheduledMessageStore() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageEventHandler(apiApp, mockStore, schMsgStore) - - contactId := uuid.New() - messageId := uuid.New() - scheduledMessageId := uuid.New() - testUserId := uuid.New() - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } - - testReq := CreateScheduledMessageEventRequest{ContactId: contactId, MessageId: messageId, ScheduledMessageId: scheduledMessageId} - jsonValue, _ := json.Marshal(testReq) - - req, _ := http.NewRequest("POST", endpoint.AddEventToScheduledMessageEndpoint, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.AddScheduledMessageEvent(rr, req) - - assert.Equal(t, http.StatusCreated, rr.Code) - - var response AddScheduledMessageEventResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error creating event %v", err) - - assert.NotEmpty(t, response.Data, "No event created") - - var event scheduling.ScheduledMessageEvent - event = response.Data[0] - - assert.NotEmpty(t, event.Created, "Created date should not be empty") -} - -func TestGetScheduledMessageEventWithMock(t *testing.T) { - now := time.Now() - - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgStore := mock.NewMockScheduledMessageStore() - schMsgEventStore := mock.NewMockScheduledMessageEventStore() - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schMsgStore) - - contactId := uuid.New() - messageId := uuid.New() - scheduledMessageId := uuid.New() - testUserId := uuid.New() - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id) - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } else if err = schMsgEventStore.CreateScheduledMessageEvent(ctx, &event); err != nil { - assert.NoError(t, err, "Error creating scheduled message event: %v", err) - } - - url := fmt.Sprintf("%s?id=%s", endpoint.GetScheduledMessageEventEndpoint, event.Id.String()) - req, _ := http.NewRequest("GET", url, nil) - rr := httptest.NewRecorder() - - handler.GetScheduledMessageEvent(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response GetScheduledMessageEventResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error creating event %v", err) - - assert.NotEmpty(t, response.Data, "No event created") - - msgEvent := response.Data[0] - - assert.NotEmpty(t, msgEvent.Created, "Created date should not be empty") -} - -func TestDeleteScheduledMessageEventWithMock(t *testing.T) { - now := time.Now() - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgStore := mock.NewMockScheduledMessageStore() - schMsgEventStore := mock.NewMockScheduledMessageEventStore() - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageEventHandler(apiApp, schMsgEventStore, schMsgStore) - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id) - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = schMsgStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } else if err = schMsgEventStore.CreateScheduledMessageEvent(ctx, &event); err != nil { - assert.NoError(t, err, "Error creating scheduled message event: %v", err) - } - - endpointValue := strings.Replace(endpoint.DeleteScheduledMessageEventEndpoint, "{id}", event.Id.String(), 1) - req, _ := http.NewRequest("DELETE", endpointValue, nil) - rr := httptest.NewRecorder() - - handler.DeleteScheduledMessageEvent(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response DeleteScheduledMessageEventResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error creating event %v", err) - - assert.NotEmpty(t, response.Data, "No event created") - - msgEvent := response.Data[0] - - assert.NotEmpty(t, msgEvent.Created, "Created date should not be empty") -} diff --git a/internal/handler/scheduled_message_test.go b/internal/handler/scheduled_message_test.go deleted file mode 100644 index 95a4936..0000000 --- a/internal/handler/scheduled_message_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package handler - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-api/internal/store/mock" -) - -type CreateScheduledMessageRequest struct { - Scheduled time.Time `json:"scheduled"` - Status string `json:"status"` - UserId uuid.UUID `json:"user_id"` -} - -func TestCreateScheduledMessageWithMock(t *testing.T) { - now := time.Now() - mockStore := mock.NewMockScheduledMessageStore() - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageHandler(apiApp, mockStore) - - testUserId := uuid.New() - testBody := createScheduledMessageRequest(testUserId, now) - jsonValue, _ := json.Marshal(testBody) - - req, _ := http.NewRequest("POST", endpoint.ScheduleMessageEndpoint, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.AddScheduledMessage(rr, req) - - assert.Equal(t, http.StatusCreated, rr.Code) - - var response AddScheduledMessageResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error Creating message %v", err) - - assert.NotEmpty(t, response.Data, "No Message created") - - assert.NotNil(t, response.Data[0].Id, "Id should not be nil") -} - -func TestGetScheduledMessageWithMock(t *testing.T) { - now := time.Now() - mockStore := mock.NewMockScheduledMessageStore() - - testUserId := uuid.New() - testBody := createScheduledMessageRequest(testUserId, now) - - ctx := t.Context() - schMsg := scheduling.ScheduledMessage{} - schMsg.Scheduled = testBody.Scheduled - schMsg.Status = testBody.Status - schMsg.UserId = testBody.UserId - if err := mockStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error Creating message %v", err) - } - - url := fmt.Sprintf("%s?id=%s&user_id=%s", endpoint.GetScheduledMessageEndpoint, schMsg.Id, schMsg.UserId) - req, _ := http.NewRequest("GET", url, nil) - rr := httptest.NewRecorder() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageHandler(apiApp, mockStore) - handler.GetScheduledMessage(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response GetScheduledMessageResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error parsing response %v", err) -} - -func TestFetchScheduledMessageWithMock(t *testing.T) { - now := time.Now() - mockStore := mock.NewMockScheduledMessageStore() - contactStore := mock.NewMockContactStore() - messageStore := mock.NewMockMessageStore() - schMsgEventStore := mock.NewMockScheduledMessageEventStore() - - contactId := uuid.New() - messageId := uuid.New() - scheduledMessageId := uuid.New() - testUserId := uuid.New() - - con := ContactTest(contactId, testUserId) - msg := MessageTest(messageId, testUserId) - schMsg := ScheduledMessageTest(scheduledMessageId, testUserId, now) - event := ScheduledMessageEventTest(*msg.Id, *con.Id, schMsg.Id) - - ctx := t.Context() - - if err := contactStore.CreateContact(ctx, &con); err != nil { - assert.NoError(t, err, "Error creating contact: %v", err) - } else if err = messageStore.CreateMessage(ctx, &msg); err != nil { - assert.NoError(t, err, "Error creating message: %v", err) - } else if err = mockStore.CreateScheduledMessage(ctx, &schMsg); err != nil { - assert.NoError(t, err, "Error creating scheduled message: %v", err) - } else if err = schMsgEventStore.CreateScheduledMessageEvent(ctx, &event); err != nil { - assert.NoError(t, err, "Error creating scheduled message event: %v", err) - } else if newStatus, err := mockStore.UpdateStatus(ctx, schMsg.Id, scheduling.Ready); err != nil { - assert.NoError(t, err, "Error updating status: %v", err) - } else { - assert.Equal(t, *newStatus, scheduling.Ready) - } - - req, _ := http.NewRequest("GET", endpoint.FetchNextScheduledMessageEndpoint, nil) - rr := httptest.NewRecorder() - - apiApp, err := GetApp() - assert.NoError(t, err, "Error getting app") - handler := NewScheduledMessageHandler(apiApp, mockStore) - handler.FetchNextMessage(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response FetchNextMessageResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err, "Error fetching scheduled message: %v", err) - - assert.NotEmpty(t, response.Data, "No data") - - fetchedSchMsg := response.Data[0] - assert.Equal(t, scheduling.Processing, fetchedSchMsg.Status, "The statuses do not match") -} - -func createScheduledMessageRequest(userId uuid.UUID, now time.Time) CreateScheduledMessageRequest { - scheduled := now.Add(5 * time.Minute) - return CreateScheduledMessageRequest{Status: scheduling.Pending, UserId: userId, Scheduled: scheduled} -} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go deleted file mode 100644 index 1cbceb1..0000000 --- a/internal/middleware/auth.go +++ /dev/null @@ -1,47 +0,0 @@ -package middleware - -import ( - "context" - "net/http" - "strings" - - "git.kundeng.us/phoenix/textsender-api/internal/services" -) - -type contextKey string - -const ( - UserContextKey contextKey = "user" -) - -func AuthMiddleware(authService *services.JWTService) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - http.Error(w, "Authorization header required", http.StatusUnauthorized) - return - } - - // Extract token from "Bearer " - parts := strings.Split(authHeader, " ") - if len(parts) != 2 || parts[0] != "Bearer" { - http.Error(w, "Invalid authorization header format", http.StatusUnauthorized) - return - } - - token := parts[1] - - // Validate token with auth service - user, err := authService.ValidateToken(token) - if err != nil { - http.Error(w, "Invalid token", http.StatusUnauthorized) - return - } - - // Add user to context - ctx := context.WithValue(r.Context(), UserContextKey, user) - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go deleted file mode 100644 index 86739a5..0000000 --- a/internal/middleware/middleware.go +++ /dev/null @@ -1,22 +0,0 @@ -package middleware - -import ( - "log" - "net/http" - "time" -) - -func Logging(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - next.ServeHTTP(w, r) - log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start)) - }) -} - -func JSONContentType(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - next.ServeHTTP(w, r) - }) -} diff --git a/internal/model/messageDraft.go b/internal/model/messageDraft.go deleted file mode 100644 index 7366ed5..0000000 --- a/internal/model/messageDraft.go +++ /dev/null @@ -1,6 +0,0 @@ -package model - -type MessageItem struct { - Id int `json:"id"` - Message string `json:"message"` -} diff --git a/internal/services/jwt_service.go b/internal/services/jwt_service.go deleted file mode 100644 index ee38e90..0000000 --- a/internal/services/jwt_service.go +++ /dev/null @@ -1,49 +0,0 @@ -package services - -import ( - "time" - - txtmodels_token "git.kundeng.us/phoenix/textsender-models/tx0/token" - txtmodels_user "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/golang-jwt/jwt/v5" -) - -type JWTService struct { - secretKey []byte -} - -func NewJWTService(secretKey string) *JWTService { - return &JWTService{ - secretKey: []byte(secretKey), - } -} - -func (s *JWTService) ValidateToken(tokenString string) (*txtmodels_user.User, error) { - // TODO: Include more user information in the claims to populate user - claims := &txtmodels_token.Claims{} - - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { - // Validate the signing method - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, jwt.ErrSignatureInvalid - } - return s.secretKey, nil - }) - - if err != nil { - return nil, err - } - - if !token.Valid { - return nil, jwt.ErrSignatureInvalid - } - - // Check token expiration - if time.Now().After(claims.ExpiresAt.Time) { - return nil, jwt.ErrTokenExpired - } - - return &txtmodels_user.User{ - Id: claims.UserId, - }, nil -} diff --git a/internal/store/contact_store.go b/internal/store/contact_store.go deleted file mode 100644 index bc67a10..0000000 --- a/internal/store/contact_store.go +++ /dev/null @@ -1,182 +0,0 @@ -package store - -import ( - "context" - "fmt" - - "git.kundeng.us/phoenix/textsender-models/tx0/contact" - "github.com/google/uuid" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" -) - -type ContactStore interface { - CreateContact(ctx context.Context, con *contact.Contact) error - GetContactByID(ctx context.Context, id uuid.UUID) (*contact.Contact, error) - GetContactByPhone(ctx context.Context, phone string, userId uuid.UUID) (*contact.Contact, error) - GetAllContacts(ctx context.Context) ([]*contact.Contact, error) - ContactExists(ctx context.Context, phone string, userId uuid.UUID) (bool, error) - UpdateNames(ctx context.Context, con *contact.Contact, firstname *string, lastname *string, nickname *string) (int64, error) -} - -type PGContactStore struct { - db *pgxpool.Pool -} - -func NewContactStore(db *pgxpool.Pool) *PGContactStore { - return &PGContactStore{db: db} -} - -func (s *PGContactStore) CreateContact(ctx context.Context, con *contact.Contact) error { - query := ` - INSERT INTO contacts (phone_number, user_id, first_name, last_name, nickname) - VALUES ($1, $2, $3, $4, $5) - RETURNING id, phone_number, user_id - ` - - return s.db.QueryRow(ctx, query, con.PhoneNumber, con.UserId, con.Firstname, con.Lastname, con.Nickname).Scan( - &con.Id, &con.PhoneNumber, &con.UserId, - ) -} - -func (s *PGContactStore) GetContactByID(ctx context.Context, id uuid.UUID) (*contact.Contact, error) { - query := `SELECT id, phone_number, user_id, first_name, last_name, nickname FROM contacts WHERE id = $1` - - var con contact.Contact - err := s.db.QueryRow(ctx, query, id).Scan( - &con.Id, &con.PhoneNumber, &con.UserId, &con.Firstname, &con.Lastname, &con.Nickname, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("getting contact by ID: %w", err) - } else { - return &con, nil - } -} - -func (s *PGContactStore) GetContactByPhone(ctx context.Context, phone string, userId uuid.UUID) (*contact.Contact, error) { - query := `SELECT id, phone_number, user_id, first_name, last_name, nickname FROM contacts WHERE phone_number = $1 AND user_id = $2` - - var con contact.Contact - err := s.db.QueryRow(ctx, query, phone, userId).Scan( - &con.Id, &con.PhoneNumber, &con.UserId, &con.Firstname, &con.Lastname, &con.Nickname, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("getting contact by phone and User ID: %w", err) - } else { - return &con, nil - } -} - -func (s *PGContactStore) GetAllContacts(ctx context.Context) ([]*contact.Contact, error) { - query := `SELECT id, phone_number, user_id, first_name, last_name, nickname FROM contacts` - - rows, err := s.db.Query(ctx, query) - if err != nil { - return nil, fmt.Errorf("querying all contacts: %w", err) - } - defer rows.Close() - - var cons []*contact.Contact - for rows.Next() { - var con contact.Contact - if err := rows.Scan( - &con.Id, &con.PhoneNumber, &con.UserId, &con.Firstname, &con.Lastname, &con.Nickname, - ); err != nil { - return nil, fmt.Errorf("scanning contact row: %w", err) - } - cons = append(cons, &con) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("iterating contact rows: %w", err) - } - - return cons, nil -} - -func (s *PGContactStore) ContactExists(ctx context.Context, phone string, userId uuid.UUID) (bool, error) { - query := `SELECT EXISTS(SELECT 1 FROM contacts WHERE phone_number = $1 AND user_id = $2)` - - var exists bool - err := s.db.QueryRow(ctx, query, phone, userId).Scan(&exists) - if err != nil { - return false, fmt.Errorf("checking if contact exists: %w", err) - } - - return exists, nil -} - -func (s *PGContactStore) UpdateNames(ctx context.Context, con *contact.Contact, firstname *string, lastname *string, nickname *string) (int64, error) { - if firstname == nil && lastname == nil && nickname == nil { - return 0, fmt.Errorf("Names are nil") - } else { - if firstname != nil && lastname != nil && nickname != nil { - query := "UPDATE contacts SET first_name = $1, last_name = $2, nickname = $3 WHERE id = $4" - if affected, err := s.db.Exec(ctx, query, firstname, lastname, nickname, con.Id); err != nil { - return 0, err - } else { - con.Firstname = firstname - con.Lastname = lastname - con.Nickname = nickname - return affected.RowsAffected(), nil - } - } else if firstname != nil && lastname != nil { - query := "UPDATE contacts SET first_name = $1, last_name = $2 WHERE id = $3" - if affected, err := s.db.Exec(ctx, query, firstname, lastname, con.Id); err != nil { - return 0, err - } else { - con.Firstname = firstname - con.Lastname = lastname - return affected.RowsAffected(), nil - } - } else if lastname != nil && nickname != nil { - query := "UPDATE contacts SET last_name = $1, nickname = $2 WHERE id = $3" - if affected, err := s.db.Exec(ctx, query, lastname, nickname, con.Id); err != nil { - return 0, err - } else { - con.Lastname = lastname - con.Nickname = nickname - return affected.RowsAffected(), nil - } - } else if nickname != nil && firstname != nil { - query := "UPDATE contacts SET nickname = $1, firstname = $2 WHERE id = $3" - if affected, err := s.db.Exec(ctx, query, nickname, firstname, con.Id); err != nil { - return 0, err - } else { - con.Firstname = firstname - con.Nickname = nickname - return affected.RowsAffected(), nil - } - } else if firstname != nil { - query := "UPDATE contacts SET firstname = $1 WHERE id = $2" - if affected, err := s.db.Exec(ctx, query, firstname, con.Id); err != nil { - return 0, err - } else { - con.Firstname = firstname - return affected.RowsAffected(), nil - } - } else if lastname != nil { - query := "UPDATE contacts SET lastname = $1 WHERE id = $2" - if affected, err := s.db.Exec(ctx, query, lastname, con.Id); err != nil { - return 0, err - } else { - con.Lastname = lastname - return affected.RowsAffected(), nil - } - } else { - query := "UPDATE contacts SET nickname = $1 WHERE id = $2" - if affected, err := s.db.Exec(ctx, query, nickname, con.Id); err != nil { - return 0, err - } else { - con.Nickname = nickname - return affected.RowsAffected(), nil - } - } - } -} diff --git a/internal/store/message_event_response_store.go b/internal/store/message_event_response_store.go deleted file mode 100644 index 4cf0a19..0000000 --- a/internal/store/message_event_response_store.go +++ /dev/null @@ -1,61 +0,0 @@ -package store - -import ( - "context" - "fmt" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/event" - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgxpool" -) - -type MessageEventResponseStore interface { - Create(ctx context.Context, mer *event.MessageEventResponse) error - GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*event.MessageEventResponse, error) -} - -type PGMessageEventResponseStore struct { - db *pgxpool.Pool -} - -func NewMessageEventResponseStore(db *pgxpool.Pool) *PGMessageEventResponseStore { - return &PGMessageEventResponseStore{db: db} -} - -func (m *PGMessageEventResponseStore) Create(ctx context.Context, mer *event.MessageEventResponse) error { - var query string - queryBase := "INSERT INTO message_event_responses" - queryReturn := "RETURNING id" - - if mer.ScheduledMessageEventId == uuid.Nil { - query = fmt.Sprintf("%s (response, user_id, sent, contact_id, message_id, status) VALUES ($1, $2, $3, $4, $5, $6) %s", queryBase, queryReturn) - return m.db.QueryRow(ctx, query, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId, mer.Status).Scan(&mer.Id) - } else { - query = fmt.Sprintf("%s (scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status) VALUES ($1, $2, $3, $4, $5, $6, $7) %s", queryBase, queryReturn) - return m.db.QueryRow(ctx, query, mer.ScheduledMessageEventId, mer.Response, mer.UserId, mer.Sent, mer.ContactId, mer.MessageId, mer.Status).Scan(&mer.Id) - } -} - -func (m *PGMessageEventResponseStore) GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*event.MessageEventResponse, error) { - query := "SELECT id, scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status FROM message_event_responses WHERE user_id = $1" - rows, err := m.db.Query(ctx, query, userId) - if err != nil { - return nil, fmt.Errorf("Error querying: %w", err) - } - defer rows.Close() - - var sentMessages []*event.MessageEventResponse - for rows.Next() { - var msg event.MessageEventResponse - if err := rows.Scan(&msg.Id, &msg.ScheduledMessageEventId, &msg.Response, &msg.UserId, &msg.Sent, &msg.ContactId, &msg.MessageId, &msg.Status); err != nil { - return nil, fmt.Errorf("Scanning row: %w", err) - } - sentMessages = append(sentMessages, &msg) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("Iterating message_event_responses: %w", err) - } - - return sentMessages, nil -} diff --git a/internal/store/message_store.go b/internal/store/message_store.go deleted file mode 100644 index 29528af..0000000 --- a/internal/store/message_store.go +++ /dev/null @@ -1,95 +0,0 @@ -package store - -import ( - "context" - "fmt" - - "git.kundeng.us/phoenix/textsender-models/tx0/message" - "github.com/google/uuid" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" -) - -type MessageStore interface { - GetMessageByID(ctx context.Context, id uuid.UUID) (*message.Message, error) - CreateMessage(ctx context.Context, msg *message.Message) error - GetAllMessages(ctx context.Context) ([]*message.Message, error) - MessageExists(ctx context.Context, msg *message.Message) (bool, error) -} - -type PGMessageStore struct { - db *pgxpool.Pool -} - -func NewMessageStore(db *pgxpool.Pool) *PGMessageStore { - return &PGMessageStore{db: db} -} - -func (m *PGMessageStore) CreateMessage(ctx context.Context, msg *message.Message) error { - query := ` - INSERT INTO messages (content, user_id) - VALUES ($1, $2) - RETURNING id - ` - - return m.db.QueryRow(ctx, query, msg.Content, msg.UserId).Scan( - &msg.Id, - ) -} - -func (m *PGMessageStore) MessageExists(ctx context.Context, msg *message.Message) (bool, error) { - query := `SELECT EXISTS(SELECT 1 FROM messages WHERE content = $1 AND user_id = $2)` - - var exists bool - err := m.db.QueryRow(ctx, query, msg.Content, msg.UserId).Scan(&exists) - if err != nil { - return false, fmt.Errorf("checking if message exists: %w", err) - } else { - return exists, nil - } -} - -func (m *PGMessageStore) GetAllMessages(ctx context.Context) ([]*message.Message, error) { - query := `SELECT id, content, user_id FROM messages` - - rows, err := m.db.Query(ctx, query) - if err != nil { - return nil, fmt.Errorf("querying all messages: %w", err) - } - defer rows.Close() - - var messages []*message.Message - for rows.Next() { - var msg message.Message - if err := rows.Scan( - &msg.Id, &msg.Content, &msg.UserId, - ); err != nil { - return nil, fmt.Errorf("scanning message row: %w", err) - } - messages = append(messages, &msg) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("iterating message rows: %w", err) - } - - return messages, nil -} - -func (m *PGMessageStore) GetMessageByID(ctx context.Context, id uuid.UUID) (*message.Message, error) { - query := `SELECT id, content, user_id FROM messages WHERE id = $1` - - var msg message.Message - err := m.db.QueryRow(ctx, query, id).Scan( - &msg.Id, &msg.Content, &msg.UserId, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("getting message by ID: %w", err) - } - - return &msg, nil -} diff --git a/internal/store/mock/contact_store.go b/internal/store/mock/contact_store.go deleted file mode 100644 index d337c40..0000000 --- a/internal/store/mock/contact_store.go +++ /dev/null @@ -1,146 +0,0 @@ -package mock - -import ( - "context" - "errors" - "fmt" - "sync" - - "git.kundeng.us/phoenix/textsender-models/tx0/contact" - "github.com/google/uuid" -) - -type Key struct { - PhoneNumber string - UserId uuid.UUID -} - -type MockContactStore struct { - Contacts map[uuid.UUID]*contact.Contact - ContactsByKey map[Key]*contact.Contact - mu sync.RWMutex - Error error // Optional: simulate errors -} - -func NewMockContactStore() *MockContactStore { - return &MockContactStore{ - Contacts: make(map[uuid.UUID]*contact.Contact), - ContactsByKey: make(map[Key]*contact.Contact), - } -} - -func (m *MockContactStore) CreateContact(ctx context.Context, con *contact.Contact) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - var id uuid.UUID - if con.Id == nil { - id = uuid.New() - con.Id = &id - } - - key := Key{PhoneNumber: con.PhoneNumber, UserId: *con.UserId} - if _, exists := m.ContactsByKey[key]; exists { - return errors.New("Contact with phone number already exists") - } - - m.Contacts[*con.Id] = con - m.ContactsByKey[key] = con - return nil -} - -func (m *MockContactStore) GetContactByID(ctx context.Context, id uuid.UUID) (*contact.Contact, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if m.Error != nil { - return nil, m.Error - } - - con, exists := m.Contacts[id] - if !exists { - return nil, errors.New("Contact not found") - } - - return con, nil -} - -func (m *MockContactStore) GetContactByPhone(ctx context.Context, phoneNumber string, userId uuid.UUID) (*contact.Contact, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - con, exists := m.ContactsByKey[Key{PhoneNumber: phoneNumber, UserId: userId}] - if !exists { - return nil, errors.New("Contact not found") - } - - return con, nil -} - -func (m *MockContactStore) GetAllContacts(ctx context.Context) ([]*contact.Contact, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - cons := make([]*contact.Contact, 0, len(m.Contacts)) - for _, con := range m.Contacts { - cons = append(cons, con) - } - - return cons, nil -} - -func (m *MockContactStore) ContactExists(ctx context.Context, phoneNumber string, userId uuid.UUID) (bool, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return false, m.Error - } - - _, exists := m.ContactsByKey[Key{PhoneNumber: phoneNumber, UserId: userId}] - - return exists, nil -} - -func (m *MockContactStore) UpdateNames(ctx context.Context, con *contact.Contact, firstname *string, lastname *string, nickname *string) (int64, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return 0, m.Error - } else if firstname == nil && lastname == nil && nickname == nil { - return 0, fmt.Errorf("Names are not provided") - } else { - if c, exists := m.Contacts[*con.Id]; !exists { - return 0, fmt.Errorf("Contact does not exist") - } else { - if firstname != nil { - c.Firstname = firstname - } - if lastname != nil { - c.Lastname = lastname - } - if nickname != nil { - c.Nickname = nickname - } - } - } - - return 0, nil -} diff --git a/internal/store/mock/message_event_response_store.go b/internal/store/mock/message_event_response_store.go deleted file mode 100644 index 5ffc0ff..0000000 --- a/internal/store/mock/message_event_response_store.go +++ /dev/null @@ -1,81 +0,0 @@ -package mock - -import ( - "context" - "fmt" - "sync" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/event" - "github.com/google/uuid" -) - -type MessageEventResponseKey struct { - ScheduledMessageEventId uuid.UUID - UserId uuid.UUID -} -type MockMessageEventResponseStore struct { - MessageEventResponses map[uuid.UUID]*event.MessageEventResponse - MessageEventResponsesByKey map[MessageEventResponseKey]*event.MessageEventResponse - mu sync.RWMutex - Error error -} - -func NewMockMessageEventResponseStore() *MockMessageEventResponseStore { - return &MockMessageEventResponseStore{ - MessageEventResponses: make(map[uuid.UUID]*event.MessageEventResponse), - MessageEventResponsesByKey: make(map[MessageEventResponseKey]*event.MessageEventResponse), - } -} - -func (m *MockMessageEventResponseStore) Create(ctx context.Context, mer *event.MessageEventResponse) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - if mer.Id == uuid.Nil { - mer.Id = uuid.New() - } - - if len(mer.Response) == 0 { - return fmt.Errorf("Response is empty") - } else if mer.UserId == uuid.Nil { - return fmt.Errorf("User Id is nil") - } else { - key := MessageEventResponseKey{ScheduledMessageEventId: mer.ScheduledMessageEventId, UserId: mer.UserId} - if mer.ScheduledMessageEventId == uuid.Nil { - key.ScheduledMessageEventId = uuid.Nil - } else { - key.ScheduledMessageEventId = mer.ScheduledMessageEventId - } - sent := time.Now() - mer.Sent = sent - - m.MessageEventResponses[mer.Id] = mer - m.MessageEventResponsesByKey[key] = mer - - return nil - } -} - -// TODO: Add code to get mock MessageEventResponse -func (m *MockMessageEventResponseStore) GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*event.MessageEventResponse, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - var messages []*event.MessageEventResponse - for _, msg := range m.MessageEventResponses { - if msg.UserId == userId { - messages = append(messages, msg) - } - } - - return messages, nil -} diff --git a/internal/store/mock/scheduled_message_event_store.go b/internal/store/mock/scheduled_message_event_store.go deleted file mode 100644 index f8631f8..0000000 --- a/internal/store/mock/scheduled_message_event_store.go +++ /dev/null @@ -1,159 +0,0 @@ -package mock - -import ( - "context" - "fmt" - "sync" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" -) - -type ScheduledMessageEventKey struct { - ContactId uuid.UUID - MessageId uuid.UUID - ScheduledMessageId uuid.UUID -} - -type MockScheduledMessageEventStore struct { - ScheduledMessageEvents map[uuid.UUID]*scheduling.ScheduledMessageEvent - ScheduledMessageEventsByKey map[ScheduledMessageEventKey]*scheduling.ScheduledMessageEvent - mu sync.RWMutex - Error error // Optional: simulate errors -} - -func NewMockScheduledMessageEventStore() *MockScheduledMessageEventStore { - return &MockScheduledMessageEventStore{ - ScheduledMessageEvents: make(map[uuid.UUID]*scheduling.ScheduledMessageEvent), - ScheduledMessageEventsByKey: make(map[ScheduledMessageEventKey]*scheduling.ScheduledMessageEvent), - } -} - -func (m *MockScheduledMessageEventStore) Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessageEvent, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if id == uuid.Nil { - return nil, fmt.Errorf("Id is nil") - } - - if _, exists := m.ScheduledMessageEvents[id]; exists { - return m.ScheduledMessageEvents[id], nil - } else { - return nil, fmt.Errorf("Not found") - } -} - -func (m *MockScheduledMessageEventStore) GetWithScheduleMessageId(ctx context.Context, schMsgId uuid.UUID) ([]*scheduling.ScheduledMessageEvent, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if schMsgId == uuid.Nil { - return nil, fmt.Errorf("Scheduled message Id is nil") - } - - var events []*scheduling.ScheduledMessageEvent - - for _, event := range m.ScheduledMessageEvents { - if event.ScheduledMessageId == schMsgId { - events = append(events, event) - } - } - - if len(events) > 0 { - return events, nil - } else { - return nil, nil - } -} - -func (m *MockScheduledMessageEventStore) Delete(ctx context.Context, id uuid.UUID) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - if id == uuid.Nil { - return fmt.Errorf("Id is nil") - } - - if _, exists := m.ScheduledMessageEvents[id]; exists { - originalAmount := len(m.ScheduledMessageEvents) - copiedEvents := make(map[uuid.UUID]*scheduling.ScheduledMessageEvent) - copiedEventsKey := make(map[ScheduledMessageEventKey]*scheduling.ScheduledMessageEvent) - - for i, schMsgEvent := range m.ScheduledMessageEvents { - key := ScheduledMessageEventKey{ContactId: schMsgEvent.ContactId, MessageId: schMsgEvent.MessageId, ScheduledMessageId: schMsgEvent.ScheduledMessageId} - if schMsgEvent.Id != id { - copiedEvents[i] = schMsgEvent - copiedEventsKey[key] = schMsgEvent - } - } - - if originalAmount > 1 { - if len(copiedEvents) > 0 && len(copiedEventsKey) > 0 { - m.ScheduledMessageEvents = copiedEvents - m.ScheduledMessageEventsByKey = copiedEventsKey - return nil - } else { - return fmt.Errorf("Not removed") - } - } else { - m.ScheduledMessageEvents = make(map[uuid.UUID]*scheduling.ScheduledMessageEvent) - m.ScheduledMessageEventsByKey = make(map[ScheduledMessageEventKey]*scheduling.ScheduledMessageEvent) - - return nil - } - } else { - return fmt.Errorf("Not found") - } -} - -func (m *MockScheduledMessageEventStore) CreateScheduledMessageEvent(ctx context.Context, event *scheduling.ScheduledMessageEvent) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - if event.Id == uuid.Nil { - event.Id = uuid.New() - } - - key := ScheduledMessageEventKey{ContactId: event.ContactId, MessageId: event.MessageId, ScheduledMessageId: event.ScheduledMessageId} - - if _, exists := m.ScheduledMessageEventsByKey[key]; exists { - return fmt.Errorf("Already exists") - } else { - event.Created = time.Now() - m.ScheduledMessageEvents[event.Id] = event - m.ScheduledMessageEventsByKey[key] = event - return nil - } -} - -func (m *MockScheduledMessageEventStore) Exists(ctx context.Context, event *scheduling.ScheduledMessageEvent) (bool, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return false, m.Error - } - - key := ScheduledMessageEventKey{ContactId: event.ContactId, MessageId: event.MessageId, ScheduledMessageId: event.ScheduledMessageId} - _, exists := m.ScheduledMessageEventsByKey[key] - - return exists, nil -} diff --git a/internal/store/mock/scheduled_message_store.go b/internal/store/mock/scheduled_message_store.go deleted file mode 100644 index 75a0853..0000000 --- a/internal/store/mock/scheduled_message_store.go +++ /dev/null @@ -1,141 +0,0 @@ -package mock - -import ( - "context" - "fmt" - "sync" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" -) - -type ScheduledMessageKey struct { - UserId uuid.UUID -} - -type MockScheduledMessageStore struct { - ScheduledMessages map[uuid.UUID]*scheduling.ScheduledMessage - ScheduledMessagesByKey map[ScheduledMessageKey]*scheduling.ScheduledMessage - mu sync.RWMutex - Error error // Optional: simulate errors -} - -func NewMockScheduledMessageStore() *MockScheduledMessageStore { - return &MockScheduledMessageStore{ - ScheduledMessages: make(map[uuid.UUID]*scheduling.ScheduledMessage), - ScheduledMessagesByKey: make(map[ScheduledMessageKey]*scheduling.ScheduledMessage), - } -} - -func (m *MockScheduledMessageStore) Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessage, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if id == uuid.Nil { - return nil, fmt.Errorf("Id is nil") - } else { - if schMsg := m.ScheduledMessages[id]; schMsg != nil { - return schMsg, nil - } else { - return nil, fmt.Errorf("Scheduled message does not exist") - } - } -} - -func (m *MockScheduledMessageStore) FetchNextScheduledMessage(ctx context.Context) (*scheduling.ScheduledMessage, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - var schMsg *scheduling.ScheduledMessage - for _, msg := range m.ScheduledMessages { - if msg.Status == scheduling.Ready { - msg.Status = scheduling.Processing - schMsg = msg - break - } - } - - if schMsg == nil { - return nil, fmt.Errorf("No scheduled message is ready") - } else { - return schMsg, nil - } -} - -func (m *MockScheduledMessageStore) GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*scheduling.ScheduledMessage, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if userId == uuid.Nil { - return nil, fmt.Errorf("User Id is nil") - } else { - var schMsgs []*scheduling.ScheduledMessage - for _, schMsg := range m.ScheduledMessages { - if schMsg.UserId == userId { - schMsgs = append(schMsgs, schMsg) - } - } - - if len(schMsgs) == 0 { - return nil, nil - } else { - return schMsgs, nil - } - } -} - -func (m *MockScheduledMessageStore) CreateScheduledMessage(ctx context.Context, schedMsg *scheduling.ScheduledMessage) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - if schedMsg.Id == uuid.Nil { - schedMsg.Id = uuid.New() - } - - key := ScheduledMessageKey{UserId: schedMsg.UserId} - - m.ScheduledMessages[schedMsg.Id] = schedMsg - m.ScheduledMessagesByKey[key] = schedMsg - return nil -} - -func (m *MockScheduledMessageStore) UpdateStatus(ctx context.Context, id uuid.UUID, updatedStatus string) (*string, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if id == uuid.Nil { - return nil, fmt.Errorf("Id is nil") - } else { - if schMsg := m.ScheduledMessages[id]; schMsg != nil { - key := ScheduledMessageKey{UserId: schMsg.UserId} - - schMsg.Status = updatedStatus - m.ScheduledMessages[schMsg.Id] = schMsg - m.ScheduledMessagesByKey[key] = schMsg - - return &updatedStatus, nil - } else { - return nil, fmt.Errorf("Scheduled message does not exist") - } - } -} diff --git a/internal/store/mock/store.go b/internal/store/mock/store.go deleted file mode 100644 index ec48680..0000000 --- a/internal/store/mock/store.go +++ /dev/null @@ -1,101 +0,0 @@ -package mock - -import ( - "context" - "errors" - "fmt" - "sync" - - "git.kundeng.us/phoenix/textsender-models/tx0/message" - "github.com/google/uuid" -) - -type MessageKey struct { - Content string - UserId uuid.UUID -} - -type MockMessageStore struct { - Messages map[uuid.UUID]*message.Message - MessagesByKey map[MessageKey]*message.Message - mu sync.RWMutex - Error error // Optional: simulate errors -} - -func NewMockMessageStore() *MockMessageStore { - return &MockMessageStore{ - Messages: make(map[uuid.UUID]*message.Message), - MessagesByKey: make(map[MessageKey]*message.Message), - } -} - -func (m *MockMessageStore) CreateMessage(ctx context.Context, msg *message.Message) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - var id uuid.UUID - if msg != nil { - if msg.Id == nil || *msg.Id == uuid.Nil { - id = uuid.New() - msg.Id = &id - } - } else { - return fmt.Errorf("Message not populated") - } - - key := MessageKey{Content: msg.Content, UserId: *msg.UserId} - if _, exists := m.MessagesByKey[key]; exists { - return errors.New("Message already exists") - } - - m.Messages[*msg.Id] = msg - m.MessagesByKey[key] = msg - return nil -} - -func (m *MockMessageStore) GetAllMessages(ctx context.Context) ([]*message.Message, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - var msgs []*message.Message - - for _, msg := range m.Messages { - msgs = append(msgs, msg) - } - - return msgs, nil -} - -func (m *MockMessageStore) GetMessageByID(ctx context.Context, id uuid.UUID) (*message.Message, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - msg := m.Messages[id] - - return msg, nil -} - -func (m *MockMessageStore) MessageExists(ctx context.Context, msg *message.Message) (bool, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return false, m.Error - } - - _, exists := m.MessagesByKey[MessageKey{Content: msg.Content, UserId: *msg.UserId}] - - return exists, nil -} diff --git a/internal/store/scheduled_message_event_store.go b/internal/store/scheduled_message_event_store.go deleted file mode 100644 index ae27c50..0000000 --- a/internal/store/scheduled_message_event_store.go +++ /dev/null @@ -1,113 +0,0 @@ -package store - -import ( - "context" - "fmt" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" -) - -type ScheduledMessageEventStore interface { - Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessageEvent, error) - GetWithScheduleMessageId(ctx context.Context, schMsgId uuid.UUID) ([]*scheduling.ScheduledMessageEvent, error) - Delete(ctx context.Context, id uuid.UUID) error - CreateScheduledMessageEvent(ctx context.Context, event *scheduling.ScheduledMessageEvent) error - Exists(ctx context.Context, event *scheduling.ScheduledMessageEvent) (bool, error) -} - -type PGScheduledMessageEventStore struct { - db *pgxpool.Pool -} - -func NewScheduledMessageEventStore(db *pgxpool.Pool) *PGScheduledMessageEventStore { - return &PGScheduledMessageEventStore{db: db} -} - -func (s *PGScheduledMessageEventStore) Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessageEvent, error) { - query := `SELECT id, contact_id, message_id, scheduled_message_id, created FROM scheduled_message_events WHERE id = $1` - - var event scheduling.ScheduledMessageEvent - err := s.db.QueryRow(ctx, query, id).Scan( - &event.Id, &event.ContactId, &event.MessageId, &event.ScheduledMessageId, &event.Created, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("getting scheduled message event by ID: %w", err) - } else { - return &event, nil - } -} - -func (s *PGScheduledMessageEventStore) GetWithScheduleMessageId(ctx context.Context, schMsgId uuid.UUID) ([]*scheduling.ScheduledMessageEvent, error) { - var events []*scheduling.ScheduledMessageEvent - query := `SELECT id, contact_id, message_id, scheduled_message_id, created FROM scheduled_message_events WHERE scheduled_message_id = $1 - ` - - rows, err := s.db.Query(ctx, query, schMsgId) - - if err != nil { - return nil, fmt.Errorf("Error retrieving rows from scheduled_message_Events: %w", err) - } - - for rows.Next() { - var event scheduling.ScheduledMessageEvent - - if err := rows.Scan(&event.Id, &event.ContactId, &event.MessageId, &event.ScheduledMessageId, &event.Created); err != nil { - return nil, fmt.Errorf("Error fetching data from row: %w", err) - } else { - events = append(events, &event) - } - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return events, nil -} - -func (s *PGScheduledMessageEventStore) Delete(ctx context.Context, id uuid.UUID) error { - query := ` - DELETE FROM scheduled_message_events WHERE id = $1 - ` - - commandTag, err := s.db.Exec(ctx, query, id) - if err != nil { - return fmt.Errorf("error deleting event: %w", err) - } - - if commandTag.RowsAffected() == 0 { - return fmt.Errorf("no event found with id %d", id) - } else { - return nil - } -} - -func (s *PGScheduledMessageEventStore) CreateScheduledMessageEvent(ctx context.Context, event *scheduling.ScheduledMessageEvent) error { - query := ` - INSERT INTO scheduled_message_events (contact_id, message_id, scheduled_message_id) - VALUES ($1, $2, $3) - RETURNING id, created - ` - - return s.db.QueryRow(ctx, query, event.ContactId, event.MessageId, event.ScheduledMessageId).Scan( - &event.Id, &event.Created, - ) -} - -func (s *PGScheduledMessageEventStore) Exists(ctx context.Context, event *scheduling.ScheduledMessageEvent) (bool, error) { - query := `SELECT EXISTS(SELECT 1 FROM scheduled_message_events WHERE scheduled_message_id = $1 AND contact_id = $2)` - - var exists bool - err := s.db.QueryRow(ctx, query, event.ScheduledMessageId, event.ContactId).Scan(&exists) - if err != nil { - return false, fmt.Errorf("Scheduled message event does not exist: %w", err) - } else { - return exists, nil - } -} diff --git a/internal/store/scheduled_message_store.go b/internal/store/scheduled_message_store.go deleted file mode 100644 index d1aeea6..0000000 --- a/internal/store/scheduled_message_store.go +++ /dev/null @@ -1,127 +0,0 @@ -package store - -import ( - "context" - "fmt" - - "git.kundeng.us/phoenix/textsender-models/tx0/message/scheduling" - "github.com/google/uuid" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" -) - -type ScheduledMessageStore interface { - Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessage, error) - FetchNextScheduledMessage(ctx context.Context) (*scheduling.ScheduledMessage, error) - GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*scheduling.ScheduledMessage, error) - CreateScheduledMessage(ctx context.Context, schedMsg *scheduling.ScheduledMessage) error - UpdateStatus(ctx context.Context, id uuid.UUID, updatedStatus string) (*string, error) -} - -type PGScheduledMessageStore struct { - db *pgxpool.Pool -} - -func NewScheduledMessageStore(db *pgxpool.Pool) *PGScheduledMessageStore { - return &PGScheduledMessageStore{db: db} -} - -func (s *PGScheduledMessageStore) Get(ctx context.Context, id uuid.UUID) (*scheduling.ScheduledMessage, error) { - query := ` - SELECT id, scheduled, created, status, user_id FROM scheduled_messages WHERE id = $1 - ` - - var schMsg scheduling.ScheduledMessage - err := s.db.QueryRow(ctx, query, id).Scan( - &schMsg.Id, &schMsg.Scheduled, &schMsg.Created, &schMsg.Status, &schMsg.UserId, - ) - - if err == pgx.ErrNoRows { - return nil, fmt.Errorf("No rows") - } else if err != nil { - return nil, fmt.Errorf("Getting scheduled message: %w", err) - } - - return &schMsg, nil -} - -func (s *PGScheduledMessageStore) FetchNextScheduledMessage(ctx context.Context) (*scheduling.ScheduledMessage, error) { - query := ` - UPDATE scheduled_messages - SET status = $1 - WHERE id = ( - SELECT id FROM scheduled_messages - WHERE status = $2 - ORDER BY id - FOR UPDATE SKIP LOCKED - LIMIT 1 - ) - RETURNING id, scheduled, created, status, user_id - ` - var schMsg scheduling.ScheduledMessage - err := s.db.QueryRow(ctx, query, scheduling.Processing, scheduling.Ready).Scan( - &schMsg.Id, &schMsg.Scheduled, &schMsg.Created, &schMsg.Status, &schMsg.UserId, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("Getting scheduled message: %w", err) - } else { - return &schMsg, nil - } -} - -func (s *PGScheduledMessageStore) GetWithUserId(ctx context.Context, userId uuid.UUID) ([]*scheduling.ScheduledMessage, error) { - query := ` - SELECT id, scheduled, created, status, user_id FROM scheduled_messages WHERE user_id = $1 - ` - rows, err := s.db.Query(ctx, query, userId) - if err != nil { - return nil, fmt.Errorf("Error retrieving rows from scheduled_messages: %w", err) - } - - var schMsgs []*scheduling.ScheduledMessage - - for rows.Next() { - var schMsg scheduling.ScheduledMessage - if err := rows.Scan(&schMsg.Id, &schMsg.Scheduled, &schMsg.Created, &schMsg.Status, &schMsg.UserId); err != nil { - return nil, fmt.Errorf("Error fetching data from row: %w", err) - } else { - schMsgs = append(schMsgs, &schMsg) - } - } - - if err := rows.Err(); err != nil { - return nil, err - } else { - return schMsgs, nil - } -} - -func (s *PGScheduledMessageStore) CreateScheduledMessage(ctx context.Context, schedMsg *scheduling.ScheduledMessage) error { - query := ` - INSERT INTO scheduled_messages (scheduled, status, user_id) - VALUES ($1, $2, $3) - RETURNING id, created - ` - - return s.db.QueryRow(ctx, query, schedMsg.Scheduled, schedMsg.Status, schedMsg.UserId).Scan( - &schedMsg.Id, &schedMsg.Created, - ) -} - -func (s *PGScheduledMessageStore) UpdateStatus(ctx context.Context, id uuid.UUID, updatedStatus string) (*string, error) { - query := ` - UPDATE scheduled_messages set status = $1 WHERE id = $2 - RETURNING status - ` - - var returnedStatus string - - if err := s.db.QueryRow(ctx, query, updatedStatus, id).Scan(&returnedStatus); err != nil { - return nil, err - } else { - return &returnedStatus, nil - } -} diff --git a/internal/version/version.go b/internal/version/version.go deleted file mode 100644 index bcac161..0000000 --- a/internal/version/version.go +++ /dev/null @@ -1,17 +0,0 @@ -package version - -import "fmt" - -var ( - Version = "dev" - BuildTime = "none" - Commit = "unknown" - GoVersion = "unknown" -) - -func String() string { - return fmt.Sprintf( - "Version: %s\nBuild Date: %s\nCommit: %s\nGo Version: %s", - Version, BuildTime, Commit, GoVersion, - ) -}