remove psql, use in-memory storage only (non thread-safe)
This commit is contained in:
parent
2fa1f83345
commit
a4b7d3050b
18
Dockerfile
18
Dockerfile
|
@ -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
7
go.mod
|
@ -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
4
go.sum
|
@ -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
27
main.go
|
@ -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()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete if expired
|
if row.token == mod.token {
|
||||||
if receiver.expires < time.Now().Unix() {
|
mod.token = row.token
|
||||||
receiver.db = mod.db
|
mod.role = row.role
|
||||||
receiver.Delete()
|
mod.expires = row.expires
|
||||||
return sql.ErrNoRows
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.token = receiver.token
|
return errors.New("not found")
|
||||||
mod.role = receiver.role
|
}
|
||||||
mod.expires = receiver.expires
|
|
||||||
|
// Create the model in storage
|
||||||
|
func (mod *Model) Create() error {
|
||||||
|
// fail if already exists
|
||||||
|
for _, row := range storage {
|
||||||
|
if row.token == mod.token {
|
||||||
|
return errors.New("already exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storage = append(storage, mod)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue