Skip to content

Commit

Permalink
Update serve index to list projects
Browse files Browse the repository at this point in the history
  • Loading branch information
Nigel2392 committed May 4, 2024
1 parent 0e43ca9 commit e12bcb4
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 120 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
require (
github.com/Nigel2392/mux v1.2.4 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/Nigel2392/goldcrest v1.0.4 h1:Xx+QLht6QjJ3Gg9uksgc6Ye1XjbtzQ1208ClZwoVWsg=
github.com/Nigel2392/goldcrest v1.0.4/go.mod h1:UpnPrYJqZY/b7TkoVKdoNNPKTlQtld+fsrZEA98c1c0=
github.com/Nigel2392/mux v1.2.4 h1:nS/Yeo3DQhQLFcLIuObhqCg9Ay1XD6EYJYiAro0AFn0=
github.com/Nigel2392/mux v1.2.4/go.mod h1:Hrj90gq33BCcFy8167rU03oBr2YIQMRvNkMf9JnRcAU=
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ func main() {
)
var server = &http.Server{
Addr: addr,
Handler: qg,
Handler: qg.HttpHandler(),
}

if qg.Config.TLSKey != "" && qg.Config.TLSCert != "" {
Expand Down
15 changes: 14 additions & 1 deletion quickgo/_templates/file.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
{{define "content"}}
<div class="box-content">
{{ template "parent_url" . }}
<div class="quickgo-content-container pre">{{.Content}}</div>
{{ if gt (len .ObjectList) 0 }}
<div class="quickgo-content-container">
{{ range $obj := .ObjectList }}
<div class="quickgo-dir-link-container">
<a class="quickgo-dir-link quickgo-dir" href="{{ ObjectURL $obj }}">{{$obj.GetName}}</a>
{{ if $obj.Size }}<span class="quickgo-datasize">{{ FileSize $obj.Size }}</span>{{end}}
</div>
{{ end }}
</div>
{{ else }}
<div style="display: flex;align-items: center;justify-content: center;height:50%;">
<h1 style="color:#ff5555;">You don't have any projects</h1>
</div>
{{ end }}
</div>
{{end}}
8 changes: 6 additions & 2 deletions quickgo/_templates/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

{{define "content"}}
<div class="box-content">
<h2>Index: {{.Dir.GetName}}</h2>
<pre class="quickgo-content-container">{{.Content}}</pre>
<h2>View all projects:</h2>
<ul>
{{ range $project := .ObjectList }}
<li><a href="{{ ProjectURL $project }}">{{$project.Name}}</a></li>
{{ end }}
</ul>
</div>
{{end}}
2 changes: 1 addition & 1 deletion quickgo/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type (
AppServeHook func(*App, http.ResponseWriter, *http.Request) (written bool, err error)

// quickgo.funcs.listProjects
AppListProjectsHook func(*App, []string) ([]string, error)
AppListProjectsHook func(*App, []*config.Project) ([]*config.Project, error)

// quickgo.project.loaded
// quickgo.project.example
Expand Down
32 changes: 0 additions & 32 deletions quickgo/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,38 +71,6 @@ func TestAppServeHook(t *testing.T) {

}

func TestAppListProjectsHook(t *testing.T) {
var hookName = "quickgo.test.TestAppListProjectsHook"
var (
err error
a = &quickgo.App{}
p = make([]string, 0)
)
p = append(p, "project1")
p = append(p, "project2")

goldcrest.Register(
hookName, 0,
func(a *quickgo.App, projects []string) ([]string, error) {
projects = append(projects, "project3")
return projects, nil
},
)
for _, hook := range goldcrest.Get[quickgo.AppListProjectsHook](hookName) {
if p, err = hook(a, p); err != nil {
t.Fatal(err)
}
}

if len(p) != 3 {
t.Fatalf("expected 3, got %d", len(p))
}

if p[2] != "project3" {
t.Fatalf("expected %q, got %q", "project3", p[2])
}
}

func TestProjectHook(t *testing.T) {
var hookName = "quickgo.test.TestProjectHook"

Expand Down
214 changes: 136 additions & 78 deletions quickgo/quickgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,9 +516,9 @@ func (a *App) CopyFileContent(proj *config.Project, file *os.File, f *quickfs.FS
)
}

func (a *App) ListProjects() ([]string, error) {
func (a *App) ListProjectObjects() ([]*config.Project, error) {
var (
projects = make([]string, 0)
projects = make([]*config.Project, 0)
dirPath = getProjectFilePath("", true)
)

Expand All @@ -534,11 +534,12 @@ func (a *App) ListProjects() ([]string, error) {

var path = filepath.Join(dirPath, d.Name())
var configName = filepath.Join(path, config.PROJECT_CONFIG_NAME)
if _, err = os.Stat(configName); err != nil {
continue
proj, err := config.LoadYaml[config.Project](configName)
if err != nil && !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "failed to load project config %s", configName)
}

projects = append(projects, d.Name())
projects = append(projects, proj)
}

for _, hook := range goldcrest.Get[AppListProjectsHook](HookQuickGoListProjects) {
Expand All @@ -554,6 +555,18 @@ func (a *App) ListProjects() ([]string, error) {
return projects, nil
}

func (a *App) ListProjects() ([]string, error) {
var p, err = a.ListProjectObjects()
if err != nil {
return nil, err
}
var names = make([]string, 0, len(p))
for _, proj := range p {
names = append(names, proj.Name)
}
return names, nil
}

func (a *App) WriteFile(data []byte, path string) error {
path = filepath.Join(executableDir, config.QUICKGO_DIR, path)
return os.WriteFile(path, data, os.ModePerm)
Expand All @@ -564,94 +577,62 @@ func (a *App) ReadFile(path string) ([]byte, error) {
return os.ReadFile(path)
}

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (a *App) HttpHandler() http.Handler {
var mux = http.NewServeMux()
mux.Handle("/", &LogHandler{
Handler: http.HandlerFunc(a.serveIndex),
Where: "index",
Level: logger.InfoLevel,
})
mux.Handle("/projects/", &LogHandler{
Handler: http.StripPrefix(
"/projects/",
http.HandlerFunc(a.serveProjects),
),
Where: "root",
Level: logger.InfoLevel,
})
mux.Handle("/static/", &LogHandler{
Handler: http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))),
Where: "static files",
Level: logger.DebugLevel,
})
mux.Handle("/favicon.ico", &LogHandler{
Handler: http.HandlerFunc(a.serveFavicon),
Where: "favicon",
Level: logger.DebugLevel,
})
return a.middleware(mux)
}

go func() {
if err := recover(); err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}()
func (a *App) serveIndex(w http.ResponseWriter, r *http.Request) {

var pathParts = strings.Split(strings.Trim(r.URL.Path, "/"), "/")
var primary = strings.ToLower(pathParts[0])

for _, hook := range goldcrest.Get[AppServeHook](HookQuickGoServer) {
if served, err := hook(a, w, r); err != nil {
logger.Errorf("Failed to serve: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
} else if served {
logger.Debugf("'%s' was served and hijacked by a hook", r.URL.Path)
return
}
var projects, err = a.ListProjectObjects()
if err != nil {
logger.Errorf("Failed to list projects: %v", err)
http.Error(w, "Failed to list projects", http.StatusInternalServerError)
return
}

switch {
case primary == "projects":

logServe("projects", r)
a.serveProjects(w, r, pathParts[1:])

case primary == "favicon.ico" && len(pathParts) == 1:

// Write out the favicon file.
// No need to log the happy path here, quite boring.
var f, err = staticFS.Open("quickgo.png")
if err != nil {
logger.Errorf("Failed to open 'quickgo.png': %v", err)
http.Error(w, "Failed to open 'quickgo.png'", http.StatusInternalServerError)
return
}
defer f.Close()

w.Header().Set("Content-Type", "image/x-icon")
if _, err = io.Copy(w, f); err != nil {
logger.Errorf("Failed to read 'quickgo.png': %v", err)
http.Error(w, "Failed to read 'quickgo.png'", http.StatusInternalServerError)
}

case primary == "static":

// Serve static files.
logServe("static file", r)
// Serve from embedded file system.
var handler = http.FileServer(http.FS(staticFS))
handler = http.StripPrefix("/static/", handler)
handler.ServeHTTP(w, r)

default:

logger.Debugf("Invalid request path '%s'", r.URL.Path)
http.Error(w, "Invalid path", http.StatusBadRequest)
var ctx = &ProjectTemplateContext{
ObjectList: projects,
}
}

func logServe(where string, r *http.Request) {
logger.Debugf("Serving %s to %s on path '%s'", where, r.RemoteAddr, r.URL.Path)
}

type ProjectTemplateContext struct {
Project *config.Project
File *quickfs.FSFile
Dir *quickfs.FSDirectory
Parent *quickfs.FSDirectory
ObjectList []quickfs.FileLike
Content string
if err = a.executeServeTemplate(w, "index.tmpl", ctx); err != nil {
logger.Errorf("Failed to render index: %v", err)
http.Error(w, "Failed to render index", http.StatusInternalServerError)
}
}

func (a *App) serveProjects(w http.ResponseWriter, r *http.Request, pathParts []string) {
func (a *App) serveProjects(w http.ResponseWriter, r *http.Request) {
var (
proj *config.Project
parent *quickfs.FSDirectory
fileLike quickfs.FileLike
err error
)

if len(pathParts) == 0 {
logger.Errorf("Invalid request path '%s'", r.URL.Path)
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
var pathParts = strings.Split(strings.Trim(r.URL.Path, "/"), "/")

proj, closeFiles, err := a.ReadProjectConfig(pathParts[0])
if err != nil {
Expand Down Expand Up @@ -723,6 +704,77 @@ func (a *App) serveProjects(w http.ResponseWriter, r *http.Request, pathParts []
}
}

func (a *App) serveFavicon(w http.ResponseWriter, r *http.Request) {
// Write out the favicon file.
// No need to log the happy path here, quite boring.
var f, err = staticFS.Open("quickgo.png")
if err != nil {
logger.Errorf("Failed to open 'quickgo.png': %v", err)
http.Error(w, "Failed to open 'quickgo.png'", http.StatusInternalServerError)
return
}
defer f.Close()

w.Header().Set("Content-Type", "image/x-icon")
if _, err = io.Copy(w, f); err != nil {
logger.Errorf("Failed to read 'quickgo.png': %v", err)
http.Error(w, "Failed to read 'quickgo.png'", http.StatusInternalServerError)
}
}

func (a *App) middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}()

for _, hook := range goldcrest.Get[AppServeHook](HookQuickGoServer) {
if served, err := hook(a, w, r); err != nil {
logger.Errorf("Failed to serve: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
} else if served {
logger.Debugf("'%s' was served and hijacked by a hook", r.URL.Path)
return
}
}

next.ServeHTTP(w, r)
})
}

type LogHandler struct {
Handler http.Handler
Level logger.LogLevel
Where string
}

func (h *LogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var s = fmt.Sprintf("Serving %s to %s on path '%s'", h.Where, r.RemoteAddr, r.URL.Path)
switch h.Level {
case logger.DebugLevel:
logger.Debug(s)
case logger.InfoLevel:
logger.Info(s)
case logger.WarnLevel:
logger.Warn(s)
case logger.ErrorLevel:
logger.Error(s)
}
h.Handler.ServeHTTP(w, r)
}

type ProjectTemplateContext struct {
Project *config.Project
File *quickfs.FSFile
Dir *quickfs.FSDirectory
Parent *quickfs.FSDirectory
ObjectList any
Content string
}

func (a *App) executeServeTemplate(w http.ResponseWriter, name string, context *ProjectTemplateContext) (err error) {
var tpl = html_template.New("base")

Expand All @@ -734,6 +786,12 @@ func (a *App) executeServeTemplate(w http.ResponseWriter, name string, context *
fl.GetPath(),
))
},
"ProjectURL": func(project *config.Project) string {
return filepath.ToSlash(path.Join(
"/projects",
project.Name,
))
},
"FileSize": func(size int64) string {
f_size := float64(size)
if f_size < 1024 {
Expand Down
Loading

0 comments on commit e12bcb4

Please sign in to comment.