Skip to content

Commit

Permalink
feat: initial service and api impl for handling creation of TinyURLs (#8
Browse files Browse the repository at this point in the history
)

* feat: initial service and api impl for handling creation of TinyURLs

* fix: rework the ci files to check for migrations only on pull_request and not on merge

* fix: split jobs and fix names
  • Loading branch information
sanathkumarbs authored Jan 26, 2024
1 parent 2d4e9bb commit e5e5d9f
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 59 deletions.
36 changes: 18 additions & 18 deletions .github/workflows/makefile.yml → .github/workflows/base-ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Makefile CI
name: Base CI Jobs

on:
push:
Expand All @@ -7,39 +7,39 @@ on:
branches: [ "main" ]

jobs:
build:

lint-fmt:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v4
with:
go-version: '^1.21.3' # The Go version to download (if necessary) and use.

- name: Run OAPI-Generate
run: make gen

- name: Run Lint Go
run: make lint-go

- name: Find modified migrations
run: |
modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'migrations/*.sql')
echo "$modified_migrations"
echo "::set-output name=file_names::$modified_migrations"
id: modified-migrations
- uses: sbdchd/squawk-action@v1
with:
pattern: ${{ steps.modified-migrations.outputs.file_names }}

- name: Run Format
run: make fmt

build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v4
with:
go-version: '^1.21.3' # The Go version to download (if necessary) and use.

- name: Run OAPI-Generate
run: make gen

- name: Make Build Go
run: make build-go

- name: Make Build Docker Images
run: make build-image
run: make build-image
24 changes: 24 additions & 0 deletions .github/workflows/migrations-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Migrations CI Jobs

on:
pull_request:
branches: [ "main" ]

jobs:
migrations:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Find modified migrations
run: |
modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'migrations/*.sql')
echo "$modified_migrations"
echo "::set-output name=file_names::$modified_migrations"
id: modified-migrations
- uses: sbdchd/squawk-action@v1
with:
pattern: ${{ steps.modified-migrations.outputs.file_names }}
37 changes: 27 additions & 10 deletions cmd/tiny/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@ package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"

"github.com/spf13/cobra"
"sanathk.com/tinyurl/internal/tiny"
"sanathk.com/tinyurl/internal/tiny/db"
srvStub "sanathk.com/tinyurl/pkg/api/services/v1/tiny"
"sanathk.com/tinyurl/pkg/apiserver"
"sanathk.com/tinyurl/pkg/postgres"
)

const (
// TODO: this should be definied and configured via env var/flags
servicePort = "8080"
defaultAPIPath = "/api/v1"
)

