diff --git a/main.go b/main.go index a2a9dcf8..b1f9b3bb 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "github.com/animenotifier/notify.moe/pages/character" "github.com/animenotifier/notify.moe/pages/charge" "github.com/animenotifier/notify.moe/pages/dashboard" + "github.com/animenotifier/notify.moe/pages/database" "github.com/animenotifier/notify.moe/pages/editanime" "github.com/animenotifier/notify.moe/pages/editor" "github.com/animenotifier/notify.moe/pages/embed" @@ -153,6 +154,10 @@ func configure(app *aero.Application) *aero.Application { app.Ajax("/editor/anilist", editor.AniList) app.Ajax("/editor/shoboi", editor.Shoboi) + // Mixed + app.Ajax("/database", database.Get) + app.Get("/api/select/:data-type/where/:field/is/:field-value", database.Select) + // Import app.Ajax("/import", listimport.Get) app.Ajax("/import/anilist/animelist", listimportanilist.Preview) diff --git a/pages/database/database.go b/pages/database/database.go new file mode 100644 index 00000000..b2cf4b24 --- /dev/null +++ b/pages/database/database.go @@ -0,0 +1,11 @@ +package database + +import ( + "github.com/aerogo/aero" + "github.com/animenotifier/notify.moe/components" +) + +// Get the dashboard. +func Get(ctx *aero.Context) string { + return ctx.HTML(components.Database()) +} diff --git a/pages/database/database.pixy b/pages/database/database.pixy new file mode 100644 index 00000000..300d19e2 --- /dev/null +++ b/pages/database/database.pixy @@ -0,0 +1,36 @@ +component Database + EditorTabs + + .widget-form + .widget + h1.mountable Database search + + .widget-section.mountable + label(for="data-type") Search + select#data-type.widget-ui-element(value="Anime") + option(value="Analytics") Analytics + option(value="Anime") Anime + option(value="AnimeList") AnimeList + option(value="Character") Character + option(value="Group") Group + option(value="Post") Post + option(value="Settings") Settings + option(value="SoundTrack") SoundTrack + option(value="Thread") Thread + option(value="User") User + + .widget-section.mountable + label(for="field") where + input#field.widget-ui-element(type="text", placeholder="Field name (e.g. Title or Title.Canonical)") + + .widget-section.mountable + label(for="field-value") is + input#field-value.widget-ui-element(type="text") + + .buttons.mountable + button.action(data-action="searchDB", data-trigger="click") + Icon("search") + span Search + + h3.text-center Results + #records \ No newline at end of file diff --git a/pages/database/database.scarlet b/pages/database/database.scarlet new file mode 100644 index 00000000..9c1638d1 --- /dev/null +++ b/pages/database/database.scarlet @@ -0,0 +1,25 @@ +#records + horizontal-wrap + justify-content space-around + margin-top content-padding + +.record + vertical + ui-element + padding 0.5rem 1rem + margin 0.5rem + +.record-id + :before + content "ID: " + +.record-view + // + +.record-view-api + // + +.record-count + text-align right + font-size 0.8rem + opacity 0.5 \ No newline at end of file diff --git a/pages/database/select.go b/pages/database/select.go new file mode 100644 index 00000000..5b1aa8f2 --- /dev/null +++ b/pages/database/select.go @@ -0,0 +1,113 @@ +package database + +import ( + "net/http" + + "github.com/aerogo/aero" + "github.com/aerogo/mirror" + "github.com/animenotifier/arn" +) + +// QueryResponse .. +type QueryResponse struct { + Results []interface{} `json:"results"` +} + +// Select ... +func Select(ctx *aero.Context) string { + dataTypeName := ctx.Get("data-type") + field := ctx.Get("field") + searchValue := ctx.Get("field-value") + + // Empty values + if dataTypeName == "+" { + dataTypeName = "" + } + + if field == "+" { + field = "" + } + + if searchValue == "+" { + searchValue = "" + } + + // Check empty parameters + if dataTypeName == "" || field == "" { + return ctx.Error(http.StatusBadRequest, "Not enough parameters", nil) + } + + // Check data type parameter + _, found := arn.DB.Types()[dataTypeName] + + if !found { + return ctx.Error(http.StatusBadRequest, "Invalid type", nil) + } + + response := &QueryResponse{ + Results: []interface{}{}, + } + + stream, err := arn.DB.All(dataTypeName) + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Error fetching data from the database", err) + } + + process := func(obj interface{}) { + _, _, value, _ := mirror.GetField(obj, field) + + if value.String() == searchValue { + response.Results = append(response.Results, obj) + } + } + + switch dataTypeName { + case "Analytics": + for obj := range stream.(chan *arn.Analytics) { + process(obj) + } + case "Anime": + for obj := range stream.(chan *arn.Anime) { + process(obj) + } + case "AnimeList": + for obj := range stream.(chan *arn.AnimeList) { + process(obj) + } + case "Character": + for obj := range stream.(chan *arn.Character) { + process(obj) + } + case "Group": + for obj := range stream.(chan *arn.Group) { + process(obj) + } + case "Post": + for obj := range stream.(chan *arn.Post) { + process(obj) + } + case "Settings": + for obj := range stream.(chan *arn.Settings) { + process(obj) + } + case "SoundTrack": + for obj := range stream.(chan *arn.SoundTrack) { + process(obj) + } + case "Thread": + for obj := range stream.(chan *arn.Thread) { + process(obj) + } + case "User": + for obj := range stream.(chan *arn.User) { + process(obj) + } + } + + for _, obj := range response.Results { + mirror.GetField(obj, field) + } + + return ctx.JSON(response) +} diff --git a/pages/editor/editor.pixy b/pages/editor/editor.pixy index ac9599b9..e62c936d 100644 --- a/pages/editor/editor.pixy +++ b/pages/editor/editor.pixy @@ -3,11 +3,12 @@ component Editor EditorTabs - p Welcome to the Editor Panel! + p.text-center.mountable Welcome to the Editor Panel! component EditorTabs .tabs Tab("Editor", "pencil", "/editor") + Tab("Search", "search", "/database") Tab("Shoboi", "calendar", "/editor/shoboi") Tab("AniList", "list", "/editor/anilist") diff --git a/scripts/Actions/Search.ts b/scripts/Actions/Search.ts index d1615cf3..0be2792f 100644 --- a/scripts/Actions/Search.ts +++ b/scripts/Actions/Search.ts @@ -14,4 +14,82 @@ export function search(arn: AnimeNotifier, search: HTMLInputElement, e: Keyboard } arn.diff("/search/" + term) +} + +// Search database +export function searchDB(arn: AnimeNotifier, input: HTMLInputElement, e: KeyboardEvent) { + if(e.ctrlKey || e.altKey) { + return + } + + let dataType = (arn.app.find("data-type") as HTMLInputElement).value || "+" + let field = (arn.app.find("field") as HTMLInputElement).value || "+" + let fieldValue = (arn.app.find("field-value") as HTMLInputElement).value || "+" + let records = arn.app.find("records") + + arn.loading(true) + + fetch(`/api/select/${dataType}/where/${field}/is/${fieldValue}`) + .then(response => { + if(response.status !== 200) { + throw response + } + + return response + }) + .then(response => response.json()) + .then(data => { + records.innerHTML = "" + let count = 0 + + if(data.results.length === 0) { + records.innerText = "No results." + return + } + + for(let record of data.results) { + count++ + + let container = document.createElement("div") + container.classList.add("record") + + let id = document.createElement("div") + id.innerText = record.id + id.classList.add("record-id") + container.appendChild(id) + + let link = document.createElement("a") + link.classList.add("record-view") + link.innerText = "Open " + dataType.toLowerCase() + + if(dataType === "User") { + link.href = "/+" + record.nick + } else { + link.href = "/" + dataType.toLowerCase() + "/" + record.id + } + + link.target = "_blank" + container.appendChild(link) + + let apiLink = document.createElement("a") + apiLink.classList.add("record-view-api") + apiLink.innerText = "JSON data" + apiLink.href = "/api/" + dataType.toLowerCase() + "/" + record.id + apiLink.target = "_blank" + container.appendChild(apiLink) + + let recordCount = document.createElement("div") + recordCount.innerText = count + "/" + data.results.length + recordCount.classList.add("record-count") + container.appendChild(recordCount) + + records.appendChild(container) + } + }) + .catch(response => { + response.text().then(text => { + arn.statusMessage.showError(text) + }) + }) + .then(() => arn.loading(false)) } \ No newline at end of file