mirror of
https://github.com/sigmasternchen/threadule
synced 2025-03-15 08:09:01 +00:00
basic setup for backend
This commit is contained in:
parent
ed683945ae
commit
a7153e44c6
16 changed files with 319 additions and 0 deletions
51
backend/cmd/threadule/main.go
Normal file
51
backend/cmd/threadule/main.go
Normal 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
10
backend/go.mod
Normal 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
25
backend/go.sum
Normal 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=
|
9
backend/internal/app/context.go
Normal file
9
backend/internal/app/context.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package app
|
||||
|
||||
import "threadule/backend/internal/config"
|
||||
|
||||
type Context struct {
|
||||
Config *config.Config
|
||||
Logic Logic
|
||||
Data Data
|
||||
}
|
4
backend/internal/app/data.go
Normal file
4
backend/internal/app/data.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package app
|
||||
|
||||
type Data interface {
|
||||
}
|
4
backend/internal/app/logic.go
Normal file
4
backend/internal/app/logic.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package app
|
||||
|
||||
type Logic interface {
|
||||
}
|
44
backend/internal/config/config.go
Normal file
44
backend/internal/config/config.go
Normal 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
|
||||
}
|
31
backend/internal/data/data.go
Normal file
31
backend/internal/data/data.go
Normal 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
|
||||
}
|
31
backend/internal/data/gorm.go
Normal file
31
backend/internal/data/gorm.go
Normal 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()))
|
||||
}
|
||||
}
|
15
backend/internal/logic/logic.go
Normal file
15
backend/internal/logic/logic.go
Normal 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
|
||||
}
|
7
backend/internal/presentation/test.go
Normal file
7
backend/internal/presentation/test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package presentation
|
||||
|
||||
import "threadule/backend/internal/web"
|
||||
|
||||
func Test(ctx *web.Context) {
|
||||
|
||||
}
|
44
backend/internal/router/router.go
Normal file
44
backend/internal/router/router.go
Normal 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))
|
||||
}
|
16
backend/internal/router/routes.go
Normal file
16
backend/internal/router/routes.go
Normal 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
|
||||
}
|
14
backend/internal/web/context.go
Normal file
14
backend/internal/web/context.go
Normal 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
|
||||
}
|
3
backend/internal/web/handler.go
Normal file
3
backend/internal/web/handler.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package web
|
||||
|
||||
type Handler func(ctx *Context)
|
11
backend/internal/web/server.go
Normal file
11
backend/internal/web/server.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue