Skip to content

Commit

Permalink
Merge pull request #7 from dorian3343/dev
Browse files Browse the repository at this point in the history
Merge v0.0.4 with main
  • Loading branch information
dorian3343 authored Mar 9, 2024
2 parents 0f9bab0 + 0af2256 commit 0bb5003
Show file tree
Hide file tree
Showing 21 changed files with 235 additions and 41 deletions.
11 changes: 11 additions & 0 deletions Scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ func main() {
}
}

//audit a project
if len(os.Args) > 1 && os.Args[1] == "audit" {
if len(os.Args) > 2 {
cmd.Audit(os.Args[2])
os.Exit(0)
} else {
fmt.Println("Error: Missing project name")
os.Exit(1)
}
}

// Run a scaffold app in current dir or a specified
if len(os.Args) > 1 && os.Args[1] == "run" {
entrypoint := "./main.yml"
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.0.3
v0.0.4
78 changes: 77 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"service/configuration"
"service/misc"
"slices"
"strings"
)
Expand All @@ -19,7 +20,8 @@ List of commands:
version print out your scaffold version
run run the scaffold from a config in a specified directory
init creates a new project from a template
auto-doc generates api documentation for your app`)
auto-doc generates api documentation for your app
audit checks your project for potential error's'`)
fmt.Println("\nTool by Dorian Kalaczyński")
os.Exit(0)

Expand Down Expand Up @@ -106,6 +108,16 @@ func GenerateDoc(path string) {
docString.WriteString("This route returns:\n```JSON\n" + string(value.Fallback) + "\n```\n")
} else {
docString.WriteString("This route runs the query:\n ```SQL\n" + value.Model.GetQuery() + "\n```\n")
if value.Model.GetJsonTemplate().Len() != 0 {
jsonT := value.Model.GetJsonTemplate()
// Generate JSON example
docString.WriteString("JSON Specification:\n```json\n{\n")
for _, Name := range jsonT.Keys() {
T, _ := jsonT.Get(Name)
docString.WriteString(fmt.Sprintf(" %s : %s\n", Name, T))
}
docString.WriteString("}\n```\n")
}
docString.WriteString("and fallsback to:\n ```JSON\n" + string(value.Fallback) + "\n```\n")
}
}
Expand All @@ -123,3 +135,67 @@ func GenerateDoc(path string) {
}
}(file)
}
func Audit(path string) {
// Generate the config struct first
conf, _ := configuration.Setup(path + "/main.yml")

// Track seen names
seenNames := make(map[string]bool)

// Track if any warnings were found
foundWarning := false

// Check controllers
for _, controller := range conf.Controllers {
if !strings.HasSuffix(controller.Name, "_controller") {
fmt.Printf("Naming warning -> Controller %s should end with '_controller'\n", controller.Name)
foundWarning = true
}
if controller.Name != strings.ToLower(controller.Name) {
fmt.Printf("Naming warning -> Controller %s should be all lowercase\n", controller.Name)
foundWarning = true
}
if seenNames[controller.Name] {
fmt.Printf("Duplicate warning -> Controller %s is duplicated\n", controller.Name)
foundWarning = true
} else {
seenNames[controller.Name] = true
}
if slices.Equal(controller.Fallback, []byte("null")) {
fmt.Printf("General warning -> Controller %s has an empty fallback\n", controller.Name)
foundWarning = true
}
}

// Check models
for _, model := range conf.Models {
if !strings.HasSuffix(model.Name, "_model") {
fmt.Printf("Naming warning -> Model %s should end with '_model'\n", model.Name)
foundWarning = true
}
if model.Name != strings.ToLower(model.Name) {
fmt.Printf("Naming warning -> Model %s should be all lowercase\n", model.Name)
foundWarning = true
}

jsonT := model.GetJsonTemplate()
for _, Name := range jsonT.Keys() {
if Name != misc.Capitalize(Name) {
fmt.Printf("Naming warning -> Model %s has non-capitalized JSON field '%s'\n", model.Name, Name)
foundWarning = true
}
}

if seenNames[model.Name] {
fmt.Printf("Duplicate warning -> Model %s is duplicated\n", model.Name)
foundWarning = true
} else {
seenNames[model.Name] = true
}
}

// If no warnings were found, print a message
if !foundWarning {
fmt.Println("Success -> No warnings found during audit.")
}
}
28 changes: 19 additions & 9 deletions components/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ type Controller struct {
Model *model.Model
Fallback []byte
cors string
cache string
verb string
http.Handler
}

