Skip to content

Commit

Permalink
Web
Browse files Browse the repository at this point in the history
  • Loading branch information
nervo committed Mar 19, 2023
1 parent 4dbc466 commit b9d4071
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ vhs.sh:
docker compose run --rm \
--entrypoint /bin/bash \
vhs

#######
# Web #
#######

web:
go run . web
.PHONY: web
2 changes: 2 additions & 0 deletions app/interfaces/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Recipe interface {
Vars() map[string]interface{}
Sync() []internalSyncer.UnitInterface
Schema() map[string]interface{}
Options() []RecipeOption
InitVars(callback func(options []RecipeOption) error) (map[string]interface{}, error)
Repository() Repository
Template() *internalTemplate.Template
Expand All @@ -27,6 +28,7 @@ type RecipeManifest interface {
Vars() map[string]interface{}
Sync() []internalSyncer.UnitInterface
Schema() map[string]interface{}
Options() []RecipeOption
ReadFrom(reader io.Reader) error
internalValidation.Reporter
InitVars(callback func(options []RecipeOption) error) (map[string]interface{}, error)
Expand Down
10 changes: 10 additions & 0 deletions app/mocks/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func (rec *RecipeMock) Schema() map[string]interface{} {
return args.Get(0).(map[string]interface{})
}

func (rec *RecipeMock) Options() []interfaces.RecipeOption {
args := rec.Called()
return args.Get(0).([]interfaces.RecipeOption)
}

func (rec *RecipeMock) InitVars(callback func(options []interfaces.RecipeOption) error) (map[string]interface{}, error) {
args := rec.Called(callback)
return args.Get(0).(map[string]interface{}), args.Error(1)
Expand Down Expand Up @@ -106,6 +111,11 @@ func (man *RecipeManifestMock) Schema() map[string]interface{} {
return args.Get(0).(map[string]interface{})
}

func (man *RecipeManifestMock) Options() []interfaces.RecipeOption {
args := man.Called()
return args.Get(0).([]interfaces.RecipeOption)
}

func (man *RecipeManifestMock) ReadFrom(reader io.Reader) error {
args := man.Called(reader)
return args.Error(0)
Expand Down
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func Execute(version string, defaultRepository string, stdout io.Writer, stderr
newMascotCmd(),
newUpdateCmd(config, log),
newWatchCmd(config, log),
newWebCmd(config, log),
)

// Docs generation command
Expand Down
70 changes: 70 additions & 0 deletions cmd/web.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/spf13/cobra"
"manala/core/application"
internalConfig "manala/internal/config"
internalLog "manala/internal/log"
"manala/web/api"
"net/http"
)

func newWebCmd(config *internalConfig.Config, log *internalLog.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "web",
Aliases: []string{"w"},
Args: cobra.NoArgs,
DisableAutoGenTag: true,
Short: "Web interface",
Long: `Web (manala web) will launch web interface.
Example: manala web -> resulting in a web interface launch`,
RunE: func(cmd *cobra.Command, args []string) error {
// Application options
var appOptions []application.Option

// Flag - Repository url
if cmd.Flags().Changed("repository") {
repoUrl, _ := cmd.Flags().GetString("repository")
appOptions = append(appOptions, application.WithRepositoryUrl(repoUrl))
}

// Flag - Repository ref
if cmd.Flags().Changed("ref") {
repoRef, _ := cmd.Flags().GetString("ref")
appOptions = append(appOptions, application.WithRepositoryRef(repoRef))
}

// Application
app := application.NewApplication(
config,
log,
appOptions...,
)

// Web Api
webApi := api.New(app)

router := chi.NewRouter()
router.Use(middleware.Logger)

router.Mount("/api", webApi.Handle())

// Server
server := &http.Server{
Addr: ":9400",
Handler: router,
}

return server.ListenAndServe()
},
}

// Flags
cmd.Flags().StringP("repository", "o", "", "use repository")
cmd.Flags().String("ref", "", "use repository ref")

return cmd
}
10 changes: 10 additions & 0 deletions core/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ func (app *Application) WalkRecipes(walker func(rec interfaces.Recipe) error) er
return app.recipeManager.WalkRecipes(repo, walker)
}

func (app *Application) LoadRecipe(name string) (interfaces.Recipe, error) {
// Load repository
repo, err := app.repositoryManager.LoadPrecedingRepository()
if err != nil {
return nil, err
}

return app.recipeManager.LoadRecipe(repo, name)
}

func (app *Application) CreateProject(
dir string,
recSelector func(recipeWalker func(walker func(rec interfaces.Recipe) error) error) (interfaces.Recipe, error),
Expand Down
4 changes: 4 additions & 0 deletions core/recipe/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ func (man *Manifest) Schema() map[string]interface{} {
return man.schema
}

func (man *Manifest) Options() []interfaces.RecipeOption {
return man.options
}

func (man *Manifest) ReadFrom(reader io.Reader) error {
// Read content
content, err := io.ReadAll(reader)
Expand Down
4 changes: 4 additions & 0 deletions core/recipe/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func (rec *Recipe) Schema() map[string]interface{} {
return rec.manifest.Schema()
}

func (rec *Recipe) Options() []interfaces.RecipeOption {
return rec.manifest.Options()
}

func (rec *Recipe) InitVars(callback func(options []interfaces.RecipeOption) error) (map[string]interface{}, error) {
return rec.manifest.InitVars(callback)
}
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
github.com/fsnotify/fsnotify v1.6.0
github.com/gdamore/tcell/v2 v2.6.0
github.com/gen2brain/beeep v0.0.0-20230307103607-6e717729cb4f
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/render v1.0.2
github.com/goccy/go-yaml v1.10.0
github.com/hashicorp/go-getter/s3/v2 v2.2.1
github.com/hashicorp/go-getter/v2 v2.2.1
Expand All @@ -31,6 +33,7 @@ require (
code.rocketnine.space/tslocum/cbind v0.1.5 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/containerd/console v1.0.3 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ=
github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
Expand Down Expand Up @@ -104,6 +106,10 @@ github.com/gen2brain/beeep v0.0.0-20230307103607-6e717729cb4f/go.mod h1:0W7dI87P
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down
104 changes: 104 additions & 0 deletions web/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package api

import (
"context"
"errors"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"manala/app/interfaces"
"manala/core"
"manala/core/application"
"manala/web/api/views"
"net/http"
)

func New(app *application.Application) *Api {
api := &Api{
app: app,
}

return api
}

type Api struct {
app *application.Application
}

func (api *Api) Handle() http.Handler {
router := chi.NewRouter()

// Recipe
router.Route("/recipe", func(router chi.Router) {
router.Get("/", api.ListRecipes)
router.Route("/{name}", func(router chi.Router) {
router.Use(api.RecipeContext)
router.Get("/", api.GetRecipe)
router.Get("/options", api.GetRecipeOptions)
})
})

return router
}

func (api *Api) ListRecipes(response http.ResponseWriter, request *http.Request) {
var recViews []*views.RecipeView

// Walk into recipes
if err := api.app.WalkRecipes(func(rec interfaces.Recipe) error {
recViews = append(recViews, views.NormalizeRecipe(rec))
return nil
}); err != nil {
http.Error(response, http.StatusText(500), 500)
return
}

render.JSON(response, request, recViews)
}

func (api *Api) RecipeContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
name := chi.URLParam(request, "name")
rec, err := api.app.LoadRecipe(name)
if err != nil {
var _notFoundRecipeManifestError *core.NotFoundRecipeManifestError
if errors.As(err, &_notFoundRecipeManifestError) {
http.Error(response, http.StatusText(404), 404)
} else {
http.Error(response, http.StatusText(500), 500)
}
return
}
ctx := context.WithValue(request.Context(), "recipe", rec)
next.ServeHTTP(response, request.WithContext(ctx))
})
}

func (api *Api) GetRecipe(response http.ResponseWriter, request *http.Request) {
ctx := request.Context()
rec, ok := ctx.Value("recipe").(interfaces.Recipe)
if !ok {
http.Error(response, http.StatusText(422), 422)
return
}

recView := views.NormalizeRecipe(rec)

render.JSON(response, request, recView)
}

func (api *Api) GetRecipeOptions(response http.ResponseWriter, request *http.Request) {
ctx := request.Context()
rec, ok := ctx.Value("recipe").(interfaces.Recipe)
if !ok {
http.Error(response, http.StatusText(422), 422)
return
}

var recOptionsViews []*views.RecipeOptionView

for _, option := range rec.Options() {
recOptionsViews = append(recOptionsViews, views.NormalizeRecipeOption(option))
}

render.JSON(response, request, recOptionsViews)
}
31 changes: 31 additions & 0 deletions web/api/views/recipe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package views

import (
"manala/app/interfaces"
)

func NormalizeRecipe(rec interfaces.Recipe) *RecipeView {
return &RecipeView{
Name: rec.Name(),
Description: rec.Description(),
}
}

type RecipeView struct {
Name string `json:"name"`
Description string `json:"description"`
}

/**********/
/* Option */
/**********/

func NormalizeRecipeOption(option interfaces.RecipeOption) *RecipeOptionView {
return &RecipeOptionView{
Label: option.Label(),
}
}

type RecipeOptionView struct {
Label string `json:"label"`
}
28 changes: 28 additions & 0 deletions web/api/views/recipe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package views

import (
"github.com/stretchr/testify/suite"
"manala/app/mocks"
"testing"
)

type RecipeSuite struct{ suite.Suite }

func TestRecipeSuite(t *testing.T) {
suite.Run(t, new(RecipeSuite))
}

func (s *RecipeSuite) TestNormalize() {
recName := "name"
recDescription := "description"

recMock := mocks.MockRecipe()
recMock.
On("Name").Return(recName).
On("Description").Return(recDescription)

recView := NormalizeRecipe(recMock)

s.Equal(recName, recView.Name)
s.Equal(recDescription, recView.Description)
}

0 comments on commit b9d4071

Please sign in to comment.