Skip to content

Commit ef39112

Browse files
authored
Merge pull request #78 from fabiante/feat/gorm-rewrite
2 parents af28e3f + bc200cb commit ef39112

File tree

12 files changed

+256
-179
lines changed

12 files changed

+256
-179
lines changed

api/server.go

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,26 @@ package api
22

33
import (
44
"errors"
5-
"fmt"
65
"net/http"
76

8-
"github.com/fabiante/persurl/api/res"
97
"github.com/fabiante/persurl/app"
108
"github.com/gin-gonic/gin"
119
)
1210

1311
type Server struct {
14-
service app.ServiceInterface
12+
resolver app.ResolveServiceInterface
13+
admin app.AdminServiceInterface
1514
}
1615

17-
func NewServer(service app.ServiceInterface) *Server {
18-
return &Server{service: service}
16+
func NewServer(resolver app.ResolveServiceInterface, admin app.AdminServiceInterface) *Server {
17+
return &Server{resolver: resolver, admin: admin}
1918
}
2019

2120
func (s *Server) Resolve(ctx *gin.Context) {
2221
domain := ctx.Param("domain")
2322
name := ctx.Param("name")
2423

25-
target, err := s.service.Resolve(domain, name)
24+
target, err := s.resolver.Resolve(domain, name)
2625
switch true {
2726
case err == nil:
2827
ctx.Redirect(http.StatusFound, target)
@@ -34,40 +33,3 @@ func (s *Server) Resolve(ctx *gin.Context) {
3433
respondWithError(ctx, http.StatusInternalServerError, err)
3534
}
3635
}
37-
38-
func (s *Server) SavePURL(ctx *gin.Context) {
39-
domain := ctx.Param("domain")
40-
name := ctx.Param("name")
41-
42-
var req res.SavePURL
43-
if err := ctx.BindJSON(&req); err != nil {
44-
ctx.Abort()
45-
return
46-
}
47-
48-
err := s.service.SavePURL(domain, name, req.Target)
49-
switch true {
50-
case err == nil:
51-
break
52-
case errors.Is(err, app.ErrBadRequest):
53-
respondWithError(ctx, http.StatusBadRequest, err)
54-
default:
55-
respondWithError(ctx, http.StatusInternalServerError, err)
56-
}
57-
58-
ctx.JSON(http.StatusOK, res.NewSavePURLResponse(fmt.Sprintf("/r/%s/%s", domain, name)))
59-
}
60-
61-
func (s *Server) CreateDomain(ctx *gin.Context) {
62-
domain := ctx.Param("domain")
63-
64-
err := s.service.CreateDomain(domain)
65-
switch true {
66-
case err == nil:
67-
ctx.Status(http.StatusNoContent)
68-
case errors.Is(err, app.ErrBadRequest):
69-
respondWithError(ctx, http.StatusBadRequest, err)
70-
default:
71-
respondWithError(ctx, http.StatusInternalServerError, err)
72-
}
73-
}

api/server_admin.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package api
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/fabiante/persurl/api/res"
9+
"github.com/fabiante/persurl/app"
10+
"github.com/gin-gonic/gin"
11+
)
12+
13+
func (s *Server) SavePURL(ctx *gin.Context) {
14+
domain := ctx.Param("domain")
15+
name := ctx.Param("name")
16+
17+
var req res.SavePURL
18+
if err := ctx.BindJSON(&req); err != nil {
19+
ctx.Abort()
20+
return
21+
}
22+
23+
err := s.admin.SavePURL(domain, name, req.Target)
24+
switch true {
25+
case err == nil:
26+
break
27+
case errors.Is(err, app.ErrBadRequest):
28+
respondWithError(ctx, http.StatusBadRequest, err)
29+
default:
30+
respondWithError(ctx, http.StatusInternalServerError, err)
31+
}
32+
33+
ctx.JSON(http.StatusOK, res.NewSavePURLResponse(fmt.Sprintf("/r/%s/%s", domain, name)))
34+
}
35+
36+
func (s *Server) CreateDomain(ctx *gin.Context) {
37+
domain := ctx.Param("domain")
38+
39+
err := s.admin.CreateDomain(domain)
40+
switch true {
41+
case err == nil:
42+
ctx.Status(http.StatusNoContent)
43+
case errors.Is(err, app.ErrBadRequest):
44+
respondWithError(ctx, http.StatusBadRequest, err)
45+
default:
46+
respondWithError(ctx, http.StatusInternalServerError, err)
47+
}
48+
}

app/models/purl.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package models
2+
3+
import "gorm.io/gorm"
4+
5+
type Domain struct {
6+
gorm.Model
7+
8+
Name string
9+
10+
PURLs []*PURL `gorm:"foreignKey:DomainID"`
11+
}
12+
13+
type PURL struct {
14+
gorm.Model
15+
16+
DomainID uint
17+
18+
Name string
19+
Target string
20+
}
21+
22+
func (P PURL) TableName() string {
23+
return "purls"
24+
}

app/service.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package app
22

33
type ServiceInterface interface {
4+
AdminServiceInterface
5+
ResolveServiceInterface
6+
}
7+
8+
type ResolveServiceInterface interface {
49
// Resolve tries to resolve a PURL based on the domain and purl name.
510
//
611
// ErrNotFound is returned if nothing was found.
712
Resolve(domain, name string) (string, error)
13+
}
814

15+
type AdminServiceInterface interface {
916
// SavePURL saves a PURL for the given domain name.
1017
//
1118
// ErrBadRequest is returned if any parameter is invalid or the domain

app/service_impl.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package app
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/fabiante/persurl/app/models"
8+
"github.com/lib/pq"
9+
"gorm.io/gorm"
10+
)
11+
12+
type service struct {
13+
db *gorm.DB
14+
}
15+
16+
func NewService(db *gorm.DB) ServiceInterface {
17+
return &service{db: db}
18+
}
19+
20+
func (s *service) Resolve(domain, name string) (string, error) {
21+
purl := &models.PURL{}
22+
23+
err := s.db.Model(&models.PURL{}).
24+
Joins("join domains on domains.id = purls.domain_id").
25+
Where("domains.name = ?", domain).
26+
Where("purls.name = ?", name).
27+
Take(purl).Error
28+
29+
switch {
30+
case err == nil:
31+
return purl.Target, nil
32+
case errors.Is(err, gorm.ErrRecordNotFound):
33+
return "", ErrNotFound
34+
default:
35+
return "", mapDBError(err)
36+
}
37+
}
38+
39+
func (s *service) CreateDomain(name string) error {
40+
domain := &models.Domain{
41+
Name: name,
42+
}
43+
44+
err := s.db.Create(domain).Error
45+
46+
if err != nil {
47+
return mapDBError(err)
48+
}
49+
50+
return nil
51+
}
52+
53+
func (s *service) SavePURL(domainName, name, target string) error {
54+
domain := &models.Domain{}
55+
56+
// get domain
57+
{
58+
err := s.db.Where(&models.Domain{Name: domainName}).Take(domain).Error
59+
if err != nil {
60+
switch {
61+
case err == nil:
62+
break
63+
case errors.Is(err, gorm.ErrRecordNotFound):
64+
return fmt.Errorf("%w: domain does not exist", ErrBadRequest)
65+
default:
66+
return mapDBError(err)
67+
}
68+
}
69+
}
70+
71+
// save purl
72+
{
73+
purl := &models.PURL{
74+
DomainID: domain.ID,
75+
Name: name,
76+
Target: target,
77+
}
78+
79+
err := s.db.FirstOrCreate(purl).Error
80+
81+
if err != nil {
82+
return mapDBError(err)
83+
}
84+
}
85+
86+
return nil
87+
}
88+
89+
const (
90+
pgErrUniqueKeyViolation = "23505"
91+
)
92+
93+
func mapDBError(err error) error {
94+
var serr *pq.Error
95+
if !errors.As(err, &serr) {
96+
return err
97+
}
98+
99+
// Error codes
100+
// SQLite: https://www.sqlite.org/rescode.html
101+
// Postgres: http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
102+
103+
code := serr.Code
104+
switch code {
105+
case pgErrUniqueKeyViolation:
106+
return fmt.Errorf("%w: %s", ErrBadRequest, err)
107+
default:
108+
return fmt.Errorf("unexpected error: %w", err)
109+
}
110+
}

cli/cmds/run.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"log"
55

66
"github.com/fabiante/persurl/api"
7+
"github.com/fabiante/persurl/app"
78
"github.com/fabiante/persurl/config"
89
"github.com/fabiante/persurl/db"
910
"github.com/gin-gonic/gin"
1011
"github.com/spf13/cobra"
12+
"gorm.io/driver/postgres"
13+
"gorm.io/gorm"
1114
)
1215

1316
func init() {
@@ -22,9 +25,16 @@ func init() {
2225
log.Fatalf("setting up database failed: %s", err)
2326
}
2427

28+
gormDB, err := gorm.Open(postgres.New(postgres.Config{
29+
Conn: database,
30+
}))
31+
if err != nil {
32+
log.Fatalf("setting up gorm database failed: %s", err)
33+
}
34+
2535
engine := gin.Default()
26-
service := db.NewDatabase(database)
27-
server := api.NewServer(service)
36+
service := app.NewService(gormDB)
37+
server := api.NewServer(service, service)
2838
api.SetupRouting(engine, server)
2939
if err := engine.Run(":8060"); err != nil {
3040
log.Fatalf("running api failed: %s", err)

0 commit comments

Comments
 (0)