Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alleged Domain Driven Design / Dependency Injection and other things #6

Merged
merged 16 commits into from
Aug 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,15 @@ Then fill in the docker-compose file with your credentials.

## Developing

Clone the repo and create a `.env.local` for an easy config setup. Use the `debug` flag to disable auth.
I recommend not using the production environment for testing and recommend running postgres and rabbitmq in Docker.
Developed on Go version 1.14+

Clone the repo and create a `.env.local` for an easy config setup. Use the `debug` flag to disable auth.

I recommend not using the production environment for testing and recommend running postgres and rabbitmq in Docker.

Updating the DB schema use `goose` to migrate safely.

Developed on Go version 1.14+
When ran with the `debug` flag set to true. 500 server errors will be returned to the browser, not just logged. Otherwise it will only return the 500 code and not not the actual error.

Try to keep all "business" logic with in the `/services` and try to keep the imports local to that package, but you'll probably need the `utils` package but we're trying to keep it modular so web-api can theoretically be split up and keeping it seperate would likely make it a lot easier.

Expand Down
10 changes: 4 additions & 6 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package main

//go:generate ./sqlboiler --wipe psql --add-global-variants

import (
"log"
"os"
Expand All @@ -13,7 +11,7 @@ import (
)

// Version returns web-api's current version
var Version = "dev (0.5.3)"
var Version = "dev (0.5.7)"

// Commit returns latest commit hash
var Commit = "unknown"
Expand All @@ -33,11 +31,11 @@ func main() {
if debug {
log.Println("Debug Mode - Disabled auth - pls don't run in production")
}
utils.InitDB()
utils.InitCDN()
db := utils.InitDB()
cdn := utils.InitCDN()
// utils.InitMessaging()

e := routes.Init(Version, Commit)
e := routes.Init(Version, Commit, db, cdn)

e.Logger.Fatal(e.Start(":8081"))
}
21 changes: 21 additions & 0 deletions controllers/v1/clapper/clapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package clapper

import (
"github.com/jmoiron/sqlx"
"github.com/ystv/web-api/services/clapper"
"github.com/ystv/web-api/services/clapper/event"
"github.com/ystv/web-api/services/clapper/position"
"github.com/ystv/web-api/services/clapper/signup"
)

// Repos encapsulates the dependency
type Repos struct {
event clapper.EventRepo
signup clapper.SignupRepo
position clapper.PositionRepo
}

// NewRepos creates our data store
func NewRepos(db *sqlx.DB) *Repos {
return &Repos{event.NewStore(db), signup.NewStore(db), position.NewStore(db)}
}
67 changes: 37 additions & 30 deletions controllers/v1/clapper/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,91 @@ package clapper

import (
"database/sql"
"log"
"errors"
"fmt"
"net/http"
"strconv"

"github.com/labstack/echo/v4"
"github.com/ystv/web-api/controllers/v1/people"
"github.com/ystv/web-api/services/clapper/event"
"github.com/ystv/web-api/services/clapper"
)

// MonthList returns all events for a month.
func MonthList(c echo.Context) error {
func (r *Repos) MonthList(c echo.Context) error {
year, err := strconv.Atoi(c.Param("year"))
if err != nil {
return c.String(http.StatusBadRequest, "Year incorrect, format /yyyy/mm")
return echo.NewHTTPError(http.StatusBadRequest, "Year incorrect, format /yyyy/mm")
}
month, err := strconv.Atoi(c.Param("month"))
if err != nil {
return c.String(http.StatusBadRequest, "Month incorrect, format /yyyy/mm")
return echo.NewHTTPError(http.StatusBadRequest, "Month incorrect, format /yyyy/mm")
}
e, err := event.ListMonth(year, month)
e, err := r.event.ListMonth(c.Request().Context(), year, month)
if err != nil {
log.Printf("MonthList failed: %v", err)
return c.JSON(http.StatusBadRequest, err)
err = fmt.Errorf("ListMonth failed: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}
return c.JSON(http.StatusOK, e)
}

// EventGet handles getting all signups and roles for a given event
func EventGet(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
func (r *Repos) EventGet(c echo.Context) error {
id, err := strconv.Atoi(c.Param("eventid"))
if err != nil {
return c.String(http.StatusBadRequest, "Bad video ID")
return echo.NewHTTPError(http.StatusBadRequest, "Invalid event ID")
}
e, err := event.Get(id)
e, err := r.event.Get(c.Request().Context(), id)
if err != nil {
log.Printf("EventGet failed: %v", err)
return c.NoContent(http.StatusInternalServerError)
err = fmt.Errorf("EventGet failed: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusOK, e)
}

// EventNew handles creating a new event
func EventNew(c echo.Context) error {
e := event.Event{}
func (r *Repos) EventNew(c echo.Context) error {
e := clapper.Event{}
err := c.Bind(&e)
if err != nil {
return c.JSON(http.StatusBadRequest, err)
err = fmt.Errorf("EventNew: failed to bind to request json: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}
p, err := people.GetToken(c)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
err = fmt.Errorf("EventNew: failed to get token: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
eventID, err := event.New(&e, p.UserID)
eventID, err := r.event.New(c.Request().Context(), &e, p.UserID)
if err != nil {
log.Printf("EventNew failed: %v", err)
return c.JSON(http.StatusInternalServerError, err)
err = fmt.Errorf("EventNew: failed to insert event: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusCreated, eventID)
}

// EventUpdate updates an existing event
func EventUpdate(c echo.Context) error {
e := event.Event{}
func (r *Repos) EventUpdate(c echo.Context) error {
e := clapper.Event{}
err := c.Bind(&e)
if err != nil {
return c.JSON(http.StatusBadRequest, err)
err = fmt.Errorf("EventUpdate: failed to bind to request json: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}
p, err := people.GetToken(c)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
err = fmt.Errorf("EventUpdate: failed to get token: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
err = event.Update(&e, p.UserID)
err = r.event.Update(c.Request().Context(), &e, p.UserID)
if err != nil {
if err == sql.ErrNoRows {
return c.JSON(http.StatusBadRequest, err)
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
log.Printf("PositionUpdate failed: %v", err)
return c.JSON(http.StatusInternalServerError, err)
err = fmt.Errorf("EventUpdate: failed to update: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
return c.NoContent(http.StatusOK)
}

// TODO add delete
45 changes: 25 additions & 20 deletions controllers/v1/clapper/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,57 @@ package clapper

import (
"database/sql"
"log"
"errors"
"fmt"
"net/http"

"github.com/labstack/echo/v4"
"github.com/ystv/web-api/services/clapper/position"
"github.com/ystv/web-api/services/clapper"
)

// PositionList handles listing all possible positions
func PositionList(c echo.Context) error {
p, err := position.List()
func (r *Repos) PositionList(c echo.Context) error {
p, err := r.position.List(c.Request().Context())
if err != nil {
log.Printf("PositionList failed: %v", err)
return c.JSON(http.StatusInternalServerError, err)
err = fmt.Errorf("PositionList: failed to list: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusOK, p)
}

// PositionNew handles creating a new position
func PositionNew(c echo.Context) error {
p := position.Position{}
func (r *Repos) PositionNew(c echo.Context) error {
p := clapper.Position{}
err := c.Bind(&p)
if err != nil {
return c.JSON(http.StatusBadRequest, err)
err = fmt.Errorf("PositionNew: failed to bind to request json: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}
err = position.New(&p)
positionID, err := r.position.New(c.Request().Context(), &p)
if err != nil {
log.Printf("PositionNew failed: %+v", err)
return c.JSON(http.StatusInternalServerError, err)
err = fmt.Errorf("PositionNew: failed to insert position: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
return c.NoContent(http.StatusCreated)
return c.JSON(http.StatusCreated, positionID)
}

// PositionUpdate updates an existing position
func PositionUpdate(c echo.Context) error {
p := position.Position{}
func (r *Repos) PositionUpdate(c echo.Context) error {
p := clapper.Position{}
err := c.Bind(&p)
if err != nil {
return c.JSON(http.StatusBadRequest, err)
err = fmt.Errorf("PositionUpdate: failed to bind to request json: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}
err = position.Update(&p)
err = r.position.Update(c.Request().Context(), &p)
if err != nil {
if err == sql.ErrNoRows {
return c.JSON(http.StatusBadRequest, err)
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
log.Printf("PositionUpdate failed: %v", err)
err = fmt.Errorf("PositionUpdate failed: %w", err)
return c.JSON(http.StatusInternalServerError, err)
}
return c.NoContent(http.StatusOK)
}

// TODO add delete
46 changes: 46 additions & 0 deletions controllers/v1/clapper/signup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package clapper

import (
"database/sql"
"errors"
"fmt"
"net/http"
"strconv"

"github.com/labstack/echo/v4"
"github.com/ystv/web-api/services/clapper"
)

// SignupNew
func (r *Repos) SignupNew(c echo.Context) error {
// Validate event ID
eventID, err := strconv.Atoi(c.Param("eventID"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Bad event ID")
}
// Bind request json to signup
s := clapper.Signup{}
err = c.Bind(&s)
if err != nil {
err = fmt.Errorf("SignupNew: failed to bind to request json: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}

// Check event exists
// TODO we might want to move this inside the service
e, err := r.event.Get(c.Request().Context(), eventID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return echo.NewHTTPError(http.StatusNotFound, "No event found")
}
return echo.NewHTTPError(http.StatusInternalServerError, err)
}

// Insert new signup sheet
signupID, err := r.signup.New(c.Request().Context(), e.EventID, s)
if err != nil {
err = fmt.Errorf("SignupNew: failed to insert new signup: %w", err)
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusCreated, signupID)
}
42 changes: 31 additions & 11 deletions controllers/v1/creator/creator.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
package creator

import (
"context"
"fmt"
"net/http"

"github.com/aws/aws-sdk-go/service/s3"
"github.com/jmoiron/sqlx"
"github.com/labstack/echo/v4"
"github.com/ystv/web-api/services/creator"
"github.com/ystv/web-api/services/creator/breadcrumb"
"github.com/ystv/web-api/services/creator/encode"
"github.com/ystv/web-api/services/creator/playlist"
"github.com/ystv/web-api/services/creator/series"
"github.com/ystv/web-api/services/creator/video"
)

// FileUpload Handles uploading a file
func FileUpload(c echo.Context) error {
creator.CreateBucket("pending", "ystv-wales-1")
url, err := creator.GenerateUploadURL("pending", c.Param("id"))
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
// Repos represents all our data repositories
type Repos struct {
video creator.VideoRepo
series creator.SeriesRepo
playlist creator.PlaylistRepo
breadcrumb creator.BreadcrumbRepo
encode creator.EncodeRepo
creator creator.StatRepo
}

// NewRepos creates our data repositories
func NewRepos(db *sqlx.DB, cdn *s3.S3) *Repos {
return &Repos{
video.NewStore(db, cdn),
series.NewController(db, cdn),
playlist.NewStore(db),
breadcrumb.NewController(db, cdn),
encode.NewStore(db),
creator.NewStore(db),
}
return c.JSON(http.StatusOK, url)
}

// Stats handles sending general stats about the video library
func Stats(c echo.Context) error {
s, err := creator.Stats(context.Background())
func (r *Repos) Stats(c echo.Context) error {
s, err := r.creator.GlobalVideo(c.Request().Context())
if err != nil {
return c.JSON(http.StatusBadRequest, err)
err = fmt.Errorf("stats failed: %w", err)
return echo.NewHTTPError(http.StatusBadRequest, err)
}
return c.JSON(http.StatusOK, s)
}
Loading