remove psql, use in-memory storage only (non thread-safe)

This commit is contained in:
Adrien Marquès 2020-03-03 20:02:15 +01:00
parent 2fa1f83345
commit a4b7d3050b
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
9 changed files with 123 additions and 187 deletions

View File

@ -2,14 +2,16 @@ FROM golang:alpine as builder
ENV GO111MODULE=on ENV GO111MODULE=on
RUN apk add git RUN apk add git
ADD . /pkg ADD . /app
WORKDIR /pkg WORKDIR /app
RUN go get -d ./... RUN go get -d ./...
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o exec RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o tiny-url
FROM redis:alpine FROM alpine:latest as production
COPY --from=builder /pkg/exec /usr/local/bin/docker-exec RUN mkdir /app
COPY --from=builder /pkg/api.json /usr/local/bin/api.json COPY --from=builder /app/tiny-url /app
RUN printf "docker-exec&" >> /usr/local/bin/docker-entrypoint.sh COPY --from=builder /app/api.json /app
WORKDIR /app
EXPOSE 4242/tcp EXPOSE 4242/tcp
# CMD ["/main"] CMD "/app/tiny-url"

7
go.mod
View File

@ -1,8 +1,5 @@
module git.xdrm.io/example/aicra module git.xdrm.io/go/tiny-url-ex
go 1.12 go 1.12
require ( require git.xdrm.io/go/aicra v0.2.0
git.xdrm.io/go/aicra v0.2.0
github.com/lib/pq v1.1.0
)

4
go.sum
View File

@ -1,4 +1,2 @@
git.xdrm.io/go/aicra v0.2.0 h1:qDGeHilx46sdQAWVFAn8buVtpLxSlMCBPGMY+yCkpNo= git.xdrm.io/go/aicra v0.2.0 h1:Hn5g4qFikAjC7l4uaWD41eiJhYLlAUsjnAx9H0r6WNQ=
git.xdrm.io/go/aicra v0.2.0/go.mod h1:ulAzCdKqUN5X4eWQSER70QXSYteSXtybAqupcUuYPdw= git.xdrm.io/go/aicra v0.2.0/go.mod h1:ulAzCdKqUN5X4eWQSER70QXSYteSXtybAqupcUuYPdw=
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=

27
main.go
View File

@ -4,10 +4,9 @@ import (
"log" "log"
"net/http" "net/http"
"git.xdrm.io/example/aicra/service/auth" "git.xdrm.io/go/tiny-url-ex/service/auth"
"git.xdrm.io/example/aicra/service/shortener" "git.xdrm.io/go/tiny-url-ex/service/shortener"
"git.xdrm.io/example/aicra/storage"
"git.xdrm.io/go/aicra" "git.xdrm.io/go/aicra"
"git.xdrm.io/go/aicra/typecheck/builtin" "git.xdrm.io/go/aicra/typecheck/builtin"
) )
@ -28,30 +27,22 @@ func main() {
server.Checkers.Add(builtin.NewString()) server.Checkers.Add(builtin.NewString())
server.Checkers.Add(builtin.NewFloat64()) server.Checkers.Add(builtin.NewFloat64())
// 3. storage connect // 3. init services
log.Printf("[storage] connecting") authService, err := auth.New()
db, err := storage.New()
if err != nil {
log.Fatalf("/!\\ cannot connect %v", err)
}
defer db.Close()
// 4. init services
authService, err := auth.New(db)
if err != nil { if err != nil {
log.Fatalf("cannot create auth service: %v", err) log.Fatalf("cannot create auth service: %v", err)
} }
shortenerService, err := shortener.New(db, authService) shortenerService, err := shortener.New(authService)
if err != nil { if err != nil {
log.Fatalf("cannot create auth service: %v", err) log.Fatalf("cannot create auth service: %v", err)
} }
// 5. wire services // 4. wire services
shortenerService.Wire(server) shortenerService.Wire(server)
authService.Wire(server) authService.Wire(server)
// 6. listen and serve // 5. listen and serve
log.Printf("[server] listening\n\n") log.Printf("[server] listening %s\n\n", listenTo)
log.Fatal(http.ListenAndServe(listenTo, server)) log.Fatal(http.ListenAndServe(listenTo, server.HTTP()))
} }

View File

