feat: nearly finished

This commit is contained in:
Reinaldy Rafli 2021-07-09 19:13:19 +07:00
parent 2e7e1c8749
commit 96931551aa
27 changed files with 490 additions and 136 deletions

6
api/.gitignore vendored
View File

@ -13,3 +13,9 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
vendor/ vendor/
# Environment variable
.env
# Heroku bin directory
bin

View File

@ -6,20 +6,52 @@ Still work in progress
```bash ```bash
# Install modules # Install modules
$ go install $ go mod download
# or # or
$ go mod vendor $ go mod vendor
# run the local server # run the local server
$ go run ./ $ go run main.go
# build everything # build everything
$ go build ./ $ go build main.go
``` ```
## Modules ## Used packages
- https://github.com/Masterminds/squirrel | Name | Version | Type |
- https://github.com/jackc/pgx | --- | --- | --- |
- https://github.com/go-redis/redis/v8 | gofiber/fiber | v2.14.0 | Framework |
- https://github.com/unrolled/secure | 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=
```

18
api/app/v1/app.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,7 @@
package handler
import "github.com/gofiber/fiber/v2"
func Health(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
}

View File

@ -8,7 +8,7 @@ import (
) )
func AddNewJoke(c *fiber.Ctx) error { func AddNewJoke(c *fiber.Ctx) error {
var body models.JokePost var body models.RequestJokePost
err := c.BodyParser(&body) err := c.BodyParser(&body)
if err != nil { if err != nil {
return err return err
@ -24,7 +24,7 @@ func AddNewJoke(c *fiber.Ctx) error {
return err return err
} }
return c.Status(fiber.StatusCreated).JSON(fiber.Map{ return c.Status(fiber.StatusCreated).JSON(models.ResponseJoke{
"link": body.Link, Link: body.Link,
}) })
} }

View File

@ -1 +1,43 @@
package handler 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",
})
}

View File

@ -2,7 +2,6 @@ package handler
import ( import (
"context" "context"
"strconv"
"time" "time"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
@ -38,20 +37,20 @@ func TodayJoke(c *fiber.Ctx) error {
if eq { if eq {
c.Attachment(joke.link) c.Attachment(joke.link)
return c.SendStatus(200) return c.SendStatus(fiber.StatusOK)
} else { } else {
var link string var link string
err := db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 WHERE random() < 0.01 LIMIT 1").Scan(&link) err := db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 WHERE random() < 0.01 LIMIT 1").Scan(&link)
if err != nil { if err != nil {
return err 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() err = redis.MSet(context.Background(), "today:link", link, "today:date", now).Err()
if err != nil { if err != nil {
return err return err
} }
c.Attachment(link) c.Attachment(link)
return c.SendStatus(200) return c.SendStatus(fiber.StatusOK)
} }
} }
@ -66,7 +65,7 @@ func SingleJoke(c *fiber.Ctx) error {
return err return err
} }
c.Attachment(link) c.Attachment(link)
return c.SendStatus(200) return c.SendStatus(fiber.StatusOK)
} }
func JokeByID(c *fiber.Ctx) error { func JokeByID(c *fiber.Ctx) error {
@ -79,8 +78,8 @@ func JokeByID(c *fiber.Ctx) error {
return err return err
} }
if link == "" { 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) c.Attachment(link)
return c.SendStatus(200) return c.SendStatus(fiber.StatusOK)
} }

View File

@ -1 +1,51 @@
package handler 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",
})
}

View File

@ -15,7 +15,7 @@ var db = database.New()
func RequireAuth() fiber.Handler { func RequireAuth() fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
var auth models.Auth var auth models.RequestAuth
err := c.BodyParser(&auth) err := c.BodyParser(&auth)
if err != nil { if err != nil {
return err return err
@ -33,8 +33,8 @@ func RequireAuth() fiber.Handler {
} }
if token == "" { if token == "" {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ return c.Status(fiber.StatusForbidden).JSON(models.ResponseError{
"error": "Invalid token", Error: "Invalid token",
}) })
} }
@ -61,8 +61,9 @@ func RequireAuth() fiber.Handler {
if verify { if verify {
c.Next() c.Next()
} }
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "Invalid key", return c.Status(fiber.StatusForbidden).JSON(models.ResponseError{
Error: "Invalid key",
}) })
} }
} }

View File

@ -1,6 +0,0 @@
package models
type Auth struct {
Key string `json:"key"`
Token string `json:"token"`
}

View File

@ -1,6 +0,0 @@
package models
type JokePost struct {
Key string `json:"string"`
Link string `json:"link"`
}

View File

@ -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"`
}

View File

@ -0,0 +1,10 @@
package models
type ResponseError struct {
Error string `json:"error"`
}
type ResponseJoke struct {
Link string `json:"link"`
Message string `json:"message"`
}

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
_ "github.com/joho/godotenv/autoload"
) )
// Connect to the database // Connect to the database

View File

