Skip to content

Commit

Permalink
feat: add spacetastic endpoints DOC-1138 (#16)
Browse files Browse the repository at this point in the history
* feat: add page routes to counter DOC-1138

* feat: add spacetastic endpoints DOC-1138

* docs: adjust readme DOC-1138

* docs: update tests and db version

* docs: update postman collection

* docs: add testcontainers to counter test

* chore: Updated coverage badge.

* docs: update Go version in Dockerfile

* docs: bump db version in makefile

* docs: remove println

* docs: adjust assertions

* docs: fix format

* docs: fix README

* Apply suggestions from code review

Co-authored-by: Karl Cardenas <[email protected]>

---------

Co-authored-by: GitHub Action <[email protected]>
Co-authored-by: Karl Cardenas <[email protected]>
  • Loading branch information
3 people authored Sep 6, 2024
1 parent 488e5e7 commit b7110ee
Show file tree
Hide file tree
Showing 16 changed files with 492 additions and 142 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- main

env:
DB_VERSION: 1.0.0
DB_VERSION: 1.1.0

concurrency:
group: ci-${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Spectro Cloud
# SPDX-License-Identifier: MPL-2.0

FROM golang:1.21.7-alpine3.19 as builder
FROM golang:1.23.0-alpine3.20 as builder
WORKDIR /go/src/app
COPY . .
RUN go build -o /go/bin/app && \
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: license


VERSION:=1.0.0
VERSION:=1.1.0

build:
go build -o hello-universe-api
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
![Coverage](https://img.shields.io/badge/Coverage-54.2%25-yellow)
![Coverage](https://img.shields.io/badge/Coverage-42.6%25-yellow)

# Hello Universe API

Expand All @@ -17,13 +17,40 @@ The [Hello Universe](https://github.com/spectrocloud/hello-universe) app include

A Postman collection is available to help you explore the API. Review the [Postman collection](./tests/postman_collection.json) to get started.

# Prerequisites
Ensure [Docker Desktop](https://www.docker.com/products/docker-desktop/) on your local machine is available.

- Use the following command and ensure you receive an output displaying the version number.
```shell
docker version
```

Alternatively, you can install [Podman](https://podman.io/docs/installation).

- If you are not using a Linux operating system, create and start the Podman Machine in your local environment. Otherwise, skip this step.
```shell
podman machine init
podman machine start
```
- Use the following command and ensure you receive an output displaying the installation information.
```shell
podman info
```

# Usage

The quickest method to start the API server locally is by using the Docker image.

```shell
docker pull ghcr.io/spectrocloud/hello-universe-api:1.0.12
docker run -p 3000:3000 ghcr.io/spectrocloud/hello-universe-api:1.0.12
docker pull ghcr.io/spectrocloud/hello-universe-api:1.1.0
docker run -p 3000:3000 ghcr.io/spectrocloud/hello-universe-api:1.1.0
```

If you choose Podman, you can use the following commands.

```shell
podman pull ghcr.io/spectrocloud/hello-universe-api:1.1.0
podman run -p 3000:3000 ghcr.io/spectrocloud/hello-universe-api:1.1.0
```

To start the API server you must have connectivity to a Postgres instance. Use [environment variables](#environment-variables) to customize the API server start parameters.
Expand Down
60 changes: 45 additions & 15 deletions endpoints/counterRoute.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ func NewCounterHandlerContext(db *sqlx.DB, ctx context.Context, authorization bo
}

func (route *CounterRoute) CounterHTTPHandler(writer http.ResponseWriter, request *http.Request) {
log.Debug().Msg("POST request received. Incrementing counter.")
page := request.PathValue("page")

writer.Header().Set("Content-Type", "application/json")
writer.Header().Set("Access-Control-Allow-Origin", "*")
writer.Header().Set("Access-Control-Allow-Headers", "*")
var payload []byte

if route.authorization && request.Method != "OPTIONS" {
if route.Authorization && request.Method != "OPTIONS" {
validation := internal.ValidateToken(request.Header.Get("Authorization"))
if !validation {
log.Info().Msg("Invalid token.")
Expand All @@ -39,15 +40,16 @@ func (route *CounterRoute) CounterHTTPHandler(writer http.ResponseWriter, reques

switch request.Method {
case "POST":
value, err := route.postHandler(request)
log.Debug().Msg("POST request received. Incrementing counter.")
value, err := route.postHandler(request, page)
if err != nil {
log.Debug().Msg("Error incrementing counter.")
http.Error(writer, "Error incrementing counter.", http.StatusInternalServerError)
}
writer.WriteHeader(http.StatusCreated)
payload = value
case "GET":
value, err := route.getHandler(request)
value, err := route.getHandler(page)
if err != nil {
log.Debug().Msg("Error getting counter value.")
http.Error(writer, "Error getting counter value.", http.StatusInternalServerError)
Expand All @@ -70,27 +72,27 @@ func (route *CounterRoute) CounterHTTPHandler(writer http.ResponseWriter, reques
}

// postHandler increments the counter in the database.
func (route *CounterRoute) postHandler(r *http.Request) ([]byte, error) {
func (route *CounterRoute) postHandler(r *http.Request, page string) ([]byte, error) {
currentTime := time.Now().UTC()
ua := useragent.Parse(r.UserAgent())
browser := ua.Name
os := ua.OS
transaction, err := route.DB.BeginTx(route.ctx, nil)
transaction, err := route.DB.BeginTx(route.Ctx, nil)
if err != nil {
log.Error().Err(err).Msg("Error beginning transaction.")
return []byte{}, err
}
sqlQuery := `INSERT INTO counter(date,browser,os) VALUES ($1, $2, $3)`
_, err = transaction.ExecContext(route.ctx, sqlQuery, currentTime, browser, os)
sqlQuery := `INSERT INTO counter(page, date, browser, os) VALUES ($1, $2, $3, $4)`
_, err = transaction.ExecContext(route.Ctx, sqlQuery, page, currentTime, browser, os)
if err != nil {
log.Error().Err(err).Msg("Error inserting counter value.")
log.Debug().Msgf("SQL query: %s", sqlQuery)
return []byte{}, err
}
log.Info().Msg("Counter incremented in database.")
getNewCountQuery := `SELECT COUNT(*) AS total FROM counter`
getNewCountQuery := `SELECT COUNT(*) AS total FROM counter WHERE page = $1`
var databaseTotal sql.NullInt64
result := transaction.QueryRowContext(route.ctx, getNewCountQuery)
result := transaction.QueryRowContext(route.Ctx, getNewCountQuery, page)
err = result.Scan(&databaseTotal)
if err != nil {
log.Error().Err(err).Msg("Error scanning counter value.")
Expand All @@ -100,7 +102,7 @@ func (route *CounterRoute) postHandler(r *http.Request) ([]byte, error) {
log.Error().Err(err).Msg("Counter value is null.")
return []byte{}, err
}
counterSummary := counterSummary{Total: databaseTotal.Int64}
counterSummary := CounterSummary{Total: databaseTotal.Int64}
err = transaction.Commit()
if err != nil {
log.Error().Err(err).Msg("Error committing transaction.")
Expand All @@ -120,13 +122,22 @@ func (route *CounterRoute) postHandler(r *http.Request) ([]byte, error) {
}

// getHandler returns the current counter value from the database as a JSON object.
func (route *CounterRoute) getHandler(r *http.Request) ([]byte, error) {
func (route *CounterRoute) getHandler(page string) ([]byte, error) {
if page != "" {
return route.getHandlerForPage(page)
}

return route.getHandlerAllPages()
}

// getHandlerAllPages returns the current counter value for all pages from the database as a JSON object.
func (route *CounterRoute) getHandlerAllPages() ([]byte, error) {
sqlQuery := `SELECT COUNT(*) AS total FROM counter`
var counterSummary counterSummary
err := route.DB.GetContext(route.ctx, &counterSummary, sqlQuery)
var counterSummary CounterSummary
err := route.DB.GetContext(route.Ctx, &counterSummary, sqlQuery)
if err != nil {
log.Error().Err(err).Msg("Error getting counter value.")
log.Debug().Msgf("SQL query: %s", sqlQuery)
log.Info().Msgf("SQL query: %s", sqlQuery)
return []byte{}, err
}
log.Info().Msg("Counter value retrieved from database.")
Expand All @@ -137,3 +148,22 @@ func (route *CounterRoute) getHandler(r *http.Request) ([]byte, error) {
}
return payload, nil
}

// getHandlerForPage returns the current counter value for a single page from the database as a JSON object.
func (route *CounterRoute) getHandlerForPage(page string) ([]byte, error) {
sqlQuery := `SELECT COUNT(*) AS total FROM counter WHERE page = $1`
var counterSummary CounterSummary
err := route.DB.GetContext(route.Ctx, &counterSummary, sqlQuery, page)
if err != nil {
log.Error().Err(err).Msg("Error getting counter value.")
log.Info().Msgf("SQL query: %s", sqlQuery)
return []byte{}, err
}
log.Info().Msgf("Counter value retrieved from database for page %s", page)
payload, err := json.MarshalIndent(counterSummary, "", " ")
if err != nil {
log.Error().Err(err).Msg("Error marshalling counterSummary struct into JSON.")
return []byte{}, err
}
return payload, nil
}
Loading

0 comments on commit b7110ee

Please sign in to comment.