func main() {
if err := cmd(); err != nil {
os.Exit(1)
Expand All @@ -33,6 +43,7 @@ func cmd() error {
}

func run() error {
slog.Info("running TinyURL service")
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
defer cancel()

Expand All @@ -58,14 +69,20 @@ func run() error {
return err
}

ticker := time.NewTicker(10 * time.Second)
for {
select {
case <-ticker.C:
svc.Hello()
case <-time.After(5 * time.Minute):
ticker.Stop()
return nil
}
api, err := tiny.NewAPIHandler(ctx, svc)
if err != nil {
return err
}

srv, err := apiserver.NewEchoServer()
if err != nil {
return err
}
srv.Group(defaultAPIPath)
srvStub.RegisterHandlersWithBaseURL(srv, api, defaultAPIPath)

if err := srv.Start(fmt.Sprintf(":%s", servicePort)); err != http.ErrServerClosed {
log.Fatal(err)
}
return nil
}
3 changes: 3 additions & 0 deletions devenv/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ services:
depends_on:
postgres:
condition: service_healthy
ports:
- "8080:8080"
postgres:
image: postgres:16.1
healthcheck:
Expand All @@ -20,6 +22,7 @@ services:
- postgres-persisted:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
PGUSER: postgres
POSTGRES_PASSWORD: admin
POSTGRES_DB: tiny
ports:
Expand Down
83 changes: 83 additions & 0 deletions internal/tiny/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package tiny

import (
"context"
"fmt"
"log/slog"
"net/http"
"time"

"github.com/labstack/echo/v4"
openapi_types "github.com/oapi-codegen/runtime/types"
srvType "sanathk.com/tinyurl/pkg/api/services/v1/tiny"
)

type APIHandler struct {
svc *Service
}

// TODO: potentially maintain this type on the OpenAPI Spec as it is for responding back to the API?
type APIError struct {
Message string `json:"message"`
Timestamp string `json:"timestamp"` // in UTC
}

type Error struct {
APIError
}

func (e Error) Error() string {
return fmt.Sprintf("[%v] %s", e.Timestamp, e.Message)
}

func NewAPIHandler(ctx context.Context, svc *Service) (*APIHandler, error) {
slog.Info("initializing a TinyURL API Handler")

return &APIHandler{
svc: svc,
}, nil
}

func (a *APIHandler) CreateTinyURL(ec echo.Context) error {
slog.Info("handling a CreateTinyURL API request")
body := srvType.TinyURLRequest{}

err := ec.Bind(&body)
if err != nil {
slog.Error("could not process request body to create a TinyURL", slog.Any("error", err.Error()))
ae := APIError{
Message: "could not process request body to create a TinyURL",
Timestamp: time.Now().UTC().String(),
}
return ec.JSON(
http.StatusBadRequest, Error{ae},
)
}

var resp TinyURLResponse
req := TinyURLRequest{
Expiry: body.Expiry.Time,
Original: body.Original,
}
resp, err = a.svc.CreateTinyURL(ec, req)
if err != nil {
slog.Error("could not complete creating TinyURL", slog.Any("error", err.Error()))
ae := APIError{
// What actionable action can we provide to the user here?
// As this error message by itself is not very useful
Message: "could not complete creating TinyURL",
Timestamp: time.Now().UTC().String(),
}
return ec.JSON(
http.StatusInternalServerError, Error{ae},
)
}

return ec.JSON(
http.StatusOK, srvType.TinyURLResponse{
Expiry: openapi_types.Date{Time: resp.Expiry},
Original: resp.Original,
Tinyurl: resp.Tinyurl,
},
)
}
45 changes: 45 additions & 0 deletions internal/tiny/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package tiny

import (
"context"
"io/fs"
"log/slog"
"time"

"github.com/labstack/echo/v4"
)

type DatabaseInterface interface {
Migrate(files fs.FS, path string) error
}

type TinyURLRequest struct {
Expiry time.Time
Original string
}

type TinyURLResponse struct {
Expiry time.Time
Original string
Tinyurl string
}

type Service struct {
db DatabaseInterface
}

func NewService(ctx context.Context, db DatabaseInterface) (*Service, error) {
slog.Info("init TinyURL internal service")
return &Service{
db: db,
}, nil
}

func (s *Service) CreateTinyURL(ec echo.Context, req TinyURLRequest) (TinyURLResponse, error) {
slog.Info("processing a TinyURL request")
return TinyURLResponse{
Expiry: req.Expiry,
Original: req.Original,
Tinyurl: "sanathk.com/tinyurl123",
}, nil
}
31 changes: 0 additions & 31 deletions internal/tiny/tiny.go

This file was deleted.

18 changes: 18 additions & 0 deletions pkg/apiserver/echo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package apiserver

import (
"log/slog"

"github.com/labstack/echo/v4"
)

type EchoServer struct {
*echo.Echo
}

func NewEchoServer() (*EchoServer, error) {
slog.Info("creating an echo server")
return &EchoServer{
echo.New(),
}, nil
}
4 changes: 4 additions & 0 deletions pkg/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io/fs"
"log/slog"
"net/url"

"github.com/golang-migrate/migrate/v4"
Expand Down Expand Up @@ -35,6 +36,7 @@ func NewPostgres(ctx context.Context, connString *url.URL) (Postgres, error) {
}

func (pg Postgres) connect(ctx context.Context) (*pgx.Conn, error) {
slog.Info("connecting to postgres")
return pgx.Connect(ctx, pg.connString.String())
}

Expand All @@ -43,6 +45,7 @@ func (pg Postgres) Conn() *pgx.Conn {
}

func (pg Postgres) Migrate(files fs.FS, path string) error {
slog.Info("migrating database schema to postgres")
c, err := pgx.ParseConfig(pg.connString.String())
if err != nil {
return fmt.Errorf("parsing postgres connString: %w", err)
Expand Down Expand Up @@ -76,5 +79,6 @@ func (pg Postgres) Migrate(files fs.FS, path string) error {
}
return fmt.Errorf("migrating up: %w", err)
}
slog.Info("successfully migrated database schema in postgres")
return nil
}

0 comments on commit e5e5d9f

Please sign in to comment.