/* Constructor for the controller, outside of package used like this 'Controller.Create(x,y)' */
func Create(name string, datamodel *model.Model, fallback []byte, cors string) Controller {
return Controller{Name: name, Model: datamodel, Fallback: fallback, cors: cors}
func Create(name string, datamodel *model.Model, fallback []byte, cors string, cache string, verb string) Controller {
return Controller{Name: name, Model: datamodel, Fallback: fallback, cors: cors, cache: cache, verb: verb}
}

func (c Controller) handleNoModelRequest(w http.ResponseWriter) {
Expand All @@ -39,17 +41,24 @@ func (c Controller) handleNoModelRequest(w http.ResponseWriter) {
return
}
}
func (c Controller) handleCors(w http.ResponseWriter) {
func (c Controller) handleHeaders(w http.ResponseWriter) {
if c.cors != "" {
w.Header().Set("Access-Control-Allow-Origin", c.cors)
}
if c.cache != "" {
w.Header().Set("Cache-Control", c.cache)
}

}

/* logic is the function to fulfill the http.Handler interface. */
func (c Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//Enable cors
c.handleCors(w)
if c.verb != "" && c.verb != r.Method {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
//set Headers
c.handleHeaders(w)
if c.Model == nil {
c.handleNoModelRequest(w)
} else {
Expand All @@ -61,7 +70,8 @@ func (c Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}

log.Trace().Msg("Building Query in : " + c.Name)
// make the db query
query, err := c.Model.Querybuilder(body)
if err != nil {
if err.Error() == "JSON request does not match spec" {
Expand All @@ -74,8 +84,8 @@ func (c Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
}

// Make the db query
log.Trace().Msg("Running Query in : " + c.Name)
// Queries the database
result, err := c.Model.Query(query)
if err != nil {
log.Err(err).Msg("Something went wrong with querying database")
Expand Down Expand Up @@ -120,6 +130,7 @@ func (c Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)

// Send the JSON response
log.Trace().Msg("Sending response in : " + c.Name)
_, err = w.Write(resp)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
Expand All @@ -139,7 +150,6 @@ func SetupControllers(services map[string]Controller) {
if route == "" {
log.Fatal().Err(errors.New("Missing route")).Msg("Something went wrong with setting up Controllers")
}

http.Handle(route, handler)
if handler.Name == "" {
wrn = append(wrn, fmt.Sprintf("Empty controller for Route: '%s'", route))
Expand Down
12 changes: 6 additions & 6 deletions components/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
/*Testing for a no model Controller using the fallback string*/
func TestController_ServeHTTP_BasicString(t *testing.T) {
x, _ := json.Marshal("Hello World")
c := Create("basicTest", nil, x, "")
c := Create("basicTest", nil, x, "", "", "")
req := httptest.NewRequest("GET", "http://google.com", nil)
w := httptest.NewRecorder()
c.ServeHTTP(w, req)
Expand All @@ -38,7 +38,7 @@ func TestController_ServeHTTP_BasicString(t *testing.T) {
/*Testing for a no model Controller using the fallback string*/
func TestController_ServeHTTP_BasicInt(t *testing.T) {
x, _ := json.Marshal(69)
c := Create("basicTest", nil, x, "")
c := Create("basicTest", nil, x, "", "", "")
req := httptest.NewRequest("GET", "http://google.com", nil)
w := httptest.NewRecorder()
c.ServeHTTP(w, req)
Expand Down Expand Up @@ -76,7 +76,7 @@ func TestController_ServeHTTP_Struct(t *testing.T) {
}

// Create a request using the input data
c := Create("basicTest", nil, requestData, "")
c := Create("basicTest", nil, requestData, "", "", "")
req := httptest.NewRequest("GET", "http://google.com", nil)
w := httptest.NewRecorder()

Expand Down Expand Up @@ -108,7 +108,7 @@ func TestController_Create(t *testing.T) {
expectedFallback, _ := json.Marshal(69)

// Call the Create function
c := Create(expectedName, nil, expectedFallback, "")
c := Create(expectedName, nil, expectedFallback, "", "", "")

// Check if the fields of the created controller match the expected values
if c.Name != expectedName {
Expand All @@ -129,8 +129,8 @@ func TestSetupControllers(t *testing.T) {
Fallback2, _ := json.Marshal("Hello World")

// Create controllers and add them to Services map
Services["/get_int"] = Create("int_controller", nil, Fallback1, "")
Services["/get_str"] = Create("str_controller", nil, Fallback2, "")
Services["/get_int"] = Create("int_controller", nil, Fallback1, "", "", "")
Services["/get_str"] = Create("str_controller", nil, Fallback2, "", "", "")

// Setup controllers
SetupControllers(Services)
Expand Down
8 changes: 6 additions & 2 deletions components/model/Model.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ func Create(name string, db *sql.DB, template string, JSON *jsonmap.Map) Model {
func (m Model) GetQuery() string {
return m.queryTemplate
}
func (m Model) GetJsonTemplate() *jsonmap.Map {
return m.json
}

// Fills out the query queryTemplate with data from the json
func (m Model) Querybuilder(x []byte) (string, error) {
Expand All @@ -37,13 +40,15 @@ func (m Model) Querybuilder(x []byte) (string, error) {

err := json.Unmarshal(x, jsonRequest)
if err != nil {
return "", errors.New("failed to decode JSON data: " + err.Error())
log.Debug().Msg("Failed to decode :" + string(x))
return "", errors.New("Failed to decode JSON data: " + err.Error())
}
//Basic type caching
var GeneratedType reflect.Type
if m.generatedTypeCache == nil {
GeneratedType = GenerateStructFromJsonMap(*m.json)
m.generatedTypeCache = &GeneratedType
log.Trace().Msg("Caching Type for model : " + m.Name)
} else {
GeneratedType = *m.generatedTypeCache
}
Expand Down Expand Up @@ -75,7 +80,6 @@ func (m Model) Querybuilder(x []byte) (string, error) {

}

// Queries the database
func (m Model) Query(query string) (*sql.Rows, error) {
rows, err := m.db.Query(query)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion components/model/TypeSpecGeneration.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ func matchesSpec(Y jsonmap.Map, T reflect.Type) bool {
// Compare the types of the field in the struct and in the JSON map
if fieldT != reflect.TypeOf(fieldValue) {
if fieldT == reflect.TypeOf(int(0)) && reflect.TypeOf(fieldValue) == reflect.TypeOf(float64(0)) {
log.Warn().Msg("Adapted type to int from float64")
} else {
log.Error().Msgf("Wrong Type in field '%s' in JSON request. Got type '%s' expected type '%s'", fieldName, fieldT, reflect.TypeOf(fieldValue))
return false
Expand Down
1 change: 0 additions & 1 deletion configuration/Config.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ func (c configuration) adapt() (*Configuration, error) {
var databaseClosure func()

if c.Database.Path == "" || c.Database.InitQuery == "" {
log.Warn().Msg("Missing Database in main.yml : Models are disabled")
// Set all the models to nil, effectively disabling models
for i := 0; i < len(c.Controllers); i++ {
newController, err := c.Controllers[i].adapt(nil)
Expand Down
13 changes: 12 additions & 1 deletion configuration/ConfigPrimitiveTypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/metalim/jsonmap"
"service/components/controller"
model2 "service/components/model"
"strings"
)

// ConfigPrimitiveTypes should include all the non-critical structs and their methods
Expand Down Expand Up @@ -35,14 +36,24 @@ type Controller struct {
Name string `yaml:"name"`
Model string `yaml:"model"`
Cors string `yaml:"cors"`
Cache string `yaml:"cache"`
Verb string `yaml:"verb"`
}

func (c Controller) adapt(model *model2.Model) (controller.Controller, error) {
JSON, err := json.Marshal(c.Fallback)
if err != nil {
return controller.Controller{}, errors.New(fmt.Sprintf("Json error in Controller : %s", c.Name))
}
return controller.Create(c.Name, model, JSON, c.Cors), nil
verb := strings.ToUpper(c.Verb)
switch verb {
case "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "":
default:
err := errors.New("Unrecognized HTTP method")
fmt.Println(err)
return controller.Controller{}, err
}
return controller.Create(c.Name, model, JSON, c.Cors, c.Cache, verb), nil
}

// Struct representing a single field of a json spec
Expand Down
4 changes: 2 additions & 2 deletions configuration/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ func TestModelAdaptModel(t *testing.T) {

func TestModelAdaptController(t *testing.T) {

sample := Controller{Name: "name", Fallback: "ok", Model: "", Cors: "*"}
sample := Controller{Name: "name", Fallback: "ok", Model: "", Cors: "*", Cache: "", Verb: "POST"}
x, err := sample.adapt(nil)
// Check if error is nil
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}

// Create an expected controller
expected := controller.Create("name", nil, []byte(`"ok"`), "*")
expected := controller.Create("name", nil, []byte(`"ok"`), "*", "", "POST")

// Check if the adapted model is equal to the expected model using reflection
if !reflect.DeepEqual(x, expected) {
Expand Down
5 changes: 3 additions & 2 deletions docs/external/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ This folder contains all the doc's for the api and how to configure your Scaffol
application.

## Content's:
1. [Components](component's.md)
2. [Good practices](good-practices.md)
1. [Scaffold Cli](./cli.md)
2. [Components](component's.md)
3. [Good practices](good-practices.md)

## Flow Chart for Scaffold's API process
Scaffold has a simple yet effective process.
Expand Down
Loading

0 comments on commit 0bb5003

Please sign in to comment.