Skip to content

Commit

Permalink
Merge pull request #78 from fabiante/feat/gorm-rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiante authored Sep 22, 2023
2 parents af28e3f + bc200cb commit ef39112
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 179 deletions.
48 changes: 5 additions & 43 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ package api

import (
"errors"
"fmt"
"net/http"

"github.com/fabiante/persurl/api/res"
"github.com/fabiante/persurl/app"
"github.com/gin-gonic/gin"
)

type Server struct {
service app.ServiceInterface
resolver app.ResolveServiceInterface
admin app.AdminServiceInterface
}

func NewServer(service app.ServiceInterface) *Server {
return &Server{service: service}
func NewServer(resolver app.ResolveServiceInterface, admin app.AdminServiceInterface) *Server {
return &Server{resolver: resolver, admin: admin}
}

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

target, err := s.service.Resolve(domain, name)
target, err := s.resolver.Resolve(domain, name)
switch true {
case err == nil:
ctx.Redirect(http.StatusFound, target)
Expand All @@ -34,40 +33,3 @@ func (s *Server) Resolve(ctx *gin.Context) {
respondWithError(ctx, http.StatusInternalServerError, err)
}
}

func (s *Server) SavePURL(ctx *gin.Context) {
domain := ctx.Param("domain")
name := ctx.Param("name")

var req res.SavePURL
if err := ctx.BindJSON(&req); err != nil {
ctx.Abort()
return
}

err := s.service.SavePURL(domain, name, req.Target)
switch true {
case err == nil:
break
case errors.Is(err, app.ErrBadRequest):
respondWithError(ctx, http.StatusBadRequest, err)
default:
respondWithError(ctx, http.StatusInternalServerError, err)
}

ctx.JSON(http.StatusOK, res.NewSavePURLResponse(fmt.Sprintf("/r/%s/%s", domain, name)))
}

func (s *Server) CreateDomain(ctx *gin.Context) {
domain := ctx.Param("domain")

err := s.service.CreateDomain(domain)
switch true {
case err == nil:
ctx.Status(http.StatusNoContent)
case errors.Is(err, app.ErrBadRequest):
respondWithError(ctx, http.StatusBadRequest, err)
default:
respondWithError(ctx, http.StatusInternalServerError, err)
}
}
48 changes: 48 additions & 0 deletions api/server_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package api

import (
"errors"
"fmt"
"net/http"

"github.com/fabiante/persurl/api/res"
"github.com/fabiante/persurl/app"
"github.com/gin-gonic/gin"
)

func (s *Server) SavePURL(ctx *gin.Context) {
domain := ctx.Param("domain")
name := ctx.Param("name")

var req res.SavePURL
if err := ctx.BindJSON(&req); err != nil {
ctx.Abort()
return
}

err := s.admin.SavePURL(domain, name, req.Target)
switch true {
case err == nil:
break
case errors.Is(err, app.ErrBadRequest):
respondWithError(ctx, http.StatusBadRequest, err)
default:
respondWithError(ctx, http.StatusInternalServerError, err)
}

ctx.JSON(http.StatusOK, res.NewSavePURLResponse(fmt.Sprintf("/r/%s/%s", domain, name)))
}

func (s *Server) CreateDomain(ctx *gin.Context) {
domain := ctx.Param("domain")

err := s.admin.CreateDomain(domain)
switch true {
case err == nil:
ctx.Status(http.StatusNoContent)
case errors.Is(err, app.ErrBadRequest):
respondWithError(ctx, http.StatusBadRequest, err)
default:
respondWithError(ctx, http.StatusInternalServerError, err)
}
}
24 changes: 24 additions & 0 deletions app/models/purl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package models

import "gorm.io/gorm"

type Domain struct {
gorm.Model

Name string

PURLs []*PURL `gorm:"foreignKey:DomainID"`
}

type PURL struct {
gorm.Model

DomainID uint

Name string
Target string
}

func (P PURL) TableName() string {
return "purls"
}
7 changes: 7 additions & 0 deletions app/service.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package app

type ServiceInterface interface {
AdminServiceInterface
ResolveServiceInterface
}

type ResolveServiceInterface interface {
// Resolve tries to resolve a PURL based on the domain and purl name.
//
// ErrNotFound is returned if nothing was found.
Resolve(domain, name string) (string, error)
}

type AdminServiceInterface interface {
// SavePURL saves a PURL for the given domain name.
//
// ErrBadRequest is returned if any parameter is invalid or the domain
Expand Down
110 changes: 110 additions & 0 deletions app/service_impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package app

import (
"errors"
"fmt"

"github.com/fabiante/persurl/app/models"
"github.com/lib/pq"
"gorm.io/gorm"
)

type service struct {
db *gorm.DB
}

func NewService(db *gorm.DB) ServiceInterface {
return &service{db: db}
}

func (s *service) Resolve(domain, name string) (string, error) {
purl := &models.PURL{}

err := s.db.Model(&models.PURL{}).
Joins("join domains on domains.id = purls.domain_id").
Where("domains.name = ?", domain).
Where("purls.name = ?", name).
Take(purl).Error

switch {
case err == nil:
return purl.Target, nil
case errors.Is(err, gorm.ErrRecordNotFound):
return "", ErrNotFound
default:
return "", mapDBError(err)
}
}

func (s *service) CreateDomain(name string) error {
domain := &models.Domain{
Name: name,
}

err := s.db.Create(domain).Error

if err != nil {
return mapDBError(err)
}

return nil
}

func (s *service) SavePURL(domainName, name, target string) error {
domain := &models.Domain{}

// get domain
{
err := s.db.Where(&models.Domain{Name: domainName}).Take(domain).Error
if err != nil {
switch {
case err == nil:
break
case errors.Is(err, gorm.ErrRecordNotFound):
return fmt.Errorf("%w: domain does not exist", ErrBadRequest)
default:
return mapDBError(err)
}
}
}

// save purl
{
purl := &models.PURL{
DomainID: domain.ID,
Name: name,
Target: target,
}

err := s.db.FirstOrCreate(purl).Error

if err != nil {
return mapDBError(err)
}
}

return nil
}

const (
pgErrUniqueKeyViolation = "23505"
)

func mapDBError(err error) error {
var serr *pq.Error
if !errors.As(err, &serr) {
return err
}

// Error codes
// SQLite: https://www.sqlite.org/rescode.html
// Postgres: http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html

code := serr.Code
switch code {
case pgErrUniqueKeyViolation:
return fmt.Errorf("%w: %s", ErrBadRequest, err)
default:
return fmt.Errorf("unexpected error: %w", err)
}
}
14 changes: 12 additions & 2 deletions cli/cmds/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"log"

"github.com/fabiante/persurl/api"
"github.com/fabiante/persurl/app"
"github.com/fabiante/persurl/config"
"github.com/fabiante/persurl/db"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

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

gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: database,
}))
if err != nil {
log.Fatalf("setting up gorm database failed: %s", err)
}

engine := gin.Default()
service := db.NewDatabase(database)
server := api.NewServer(service)
service := app.NewService(gormDB)
server := api.NewServer(service, service)
api.SetupRouting(engine, server)
if err := engine.Run(":8060"); err != nil {
log.Fatalf("running api failed: %s", err)
Expand Down
Loading

0 comments on commit ef39112

Please sign in to comment.