diff --git a/Dockerfile b/Dockerfile index 95e5088..cebfefd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,14 +2,16 @@ FROM golang:alpine as builder ENV GO111MODULE=on RUN apk add git -ADD . /pkg -WORKDIR /pkg +ADD . /app +WORKDIR /app 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 -COPY --from=builder /pkg/exec /usr/local/bin/docker-exec -COPY --from=builder /pkg/api.json /usr/local/bin/api.json -RUN printf "docker-exec&" >> /usr/local/bin/docker-entrypoint.sh +FROM alpine:latest as production +RUN mkdir /app +COPY --from=builder /app/tiny-url /app +COPY --from=builder /app/api.json /app + +WORKDIR /app EXPOSE 4242/tcp -# CMD ["/main"] \ No newline at end of file +CMD "/app/tiny-url" \ No newline at end of file diff --git a/go.mod b/go.mod index 5f33e27..e327643 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,5 @@ -module git.xdrm.io/example/aicra +module git.xdrm.io/go/tiny-url-ex go 1.12 -require ( - git.xdrm.io/go/aicra v0.2.0 - github.com/lib/pq v1.1.0 -) +require git.xdrm.io/go/aicra v0.2.0 diff --git a/go.sum b/go.sum index efad871..9833c56 100644 --- a/go.sum +++ b/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= -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= diff --git a/main.go b/main.go index 84e2824..9b49113 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,9 @@ import ( "log" "net/http" - "git.xdrm.io/example/aicra/service/auth" - "git.xdrm.io/example/aicra/service/shortener" + "git.xdrm.io/go/tiny-url-ex/service/auth" + "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/typecheck/builtin" ) @@ -28,30 +27,22 @@ func main() { server.Checkers.Add(builtin.NewString()) server.Checkers.Add(builtin.NewFloat64()) - // 3. storage connect - log.Printf("[storage] connecting") - db, err := storage.New() - if err != nil { - log.Fatalf("/!\\ cannot connect %v", err) - } - defer db.Close() - - // 4. init services - authService, err := auth.New(db) + // 3. init services + authService, err := auth.New() if err != nil { log.Fatalf("cannot create auth service: %v", err) } - shortenerService, err := shortener.New(db, authService) + shortenerService, err := shortener.New(authService) if err != nil { log.Fatalf("cannot create auth service: %v", err) } - // 5. wire services + // 4. wire services shortenerService.Wire(server) authService.Wire(server) - // 6. listen and serve - log.Printf("[server] listening\n\n") - log.Fatal(http.ListenAndServe(listenTo, server)) + // 5. listen and serve + log.Printf("[server] listening %s\n\n", listenTo) + log.Fatal(http.ListenAndServe(listenTo, server.HTTP())) } diff --git a/service/auth/auth.go b/service/auth/auth.go index a3d531b..ac3b1b5 100644 --- a/service/auth/auth.go +++ b/service/auth/auth.go @@ -2,7 +2,6 @@ package auth import ( "crypto/sha512" - "database/sql" "encoding/hex" "log" "net/http" @@ -15,26 +14,14 @@ import ( ) // Service manages the url shortener -type Service struct { - storage *sql.DB - repo *repository -} +type Service struct{} // 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 repo") - repo, err := newRepo(storage) - if err != nil { - return nil, err - } - log.Printf("[auth] repo created") - log.Printf("[auth] service created") - return &Service{ - repo: repo, - }, nil + return &Service{}, nil } // 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)) // 3. store token - model := svc.repo.NewModel(token, role, 5*time.Minute) + model := NewModel(token, role, 5*time.Minute) if model.Create() != nil { res.SetError(api.ErrorFailure()) return @@ -91,7 +78,7 @@ func (svc *Service) CheckToken(handler api.HandlerFunc) api.HandlerFunc { return } - model := svc.repo.NewModel(headerToken, "", 0) + model := NewModel(headerToken, "", 0) if model.Search() != nil { res.SetError(api.ErrorToken()) return diff --git a/service/auth/model.go b/service/auth/model.go index 38b813b..3a093b9 100644 --- a/service/auth/model.go +++ b/service/auth/model.go @@ -1,70 +1,71 @@ package auth import ( - "database/sql" + "errors" "time" ) -// tokenModel represents an actual tiny url entry in the database. -type tokenModel struct { - db *sql.DB +// Model represents an actual tiny url entry in the database. +type Model struct { token string role string expires int64 } -type repository struct { - db *sql.DB -} +// storage contains entries +var storage []*Model = make([]*Model, 0) -// newRepo returns an initialized repository. -func newRepo(db *sql.DB) (*repository, error) { - _, err := db.Exec(`CREATE TABLE if not exists token( - 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, +// NewModel builds a token model from arguments +func NewModel(token, role string, duration time.Duration) *Model { + return &Model{ token: token, role: role, expires: time.Now().Add(duration).Unix(), } } -func (mod *tokenModel) Search() error { - row := mod.db.QueryRow(`SELECT token, role, expires FROM token WHERE token = $1 LIMIT 1;`, mod.token) +// Search for model in storage +func (mod *Model) Search() error { + for _, row := range storage { - receiver := &tokenModel{} - err := row.Scan(&receiver.token, &receiver.role, &receiver.expires) - if err != nil { - return err + // silently delete if expired + if row.expires < time.Now().Unix() { + row.Delete() + continue + } + + if row.token == mod.token { + mod.token = row.token + mod.role = row.role + mod.expires = row.expires + return nil + } } - // delete if expired - if receiver.expires < time.Now().Unix() { - receiver.db = mod.db - receiver.Delete() - return sql.ErrNoRows + return errors.New("not found") +} + +// 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") + } } - mod.token = receiver.token - mod.role = receiver.role - mod.expires = receiver.expires + storage = append(storage, mod) return nil } -func (mod *tokenModel) Create() error { - _, err := mod.db.Exec(`INSERT INTO token (token, role, expires) VALUES ($1, $2, $3);`, mod.token, mod.role, mod.expires) - return err -} +// Delete the model from storage +func (mod *Model) Delete() error { + for i, row := range storage { + if row.token == mod.token { + storage = append(storage[:i], storage[i+1:]...) + return nil + } + } -func (mod *tokenModel) Delete() error { - _, err := mod.db.Exec(`DELETE FROM token WHERE token = $1;`, mod.token) - return err + return errors.New("not found") } diff --git a/service/shortener/model.go b/service/shortener/model.go index 9d0a911..6d76cf0 100644 --- a/service/shortener/model.go +++ b/service/shortener/model.go @@ -1,64 +1,71 @@ package shortener import ( - "database/sql" + "errors" ) -// tinyModel represents an actual tiny url entry in the database. -type tinyModel struct { - db *sql.DB +// Model represents an actual tiny url entry in the database. +type Model struct { tiny string long string } -type repository struct { - db *sql.DB -} +// storage contains entries +var storage []*Model = make([]*Model, 0) -// newRepo returns an initialized repository. -func newRepo(db *sql.DB) (*repository, error) { - _, err := db.Exec(`CREATE TABLE if not exists tiny( - 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, +// NewModel builds a tiny model from arguments +func NewModel(tiny string, long string) *Model { + return &Model{ tiny: tiny, long: long, } } -func (mod *tinyModel) Search() error { - row := mod.db.QueryRow(`SELECT tiny,long FROM tiny WHERE tiny = $1 LIMIT 1;`, mod.tiny) - - receiver := &tinyModel{} - err := row.Scan(&receiver.tiny, &receiver.long) - if err != nil { - return err +// Search for model in storage +func (mod *Model) Search() error { + for _, row := range storage { + if row.tiny == mod.tiny { + mod.tiny = row.tiny + mod.long = row.long + return nil + } } - mod.tiny = receiver.tiny - mod.long = receiver.long + return errors.New("not found") +} + +// 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 - } -func (mod *tinyModel) Create() error { - _, err := mod.db.Exec(`INSERT INTO tiny(tiny,long) VALUES($1,$2);`, mod.tiny, mod.long) - return err +// Update the model in storage +func (mod *Model) Update() error { + 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 { - _, err := mod.db.Exec(`UPDATE tiny SET long = $1 WHERE tiny = $2;`, mod.long, mod.tiny) - return err -} +// Delete the model from storage +func (mod *Model) Delete() error { + 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 { - _, err := mod.db.Exec(`DELETE FROM tiny WHERE tiny = $1;`, mod.tiny) - return err + return errors.New("not found") } diff --git a/service/shortener/shortener.go b/service/shortener/shortener.go index 4796539..24d1e0a 100644 --- a/service/shortener/shortener.go +++ b/service/shortener/shortener.go @@ -1,36 +1,25 @@ package shortener import ( - "database/sql" "log" "net/http" - "git.xdrm.io/example/aicra/service/auth" "git.xdrm.io/go/aicra" "git.xdrm.io/go/aicra/api" + "git.xdrm.io/go/tiny-url-ex/service/auth" ) // Service manages the url shortener type Service struct { - storage *sql.DB authService *auth.Service - repo *repository } // 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 repo") - repo, err := newRepo(storage) - if err != nil { - return nil, err - } - log.Printf("[shortener] repo created") - log.Printf("[shortener] service created") return &Service{ - repo: repo, authService: auth, }, nil } @@ -55,7 +44,7 @@ func (svc *Service) redirect(req api.Request, res *api.Response) { } // 2. check in db if exists - model := svc.repo.NewModel(tinyURL, "") + model := NewModel(tinyURL, "") if model.Search() != nil { res.SetError(api.ErrorNoMatchFound()) return @@ -84,7 +73,7 @@ func (svc *Service) register(req api.Request, res *api.Response) { } // 2. fail if already used - model := svc.repo.NewModel(tinyURL, "") + model := NewModel(tinyURL, "") if model.Search() == nil { res.SetError(api.ErrorAlreadyExists(), "url") return @@ -117,7 +106,7 @@ func (svc *Service) update(req api.Request, res *api.Response) { } // 2. fail if not already existing - model := svc.repo.NewModel(tinyURL, "") + model := NewModel(tinyURL, "") if model.Search() != nil { res.SetError(api.ErrorNoMatchFound()) return @@ -145,7 +134,7 @@ func (svc *Service) delete(req api.Request, res *api.Response) { } // 2. fail if not already existing - model := svc.repo.NewModel(tinyURL, "") + model := NewModel(tinyURL, "") if model.Search() != nil { res.SetError(api.ErrorNoMatchFound()) return diff --git a/storage/postgres.go b/storage/postgres.go deleted file mode 100644 index 7fd6c5f..0000000 --- a/storage/postgres.go +++ /dev/null @@ -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 -}