basic setup for backend

This commit is contained in:
overflowerror 2021-08-14 12:08:36 +02:00
parent ed683945ae
commit a7153e44c6
16 changed files with 319 additions and 0 deletions

View file

@ -0,0 +1,51 @@
package main
import (
"flag"
"fmt"
"threadule/backend/internal/app"
"threadule/backend/internal/config"
"threadule/backend/internal/data"
"threadule/backend/internal/logic"
router "threadule/backend/internal/router"
"threadule/backend/internal/web"
)
var configFile = flag.String("c", "config.toml", "path to config file (will be autogenerated if it doesn't yet exist)")
func main() {
flag.Parse()
cfg, err := config.Read(*configFile)
if err != nil {
fmt.Println("reading config file failed")
fmt.Println(err)
return
}
ctx := &app.Context{
Config: cfg,
}
ctx.Logic, err = logic.Setup(ctx)
if err != nil {
fmt.Println("couldn't setup logic layer")
fmt.Println(err)
return
}
ctx.Data, err = data.Setup(ctx)
if err != nil {
fmt.Println("couldn't setup persistence layer")
fmt.Println(err)
return
}
handler := router.Setup(ctx)
err = web.StartServer(ctx, handler)
if err != nil {
fmt.Println("couldn't start web server")
fmt.Println(err)
return
}
}

10
backend/go.mod Normal file
View file

@ -0,0 +1,10 @@
module threadule/backend
go 1.16
require (
github.com/julienschmidt/httprouter v1.3.0
github.com/pelletier/go-toml/v2 v2.0.0-beta.3
gorm.io/driver/mysql v1.1.2
gorm.io/gorm v1.21.13
)

25
backend/go.sum Normal file
View file

@ -0,0 +1,25 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/pelletier/go-toml/v2 v2.0.0-beta.3 h1:PNCTU4naEJ8mKal97P3A2qDU74QRQGlv4FXiL1XDqi4=
github.com/pelletier/go-toml/v2 v2.0.0-beta.3/go.mod h1:aNseLYu/uKskg0zpr/kbr2z8yGuWtotWf/0BpGIAL2Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 h1:t0lM6y/M5IiUZyvbBTcngso8SZEZICH7is9B6g/obVU=
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.13 h1:JU5A4yVemRjdMndJ0oZU7VX+Nr2ICE3C60U5bgR6mHE=
gorm.io/gorm v1.21.13/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=

View file

@ -0,0 +1,9 @@
package app
import "threadule/backend/internal/config"
type Context struct {
Config *config.Config
Logic Logic
Data Data
}

View file

@ -0,0 +1,4 @@
package app
type Data interface {
}

View file

@ -0,0 +1,4 @@
package app
type Logic interface {
}

View file

@ -0,0 +1,44 @@
package config
import (
"fmt"
"github.com/pelletier/go-toml/v2"
"io/fs"
"os"
)
type database struct {
DSN string
}
type Config struct {
Port uint
Database database
}
func writeDefaultConfig(path string) error {
content, err := toml.Marshal(&Config{
Port: 8080,
Database: database{DSN: ""},
})
if err != nil {
return fmt.Errorf("couldn't generate default config: %w", err)
}
return os.WriteFile(path, content, fs.FileMode(0640))
}
func Read(path string) (*Config, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, writeDefaultConfig(path)
}
var config Config
err = toml.Unmarshal(content, &config)
if err != nil {
return nil, err
}
return &config, nil
}

View file

@ -0,0 +1,31 @@
package data
import (
"fmt"
"gorm.io/gorm"
"threadule/backend/internal/app"
)
type Data struct {
ctx *app.Context
db *gorm.DB
}
var _ app.Data = &Data{}
func Setup(ctx *app.Context) (app.Data, error) {
db, err := connect(ctx.Config.Database.DSN)
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
err = migrate(db)
if err != nil {
return nil, fmt.Errorf("failed to migrate models: %w", err)
}
return &Data{
ctx: ctx,
db: db,
}, nil
}

View file

@ -0,0 +1,31 @@
package data
import (
"errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"strings"
)
func connect(dsn string) (*gorm.DB, error) {
return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
func migrate(db *gorm.DB) error {
var errs []error
//errs = append(errs, db.AutoMigrate())
errorBuilder := strings.Builder{}
for _, err := range errs {
if err != nil {
errorBuilder.WriteString(err.Error())
errorBuilder.WriteString("\n")
}
}
if errorBuilder.Len() == 0 {
return nil
} else {
return errors.New(strings.TrimSpace(errorBuilder.String()))
}
}

View file

@ -0,0 +1,15 @@
package logic
import "threadule/backend/internal/app"
type Logic struct {
ctx *app.Context
}
var _ app.Logic = &Logic{}
func Setup(ctx *app.Context) (app.Logic, error) {
return &Logic{
ctx: ctx,
}, nil
}

View file

@ -0,0 +1,7 @@
package presentation
import "threadule/backend/internal/web"
func Test(ctx *web.Context) {
}

View file

@ -0,0 +1,44 @@
package router
import (
"github.com/julienschmidt/httprouter"
"net/http"
"threadule/backend/internal/app"
"threadule/backend/internal/web"
)
func ctxWrapper(appCtx *app.Context, handler web.Handler) httprouter.Handle {
return func(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
handler(&web.Context{
Response: writer,
Request: request,
Params: params,
AppCtx: appCtx,
})
}
}
type router struct {
*httprouter.Router
appCtx *app.Context
}
func (r *router) GET(path string, handler web.Handler) {
r.Router.GET(path, ctxWrapper(r.appCtx, handler))
}
func (r *router) POST(path string, handler web.Handler) {
r.Router.POST(path, ctxWrapper(r.appCtx, handler))
}
func (r *router) OPTIONS(path string, handler web.Handler) {
r.Router.OPTIONS(path, ctxWrapper(r.appCtx, handler))
}
func (r *router) HEAD(path string, handler web.Handler) {
r.Router.HEAD(path, ctxWrapper(r.appCtx, handler))
}
func (r *router) DELETE(path string, handler web.Handler) {
r.Router.DELETE(path, ctxWrapper(r.appCtx, handler))
}

View file

@ -0,0 +1,16 @@
package router
import (
"github.com/julienschmidt/httprouter"
"net/http"
"threadule/backend/internal/app"
"threadule/backend/internal/presentation"
)
func Setup(ctx *app.Context) http.Handler {
router := &router{Router: httprouter.New(), appCtx: ctx}
router.GET("/", presentation.Test)
return router
}

View file

@ -0,0 +1,14 @@
package web
import (
"github.com/julienschmidt/httprouter"
"net/http"
"threadule/backend/internal/app"
)
type Context struct {
Response http.ResponseWriter
Request *http.Request
Params httprouter.Params
AppCtx *app.Context
}

View file

@ -0,0 +1,3 @@
package web
type Handler func(ctx *Context)

View file

@ -0,0 +1,11 @@
package web
import (
"fmt"
"net/http"
"threadule/backend/internal/app"
)
func StartServer(ctx *app.Context, handler http.Handler) error {
return http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", ctx.Config.Port), handler)
}