diff --git a/.gitignore b/.gitignore index 3962b44..b66bc5d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ install.sh *.pid config-*.yml !config-test.yml +data diff --git a/Makefile b/Makefile index 4815468..58bdbe9 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ ifeq ($(INSTALLDEST),) INSTALLDEST := /opt/${PROJECT_NAME} endif +VERSION := $(shell git describe --tags --exact HEAD || printf "%s" $(shell git rev-parse --short HEAD)) +BUILDDATE := $(shell date -Iminutes) + install: mkdir -p $(INSTALLDEST) cp -r dist/* $(INSTALLDEST) @@ -25,6 +28,8 @@ dev: done webroot: $(wildcard webroot/**) FORCE + mkdir -p dist + cp -r assets dist cp -r webroot dist (cd dist/webroot; ../../scripts/webroot-build.fish) @@ -37,7 +42,10 @@ clean: rm -rf dist dist/%: ${CMD_DIR}/% FORCE - go build -o $@ ${MODULE_PATH}/$< + go build \ + -ldflags "-X ${MODULE_PATH}/internal/version.Version=$(VERSION) \ + -X ${MODULE_PATH}/internal/version.BuildDate=$(BUILDDATE)" \ + -o $@ ${MODULE_PATH}/$< .PHONY: build clean FORCE: \ No newline at end of file diff --git a/assets/msg-canvas-grades.tpl.html b/assets/msg-canvas-grades.tpl.html new file mode 100644 index 0000000..be2f7f7 --- /dev/null +++ b/assets/msg-canvas-grades.tpl.html @@ -0,0 +1,29 @@ +
Name | +Course | +Assignment | +Due | +Grade | +Graded At | +Posted At | +
{{ .SubmissionUserName }} | +{{ .CourseCode }} | +{{ .Name }} | +{{if eq .Due " -" }}No Due{{else}}{{.Due}}{{end}} | +{{if .GradeHidden }}(hidden){{else}}{{if le .Score -0.01}}(not graded){{else}}{{ .Score | sprintf "%.2f" + }} ({{ .Grade }}){{end}} /{{ .PossiblePoints | sprintf "%.2f" }} {{end}} | +{{ .GradedAt }} | +{{ .PostedAt }} | +
Refer to this message: {{.Message}}
+`)) + +type htmlErrorTemplateCtx struct { + Code int + CodeText string + Message string +} + +type ErrorWriter func(c echo.Context, err error) error + +var ( + JSONWriter = func(c echo.Context, err error) error { + if httpError, ok := err.(HTTPError); ok { + jsonStr, err := json.Marshal(map[string]interface{}{ + "code": httpError.Code(), + "ok": false, + "message": httpError.Error(), + "error": err, + }) + if err != nil { + jsonStr, err = json.Marshal(map[string]interface{}{ + "code": httpError.Code(), + "ok": false, + "message": httpError.Error(), + "error": nil, + }) + if err != nil { + return err + } + } + return c.JSONBlob(httpError.Code(), jsonStr) + } + return c.JSON(500, map[string]interface{}{ + "code": 500, + "ok": false, + "message": err.Error(), + }) + } + + HTMLWriter = func(c echo.Context, err error) error { + errContext := htmlErrorTemplateCtx{ + Code: 500, + Message: "Internal Server Error", + } + if httpError, ok := err.(HTTPError); ok { + errContext.Code = httpError.Code() + errContext.Message = httpError.Error() + } else { + errContext.Message = err.Error() + } + errContext.CodeText = http.StatusText(errContext.Code) + c.Response().Status = errContext.Code + c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + return htmlErrorTemplate.Execute(c.Response().Writer, errContext) + } +) + +func Middleware(errorWriter ErrorWriter) func(next echo.HandlerFunc) echo.HandlerFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + err := next(c) + if err != nil { + if errEcho, ok := err.(*echo.HTTPError); ok { + err = NewHttp(errEcho.Code, fmt.Errorf("%s", errEcho.Message)) + } + if err := errorWriter(c, err); err != nil { + return err + } + } + return nil + } + } +} diff --git a/internal/entertainment/api.go b/internal/entertainment/api.go new file mode 100644 index 0000000..7d90b9c --- /dev/null +++ b/internal/entertainment/api.go @@ -0,0 +1,11 @@ +package entertainment + +import ( + "github.com/eternal-flame-AD/yoake/internal/db" + "github.com/labstack/echo/v4" +) + +func Register(g *echo.Group, database db.DB) { + youtube := g.Group("/youtube") + registerYoutube(youtube, database) +} diff --git a/internal/entertainment/util.go b/internal/entertainment/util.go new file mode 100644 index 0000000..86a110e --- /dev/null +++ b/internal/entertainment/util.go @@ -0,0 +1,10 @@ +package entertainment + +func contain[V comparable](slice []V, value V) bool { + for _, v := range slice { + if v == value { + return true + } + } + return false +} diff --git a/internal/entertainment/youtube.go b/internal/entertainment/youtube.go new file mode 100644 index 0000000..1349bf3 --- /dev/null +++ b/internal/entertainment/youtube.go @@ -0,0 +1,421 @@ +package entertainment + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" + "time" + + "github.com/eternal-flame-AD/yoake/internal/auth" + "github.com/eternal-flame-AD/yoake/internal/db" + "github.com/eternal-flame-AD/yoake/internal/echoerror" + "github.com/labstack/echo/v4" +) + +type YoutubeVideoStore struct { + VideoID string `json:"video_id" form:"video_id" query:"video_id"` + Meta *YoutubeVideoEmbedMeta `json:"meta,omitempty"` + Tags []string `json:"tags"` + Category string `param:"category" json:"category" form:"category" query:"category"` + + Comment string `json:"comment" form:"comment"` +} + +type YoutubeVideoEmbedMeta struct { + Title string `json:"title"` + AuthorName string `json:"author_name"` + AuthorURL string `json:"author_url"` + Type string `json:"type"` + ProviderName string `json:"provider_name"` + ProviderURL string `json:"provider_url"` + ThumbnailURL string `json:"thumbnail_url"` + ThumbnailWidth int `json:"thumbnail_width"` + ThumbnailHeight int `json:"thumbnail_height"` + Html string `json:"html"` + Version string `json:"version"` + Height int `json:"height"` + Width int `json:"width"` +} + +func GetYoutubeVideoInfo(videoUrl string) (info *YoutubeVideoEmbedMeta, err error) { + client := &http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Get("https://www.youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v=" + url.QueryEscape(videoUrl)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + info = new(YoutubeVideoEmbedMeta) + err = json.NewDecoder(resp.Body).Decode(info) + if err != nil { + return nil, err + } + return info, nil +} + +type YoutubeCategoryStore struct { + ID string `json:"id" form:"id"` + DisplayName string `json:"display_name" form:"display_name"` +} + +type YoutubeTagStore struct { + ID string `json:"id" form:"id"` + DisplayName string `json:"display_name" form:"display_name"` +} + +type YoutubeVideoDBTxn struct { + txn db.DBTxn +} + +func newYoutubeDBTxn(txn db.DBTxn) *YoutubeVideoDBTxn { + return &YoutubeVideoDBTxn{ + txn: txn, + } +} + +func (t *YoutubeVideoDBTxn) GetCategories() (categories []YoutubeCategoryStore, err error) { + err = db.GetJSON(t.txn, []byte("youtube_categories"), &categories) + return +} + +func (t *YoutubeVideoDBTxn) SetCategories(categories []YoutubeCategoryStore) (err error) { + return db.SetJSON(t.txn, []byte("youtube_categories"), categories) +} + +func (t *YoutubeVideoDBTxn) DeleteCategory(category string) (err error) { + categories, err := t.GetCategories() + if err != nil { + return err + } + newCategories := make([]YoutubeCategoryStore, 0, len(categories)) + for _, categoryS := range categories { + if categoryS.ID != category { + newCategories = append(newCategories, categoryS) + } + } + if err = t.SetCategories(newCategories); err != nil { + return err + } + + if err := t.txn.Delete([]byte("youtube_category:" + category + "_tags")); err != nil && !db.IsNotFound(err) { + return err + } + if err := t.txn.Delete([]byte("youtube_category:" + category + "_videos")); err != nil && !db.IsNotFound(err) { + return err + } + return nil +} + +func (t *YoutubeVideoDBTxn) GetTags(category string) (tags []YoutubeTagStore, err error) { + categories, err := t.GetCategories() + if err != nil { + return nil, err + } + for _, categoryS := range categories { + if categoryS.ID == category { + err = db.GetJSON(t.txn, []byte("youtube_category:"+category+"_tags"), &tags) + return + } + } + return nil, echoerror.NewHttp(404, fmt.Errorf("category not found")) +} + +func (t *YoutubeVideoDBTxn) SetTags(category string, tags []YoutubeTagStore) (err error) { + categories, err := t.GetCategories() + if err != nil { + return err + } + for _, categoryS := range categories { + if categoryS.ID == category { + + return db.SetJSON(t.txn, []byte("youtube_category:"+category+"_tags"), tags) + } + } + return echoerror.NewHttp(404, fmt.Errorf("category not found")) +} + +func (t *YoutubeVideoDBTxn) GetVideos(category string, tags []string) (videos []YoutubeVideoStore, err error) { + videos = make([]YoutubeVideoStore, 0, 16) + categories, err := t.GetCategories() + if err != nil { + return nil, err + } + for _, categoryS := range categories { + if categoryS.ID == category { + tagsAvail, err := t.GetTags(category) + if err != nil { + return nil, err + } + var tagSelected []string + for _, tag := range tags { + for _, tagAvail := range tagsAvail { + if tagAvail.ID == tag { + tagSelected = append(tagSelected, tag) + break + } + } + } + + var videosS []YoutubeVideoStore + if err = db.GetJSON(t.txn, []byte("youtube_category:"+category+"_videos"), &videosS); err != nil { + return nil, err + } + if len(tagSelected) == 0 { + return videosS, nil + } + for _, video := range videosS { + matchtag: + for _, tagA := range tagSelected { + for _, tag := range video.Tags { + if tagA == tag { + videos = append(videos, video) + break matchtag + } + } + } + } + } + } + return videos, nil +} + +func (t *YoutubeVideoDBTxn) SetVideos(category string, videos []YoutubeVideoStore) (err error) { + categories, err := t.GetCategories() + if err != nil { + return err + } + for _, categoryS := range categories { + if categoryS.ID == category { + existingTags, err := t.GetTags(category) + if err != nil { + return err + } + tagsUsed := make(map[string]YoutubeTagStore) + for _, video := range videos { + for _, tag := range video.Tags { + found := false + for _, existingTag := range existingTags { + if existingTag.ID == tag { + tagsUsed[tag] = existingTag + found = true + break + } + } + if !found { + return echoerror.NewHttp(400, fmt.Errorf("tag %s not found", tag)) + } + } + } + tagsUsedList := make([]YoutubeTagStore, 0, len(tagsUsed)) + for _, tag := range tagsUsed { + tagsUsedList = append(tagsUsedList, tag) + } + if err = t.SetTags(category, tagsUsedList); err != nil { + return err + } + return db.SetJSON(t.txn, []byte("youtube_category:"+category+"_videos"), videos) + } + } + return echoerror.NewHttp(404, fmt.Errorf("category not found")) +} + +func registerYoutube(g *echo.Group, database db.DB) { + g.GET("/categories", func(c echo.Context) error { + txn := newYoutubeDBTxn(database.NewTransaction(false)) + defer txn.txn.Discard() + + categories, err := txn.GetCategories() + if err != nil { + return err + } + return c.JSON(http.StatusOK, categories) + }) + + g.GET("/category/:category/tags", func(c echo.Context) error { + txn := newYoutubeDBTxn(database.NewTransaction(false)) + defer txn.txn.Discard() + + tags, err := txn.GetTags(c.Param("category")) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, tags) + }) + g.GET("/category/:category/videos", func(c echo.Context) error { + tags := strings.Split(c.QueryParam("tags"), ",") + txn := newYoutubeDBTxn(database.NewTransaction(false)) + defer txn.txn.Discard() + + videos, err := txn.GetVideos(c.Param("category"), tags) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, videos) + }) + adminG := g.Group("", auth.RequireMiddleware(auth.RoleAdmin)) + { + adminG.POST("/categories", func(c echo.Context) error { + var category YoutubeCategoryStore + if err := c.Bind(&category); err != nil { + return err + } + if category.DisplayName == "" { + return echoerror.NewHttp(400, fmt.Errorf("display name is required")) + } + if category.ID == "" { + category.ID = strings.ToLower( + regexp.MustCompile(`[^0-9a-zA-Z]`).ReplaceAllString(category.DisplayName, "-")) + } + txn := newYoutubeDBTxn(database.NewTransaction(true)) + defer txn.txn.Discard() + + updatedExisting := false + existingCategories, err := txn.GetCategories() + if err != nil { + if !db.IsNotFound(err) { + return err + } + } else { + for i, existingCategory := range existingCategories { + if existingCategory.ID == category.ID { + existingCategories[i].DisplayName = category.DisplayName + updatedExisting = true + } + } + } + if !updatedExisting { + existingCategories = append(existingCategories, category) + } + if err = txn.SetCategories(existingCategories); err != nil { + return err + } + if err := txn.txn.Commit(); err != nil { + return err + } + return c.JSON(http.StatusOK, category) + }) + + adminG.DELETE("/category/:category", func(c echo.Context) error { + txn := newYoutubeDBTxn(database.NewTransaction(true)) + defer txn.txn.Discard() + + if err := txn.DeleteCategory(c.Param("category")); err != nil { + return err + } + + if err := txn.txn.Commit(); err != nil { + return err + } + return c.NoContent(http.StatusOK) + }) + + adminG.POST("/category/:category/tags", func(c echo.Context) error { + var tag YoutubeTagStore + if err := c.Bind(&tag); err != nil { + return err + } + if tag.DisplayName == "" { + return echoerror.NewHttp(400, fmt.Errorf("display name is required")) + } + if tag.ID == "" { + tag.ID = strings.ToLower( + regexp.MustCompile(`[^0-9a-zA-Z]`).ReplaceAllString(tag.DisplayName, "-")) + } + txn := newYoutubeDBTxn(database.NewTransaction(true)) + defer txn.txn.Discard() + updatedExisting := false + + existingTags, err := txn.GetTags(c.Param("category")) + if err != nil { + if !db.IsNotFound(err) { + return err + } + } else { + for i, existingTag := range existingTags { + if existingTag.ID == tag.ID { + existingTags[i].DisplayName = tag.DisplayName + updatedExisting = true + } + } + } + if !updatedExisting { + existingTags = append(existingTags, tag) + } + if err = txn.SetTags(c.Param("category"), existingTags); err != nil { + return err + } + if err := txn.txn.Commit(); err != nil { + return err + } + return c.JSON(http.StatusOK, tag) + }) + + adminG.POST("/category/:category/videos", func(c echo.Context) error { + var video YoutubeVideoStore + if err := c.Bind(&video); err != nil { + return err + } + meta, err := GetYoutubeVideoInfo(video.VideoID) + if err != nil { + return err + } + video.Meta = meta + txn := newYoutubeDBTxn(database.NewTransaction(true)) + defer txn.txn.Discard() + videos, err := txn.GetVideos(c.Param("category"), nil) + updatedExisting := false + if err != nil { + if !db.IsNotFound(err) { + return err + } + } else { + for i, existingVideo := range videos { + if existingVideo.VideoID == video.VideoID { + videos[i].Tags = video.Tags + videos[i].Category = video.Category + videos[i].Meta = video.Meta + videos[i].Comment = video.Comment + updatedExisting = true + } + } + } + if !updatedExisting { + videos = append(videos, video) + } + if err := txn.SetVideos(c.Param("category"), videos); err != nil { + return err + } + if err := txn.txn.Commit(); err != nil { + return err + } + return c.JSON(http.StatusOK, video) + }) + + adminG.DELETE("/category/:category/video/:id", func(c echo.Context) error { + txn := newYoutubeDBTxn(database.NewTransaction(true)) + defer txn.txn.Discard() + videos, err := txn.GetVideos(c.Param("category"), nil) + if err != nil { + return err + } + for i, video := range videos { + if video.VideoID == c.Param("id") { + videos = append(videos[:i], videos[i+1:]...) + } + } + if err := txn.SetVideos(c.Param("category"), videos); err != nil { + return err + } + if err := txn.txn.Commit(); err != nil { + return err + } + return c.NoContent(http.StatusOK) + }) + } +} diff --git a/internal/servetpl/funcmap/auth.go b/internal/servetpl/funcmap/auth.go index ca5eed3..c999008 100644 --- a/internal/servetpl/funcmap/auth.go +++ b/internal/servetpl/funcmap/auth.go @@ -13,11 +13,3 @@ func AuthGet(c echo.Context) auth.RequestAuth { return a } } - -func AuthLogin(c echo.Context) error { - if e := auth.Login(c); e != nil { - c.Error(e) - return e - } - return nil -} diff --git a/internal/servetpl/funcmap/funcmap.go b/internal/servetpl/funcmap/funcmap.go index 2a6be46..7453240 100644 --- a/internal/servetpl/funcmap/funcmap.go +++ b/internal/servetpl/funcmap/funcmap.go @@ -109,6 +109,10 @@ func GetFuncMap() map[string]interface{} { "parse_json": ParseJSON, "json": MarshalJSON, "get_auth": AuthGet, - "auth_login": AuthLogin, + "sprintf": func(format string, input ...interface{}) interface{} { + return fmt.Sprintf(format, input...) + }, + "http": HttpRequest, + "version": Version, } } diff --git a/internal/servetpl/funcmap/http.go b/internal/servetpl/funcmap/http.go new file mode 100644 index 0000000..5309674 --- /dev/null +++ b/internal/servetpl/funcmap/http.go @@ -0,0 +1,52 @@ +package funcmap + +import ( + "io/ioutil" + "net/http" + + "github.com/PuerkitoBio/goquery" +) + +const ( + ResponseTypeHTML = "html" + ResponseTypeStrippedHTML = "html_stripped" + ResponseTypeText = "text" +) + +func HttpRequest(method string, URL string, selector string, responseType string) (data interface{}, err error) { + if method == "" { + method = http.MethodGet + } + if responseType == "" { + responseType = ResponseTypeHTML + } + req, err := http.NewRequest(method, URL, nil) + if err != nil { + return nil, err + } + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if responseType == ResponseTypeHTML || responseType == ResponseTypeStrippedHTML { + doc, err := goquery.NewDocumentFromReader(resp.Body) + if err != nil { + return nil, err + } + result := doc.Contents() + if selector != "" { + result = result.Find(selector) + } + if responseType == ResponseTypeStrippedHTML { + return result.Text(), nil + } + return result.Html() + } + response, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return string(response), nil +} diff --git a/internal/servetpl/funcmap/version.go b/internal/servetpl/funcmap/version.go new file mode 100644 index 0000000..2210b20 --- /dev/null +++ b/internal/servetpl/funcmap/version.go @@ -0,0 +1,15 @@ +package funcmap + +import "github.com/eternal-flame-AD/yoake/internal/version" + +type V struct { + Version string + BuildDate string +} + +func Version() (*V, error) { + return &V{ + Version: version.Version, + BuildDate: version.BuildDate, + }, nil +} diff --git a/internal/servetpl/middleware.go b/internal/servetpl/middleware.go index fffc2d4..b9ee9c4 100644 --- a/internal/servetpl/middleware.go +++ b/internal/servetpl/middleware.go @@ -7,6 +7,7 @@ import ( "html/template" "io" "log" + "mime" "net/http" "os" "path" @@ -107,11 +108,11 @@ func ServeTemplateDir(dir string) echo.MiddlewareFunc { log.Printf("parsing template: %s", file.Name) if path.Ext(file.File) == ".html" { - if _, err := parseTemplateFileAs[template.FuncMap](templates, file.Name, file.File); err != nil { + if _, err := ParseTemplateFileAs[template.FuncMap](templates, file.Name, file.File); err != nil { log.Panicf("templates failed to parse: %s", err) } } else { - if _, err := parseTemplateFileAs[textTemplate.FuncMap](textTemplates, file.Name, file.File); err != nil { + if _, err := ParseTemplateFileAs[textTemplate.FuncMap](textTemplates, file.Name, file.File); err != nil { log.Panicf("templates failed to parse: %s", err) } } @@ -147,6 +148,8 @@ func ServeTemplateDir(dir string) echo.MiddlewareFunc { return func(c echo.Context) error { req, resp := c.Request(), c.Response() p := path.Clean("/" + req.URL.Path) + ext := path.Ext(p) + c.Response().Header().Set(echo.HeaderContentType, mime.TypeByExtension(ext)) body := &bodyBuffer{resp: resp} defer body.WriteHeader() diff --git a/internal/servetpl/parse.go b/internal/servetpl/parse.go index 5dd9d26..628f2d0 100644 --- a/internal/servetpl/parse.go +++ b/internal/servetpl/parse.go @@ -9,7 +9,7 @@ import ( "github.com/eternal-flame-AD/yoake/internal/servetpl/funcmap" ) -func parseTemplateFileAs[M interface{ ~map[string]any }, T interface { +func ParseTemplateFileAs[M interface{ ~map[string]any }, T interface { *template.Template | *textTemplate.Template Parse(string) (T, error) New(name string) T diff --git a/internal/utilapi/handler.go b/internal/utilapi/handler.go new file mode 100644 index 0000000..682d6bf --- /dev/null +++ b/internal/utilapi/handler.go @@ -0,0 +1,41 @@ +package utilapi + +import ( + "errors" + "time" + + "github.com/alexedwards/argon2id" + "github.com/eternal-flame-AD/yoake/internal/auth" + "github.com/eternal-flame-AD/yoake/internal/echoerror" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func Register(g *echo.Group) (err error) { + limiterStore := middleware.NewRateLimiterMemoryStoreWithConfig(middleware.RateLimiterMemoryStoreConfig{ + Rate: 1, + Burst: 5, + ExpiresIn: 1 * time.Minute, + }) + + cryptoG := g.Group("/crypto") + { + cryptoG.POST("/argon2id", func(c echo.Context) error { + if passwd := c.FormValue("password"); passwd != "" { + if hash, err := argon2id.CreateHash(passwd, auth.Argon2IdParams); err != nil { + return err + } else { + return c.JSON(200, map[string]string{"hash": hash}) + } + } + return echoerror.NewHttp(400, errors.New("password not provided")) + }, middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ + Skipper: func(c echo.Context) bool { + return auth.GetRequestAuth(c).HasRole(auth.RoleAdmin) + }, + Store: limiterStore, + })) + } + + return nil +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..389d5c5 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,6 @@ +package version + +var ( + Version = "unknown" + BuildDate = "unknown" +) diff --git a/server/webroot/log.go b/server/webroot/log.go index c5bbd14..a25513b 100644 --- a/server/webroot/log.go +++ b/server/webroot/log.go @@ -101,11 +101,14 @@ func logMiddleware(category string, backend echo.MiddlewareFunc) echo.Middleware return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { logSetRequestCategory(c, category) - wrappedNext := func(c echo.Context) error { - logRemoveRequestCategory(c, category) - return next(c) + if backend != nil { + wrappedNext := func(c echo.Context) error { + logRemoveRequestCategory(c, category) + return next(c) + } + return backend(wrappedNext)(c) } - return backend(wrappedNext)(c) + return next(c) } } } diff --git a/server/webroot/server.go b/server/webroot/server.go index b3d74fb..2760152 100644 --- a/server/webroot/server.go +++ b/server/webroot/server.go @@ -7,9 +7,15 @@ import ( "github.com/eternal-flame-AD/yoake/config" "github.com/eternal-flame-AD/yoake/internal/auth" + "github.com/eternal-flame-AD/yoake/internal/canvaslms" + "github.com/eternal-flame-AD/yoake/internal/comm" + "github.com/eternal-flame-AD/yoake/internal/db" + "github.com/eternal-flame-AD/yoake/internal/echoerror" + "github.com/eternal-flame-AD/yoake/internal/entertainment" "github.com/eternal-flame-AD/yoake/internal/servetpl" "github.com/eternal-flame-AD/yoake/internal/session" "github.com/eternal-flame-AD/yoake/internal/twilio" + "github.com/eternal-flame-AD/yoake/internal/utilapi" "github.com/eternal-flame-AD/yoake/server" "github.com/gorilla/context" "github.com/gorilla/sessions" @@ -17,7 +23,7 @@ import ( "github.com/labstack/echo/v4/middleware" ) -func Init(hostname string) { +func Init(hostname string, comm *comm.CommProvider, database db.DB) { e := echo.New() webroot := config.Config().WebRoot @@ -57,14 +63,25 @@ func Init(hostname string) { e.Use(middleware.RequestLoggerWithConfig(lc)) } - e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - defer context.Clear(c.Request()) - c.Set(session.SessionStoreKeyPrefix+"cookie", (sessions.Store)(sessionCookie)) - c.Set(session.SessionStoreKeyPrefix+"fs", (sessions.Store)(fsCookie)) - return next(c) - } - }, + api := e.Group("/api", echoerror.Middleware(echoerror.JSONWriter)) + { + canvaslms.Register(api.Group("/canvas", logMiddleware("api_canvas", nil)), comm) + utilapi.Register(api.Group("/util", logMiddleware("api_util", nil))) + comm.RegisterAPIRoute(api.Group("/comm", logMiddleware("api_comm", nil))) + auth.Register(api.Group("/auth", logMiddleware("api_auth", nil))) + entertainment.Register(api.Group("/entertainment", logMiddleware("api_entertainment", nil)), database) + } + + e.Use( + echoerror.Middleware(echoerror.HTMLWriter), + func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + defer context.Clear(c.Request()) + c.Set(session.SessionStoreKeyPrefix+"cookie", (sessions.Store)(sessionCookie)) + c.Set(session.SessionStoreKeyPrefix+"fs", (sessions.Store)(fsCookie)) + return next(c) + } + }, middleware.Gzip(), auth.Middleware(sessionCookie), logMiddleware("twilio", twilio.VerifyMiddleware("/twilio", config.Config().Twilio.BaseURL)), diff --git a/webroot/auth.tpl.json b/webroot/auth.tpl.json deleted file mode 100644 index 2b7d036..0000000 --- a/webroot/auth.tpl.json +++ /dev/null @@ -1,10 +0,0 @@ -{{ $method := .Request.Method }} -{{ if eq $method "POST" }} - {{ if not ($err := (auth_login .C)) }} - { "status": "success", "message": "Login successful", "success": true } - {{ end }} -{{ else if eq $method "DELETE" }} - {{ void (auth_login .C) }} -{{ else if eq $method "GET" }} -{{ (get_auth .C) | json }} -{{ end }} \ No newline at end of file diff --git a/webroot/includes/head.tpl.html b/webroot/includes/head.tpl.html index bb03ca8..2bc29d6 100644 --- a/webroot/includes/head.tpl.html +++ b/webroot/includes/head.tpl.html @@ -1,4 +1,4 @@ - + @@ -10,7 +10,19 @@ -