Skip to content

Commit

Permalink
implemented subscribing & docker
Browse files Browse the repository at this point in the history
  • Loading branch information
vladyslavpavlenko committed May 16, 2024
1 parent 562845a commit b989c82
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 52 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ go.work
# Other
.idea
.DS_Store
.env
.env
/db-data/
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# build a tiny docker image
FROM alpine:latest

RUN mkdir /app

COPY apiApp /app

CMD ["/app/apiApp"]
27 changes: 27 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
API_APP_BINARY=apiApp

## up: starts all containers in the background without forcing build.
up:
@echo "Starting Docker images..."
docker-compose up -d
@echo "Docker images started!"

## up_build: stops docker-compose (if running), builds all projects and starts docker compose.
up_build: build_app
@echo "Stopping docker images (if running...)"
docker-compose down
@echo "Building (when required) and starting docker images..."
docker-compose up --build -d
@echo "Docker images built and started!"

## down: stop docker compose.
down:
@echo "Stopping docker compose..."
docker-compose down
@echo "Done!"

## build_app: builds the app binary as a linux executable.
build_app:
@echo "Building app binary..."
cd . && env GOOS=linux CGO_ENABLED=0 go build -o ${API_APP_BINARY} ./cmd/api
@echo "Done!"
Binary file added apiApp
Binary file not shown.
11 changes: 5 additions & 6 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package main

import (
"flag"
"fmt"
"github.com/vladyslavpavlenko/genesis-api-project/internal/config"
"log"
"net/http"
)

const webPort = 8080

var app config.AppConfig

func main() {
Expand All @@ -15,13 +17,10 @@ func main() {
log.Fatal()
}

addr := flag.String("addr", ":8080", "the api address")
flag.Parse()

log.Printf("Running on port %s", *addr)
log.Printf("Running on port %d", webPort)

srv := &http.Server{
Addr: *addr,
Addr: fmt.Sprintf(":%d", webPort),
Handler: routes(&app),
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func routes(app *config.AppConfig) http.Handler {
MaxAge: 300,
}))

mux.Use(middleware.Heartbeat("/"))

mux.Get("/rate", handlers.Repo.GetRate)
mux.Post("/subscribe", handlers.Repo.Subscribe)
mux.Post("/sendEmails", handlers.Repo.SendEmails)
Expand Down
122 changes: 88 additions & 34 deletions cmd/api/setup.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,123 @@
package main

import (
"errors"
"fmt"
"github.com/joho/godotenv"
"github.com/vladyslavpavlenko/genesis-api-project/internal/config"
"github.com/vladyslavpavlenko/genesis-api-project/internal/handlers"
"github.com/vladyslavpavlenko/genesis-api-project/internal/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
"os"
"time"
)

var counts int64

func setup(app *config.AppConfig) error {
// Get environment variables
env, err := loadEvnVariables()
// Connect to the database and run migrations
db, err := connectToDB()
if err != nil {
return err
log.Fatal(err)
}

app.Env = env

// Connect to the database and run migrations
db, err := connectToPostgresAndMigrate(env)
err = runDBMigrations(db)
if err != nil {
return err
log.Fatal(err)
}

app.DB = db

app.Models = models.New(db)
repo := handlers.NewRepo(app)
handlers.NewHandlers(repo)
render.NewRenderer(app)

return nil
}

// loadEvnVariables loads variables from the .env file.
func loadEvnVariables() (*config.EnvVariables, error) {
err := godotenv.Load()
func openDB(dsn string) (*gorm.DB, error) {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("error getting environment variables: %v", err)
return nil, err
}

postgresHost := os.Getenv("POSTGRES_HOST")
postgresUser := os.Getenv("POSTGRES_USER")
postgresPass := os.Getenv("POSTGRES_PASS")
postgresDBName := os.Getenv("POSTGRES_DBNAME")
jwtSecret := os.Getenv("JWT_SECRET")

return &config.EnvVariables{
PostgresHost: postgresHost,
PostgresUser: postgresUser,
PostgresPass: postgresPass,
PostgresDBName: postgresDBName,
}, nil
return db, nil
}

// connectToPostgresAndMigrate initializes a PostgreSQL db session and runs GORM migrations.
func connectToPostgresAndMigrate(env *config.EnvVariables) (*gorm.DB, error) {
dsn := fmt.Sprintf("host=%s user=%s dbname=%s password=%s sslmode=disable",
env.PostgresHost, env.PostgresUser, env.PostgresDBName, env.PostgresPass)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
func connectToDB() (*gorm.DB, error) {
dsn := os.Getenv("DSN")

for {
connection, err := openDB(dsn)
if err != nil {
log.Println("Postgres not yet ready...")
counts++
} else {
log.Println("Connected to Postgres!")
return connection, nil
}

if counts > 10 {
log.Println(err)
return nil, err
}

log.Println("Backing off for two seconds...")
time.Sleep(2 * time.Second)
continue
}
}

// runDBMigrations runs database migrations.
func runDBMigrations(db *gorm.DB) error {
log.Println("Running migrations...")
// create tables
err := db.AutoMigrate(&models.User{})
if err != nil {
log.Fatal("could not connect: ", err)
return err
}

return db, nil
err = db.AutoMigrate(&models.Currency{})
if err != nil {
return err
}

err = db.AutoMigrate(&models.Subscription{})
if err != nil {
return err
}

// populate tables with initial data
err = createInitialCurrencies(db)
if err != nil {
return errors.New(fmt.Sprint("error creating initial currencies:", err))
}

log.Println("Database migrated!")

return nil
}

// createInitialCurrencies creates initial currencies in the `currencies` table.
func createInitialCurrencies(db *gorm.DB) error {
var count int64

if err := db.Model(&models.Currency{}).Count(&count).Error; err != nil {
return err
}

if count > 0 {
return nil
}

initialCurrencies := []models.Currency{
{Code: "USD", Name: "United States Dollar"},
{Code: "UAH", Name: "Ukrainian Hryvnia"},
}

if err := db.Create(&initialCurrencies).Error; err != nil {
return err
}

return nil
}
28 changes: 28 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
services:
postgres:
image: 'postgres:14.0'
ports:
- "5432:5432"
restart: always
deploy:
mode: replicated
replicas: 1
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: emails
volumes:
- ./db-data/postgres/:/var/lib/postgresql/data/

api-app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
restart: always
deploy:
mode: replicated
replicas: 1
environment:
DSN: "host=postgres port=5432 user=postgres password=password dbname=emails sslmode=disable timezone=UTC connect_timeout=5"
13 changes: 3 additions & 10 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
package config

import (
"github.com/vladyslavpavlenko/genesis-api-project/internal/models"
"gorm.io/gorm"
)

// AppConfig holds the application config.
type AppConfig struct {
DB *gorm.DB
Env *EnvVariables
}

// EnvVariables holds environment variables used in the application.
type EnvVariables struct {
PostgresHost string
PostgresUser string
PostgresPass string
PostgresDBName string
DB *gorm.DB
Models models.Models
}
Loading

0 comments on commit b989c82

Please sign in to comment.