@ -2,7 +2,6 @@ package auth
import ( import (
"crypto/sha512" "crypto/sha512"
"database/sql"
"encoding/hex" "encoding/hex"
"log" "log"
"net/http" "net/http"
@ -15,26 +14,14 @@ import (
) )
// Service manages the url shortener // Service manages the url shortener
type Service struct { type Service struct{}
storage *sql.DB
repo *repository
}
// New returns a bare service // New returns a bare service
func New(storage *sql.DB) (*Service, error) { func New() (*Service, error) {
log.Printf("[auth] creating service") log.Printf("[auth] creating service")
log.Printf("[auth] creating repo")
repo, err := newRepo(storage)
if err != nil {
return nil, err
}
log.Printf("[auth] repo created")
log.Printf("[auth] service created") log.Printf("[auth] service created")
return &Service{ return &Service{}, nil
repo: repo,
}, nil
} }
// Wire to the aicra server // Wire to the aicra server
@ -60,7 +47,7 @@ func (svc *Service) generateToken(req api.Request, res *api.Response) {
token := hex.EncodeToString(hasher.Sum(nil)) token := hex.EncodeToString(hasher.Sum(nil))
// 3. store token // 3. store token
model := svc.repo.NewModel(token, role, 5*time.Minute) model := NewModel(token, role, 5*time.Minute)
if model.Create() != nil { if model.Create() != nil {
res.SetError(api.ErrorFailure()) res.SetError(api.ErrorFailure())
return return
@ -91,7 +78,7 @@ func (svc *Service) CheckToken(handler api.HandlerFunc) api.HandlerFunc {
return return
} }
model := svc.repo.NewModel(headerToken, "", 0) model := NewModel(headerToken, "", 0)
if model.Search() != nil { if model.Search() != nil {
res.SetError(api.ErrorToken()) res.SetError(api.ErrorToken())
return return

View File

@ -1,70 +1,71 @@
package auth package auth
import ( import (
"database/sql" "errors"
"time" "time"
) )
// tokenModel represents an actual tiny url entry in the database. // Model represents an actual tiny url entry in the database.
type tokenModel struct { type Model struct {
db *sql.DB
token string token string
role string role string
expires int64 expires int64
} }
type repository struct { // storage contains entries
db *sql.DB var storage []*Model = make([]*Model, 0)
}
// newRepo returns an initialized repository. // NewModel builds a token model from arguments
func newRepo(db *sql.DB) (*repository, error) { func NewModel(token, role string, duration time.Duration) *Model {
_, err := db.Exec(`CREATE TABLE if not exists token( return &Model{
token varchar(128) PRIMARY KEY,
role varchar(300) NOT NULL,
expires INT NOT NULL
);`)
return &repository{db}, err
}
func (repo *repository) NewModel(token, role string, duration time.Duration) *tokenModel {
return &tokenModel{
db: repo.db,
token: token, token: token,
role: role, role: role,
expires: time.Now().Add(duration).Unix(), expires: time.Now().Add(duration).Unix(),
} }
} }
func (mod *tokenModel) Search() error { // Search for model in storage
row := mod.db.QueryRow(`SELECT token, role, expires FROM token WHERE token = $1 LIMIT 1;`, mod.token) func (mod *Model) Search() error {
for _, row := range storage {
receiver := &tokenModel{} // silently delete if expired
err := row.Scan(&receiver.token, &receiver.role, &receiver.expires) if row.expires < time.Now().Unix() {
if err != nil { row.Delete()
return err continue
}
if row.token == mod.token {
mod.token = row.token
mod.role = row.role
mod.expires = row.expires
return nil
}
} }
// delete if expired return errors.New("not found")
if receiver.expires < time.Now().Unix() { }
receiver.db = mod.db
receiver.Delete() // Create the model in storage
return sql.ErrNoRows func (mod *Model) Create() error {
// fail if already exists
for _, row := range storage {
if row.token == mod.token {
return errors.New("already exists")
}
} }
mod.token = receiver.token storage = append(storage, mod)
mod.role = receiver.role
mod.expires = receiver.expires
return nil return nil
} }
func (mod *tokenModel) Create() error { // Delete the model from storage
_, err := mod.db.Exec(`INSERT INTO token (token, role, expires) VALUES ($1, $2, $3);`, mod.token, mod.role, mod.expires) func (mod *Model) Delete() error {
return err for i, row := range storage {
} if row.token == mod.token {
storage = append(storage[:i], storage[i+1:]...)
return nil
}
}
func (mod *tokenModel) Delete() error { return errors.New("not found")
_, err := mod.db.Exec(`DELETE FROM token WHERE token = $1;`, mod.token)
return err
} }

View File

@ -1,64 +1,71 @@
package shortener package shortener
import ( import (
"database/sql" "errors"
) )
// tinyModel represents an actual tiny url entry in the database. // Model represents an actual tiny url entry in the database.
type tinyModel struct { type Model struct {
db *sql.DB
tiny string tiny string
long string long string
} }
type repository struct { // storage contains entries
db *sql.DB var storage []*Model = make([]*Model, 0)
}
// newRepo returns an initialized repository. // NewModel builds a tiny model from arguments
func newRepo(db *sql.DB) (*repository, error) { func NewModel(tiny string, long string) *Model {
_, err := db.Exec(`CREATE TABLE if not exists tiny( return &Model{
tiny varchar(30) PRIMARY KEY,
long varchar(300) NOT NULL
);`)
return &repository{db}, err
}
func (repo *repository) NewModel(tiny string, long string) *tinyModel {
return &tinyModel{
db: repo.db,
tiny: tiny, tiny: tiny,
long: long, long: long,
} }
} }
func (mod *tinyModel) Search() error { // Search for model in storage
row := mod.db.QueryRow(`SELECT tiny,long FROM tiny WHERE tiny = $1 LIMIT 1;`, mod.tiny) func (mod *Model) Search() error {
for _, row := range storage {
receiver := &tinyModel{} if row.tiny == mod.tiny {
err := row.Scan(&receiver.tiny, &receiver.long) mod.tiny = row.tiny
if err != nil { mod.long = row.long
return err return nil
}
} }
mod.tiny = receiver.tiny return errors.New("not found")
mod.long = receiver.long }
// Create the model in storage
func (mod *Model) Create() error {
// fail if already exists
if mod.Search() == nil {
return errors.New("already exists")
}
storage = append(storage, mod)
return nil return nil
} }
func (mod *tinyModel) Create() error { // Update the model in storage
_, err := mod.db.Exec(`INSERT INTO tiny(tiny,long) VALUES($1,$2);`, mod.tiny, mod.long) func (mod *Model) Update() error {
return err for _, row := range storage {
if row.tiny == mod.tiny {
row.tiny = mod.tiny
row.long = mod.long
return nil
}
}
return errors.New("not found")
} }
func (mod *tinyModel) Update() error { // Delete the model from storage
_, err := mod.db.Exec(`UPDATE tiny SET long = $1 WHERE tiny = $2;`, mod.long, mod.tiny) func (mod *Model) Delete() error {
return err for i, row := range storage {
} if row.tiny == mod.tiny && row.long == mod.long {
storage = append(storage[:i], storage[i+1:]...)
return nil
}
}
func (mod *tinyModel) Delete() error { return errors.New("not found")
_, err := mod.db.Exec(`DELETE FROM tiny WHERE tiny = $1;`, mod.tiny)
return err
} }

View File

@ -1,36 +1,25 @@
package shortener package shortener
import ( import (
"database/sql"
"log" "log"
"net/http" "net/http"
"git.xdrm.io/example/aicra/service/auth"
"git.xdrm.io/go/aicra" "git.xdrm.io/go/aicra"
"git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/tiny-url-ex/service/auth"
) )
// Service manages the url shortener // Service manages the url shortener
type Service struct { type Service struct {
storage *sql.DB
authService *auth.Service authService *auth.Service
repo *repository
} }
// New returns a bare service // New returns a bare service
func New(storage *sql.DB, auth *auth.Service) (*Service, error) { func New(auth *auth.Service) (*Service, error) {
log.Printf("[shortener] creating service") log.Printf("[shortener] creating service")
log.Printf("[shortener] creating repo")
repo, err := newRepo(storage)
if err != nil {
return nil, err
}
log.Printf("[shortener] repo created")
log.Printf("[shortener] service created") log.Printf("[shortener] service created")
return &Service{ return &Service{
repo: repo,
authService: auth, authService: auth,
}, nil }, nil
} }
@ -55,7 +44,7 @@ func (svc *Service) redirect(req api.Request, res *api.Response) {
} }
// 2. check in db if exists // 2. check in db if exists
model := svc.repo.NewModel(tinyURL, "") model := NewModel(tinyURL, "")
if model.Search() != nil { if model.Search() != nil {
res.SetError(api.ErrorNoMatchFound()) res.SetError(api.ErrorNoMatchFound())
return return
@ -84,7 +73,7 @@ func (svc *Service) register(req api.Request, res *api.Response) {
} }
// 2. fail if already used // 2. fail if already used
model := svc.repo.NewModel(tinyURL, "") model := NewModel(tinyURL, "")
if model.Search() == nil { if model.Search() == nil {
res.SetError(api.ErrorAlreadyExists(), "url") res.SetError(api.ErrorAlreadyExists(), "url")
return return
@ -117,7 +106,7 @@ func (svc *Service) update(req api.Request, res *api.Response) {
} }
// 2. fail if not already existing // 2. fail if not already existing
model := svc.repo.NewModel(tinyURL, "") model := NewModel(tinyURL, "")
if model.Search() != nil { if model.Search() != nil {
res.SetError(api.ErrorNoMatchFound()) res.SetError(api.ErrorNoMatchFound())
return return
@ -145,7 +134,7 @@ func (svc *Service) delete(req api.Request, res *api.Response) {
} }
// 2. fail if not already existing // 2. fail if not already existing
model := svc.repo.NewModel(tinyURL, "") model := NewModel(tinyURL, "")
if model.Search() != nil { if model.Search() != nil {
res.SetError(api.ErrorNoMatchFound()) res.SetError(api.ErrorNoMatchFound())
return return

View File

@ -1,36 +0,0 @@
package storage
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
const (
host = "localhost"
port = 5432
user = "postgres"
password = "@#postgres-passWoRD#@"
dbname = "aicratest"
)
var psqlInfo = fmt.Sprintf("host=%s port=%d user=%s password='%s' dbname=%s sslmode=disable", host, port, user, password, dbname)
// New returns new postgres connection.
func New() (*sql.DB, error) {
conn, err := sql.Open("postgres", psqlInfo)
if err != nil {
return nil, err
}
// defer conn.Close()
// actually connect
err = conn.Ping()
if err != nil {
return nil, err
}
return conn, nil
}