@ -5,105 +5,55 @@ import (
"log" "log"
"strings" "strings"
sq "github.com/Masterminds/squirrel"
"github.com/aldy505/bob" "github.com/aldy505/bob"
) )
// Set up the table connection, create table if not exists // Set up the table connection, create table if not exists
func Setup() error { func Setup() error {
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
db := New() db := New()
// Jokesbapak2 table & data // Jokesbapak2 table & data
// Check if table exists sql, _, err := bob.CreateTableIfNotExists("jokesbapak2").
sql, args, err := bob.HasTable("jokesbapak2").PlaceholderFormat(bob.Dollar).ToSQL() Columns("id", "link", "key").
Types("SERIAL", "TEXT", "VARCHAR(255)").
Primary("id").ToSql()
if err != nil { if err != nil {
log.Fatalln("10 - failed on table creation: ", err)
log.Fatalln("failed on checking database table:", 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()
if err != nil {
log.Fatalln("failed on table creation:", err)
} }
splitSql := strings.Split(sql, ";") splitSql := strings.Split(sql, ";")
for i := range splitSql { for i := range splitSql {
_, err = db.Query(context.Background(), splitSql[i]) _, err = db.Query(context.Background(), splitSql[i])
if err != nil { if err != nil {
log.Println(sql) log.Fatalln("11 - failed on table creation: ", err)
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)
return err return err
} }
} }
// Authorization // Authorization
// Check if table exists sql, _, err = bob.CreateTableIfNotExists("authorization").
sql, args, err = bob.HasTable("authorization").PlaceholderFormat(bob.Dollar).ToSQL()
if err != nil {
log.Fatalln("failed on checking database table:", 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"). Columns("id", "token", "key").
Types("SERIAL", "VARCHAR(255)", "VARCHAR(255)"). Types("SERIAL", "TEXT", "VARCHAR(255)").
Primary("id"). Primary("id").
Unique("token"). Unique("token").
ToSQL() ToSql()
if err != nil { if err != nil {
log.Fatalln("Failed on table creation: ", err) log.Fatalln("14 - failed on table creation: ", err)
return err return err
} }
splitSql := strings.Split(sql, ";") splitSql = strings.Split(sql, ";")
for i := range splitSql { for i := range splitSql {
_, err = db.Query(context.Background(), splitSql[i]) _, err = db.Query(context.Background(), splitSql[i])
if err != nil { if err != nil {
log.Fatalln("Failed on table creation: ", err) log.Fatalln("15 - failed on table creation: ", err)
return 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 return nil
} }

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
_ "github.com/joho/godotenv/autoload"
) )
// Connect to the database // Connect to the database

View File

@ -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
}

View File

@ -12,31 +12,24 @@ import (
var db = database.New() var db = database.New()
var redis = cache.New() var redis = cache.New()
func New() *fiber.App { func Joke(app *fiber.App) *fiber.App {
app := fiber.New(fiber.Config{
ETag: true,
DisableKeepalive: true,
CaseSensitive: true,
})
v1 := app.Group("/v1")
// Single route // Single route
v1.Get("/", handler.SingleJoke) app.Get("/", handler.SingleJoke)
// Today's joke // Today's joke
v1.Get("/today", handler.TodayJoke) app.Get("/today", handler.TodayJoke)
// Joke by ID // Joke by ID
v1.Get("/:id", handler.JokeByID) app.Get("/:id", handler.JokeByID)
// Add new joke // Add new joke
v1.Put("/", middleware.RequireAuth(), handler.AddNewJoke) app.Put("/", middleware.RequireAuth(), handler.AddNewJoke)
// Update a joke // Update a joke
v1.Patch("/:id") app.Patch("/:id", middleware.RequireAuth(), handler.UpdateJoke)
// Delete a joke // Delete a joke
v1.Delete("/:id") app.Delete("/:id", middleware.RequireAuth(), handler.DeleteJoke)
return app return app
} }

View File

@ -4,15 +4,13 @@ import "time"
// IsToday checks if a date is in fact today or not. // IsToday checks if a date is in fact today or not.
func IsToday(date string) (bool, error) { func IsToday(date string) (bool, error) {
parse, err := time.Parse(time.UnixDate, date) parse, err := time.Parse(time.RFC3339, date)
if err != nil { if err != nil {
return false, err return false, err
} }
now := time.Now() y1, m1, d1 := parse.Date()
y2, m2, d2 := time.Now().Date()
if parse.Equal(now) { return y1 == y2 && m1 == m2 && d1 == d2, nil
return true, nil
}
return false, nil
} }

View File

@ -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)
}
})
}

33
api/app/v1/utils/parse.go Normal file
View File

@ -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
}

View File

@ -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))
}
})
}

View File

@ -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
}

View File

@ -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)
}
})
}

View File

@ -4,10 +4,11 @@ go 1.16
require ( require (
github.com/Masterminds/squirrel v1.5.0 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/aldy505/phc-crypto v1.1.0
github.com/getsentry/sentry-go v0.11.0 github.com/getsentry/sentry-go v0.11.0
github.com/go-redis/redis/v8 v8.11.0 github.com/go-redis/redis/v8 v8.11.0
github.com/gofiber/fiber/v2 v2.14.0 github.com/gofiber/fiber/v2 v2.14.0
github.com/jackc/pgx/v4 v4.11.0 github.com/jackc/pgx/v4 v4.11.0
github.com/joho/godotenv v1.3.0
) )

View File

@ -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/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 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.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 h1:BagRKCrB7FOYy5vnuXR6xs6ml2gJD/CvSJkX/Ozo63w=
github.com/aldy505/phc-crypto v1.1.0/go.mod h1:LJugClOkOWKnpLrWhSaIDRN/5ftvZPD48S5oXsT7iTg= 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= 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 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 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/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/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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=

View File

@ -3,16 +3,18 @@ package main
import ( import (
"log" "log"
"os" "os"
"os/signal"
"time" "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/platform/database"
"github.com/aldy505/jokes-bapak2-api/api/app/v1/routes"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/etag"
"github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/fiber/v2/middleware/limiter"
_ "github.com/joho/godotenv/autoload"
) )
func main() { func main() {
@ -45,14 +47,54 @@ func main() {
app.Use(limiter.New()) app.Use(limiter.New())
app.Use(etag.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 { func errorHandler(c *fiber.Ctx, err error) error {
sentry.CaptureException(err) 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", "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)
}
}