feat: nearly finished
This commit is contained in:
parent
2e7e1c8749
commit
96931551aa
|
@ -12,4 +12,10 @@
|
|||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
vendor/
|
||||
|
||||
# Environment variable
|
||||
.env
|
||||
|
||||
# Heroku bin directory
|
||||
bin
|
|
@ -6,20 +6,52 @@ Still work in progress
|
|||
|
||||
```bash
|
||||
# Install modules
|
||||
$ go install
|
||||
$ go mod download
|
||||
# or
|
||||
$ go mod vendor
|
||||
|
||||
# run the local server
|
||||
$ go run ./
|
||||
$ go run main.go
|
||||
|
||||
# build everything
|
||||
$ go build ./
|
||||
$ go build main.go
|
||||
```
|
||||
|
||||
## Modules
|
||||
## Used packages
|
||||
|
||||
- https://github.com/Masterminds/squirrel
|
||||
- https://github.com/jackc/pgx
|
||||
- https://github.com/go-redis/redis/v8
|
||||
- https://github.com/unrolled/secure
|
||||
| Name | Version | Type |
|
||||
| --- | --- | --- |
|
||||
| gofiber/fiber | v2.14.0 | Framework |
|
||||
| jackc/pgx | v4.11.0 | Database |
|
||||
| go-redis/redis | v8.11.0 | Cache |
|
||||
| joho/godotenv | v1.3.0 | Config |
|
||||
| getsentry/sentry-go | v0.11.0 | Logging |
|
||||
| aldy505/phc-crypto | v1.1.0 | Utils |
|
||||
| Masterminds/squirrel | v1.5.0 | Utils |
|
||||
| aldy505/bob | v0.0.1 | Utils |
|
||||
|
||||
## Directory structure
|
||||
|
||||
```
|
||||
└-- /app
|
||||
└---- /v1
|
||||
└---- /handler
|
||||
└---- /middleware folder for add middleware
|
||||
└---- /models
|
||||
└---- /platform
|
||||
└--------- /cache folder with in-memory cache setup functions
|
||||
└--------- /database folder with database setup functions
|
||||
└---- /routes folder for describe routes
|
||||
└---- /utils folder with utility functions
|
||||
```
|
||||
## `.env` configuration
|
||||
|
||||
```ini
|
||||
ENV=development
|
||||
PORT=5000
|
||||
|
||||
DATABASE_URL=postgres://postgres:password@localhost:5432/jokesbapak2
|
||||
REDIS_URL=redis://@localhost:6379
|
||||
|
||||
SENTRY_DSN=
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/routes"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func New() *fiber.App {
|
||||
app := fiber.New(fiber.Config{
|
||||
DisableKeepalive: true,
|
||||
CaseSensitive: true,
|
||||
})
|
||||
|
||||
routes.Health(app)
|
||||
routes.Joke(app)
|
||||
|
||||
return app
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package handler
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
func Health(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func AddNewJoke(c *fiber.Ctx) error {
|
||||
var body models.JokePost
|
||||
var body models.RequestJokePost
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -24,7 +24,7 @@ func AddNewJoke(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||
"link": body.Link,
|
||||
return c.Status(fiber.StatusCreated).JSON(models.ResponseJoke{
|
||||
Link: body.Link,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1 +1,43 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func DeleteJoke(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
// Check if the joke exists
|
||||
sql, args, err := psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var jokeID string
|
||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jokeID == id {
|
||||
sql, args, err = psql.Delete("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{
|
||||
Message: "specified joke id has been deleted",
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusNotAcceptable).JSON(models.ResponseError{
|
||||
Error: "specified joke id does not exists",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package handler
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
|
@ -38,20 +37,20 @@ func TodayJoke(c *fiber.Ctx) error {
|
|||
|
||||
if eq {
|
||||
c.Attachment(joke.link)
|
||||
return c.SendStatus(200)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
} else {
|
||||
var link string
|
||||
err := db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 WHERE random() < 0.01 LIMIT 1").Scan(&link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := strconv.Itoa(int(time.Now().Unix()))
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
err = redis.MSet(context.Background(), "today:link", link, "today:date", now).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Attachment(link)
|
||||
return c.SendStatus(200)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -66,7 +65,7 @@ func SingleJoke(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
c.Attachment(link)
|
||||
return c.SendStatus(200)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func JokeByID(c *fiber.Ctx) error {
|
||||
|
@ -79,8 +78,8 @@ func JokeByID(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
if link == "" {
|
||||
return c.Status(404).Send([]byte("Requested ID was not found."))
|
||||
return c.Status(fiber.StatusNotFound).Send([]byte("Requested ID was not found."))
|
||||
}
|
||||
c.Attachment(link)
|
||||
return c.SendStatus(200)
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
|
|
@ -1 +1,51 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func UpdateJoke(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
// Check if the joke exists
|
||||
sql, args, err := psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var jokeID string
|
||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jokeID == id {
|
||||
body := new(models.RequestJokePost)
|
||||
err = c.BodyParser(&body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sql, args, err = psql.Update("jokesbapak2").Set("link", body.Link).Set("key", body.Key).ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := db.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{
|
||||
Message: "specified joke id has been updated",
|
||||
Link: body.Link,
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusNotAcceptable).JSON(models.ResponseError{
|
||||
Error: "specified joke id does not exists",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ var db = database.New()
|
|||
|
||||
func RequireAuth() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var auth models.Auth
|
||||
var auth models.RequestAuth
|
||||
err := c.BodyParser(&auth)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -33,8 +33,8 @@ func RequireAuth() fiber.Handler {
|
|||
}
|
||||
|
||||
if token == "" {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "Invalid token",
|
||||
return c.Status(fiber.StatusForbidden).JSON(models.ResponseError{
|
||||
Error: "Invalid token",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -61,8 +61,9 @@ func RequireAuth() fiber.Handler {
|
|||
if verify {
|
||||
c.Next()
|
||||
}
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "Invalid key",
|
||||
|
||||
return c.Status(fiber.StatusForbidden).JSON(models.ResponseError{
|
||||
Error: "Invalid key",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package models
|
||||
|
||||
type Auth struct {
|
||||
Key string `json:"key"`
|
||||
Token string `json:"token"`
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package models
|
||||
|
||||
type JokePost struct {
|
||||
Key string `json:"string"`
|
||||
Link string `json:"link"`
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package models
|
||||
|
||||
type RequestJokePost struct {
|
||||
Key string `json:"string"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
type RequestAuth struct {
|
||||
Key string `json:"key"`
|
||||
Token string `json:"token"`
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package models
|
||||
|
||||
type ResponseError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type ResponseJoke struct {
|
||||
Link string `json:"link"`
|
||||
Message string `json:"message"`
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
)
|
||||
|
||||
// Connect to the database
|
||||
|
|
|
@ -5,105 +5,55 @@ import (
|
|||
"log"
|
||||
"strings"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/aldy505/bob"
|
||||
)
|
||||
|
||||
// Set up the table connection, create table if not exists
|
||||
func Setup() error {
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
db := New()
|
||||
|
||||
// Jokesbapak2 table & data
|
||||
// Check if table exists
|
||||
sql, args, err := bob.HasTable("jokesbapak2").PlaceholderFormat(bob.Dollar).ToSQL()
|
||||
sql, _, err := bob.CreateTableIfNotExists("jokesbapak2").
|
||||
Columns("id", "link", "key").
|
||||
Types("SERIAL", "TEXT", "VARCHAR(255)").
|
||||
Primary("id").ToSql()
|
||||
if err != nil {
|
||||
|
||||
log.Fatalln("failed on checking database table:", err)
|
||||
log.Fatalln("10 - failed on table creation: ", err)
|
||||
}
|
||||
|
||||
var hasTableJokes bool
|
||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&hasTableJokes)
|
||||
if err != nil {
|
||||
if err.Error() == "no rows in result set" {
|
||||
hasTableJokes = false
|
||||
} else {
|
||||
log.Fatalln("failed on checking database table:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasTableJokes {
|
||||
sql, _, err = bob.CreateTable("jokesbapak2").
|
||||
Columns("id", "link").
|
||||
Types("SERIAL", "VARCHAR(255)").
|
||||
Primary("id").ToSQL()
|
||||
splitSql := strings.Split(sql, ";")
|
||||
for i := range splitSql {
|
||||
_, err = db.Query(context.Background(), splitSql[i])
|
||||
if err != nil {
|
||||
log.Fatalln("failed on table creation:", err)
|
||||
}
|
||||
|
||||
splitSql := strings.Split(sql, ";")
|
||||
for i := range splitSql {
|
||||
_, err = db.Query(context.Background(), splitSql[i])
|
||||
if err != nil {
|
||||
log.Println(sql)
|
||||
log.Fatalln("Failed on table creation: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
insertQuery, args, err := psql.Insert("jokesbapak2").
|
||||
Columns("link").
|
||||
Values("https://i.ibb.co/19pntdQ/Ea-p8-BWU8-AAtbjp.jpg").
|
||||
ToSql()
|
||||
if err != nil {
|
||||
log.Fatalln("Failed on query creation: ", err)
|
||||
return err
|
||||
}
|
||||
_, err = db.Query(context.Background(), insertQuery, args...)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed on table insertion: ", err)
|
||||
log.Fatalln("11 - failed on table creation: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Authorization
|
||||
// Check if table exists
|
||||
sql, args, err = bob.HasTable("authorization").PlaceholderFormat(bob.Dollar).ToSQL()
|
||||
sql, _, err = bob.CreateTableIfNotExists("authorization").
|
||||
Columns("id", "token", "key").
|
||||
Types("SERIAL", "TEXT", "VARCHAR(255)").
|
||||
Primary("id").
|
||||
Unique("token").
|
||||
ToSql()
|
||||
if err != nil {
|
||||
log.Fatalln("failed on checking database table:", err)
|
||||
log.Fatalln("14 - failed on table creation: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var hasTableAuth bool
|
||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&hasTableAuth)
|
||||
if err != nil {
|
||||
if err.Error() == "no rows in result set" {
|
||||
hasTableAuth = false
|
||||
} else {
|
||||
log.Fatalln("failed on checking database table:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasTableAuth {
|
||||
sql, _, err = bob.CreateTable("authorization").
|
||||
Columns("id", "token", "key").
|
||||
Types("SERIAL", "VARCHAR(255)", "VARCHAR(255)").
|
||||
Primary("id").
|
||||
Unique("token").
|
||||
ToSQL()
|
||||
splitSql = strings.Split(sql, ";")
|
||||
for i := range splitSql {
|
||||
_, err = db.Query(context.Background(), splitSql[i])
|
||||
if err != nil {
|
||||
log.Fatalln("Failed on table creation: ", err)
|
||||
log.Fatalln("15 - failed on table creation: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
splitSql := strings.Split(sql, ";")
|
||||
for i := range splitSql {
|
||||
_, err = db.Query(context.Background(), splitSql[i])
|
||||
if err != nil {
|
||||
log.Fatalln("Failed on table creation: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.Query(context.Background(), "ALTER TABLE jokesbapak2 ADD CONSTRAINT fk_jokesbapak2_key FOREIGN KEY (key) REFERENCES authorization (id)")
|
||||
if err != nil {
|
||||
log.Fatalln("16 - failed on foreign key iteration: ", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
)
|
||||
|
||||
// Connect to the database
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/handler"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Health(app *fiber.App) *fiber.App {
|
||||
// Health check
|
||||
app.Get("/", handler.Health)
|
||||
return app
|
||||
}
|
|
@ -12,31 +12,24 @@ import (
|
|||
var db = database.New()
|
||||
var redis = cache.New()
|
||||
|
||||
func New() *fiber.App {
|
||||
app := fiber.New(fiber.Config{
|
||||
ETag: true,
|
||||
DisableKeepalive: true,
|
||||
CaseSensitive: true,
|
||||
})
|
||||
|
||||
v1 := app.Group("/v1")
|
||||
func Joke(app *fiber.App) *fiber.App {
|
||||
// Single route
|
||||
v1.Get("/", handler.SingleJoke)
|
||||
app.Get("/", handler.SingleJoke)
|
||||
|
||||
// Today's joke
|
||||
v1.Get("/today", handler.TodayJoke)
|
||||
app.Get("/today", handler.TodayJoke)
|
||||
|
||||
// Joke by ID
|
||||
v1.Get("/:id", handler.JokeByID)
|
||||
app.Get("/:id", handler.JokeByID)
|
||||
|
||||
// Add new joke
|
||||
v1.Put("/", middleware.RequireAuth(), handler.AddNewJoke)
|
||||
app.Put("/", middleware.RequireAuth(), handler.AddNewJoke)
|
||||
|
||||
// Update a joke
|
||||
v1.Patch("/:id")
|
||||
app.Patch("/:id", middleware.RequireAuth(), handler.UpdateJoke)
|
||||
|
||||
// Delete a joke
|
||||
v1.Delete("/:id")
|
||||
app.Delete("/:id", middleware.RequireAuth(), handler.DeleteJoke)
|
||||
|
||||
return app
|
||||
}
|
||||
|
|
|
@ -4,15 +4,13 @@ import "time"
|
|||
|
||||
// IsToday checks if a date is in fact today or not.
|
||||
func IsToday(date string) (bool, error) {
|
||||
parse, err := time.Parse(time.UnixDate, date)
|
||||
parse, err := time.Parse(time.RFC3339, date)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
y1, m1, d1 := parse.Date()
|
||||
y2, m2, d2 := time.Now().Date()
|
||||
|
||||
if parse.Equal(now) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
return y1 == y2 && m1 == m2 && d1 == d2, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/utils"
|
||||
)
|
||||
|
||||
func TestIsToday(t *testing.T) {
|
||||
t.Run("should be able to tell if it's today", func(t *testing.T) {
|
||||
today, err := utils.IsToday(time.Now().UTC().Format(time.RFC3339))
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if today == false {
|
||||
t.Error("today should be true:", today)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should be able to tell if it's not today", func(t *testing.T) {
|
||||
today, err := utils.IsToday("2021-01-01T11:48:24Z")
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if today == true {
|
||||
t.Error("today should be false:", today)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ParseToFormBody converts a body to form data type
|
||||
func ParseToFormBody(body map[string]interface{}) ([]byte, error) {
|
||||
var form string
|
||||
for key, value := range body {
|
||||
form += key + "="
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
form += v
|
||||
case int:
|
||||
form += strconv.Itoa(v)
|
||||
case bool:
|
||||
form += strconv.FormatBool(v)
|
||||
}
|
||||
form += "&"
|
||||
}
|
||||
return []byte(form), nil
|
||||
}
|
||||
|
||||
// ParseToJSONBody converts a body to json data type
|
||||
func ParseToJSONBody(body map[string]interface{}) ([]byte, error) {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/utils"
|
||||
)
|
||||
|
||||
func TestParseToJSONBody(t *testing.T) {
|
||||
t.Run("should be able to parse a json string", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "Scott",
|
||||
"age": 32,
|
||||
"fat": true,
|
||||
}
|
||||
parsed, err := utils.ParseToJSONBody(body)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
result := "{\"age\":32,\"fat\":true,\"name\":\"Scott\"}"
|
||||
if string(parsed) != result {
|
||||
t.Error("parsed string is not the same as result:", string(parsed))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseToFormBody(t *testing.T) {
|
||||
t.Run("should be able to parse a form body", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "Scott",
|
||||
"age": 32,
|
||||
"fat": true,
|
||||
}
|
||||
parsed, err := utils.ParseToFormBody(body)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
result := "name=Scott&age=32&fat=true&"
|
||||
if string(parsed) != result {
|
||||
t.Error("parsed string is not the same as result:", string(parsed))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ContentType int
|
||||
|
||||
const (
|
||||
JSON ContentType = iota
|
||||
Form
|
||||
)
|
||||
|
||||
type RequestConfig struct {
|
||||
URL string
|
||||
Method string
|
||||
Headers map[string]interface{}
|
||||
Body map[string]interface{}
|
||||
ContentType ContentType
|
||||
}
|
||||
|
||||
// Request is a simple wrapper around http.NewRequest
|
||||
func Request(config RequestConfig) (response *http.Response, err error) {
|
||||
client := &http.Client{}
|
||||
|
||||
var body []byte
|
||||
if config.ContentType == JSON {
|
||||
parsed, err := ParseToJSONBody(config.Body)
|
||||
if err != nil {
|
||||
return &http.Response{}, err
|
||||
}
|
||||
|
||||
body = parsed
|
||||
} else if config.ContentType == Form {
|
||||
parsed, err := ParseToFormBody(config.Body)
|
||||
if err != nil {
|
||||
return &http.Response{}, err
|
||||
}
|
||||
|
||||
body = parsed
|
||||
}
|
||||
|
||||
request, err := http.NewRequest(config.Method, config.URL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err = client.Do(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package utils_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/utils"
|
||||
)
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
t.Run("should be able to do a get request", func(t *testing.T) {
|
||||
res, err := utils.Request(utils.RequestConfig{
|
||||
URL: "https://jsonplaceholder.typicode.com/todos/1",
|
||||
Method: http.MethodGet,
|
||||
Headers: map[string]interface{}{
|
||||
"User-Agent": "Jokesbapak2 Test API",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
t.Error("response does not have 200 status", res.Status)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -4,10 +4,11 @@ go 1.16
|
|||
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.5.0
|
||||
github.com/aldy505/bob v0.0.0-20210630160113-75547d606a54
|
||||
github.com/aldy505/bob v0.0.1
|
||||
github.com/aldy505/phc-crypto v1.1.0
|
||||
github.com/getsentry/sentry-go v0.11.0
|
||||
github.com/go-redis/redis/v8 v8.11.0
|
||||
github.com/gofiber/fiber/v2 v2.14.0
|
||||
github.com/jackc/pgx/v4 v4.11.0
|
||||
github.com/joho/godotenv v1.3.0
|
||||
)
|
||||
|
|
|
@ -17,6 +17,8 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia
|
|||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/aldy505/bob v0.0.0-20210630160113-75547d606a54 h1:U9GFaqa00Ft1kqmUxoNz+CugEmXGdX6bz2L+O/dbB/8=
|
||||
github.com/aldy505/bob v0.0.0-20210630160113-75547d606a54/go.mod h1:/8HuD17XXgzuaFw5j4oDyB8O+NlW8mKWd0QCCbeoLVE=
|
||||
github.com/aldy505/bob v0.0.1 h1:L/nvD9+ViLJaWbgbeBes/4xQfz7YtQLJtk8OjSa9L2k=
|
||||
github.com/aldy505/bob v0.0.1/go.mod h1:/8HuD17XXgzuaFw5j4oDyB8O+NlW8mKWd0QCCbeoLVE=
|
||||
github.com/aldy505/phc-crypto v1.1.0 h1:BagRKCrB7FOYy5vnuXR6xs6ml2gJD/CvSJkX/Ozo63w=
|
||||
github.com/aldy505/phc-crypto v1.1.0/go.mod h1:LJugClOkOWKnpLrWhSaIDRN/5ftvZPD48S5oXsT7iTg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
|
@ -243,6 +245,8 @@ github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
|||
github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
|
50
api/main.go
50
api/main.go
|
@ -3,16 +3,18 @@ package main
|
|||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
v1 "github.com/aldy505/jokes-bapak2-api/api/app/v1"
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/platform/database"
|
||||
"github.com/aldy505/jokes-bapak2-api/api/app/v1/routes"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/etag"
|
||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -45,14 +47,54 @@ func main() {
|
|||
app.Use(limiter.New())
|
||||
app.Use(etag.New())
|
||||
|
||||
app.Mount("/", routes.New())
|
||||
app.Mount("/v1", v1.New())
|
||||
|
||||
log.Fatal(app.Listen(":" + os.Getenv("PORT")))
|
||||
// Start server (with or without graceful shutdown).
|
||||
if os.Getenv("ENV") == "development" {
|
||||
StartServer(app)
|
||||
} else {
|
||||
StartServerWithGracefulShutdown(app)
|
||||
}
|
||||
}
|
||||
|
||||
func errorHandler(c *fiber.Ctx, err error) error {
|
||||
sentry.CaptureException(err)
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Something went wrong on our end",
|
||||
})
|
||||
}
|
||||
|
||||
// StartServerWithGracefulShutdown function for starting server with a graceful shutdown.
|
||||
func StartServerWithGracefulShutdown(a *fiber.App) {
|
||||
// Create channel for idle connections.
|
||||
idleConnsClosed := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
sigint := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, os.Interrupt) // Catch OS signals.
|
||||
<-sigint
|
||||
|
||||
// Received an interrupt signal, shutdown.
|
||||
if err := a.Shutdown(); err != nil {
|
||||
// Error from closing listeners, or context timeout:
|
||||
log.Printf("Oops... Server is not shutting down! Reason: %v", err)
|
||||
}
|
||||
|
||||
close(idleConnsClosed)
|
||||
}()
|
||||
|
||||
// Run server.
|
||||
if err := a.Listen(":" + os.Getenv("PORT")); err != nil {
|
||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||
}
|
||||
|
||||
<-idleConnsClosed
|
||||
}
|
||||
|
||||
// StartServer func for starting a simple server.
|
||||
func StartServer(a *fiber.App) {
|
||||
// Run server.
|
||||
if err := a.Listen(":" + os.Getenv("PORT")); err != nil {
|
||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue