diff --git a/assets/css/main.scss b/assets/css/main.scss index addf5ce..dc6ad7d 100644 --- a/assets/css/main.scss +++ b/assets/css/main.scss @@ -10,15 +10,6 @@ $container-max-widths: ( @import "bootstrap/scss/bootstrap.scss"; -// Navbar - -.navbar-site-visit { - display: inline-block; - line-height: 2em; - margin-bottom: 4px; - margin-right: 10px; -} - // Post list .postlist .post img { @@ -31,24 +22,19 @@ $container-max-widths: ( font-size: 0.9rem; } -// Large editor -// -// Used for edit canvases which take up the entire window +// Post form -.large-editor { +// Post edit page styling +.post-edit-page { height: 100vh; } -.large-editor main { +.post-edit-page main { display: flex; flex-direction: column; overflow: hidden; } -// Post form - -// Post edit page styling - .post-edit-page .post-form { flex: 1; display: flex; diff --git a/assets/js/controllers/edit_upload.js b/assets/js/controllers/edit_upload.js deleted file mode 100644 index 95cbb1e..0000000 --- a/assets/js/controllers/edit_upload.js +++ /dev/null @@ -1,233 +0,0 @@ -import feather from "feather-icons/dist/feather.js"; -import Handlebars from "handlebars"; -import {Controller} from "@hotwired/stimulus"; - -Handlebars.registerHelper("submit_on", function (id, event) { - return `data-action="${event}->edit-upload#updateProcessor" data-edit-upload-id-param="${id}"` -}); - -const processorFrame = Handlebars.compile(` -
-
- {{name}} - -
-
-
{{{props}}}
-
-
-`); - -const processorUIs = { - "shadow": { - label: "Shadow", - template: Handlebars.compile(` -
- -
- -
-
-
- -
- -
-
- `), - }, - "resize": { - label: "Resize", - template: Handlebars.compile(` -
- - -
-
- - -
- `), - }, -}; - -export default class UploadEditController extends Controller { - static targets = ['processList', 'preview']; - static values = { - uploadId: Number, - siteId: Number, - }; - - connect() { - this._rebuildProcessList(); - this._createSession(); - } - - async addProcessor(ev) { - ev.preventDefault(); - await this._addProcessor({ - type: "shadow" - }); - } - - async removeProcessor(ev) { - ev.preventDefault(); - let id = ev.params.id; - await this._removeProcessor(id); - } - - async saveUpload(ev) { - ev.preventDefault(); - await this._save("replace"); - } - - async saveNewUpload(ev) { - ev.preventDefault(); - await this._save("copy"); - } - - async updateProcessor(ev) { - ev.preventDefault(); - let id = ev.params.id; - - let paramParentEl = ev.target.closest('[data-role="processor-params"]'); - let params = Object.fromEntries(new FormData(paramParentEl).entries()); - - await this._updateProcessor(id, params); - } - - _rebuildProcessList() { - let el = this.processListTarget; - - if ((!this._state) || (!this._state.session) || (!this._state.session.processors)) { - return; - } - - el.innerHTML = ""; - for (let p of this._state.session.processors) { - let ui = processorUIs[p.type]; - if (!ui) { - continue; - } - let cardOuter = processorFrame({ - id: p.id, - name: ui.label, - props: ui.template(p), - }); - el.innerHTML += cardOuter; - } - - feather.replace(); - } - - async _createSession() { - try { - let resp = await fetch(`/sites/${this.siteIdValue}/imageedit/`, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - "base_upload": this.uploadIdValue, - }) - }); - - this._state = await resp.json(); - - this._rebuildProcessList(); - this.previewTarget.src = this._state.preview_url; - } catch (e) { - console.error(e); - } - } - - async _addProcessor(processor) { - try { - let resp = await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors`, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(processor) - }); - - this._state = await resp.json(); - - this._rebuildProcessList(); - this.previewTarget.src = this._state.preview_url; - } catch (e) { - console.error(e); - } - } - - async _updateProcessor(processorID, params) { - await this._doReturningState(async () => { - return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}`, { - method: 'PATCH', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - processor: { - id: processorID, - props: params, - } - }) - })).json(); - }) - } - - - async _removeProcessor(processorID) { - await this._doReturningState(async () => { - return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors/${processorID}`, { - method: 'DELETE', - })).json(); - }) - } - - async _save(mode) { - if (!this._state || !this._state.session) { - return; - } - - try { - let resp = await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/save`, { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ mode }) - }); - - if (!resp.ok) { - console.error("Save failed:", resp.statusText); - return; - } - - let result = await resp.json(); - window.location.href = `/sites/${this.siteIdValue}/uploads/${result.upload_id}`; - } catch (e) { - console.error(e); - } - } - - async _doReturningState(fn) { - try { - this._state = await fn(); - - this._rebuildProcessList(); - this.previewTarget.src = this._state.preview_url; - } catch (e) { - console.error(e); - } - - } -} \ No newline at end of file diff --git a/assets/js/controllers/postedit.js b/assets/js/controllers/postedit.js index f800c44..71328e3 100644 --- a/assets/js/controllers/postedit.js +++ b/assets/js/controllers/postedit.js @@ -60,16 +60,6 @@ export default class PosteditController extends Controller { try { const formData = new FormData(this.element); let data = Object.fromEntries(formData.entries()); - - // Special handling for categories - let categoryIDs = []; - for (let i of formData.entries()) { - if (i[0] === "category_ids") { - categoryIDs.push(parseInt(i[1])) - } - } - - data["category_ids"] = categoryIDs; data = {...data, action: action || 'save'}; const response = await fetch(this.element.getAttribute("action"), { diff --git a/assets/js/main.js b/assets/js/main.js index d3ff4c6..28451fb 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,4 +1,3 @@ -import feather from "feather-icons/dist/feather.js"; import { Application } from "@hotwired/stimulus"; import ToastController from "./controllers/toast"; @@ -8,7 +7,6 @@ import LogoutController from "./controllers/logout"; import FirstRunController from "./controllers/firstrun"; import UploadController from "./controllers/upload"; import ShowUploadController from "./controllers/show_upload"; -import EditUploadController from "./controllers/edit_upload"; import PagelistController from "./controllers/pagelist"; window.Stimulus = Application.start() @@ -19,7 +17,4 @@ Stimulus.register("logout", LogoutController); Stimulus.register("first-run", FirstRunController); Stimulus.register("upload", UploadController); Stimulus.register("show-upload", ShowUploadController); -Stimulus.register("edit-upload", EditUploadController); -Stimulus.register("pagelist", PagelistController); - -feather.replace(); \ No newline at end of file +Stimulus.register("pagelist", PagelistController); \ No newline at end of file diff --git a/cmds/server.go b/cmds/server.go index 29a8c2a..89310bd 100644 --- a/cmds/server.go +++ b/cmds/server.go @@ -111,27 +111,15 @@ Starting weiro without any arguments will start the server. lh := handlers.LoginHandler{Config: cfg, AuthService: svcs.Auth} ph := handlers.PostsHandler{PostService: svcs.Posts, CategoryService: svcs.Categories} uh := handlers.UploadsHandler{UploadsService: svcs.Uploads} - ieh := handlers.ImageEditHandlers{ImageEditService: svcs.ImageEdit} ssh := handlers.SiteSettingsHandler{SiteService: svcs.Sites} ch := handlers.CategoriesHandler{CategoryService: svcs.Categories} pgh := handlers.PagesHandler{PageService: svcs.Pages} - oih := handlers.ObsImportHandler{ObsImportService: svcs.ObsImport, ScratchDir: cfg.ScratchDir} app.Get("/login", lh.Login) app.Post("/login", lh.DoLogin) app.Post("/logout", lh.Logout) - app.Get("/", middleware.OptionalUser(svcs.Auth), ih.Index) - app.Get("/first-run", ih.FirstRun) - app.Post("/first-run", ih.FirstRunSubmit) - - app.Get("/static/*", static.New("./static")) - - app.Use(middleware.LogErrors(), middleware.RequireUser(svcs.Auth)) - - app.Get("/sites/new", ssh.New) - app.Post("/sites", ssh.Create) - siteGroup := app.Group("/sites/:siteID", middleware.RequiresSite(svcs.Sites)) + siteGroup := app.Group("/sites/:siteID", middleware.LogErrors(), middleware.RequireUser(svcs.Auth), middleware.RequiresSite(svcs.Sites)) siteGroup.Get("/posts", ph.Index) siteGroup.Get("/posts/new", ph.New) @@ -151,21 +139,10 @@ Starting weiro without any arguments will start the server. siteGroup.Post("/uploads/pending/:guid", uh.UploadPart) siteGroup.Post("/uploads/pending/:guid/finalize", uh.UploadComplete) siteGroup.Delete("/uploads/:uploadID", uh.Delete) - siteGroup.Get("/uploads/:uploadID/edit", uh.Edit) - - siteGroup.Post("/imageedit", ieh.Create) - siteGroup.Patch("/imageedit/:sessionID", ieh.PatchSession) - siteGroup.Post("/imageedit/:sessionID/processors", ieh.AddProcessor) - siteGroup.Delete("/imageedit/:sessionID/processors/:processorID", ieh.DeleteProcessor) - siteGroup.Post("/imageedit/:sessionID/save", ieh.Save) - siteGroup.Get("/imageedit/:sessionID/preview/:versionID", ieh.Preview) siteGroup.Get("/settings", ssh.General) siteGroup.Post("/settings", ssh.UpdateGeneral) - siteGroup.Get("/import/obsidian", oih.Form) - siteGroup.Post("/import/obsidian", oih.Upload) - siteGroup.Get("/categories", ch.Index) siteGroup.Get("/categories/new", ch.New) siteGroup.Get("/categories/:categoryID", ch.Edit) @@ -181,6 +158,12 @@ Starting weiro without any arguments will start the server. siteGroup.Post("/pages/:pageID", pgh.Update) siteGroup.Post("/pages/:pageID/delete", pgh.Delete) + app.Get("/", middleware.OptionalUser(svcs.Auth), ih.Index) + app.Get("/first-run", ih.FirstRun) + app.Post("/first-run", ih.FirstRunSubmit) + + app.Get("/static/*", static.New("./static")) + if err := app.Listen(":3000"); err != nil { log.Println(err) } diff --git a/handlers/imageedit.go b/handlers/imageedit.go deleted file mode 100644 index 27a01b0..0000000 --- a/handlers/imageedit.go +++ /dev/null @@ -1,165 +0,0 @@ -package handlers - -import ( - "bufio" - "io" - "log" - "net/http" - - "github.com/gofiber/fiber/v3" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services/imgedit" -) - -type ImageEditHandlers struct { - ImageEditService *imgedit.Service -} - -type sessionResponse struct { - Session *models.ImageEditSession `json:"session"` - PreviewURL string `json:"preview_url"` -} - -func (ieh ImageEditHandlers) Create(c fiber.Ctx) error { - var req struct { - BaseUploadID int64 `json:"base_upload"` - } - - if err := c.Bind().JSON(&req); err != nil { - return err - } - - res, err := ieh.ImageEditService.NewSession(c.Context(), req.BaseUploadID) - if err != nil { - return err - } - - var resp = sessionResponse{ - Session: res, - PreviewURL: res.PreviewURL(), - } - - return c.Status(http.StatusCreated).JSON(resp) -} - -func (ieh ImageEditHandlers) Preview(c fiber.Ctx) error { - log.Printf("Previewing image edit session %v/%v", c.Params("sessionID"), c.Params("versionID")) - sessionID := c.Params("sessionID") - versionID := c.Params("versionID") - - mimeTime, rw, err := ieh.ImageEditService.LoadImageVersion(c.Context(), sessionID, versionID) - if err != nil { - return err - } - - c.Set("Content-Type", mimeTime) - c.Status(http.StatusOK) - return c.SendStreamWriter(func(w *bufio.Writer) { - rw, err := rw() - if err != nil { - return - } - defer rw.Close() - - _, err = io.Copy(w, rw) - if err != nil { - return - } - }) -} - -func (ieh ImageEditHandlers) AddProcessor(c fiber.Ctx) error { - sessionID := c.Params("sessionID") - if sessionID == "" { - log.Println("No session ID") - return fiber.ErrBadRequest - } - - var req imgedit.AddProcessorReq - if err := c.Bind().Body(&req); err != nil { - log.Printf("Failed to parse request body: %v", err) - return fiber.ErrBadRequest - } - - res, err := ieh.ImageEditService.AddProcessor(c.Context(), sessionID, req) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(sessionResponse{ - Session: res, - PreviewURL: res.PreviewURL(), - }) -} - -func (ieh ImageEditHandlers) DeleteProcessor(c fiber.Ctx) error { - sessionID := c.Params("sessionID") - if sessionID == "" { - return fiber.ErrBadRequest - } - - processorID := c.Params("processorID") - if processorID == "" { - return fiber.ErrBadRequest - } - - res, err := ieh.ImageEditService.DeleteProcessor(c.Context(), sessionID, processorID) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(sessionResponse{ - Session: res, - PreviewURL: res.PreviewURL(), - }) -} - -func (ieh ImageEditHandlers) Save(c fiber.Ctx) error { - sessionID := c.Params("sessionID") - if sessionID == "" { - return fiber.ErrBadRequest - } - - var req struct { - Mode string `json:"mode"` - } - if err := c.Bind().JSON(&req); err != nil { - return fiber.ErrBadRequest - } - - result, err := ieh.ImageEditService.Save(c.Context(), sessionID, req.Mode) - if err != nil { - return err - } - - return c.Status(http.StatusOK).JSON(result) -} - -func (ieh ImageEditHandlers) PatchSession(c fiber.Ctx) error { - var req struct { - UpdateProc *imgedit.UpdateProcessorReq `json:"processor"` - } - - sessionID := c.Params("sessionID") - if sessionID == "" { - return fiber.ErrBadRequest - } - - if err := c.Bind().Body(&req); err != nil { - return err - } - log.Printf("Got request: %v", *req.UpdateProc) - - if req.UpdateProc != nil { - res, err := ieh.ImageEditService.UpdateProcessor(c.Context(), sessionID, *req.UpdateProc) - if err != nil { - return err - } - return c.Status(http.StatusOK).JSON(sessionResponse{ - Session: res, - PreviewURL: res.PreviewURL(), - }) - } - - return fiber.ErrBadRequest -} diff --git a/handlers/index.go b/handlers/index.go index 410c347..6062237 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -2,7 +2,6 @@ package handlers import ( "fmt" - "log" "net/url" "regexp" @@ -38,13 +37,6 @@ func (h IndexHandler) Index(c fiber.Ctx) error { } } - sess := session.FromContext(c) - lastSiteID, ok := sess.Get("last_site_id").(int64) - log.Printf("last site id: %v", lastSiteID) - if ok { - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", lastSiteID)) - } - site, err := h.SiteService.BestSite(c.Context(), user) if err != nil { return err diff --git a/handlers/login.go b/handlers/login.go index 34c1e96..30ed0b4 100644 --- a/handlers/login.go +++ b/handlers/login.go @@ -37,8 +37,9 @@ func (lh *LoginHandler) Logout(c fiber.Ctx) error { func (lh *LoginHandler) DoLogin(c fiber.Ctx) error { var req struct { - Username string `form:"username"` - Password string `form:"password"` + Username string `form:"username"` + Password string `form:"password"` + LoginChallenge string `form:"_login_challenge"` } if err := c.Bind().Body(&req); err != nil { return c.Status(fiber.StatusBadRequest).SendString("Failed to parse request body") @@ -50,6 +51,11 @@ func (lh *LoginHandler) DoLogin(c fiber.Ctx) error { sess := session.FromContext(c) + challenge, _ := sess.Get("_login_challenge").(string) + if challenge != req.LoginChallenge { + return c.Redirect().To("/login") + } + user, err := lh.AuthService.Login(c.Context(), req.Username, req.Password) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to login") diff --git a/handlers/middleware/errlog.go b/handlers/middleware/errlog.go index 2acac04..5b6dfa6 100644 --- a/handlers/middleware/errlog.go +++ b/handlers/middleware/errlog.go @@ -9,7 +9,7 @@ import ( func LogErrors() func(c fiber.Ctx) error { return func(c fiber.Ctx) error { if err := c.Next(); err != nil { - log.Printf("%v: error: %v\n", c.Path(), err) + log.Printf("error: %v\n", err) return err } return nil diff --git a/handlers/middleware/site.go b/handlers/middleware/site.go index 1d3ddf2..54211bc 100644 --- a/handlers/middleware/site.go +++ b/handlers/middleware/site.go @@ -5,7 +5,6 @@ import ( "emperror.dev/errors" "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/middleware/session" "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db" "lmika.dev/lmika/weiro/services/sites" @@ -33,22 +32,9 @@ func RequiresSite(sites *sites.Service) func(c fiber.Ctx) error { return err } } + c.Locals("site", site) c.SetContext(models.WithSite(c.Context(), site)) - - sitesOwnedByUser, err := sites.ListSites(c.Context()) - if err != nil { - return err - } - c.Locals("allSites", sitesOwnedByUser) - - sess := session.FromContext(c) - sess.Set("last_site_id", siteID) - - if pubTargets, err := sites.BestPubTarget(c.Context(), site); err == nil { - c.Locals("pubTarget", pubTargets) - } - return c.Next() } } diff --git a/handlers/obsimport.go b/handlers/obsimport.go deleted file mode 100644 index e20be77..0000000 --- a/handlers/obsimport.go +++ /dev/null @@ -1,50 +0,0 @@ -package handlers - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/gofiber/fiber/v3" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services/obsimport" -) - -type ObsImportHandler struct { - ObsImportService *obsimport.Service - ScratchDir string -} - -func (h ObsImportHandler) Form(c fiber.Ctx) error { - return c.Render("obsimport/form", fiber.Map{}) -} - -func (h ObsImportHandler) Upload(c fiber.Ctx) error { - site := c.Locals("site").(models.Site) - - fileHeader, err := c.FormFile("zipfile") - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "no file provided") - } - - // Save uploaded file to scratch dir - if err := os.MkdirAll(h.ScratchDir, 0755); err != nil { - return err - } - - dstPath := filepath.Join(h.ScratchDir, models.NewNanoID()+".zip") - if err := c.SaveFile(fileHeader, dstPath); err != nil { - return err - } - defer os.Remove(dstPath) - - result, err := h.ObsImportService.ImportZip(c.Context(), dstPath) - if err != nil { - return err - } - - return c.Render("obsimport/result", fiber.Map{ - "result": result, - "siteURL": fmt.Sprintf("/sites/%v/posts", site.ID), - }) -} diff --git a/handlers/posts.go b/handlers/posts.go index 0e491aa..3326533 100644 --- a/handlers/posts.go +++ b/handlers/posts.go @@ -75,7 +75,7 @@ func (ph PostsHandler) New(c fiber.Ctx) error { "post": p, "categories": cats, "selectedCategories": map[int64]bool{}, - "bodyClass": "large-editor", + "bodyClass": "post-edit-page", }) } @@ -116,7 +116,7 @@ func (ph PostsHandler) Edit(c fiber.Ctx) error { "post": post, "categories": cats, "selectedCategories": selectedCategories, - "bodyClass": "large-editor", + "bodyClass": "post-edit-page", }) })) } diff --git a/handlers/sitesettings.go b/handlers/sitesettings.go index e61ced4..0fe2100 100644 --- a/handlers/sitesettings.go +++ b/handlers/sitesettings.go @@ -12,28 +12,10 @@ type SiteSettingsHandler struct { SiteService *sites.Service } -func (s *SiteSettingsHandler) New(c fiber.Ctx) error { - return c.Render("sitesettings/new", fiber.Map{}, "layouts/bare_with_scripts") -} +func (s *SiteSettingsHandler) General(ctx fiber.Ctx) error { + site := ctx.Locals("site").(models.Site) -func (s *SiteSettingsHandler) Create(c fiber.Ctx) error { - var params sites.CreateSiteParams - if err := c.Bind().Body(¶ms); err != nil { - return err - } - - newSite, err := s.SiteService.CreateSite(c.Context(), params) - if err != nil { - return err - } - - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", newSite.ID)) -} - -func (s *SiteSettingsHandler) General(c fiber.Ctx) error { - site := c.Locals("site").(models.Site) - - return c.Render("sitesettings/general", fiber.Map{ + return ctx.Render("sitesettings/general", fiber.Map{ "site": site, "tzones": sites.ListZones(), }) diff --git a/handlers/uploads.go b/handlers/uploads.go index 3553b09..fa2cb98 100644 --- a/handlers/uploads.go +++ b/handlers/uploads.go @@ -162,24 +162,3 @@ func (uh UploadsHandler) UploadComplete(c fiber.Ctx) error { return c.Status(fiber.StatusAccepted).JSON(fiber.Map{}) } - -func (uh UploadsHandler) Edit(c fiber.Ctx) error { - uploadIDStr := c.Params("uploadID") - if uploadIDStr == "" { - return fiber.ErrBadRequest - } - uploadID, err := strconv.ParseInt(uploadIDStr, 10, 64) - if err != nil { - return fiber.ErrBadRequest - } - - upload, err := uh.UploadsService.FetchUpload(c.Context(), uploadID) - if err != nil { - return err - } - - return c.Render("uploads/edit", fiber.Map{ - "upload": upload, - "bodyClass": "large-editor", - }) -} diff --git a/layouts/simplecss/templates/layout_main.html b/layouts/simplecss/templates/layout_main.html index d3d27bd..4aa5199 100644 --- a/layouts/simplecss/templates/layout_main.html +++ b/layouts/simplecss/templates/layout_main.html @@ -13,13 +13,6 @@

{{ .Site.Title }}

{{ .Site.Tagline }}

- {{ if .Site.NavItems }} - - {{ end }}
diff --git a/models/errors.go b/models/errors.go index 2c4ae68..3efadbc 100644 --- a/models/errors.go +++ b/models/errors.go @@ -8,4 +8,3 @@ var NotFoundError = errors.New("not found") var SiteRequiredError = errors.New("site required") var DeleteDebounceError = errors.New("permanent delete too soon, try again in a few seconds") var SlugConflictError = errors.New("a record with this slug already exists") -var UnsupportedImageFormat = errors.New("unsupported image format") diff --git a/models/imgedit.go b/models/imgedit.go deleted file mode 100644 index b954402..0000000 --- a/models/imgedit.go +++ /dev/null @@ -1,62 +0,0 @@ -package models - -import ( - "crypto/md5" - "encoding/json" - "fmt" - "strings" - "time" -) - -type ImageEditSession struct { - GUID string `json:"guid"` - SiteID int64 `json:"siteId"` - UserID int64 `json:"userId"` - BaseUploadID int64 `json:"baseUploadId"` - ImageExt string `json:"imageExt"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Processors []ImageEditProcessor `json:"processors"` -} - -func (ieh ImageEditSession) PreviewURL() string { - return fmt.Sprintf("/sites/%v/imageedit/%v/preview/%v", ieh.SiteID, ieh.GUID, ieh.Processors[len(ieh.Processors)-1].VersionID) -} - -func (ieh *ImageEditSession) RecalcVersionIDs() { - for i, p := range ieh.Processors { - if i == 0 { - p.SetVersionID("") - } else { - p.SetVersionID(ieh.Processors[i-1].VersionID) - } - - ieh.Processors[i] = p - } -} - -type ImageEditProcessor struct { - ID string `json:"id"` - Type string `json:"type"` - Props json.RawMessage `json:"props"` - - // VersionID is a unique hash of the particular processor. This includes the version ID of the previous processor, - // thereby causing a change of one processor to affect the version IDs of processors down the line. - VersionID string `json:"versionId"` -} - -func (ieh *ImageEditProcessor) SetVersionID(previousVersionID string) { - var sb strings.Builder - sb.WriteString(ieh.ID) - sb.WriteString("-") - sb.WriteString(previousVersionID) - sb.WriteString("-") - sb.WriteString(ieh.Type) - sb.WriteString("-") - sb.WriteString(string(ieh.Props)) - ieh.VersionID = fmt.Sprintf("%x", md5.Sum([]byte(sb.String()))) -} - -type CopyUploadProps struct { - UploadID int64 `json:"uploadId"` -} diff --git a/models/pubmodel/sites.go b/models/pubmodel/sites.go index 9f25b2f..38ba614 100644 --- a/models/pubmodel/sites.go +++ b/models/pubmodel/sites.go @@ -6,7 +6,6 @@ import ( "iter" "lmika.dev/lmika/weiro/models" - "lmika.dev/pkg/modash/moslice" ) type Site struct { @@ -21,7 +20,3 @@ type Site struct { CategoriesOfPost func(ctx context.Context, postID int64) ([]*models.Category, error) Pages []*models.Page } - -func (s Site) NavItems() []*models.Page { - return moslice.Filter(s.Pages, func(p *models.Page) bool { return p.ShowInNav }) -} diff --git a/package-lock.json b/package-lock.json index eadf529..c4f391c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,7 @@ "dependencies": { "@hotwired/stimulus": "^3.2.2", "bootstrap": "^5.3.8", - "esbuild-sass-plugin": "^3.6.0", - "feather-icons": "^4.29.2", - "handlebars": "^4.7.8" + "esbuild-sass-plugin": "^3.6.0" }, "devDependencies": { "esbuild": "0.27.3" @@ -785,12 +783,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, "node_modules/colorjs.io": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", @@ -798,17 +790,6 @@ "license": "MIT", "peer": true }, - "node_modules/core-js": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", - "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -874,16 +855,6 @@ "sass-embedded": "^1.97.2" } }, - "node_modules/feather-icons": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.29.2.tgz", - "integrity": "sha512-0TaCFTnBTVCz6U+baY2UJNKne5ifGh7sMG4ZC2LoBWCZdIyPa+y6UiR4lEYGws1JOFWdee8KAsAIvu0VcXqiqA==", - "license": "MIT", - "dependencies": { - "classnames": "^2.2.5", - "core-js": "^3.1.3" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -893,27 +864,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -937,9 +887,9 @@ } }, "node_modules/immutable": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", - "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", "license": "MIT" }, "node_modules/is-core-module": { @@ -980,21 +930,6 @@ "node": ">=0.10.0" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -1432,15 +1367,6 @@ "node": ">=14.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1508,31 +1434,12 @@ "license": "0BSD", "peer": true }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", "license": "MIT", "peer": true - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "license": "MIT" } } } diff --git a/package.json b/package.json index 3455630..64e6fca 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,6 @@ "dependencies": { "@hotwired/stimulus": "^3.2.2", "bootstrap": "^5.3.8", - "esbuild-sass-plugin": "^3.6.0", - "feather-icons": "^4.29.2", - "handlebars": "^4.7.8" + "esbuild-sass-plugin": "^3.6.0" } } diff --git a/providers/db/categories.go b/providers/db/categories.go index 23a9e67..72fac94 100644 --- a/providers/db/categories.go +++ b/providers/db/categories.go @@ -82,8 +82,8 @@ func (db *Provider) SelectCategoriesOfPost(ctx context.Context, postID int64) ([ return cats, nil } -func (db *Provider) SelectPublishedPostsOfCategory(ctx context.Context, categoryID int64, pp PagingParams) ([]*models.Post, error) { - rows, err := db.queries.SelectPublishedPostsOfCategory(ctx, sqlgen.SelectPublishedPostsOfCategoryParams{ +func (db *Provider) SelectPostsOfCategory(ctx context.Context, categoryID int64, pp PagingParams) ([]*models.Post, error) { + rows, err := db.queries.SelectPostsOfCategory(ctx, sqlgen.SelectPostsOfCategoryParams{ CategoryID: categoryID, Limit: pp.Limit, Offset: pp.Offset, diff --git a/providers/db/gen/sqlgen/categories.sql.go b/providers/db/gen/sqlgen/categories.sql.go index f6a291f..d5bc40d 100644 --- a/providers/db/gen/sqlgen/categories.sql.go +++ b/providers/db/gen/sqlgen/categories.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: categories.sql package sqlgen @@ -227,7 +227,7 @@ func (q *Queries) SelectCategoryBySlugAndSite(ctx context.Context, arg SelectCat return i, err } -const selectPublishedPostsOfCategory = `-- name: SelectPublishedPostsOfCategory :many +const selectPostsOfCategory = `-- name: SelectPostsOfCategory :many SELECT p.id, p.site_id, p.state, p.guid, p.title, p.body, p.slug, p.created_at, p.updated_at, p.published_at, p.deleted_at FROM posts p INNER JOIN post_categories pc ON pc.post_id = p.id WHERE pc.category_id = ? AND p.state = 0 AND p.deleted_at = 0 @@ -235,14 +235,14 @@ ORDER BY p.published_at DESC LIMIT ? OFFSET ? ` -type SelectPublishedPostsOfCategoryParams struct { +type SelectPostsOfCategoryParams struct { CategoryID int64 Limit int64 Offset int64 } -func (q *Queries) SelectPublishedPostsOfCategory(ctx context.Context, arg SelectPublishedPostsOfCategoryParams) ([]Post, error) { - rows, err := q.db.QueryContext(ctx, selectPublishedPostsOfCategory, arg.CategoryID, arg.Limit, arg.Offset) +func (q *Queries) SelectPostsOfCategory(ctx context.Context, arg SelectPostsOfCategoryParams) ([]Post, error) { + rows, err := q.db.QueryContext(ctx, selectPostsOfCategory, arg.CategoryID, arg.Limit, arg.Offset) if err != nil { return nil, err } diff --git a/providers/db/gen/sqlgen/db.go b/providers/db/gen/sqlgen/db.go index 7d9d9e7..8eab959 100644 --- a/providers/db/gen/sqlgen/db.go +++ b/providers/db/gen/sqlgen/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 package sqlgen diff --git a/providers/db/gen/sqlgen/models.go b/providers/db/gen/sqlgen/models.go index 348c1ab..3df1193 100644 --- a/providers/db/gen/sqlgen/models.go +++ b/providers/db/gen/sqlgen/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 package sqlgen diff --git a/providers/db/gen/sqlgen/pages.sql.go b/providers/db/gen/sqlgen/pages.sql.go index 7dd5105..1d53291 100644 --- a/providers/db/gen/sqlgen/pages.sql.go +++ b/providers/db/gen/sqlgen/pages.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: pages.sql package sqlgen diff --git a/providers/db/gen/sqlgen/pending_uploads.sql.go b/providers/db/gen/sqlgen/pending_uploads.sql.go index a831bbe..63eeb60 100644 --- a/providers/db/gen/sqlgen/pending_uploads.sql.go +++ b/providers/db/gen/sqlgen/pending_uploads.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: pending_uploads.sql package sqlgen diff --git a/providers/db/gen/sqlgen/posts.sql.go b/providers/db/gen/sqlgen/posts.sql.go index b1d3afb..ef3d170 100644 --- a/providers/db/gen/sqlgen/posts.sql.go +++ b/providers/db/gen/sqlgen/posts.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: posts.sql package sqlgen @@ -200,54 +200,6 @@ func (q *Queries) SelectPostsOfSite(ctx context.Context, arg SelectPostsOfSitePa return items, nil } -const selectPublishedPostsOfSite = `-- name: SelectPublishedPostsOfSite :many -SELECT id, site_id, state, guid, title, body, slug, created_at, updated_at, published_at, deleted_at -FROM posts -WHERE site_id = ?1 AND state = 0 AND deleted_at = 0 -ORDER BY published_at DESC LIMIT ?3 OFFSET ?2 -` - -type SelectPublishedPostsOfSiteParams struct { - SiteID int64 - Offset int64 - Limit int64 -} - -func (q *Queries) SelectPublishedPostsOfSite(ctx context.Context, arg SelectPublishedPostsOfSiteParams) ([]Post, error) { - rows, err := q.db.QueryContext(ctx, selectPublishedPostsOfSite, arg.SiteID, arg.Offset, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Post - for rows.Next() { - var i Post - if err := rows.Scan( - &i.ID, - &i.SiteID, - &i.State, - &i.Guid, - &i.Title, - &i.Body, - &i.Slug, - &i.CreatedAt, - &i.UpdatedAt, - &i.PublishedAt, - &i.DeletedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const softDeletePost = `-- name: SoftDeletePost :exec UPDATE posts SET deleted_at = ? WHERE id = ? ` diff --git a/providers/db/gen/sqlgen/pubtargets.sql.go b/providers/db/gen/sqlgen/pubtargets.sql.go index cd5cfa6..69c09df 100644 --- a/providers/db/gen/sqlgen/pubtargets.sql.go +++ b/providers/db/gen/sqlgen/pubtargets.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: pubtargets.sql package sqlgen diff --git a/providers/db/gen/sqlgen/sites.sql.go b/providers/db/gen/sqlgen/sites.sql.go index 797eaad..80ccbc0 100644 --- a/providers/db/gen/sqlgen/sites.sql.go +++ b/providers/db/gen/sqlgen/sites.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: sites.sql package sqlgen diff --git a/providers/db/gen/sqlgen/uploads.sql.go b/providers/db/gen/sqlgen/uploads.sql.go index 7ad3828..189de2d 100644 --- a/providers/db/gen/sqlgen/uploads.sql.go +++ b/providers/db/gen/sqlgen/uploads.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: uploads.sql package sqlgen @@ -18,7 +18,7 @@ func (q *Queries) DeleteUpload(ctx context.Context, id int64) error { return err } -const insertUpload = `-- name: InsertUpload :one +const insertUpload = `-- name: InsertUpload :exec INSERT INTO uploads ( site_id, guid, @@ -43,8 +43,8 @@ type InsertUploadParams struct { CreatedAt int64 } -func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertUpload, +func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) error { + _, err := q.db.ExecContext(ctx, insertUpload, arg.SiteID, arg.Guid, arg.MimeType, @@ -54,9 +54,7 @@ func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) (int arg.Alt, arg.CreatedAt, ) - var id int64 - err := row.Scan(&id) - return id, err + return err } const selectUploadByID = `-- name: SelectUploadByID :one @@ -156,17 +154,3 @@ func (q *Queries) UpdateUpload(ctx context.Context, arg UpdateUploadParams) erro _, err := q.db.ExecContext(ctx, updateUpload, arg.Alt, arg.ID) return err } - -const updateUploadFileSize = `-- name: UpdateUploadFileSize :exec -UPDATE uploads SET file_size = ? WHERE id = ? -` - -type UpdateUploadFileSizeParams struct { - FileSize int64 - ID int64 -} - -func (q *Queries) UpdateUploadFileSize(ctx context.Context, arg UpdateUploadFileSizeParams) error { - _, err := q.db.ExecContext(ctx, updateUploadFileSize, arg.FileSize, arg.ID) - return err -} diff --git a/providers/db/gen/sqlgen/users.sql.go b/providers/db/gen/sqlgen/users.sql.go index a70a3bf..6007589 100644 --- a/providers/db/gen/sqlgen/users.sql.go +++ b/providers/db/gen/sqlgen/users.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.30.0 +// sqlc v1.28.0 // source: users.sql package sqlgen diff --git a/providers/db/posts.go b/providers/db/posts.go index 3b86aaf..7f58d1a 100644 --- a/providers/db/posts.go +++ b/providers/db/posts.go @@ -47,23 +47,6 @@ func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64, showDel return posts, nil } -func (db *Provider) SelectPublishedPostsOfSite(ctx context.Context, siteID int64, pp PagingParams) ([]*models.Post, error) { - rows, err := db.queries.SelectPublishedPostsOfSite(ctx, sqlgen.SelectPublishedPostsOfSiteParams{ - SiteID: siteID, - Limit: pp.Limit, - Offset: pp.Offset, - }) - if err != nil { - return nil, err - } - - posts := make([]*models.Post, len(rows)) - for i, row := range rows { - posts[i] = dbPostToPost(row) - } - return posts, nil -} - func (db *Provider) SelectPost(ctx context.Context, postID int64) (*models.Post, error) { row, err := db.queries.SelectPost(ctx, postID) if err != nil { diff --git a/providers/db/uploads.go b/providers/db/uploads.go index b3033ab..006b7cc 100644 --- a/providers/db/uploads.go +++ b/providers/db/uploads.go @@ -44,7 +44,7 @@ func (db *Provider) SelectUploadBySiteIDAndSlug(ctx context.Context, siteID int6 func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error { if upload.ID == 0 { - newID, err := db.queries.InsertUpload(ctx, sqlgen.InsertUploadParams{ + if err := db.queries.InsertUpload(ctx, sqlgen.InsertUploadParams{ SiteID: upload.SiteID, Guid: upload.GUID, MimeType: upload.MIMEType, @@ -53,11 +53,9 @@ func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error Slug: upload.Slug, Alt: upload.Alt, CreatedAt: upload.CreatedAt.Unix(), - }) - if err != nil { + }); err != nil { return err } - upload.ID = newID return nil } @@ -67,13 +65,6 @@ func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error }) } -func (db *Provider) UpdateUploadFileSize(ctx context.Context, id int64, fileSize int64) error { - return db.queries.UpdateUploadFileSize(ctx, sqlgen.UpdateUploadFileSizeParams{ - FileSize: fileSize, - ID: id, - }) -} - func (db *Provider) DeleteUpload(ctx context.Context, id int64) error { return db.queries.DeleteUpload(ctx, id) } diff --git a/providers/markdown/renderer.go b/providers/markdown/renderer.go index 828ba96..aedd184 100644 --- a/providers/markdown/renderer.go +++ b/providers/markdown/renderer.go @@ -22,7 +22,7 @@ type Renderer struct { func NewRendererForUI() *Renderer { mdParser := goldmark.New( - goldmark.WithExtensions(extension.GFM, extension.Footnote), + goldmark.WithExtensions(extension.GFM), goldmark.WithRendererOptions( gm_html.WithUnsafe(), ), @@ -48,7 +48,7 @@ func NewRendererForUI() *Renderer { func NewRendererForSite() *Renderer { mdParser := goldmark.New( - goldmark.WithExtensions(extension.GFM, extension.Footnote), + goldmark.WithExtensions(extension.GFM), goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), diff --git a/providers/sitebuilder/builder.go b/providers/sitebuilder/builder.go index d0bf17b..71ce926 100644 --- a/providers/sitebuilder/builder.go +++ b/providers/sitebuilder/builder.go @@ -49,7 +49,6 @@ func New(site pubmodel.Site, opts Options) (*Builder, error) { mdRenderer: markdown.NewRendererForSite(), postMDProcessors: []postMDProcessor{ uploadAbsoluteURL, - removeFootnoteHRs, }, }, nil } diff --git a/providers/sitebuilder/processors.go b/providers/sitebuilder/processors.go index 605d077..c699160 100644 --- a/providers/sitebuilder/processors.go +++ b/providers/sitebuilder/processors.go @@ -35,8 +35,3 @@ func uploadAbsoluteURL(site pubmodel.Site, dom *goquery.Document) error { }) return nil } - -func removeFootnoteHRs(site pubmodel.Site, dom *goquery.Document) error { - dom.Find("div.footnotes > hr").Remove() - return nil -} diff --git a/providers/uploadfiles/provider.go b/providers/uploadfiles/provider.go index 610a6f9..2eb84e4 100644 --- a/providers/uploadfiles/provider.go +++ b/providers/uploadfiles/provider.go @@ -66,11 +66,6 @@ func copyFile(src, dst string) error { return err } -func (p *Provider) ReplaceFile(site models.Site, up models.Upload, srcPath string) error { - fullPath := p.uploadFileName(site, up) - return copyFile(srcPath, fullPath) -} - func (p *Provider) OpenUpload(site models.Site, up models.Upload) (io.ReadCloser, error) { fullPath := p.uploadFileName(site, up) return os.Open(fullPath) diff --git a/services/imgedit/processing.go b/services/imgedit/processing.go deleted file mode 100644 index ec84199..0000000 --- a/services/imgedit/processing.go +++ /dev/null @@ -1,171 +0,0 @@ -package imgedit - -import ( - "context" - "encoding/json" - "fmt" - "image" - "image/color" - "os" - "path/filepath" - - "github.com/disintegration/imaging" - "lmika.dev/lmika/weiro/models" -) - -type imageProcessor struct { - newParams func() any - processImage func(ctx context.Context, srcImg image.Image, params any) (image.Image, error) -} - -type shadowProcessorArgs struct { - Color string `json:"color"` - OffsetY int `json:"offset_y,string"` -} - -var processors = map[string]imageProcessor{ - "shadow": { - newParams: func() any { - return &shadowProcessorArgs{ - Color: "#000000", - OffsetY: 0, - } - }, - processImage: func(ctx context.Context, srcImg image.Image, params any) (image.Image, error) { - p := params.(*shadowProcessorArgs) - - shadowColor, err := parseHexColor(p.Color) - if err != nil { - return nil, fmt.Errorf("invalid shadow color: %w", err) - } - - shadow := makeBoxShadow(srcImg, shadowColor, 4, 10, p.OffsetY) - composit := imaging.OverlayCenter(shadow, srcImg, 1.0) - return composit, nil - }, - }, -} - -func (s *Service) reprocess(ctx context.Context, session *models.ImageEditSession) (imageSource, error) { - var img imageSource - - for _, p := range session.Processors { - // Check if there's currently a cached image of this processor - cachedImageFile := filepath.Join(s.scratchDir, session.GUID, fmt.Sprintf("%v.%v", p.VersionID, session.ImageExt)) - if s, err := os.Stat(cachedImageFile); err == nil && !s.IsDir() { - img = fileImageSource(cachedImageFile) - continue - } - - // Need to process the image - var srcImg image.Image - if img != nil { - var err error - srcImg, err = img.image() - if err != nil { - return nil, err - } - } - - resImg, err := s.processImage(ctx, srcImg, p) - if err != nil { - return nil, err - } - - // Cache the processed image - if err := imaging.Save(resImg, cachedImageFile); err != nil { - return nil, err - } - img = imageImageSource{resImg} - } - - return img, nil -} - -func (s *Service) processImage(ctx context.Context, srcImg image.Image, processor models.ImageEditProcessor) (image.Image, error) { - switch processor.Type { - case "copy-upload": - var p models.CopyUploadProps - if err := json.Unmarshal(processor.Props, &p); err != nil { - return nil, err - } - - _, rc, err := s.uploadService.OpenUpload(ctx, p.UploadID) - if err != nil { - return nil, err - } - - f, err := rc() - if err != nil { - return nil, err - } - defer f.Close() - - return imaging.Decode(f) - } - - proc, ok := processors[processor.Type] - if !ok { - return nil, fmt.Errorf("unknown processor type: %v", processor.Type) - } - - paramType := proc.newParams() - if err := json.Unmarshal(processor.Props, paramType); err != nil { - return nil, err - } - return proc.processImage(ctx, srcImg, paramType) -} - -type imageSource interface { - image() (image.Image, error) -} - -type fileImageSource string - -func (f fileImageSource) image() (image.Image, error) { - return imaging.Open(string(f)) -} - -type imageImageSource struct { - img image.Image -} - -func (i imageImageSource) image() (image.Image, error) { - return i.img, nil -} - -func parseHexColor(s string) (color.Color, error) { - // Remove leading hash if present - if len(s) > 0 && s[0] == '#' { - s = s[1:] - } - - // Parse based on length - var r, g, b, a uint8 - switch len(s) { - case 6: - // RGB format - var rgb uint32 - if _, err := fmt.Sscanf(s, "%06x", &rgb); err != nil { - return nil, fmt.Errorf("invalid hex color format: %w", err) - } - r = uint8((rgb >> 16) & 0xFF) - g = uint8((rgb >> 8) & 0xFF) - b = uint8(rgb & 0xFF) - a = 0xFF - case 8: - // RGBA format - var rgba uint32 - if _, err := fmt.Sscanf(s, "%08x", &rgba); err != nil { - return nil, fmt.Errorf("invalid hex color format: %w", err) - } - r = uint8((rgba >> 24) & 0xFF) - g = uint8((rgba >> 16) & 0xFF) - b = uint8((rgba >> 8) & 0xFF) - a = uint8(rgba & 0xFF) - default: - return nil, fmt.Errorf("invalid hex color length: expected 6 or 8 characters, got %d", len(s)) - } - - return color.RGBA{R: r, G: g, B: b, A: a}, nil -} diff --git a/services/imgedit/service.go b/services/imgedit/service.go deleted file mode 100644 index 926633c..0000000 --- a/services/imgedit/service.go +++ /dev/null @@ -1,266 +0,0 @@ -package imgedit - -import ( - "context" - "encoding/json" - "fmt" - "io" - "time" - - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services/uploads" - "lmika.dev/pkg/modash/moslice" -) - -type Service struct { - scratchDir string - uploadService *uploads.Service - sessionStore *sessionStore -} - -func New( - uploadService *uploads.Service, - scratchDir string, -) *Service { - return &Service{ - scratchDir: scratchDir, - uploadService: uploadService, - sessionStore: &sessionStore{baseDir: scratchDir}, - } -} - -func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (*models.ImageEditSession, error) { - site, user, err := s.fetchSiteAndUser(ctx) - if err != nil { - return nil, err - } - - upload, _, err := s.uploadService.OpenUpload(ctx, baseUploadID) - if err != nil { - return nil, err - } - - var ext string - switch upload.MIMEType { - case "image/jpeg": - ext = "jpg" - case "image/png": - ext = "png" - default: - return nil, models.UnsupportedImageFormat - } - - newSession := models.ImageEditSession{ - GUID: models.NewNanoID(), - SiteID: site.ID, - UserID: user.ID, - BaseUploadID: baseUploadID, - ImageExt: ext, - CreatedAt: time.Now().UTC(), - UpdatedAt: time.Now().UTC(), - Processors: []models.ImageEditProcessor{ - { - ID: models.NewNanoID(), - Type: "copy-upload", - Props: mustToJSON(models.CopyUploadProps{UploadID: baseUploadID}), - }, - }, - } - - newSession.RecalcVersionIDs() - if err := s.sessionStore.save(&newSession); err != nil { - return nil, err - } - - if _, err := s.reprocess(ctx, &newSession); err != nil { - return nil, err - } - - return &newSession, nil -} - -func (s *Service) LoadImageVersion(ctx context.Context, sessionID string, versionID string) (mimeType string, rw func() (io.ReadCloser, error), err error) { - session, err := s.loadAndVerifySession(ctx, sessionID) - if err != nil { - return "", nil, err - } - - return s.sessionStore.getImage(session, versionID+"."+session.ImageExt) -} - -type AddProcessorReq struct { - Type string `json:"type"` -} - -func (s *Service) AddProcessor(ctx context.Context, sessionID string, req AddProcessorReq) (*models.ImageEditSession, error) { - session, err := s.loadAndVerifySession(ctx, sessionID) - if err != nil { - return nil, err - } - - proc, ok := processors[req.Type] - if !ok { - return nil, fmt.Errorf("unknown processor type: %v", req.Type) - } - - paramType := proc.newParams() - paramBytes, err := json.Marshal(paramType) - if err != nil { - return nil, err - } - - session.Processors = append(session.Processors, models.ImageEditProcessor{ - ID: models.NewNanoID(), - Type: req.Type, - Props: paramBytes, - }) - - session.RecalcVersionIDs() - if err := s.sessionStore.save(session); err != nil { - return nil, err - } - - if _, err := s.reprocess(ctx, session); err != nil { - return nil, err - } - - return session, nil -} - -func (s *Service) DeleteProcessor(ctx context.Context, sessionID, processorID string) (*models.ImageEditSession, error) { - session, err := s.loadAndVerifySession(ctx, sessionID) - if err != nil { - return nil, err - } - - session.Processors = moslice.Filter(session.Processors, func(p models.ImageEditProcessor) bool { return p.ID != processorID }) - session.RecalcVersionIDs() - if err := s.sessionStore.save(session); err != nil { - return nil, err - } - - if _, err := s.reprocess(ctx, session); err != nil { - return nil, err - } - - return session, nil -} - -type UpdateProcessorReq struct { - ID string `json:"id"` - Props json.RawMessage `json:"props"` -} - -func (s *Service) UpdateProcessor(ctx context.Context, sessionID string, req UpdateProcessorReq) (*models.ImageEditSession, error) { - session, err := s.loadAndVerifySession(ctx, sessionID) - if err != nil { - return nil, err - } - - for i, p := range session.Processors { - if p.ID == req.ID { - session.Processors[i].Props = req.Props - break - } - } - - session.RecalcVersionIDs() - if err := s.sessionStore.save(session); err != nil { - return nil, err - } - if _, err := s.reprocess(ctx, session); err != nil { - return nil, err - } - - return session, nil -} - -type SaveResult struct { - UploadID int64 `json:"upload_id"` -} - -func (s *Service) Save(ctx context.Context, sessionID string, mode string) (*SaveResult, error) { - session, err := s.loadAndVerifySession(ctx, sessionID) - if err != nil { - return nil, err - } - - if len(session.Processors) == 0 { - return nil, fmt.Errorf("no processors in session") - } - - lastProc := session.Processors[len(session.Processors)-1] - finalImagePath := fmt.Sprintf("%v/%v/%v.%v", s.scratchDir, session.GUID, lastProc.VersionID, session.ImageExt) - - var mimeType string - switch session.ImageExt { - case "jpg", "jpeg": - mimeType = "image/jpeg" - case "png": - mimeType = "image/png" - } - - var uploadID int64 - switch mode { - case "replace": - upload, err := s.uploadService.ReplaceUploadFile(ctx, session.BaseUploadID, finalImagePath) - if err != nil { - return nil, err - } - uploadID = upload.ID - case "copy": - baseUpload, _, err := s.uploadService.OpenUpload(ctx, session.BaseUploadID) - if err != nil { - return nil, err - } - upload, err := s.uploadService.CreateUploadFromFile(ctx, finalImagePath, baseUpload.Filename, mimeType) - if err != nil { - return nil, err - } - uploadID = upload.ID - default: - return nil, fmt.Errorf("unknown save mode: %v", mode) - } - - s.sessionStore.delete(session.GUID) - - return &SaveResult{UploadID: uploadID}, nil -} - -func (s *Service) loadAndVerifySession(ctx context.Context, sessionID string) (*models.ImageEditSession, error) { - site, user, err := s.fetchSiteAndUser(ctx) - if err != nil { - return nil, err - } - - session, err := s.sessionStore.get(sessionID) - if err != nil { - return nil, err - } else if session.SiteID != site.ID || session.UserID != user.ID { - return nil, models.PermissionError - } - return session, nil -} - -func (s *Service) fetchSiteAndUser(ctx context.Context) (models.Site, models.User, error) { - user, ok := models.GetUser(ctx) - if !ok { - return models.Site{}, models.User{}, models.UserRequiredError - } - - site, ok := models.GetSite(ctx) - if !ok { - return models.Site{}, models.User{}, models.SiteRequiredError - } - - if site.OwnerID != user.ID { - return models.Site{}, models.User{}, models.PermissionError - } - - return site, user, nil -} - -func mustToJSON(a any) json.RawMessage { - b, _ := json.Marshal(a) - return b -} diff --git a/services/imgedit/shadow.go b/services/imgedit/shadow.go deleted file mode 100644 index 4a308d0..0000000 --- a/services/imgedit/shadow.go +++ /dev/null @@ -1,35 +0,0 @@ -package imgedit - -import ( - "image" - "image/color" - - "github.com/disintegration/imaging" -) - -func makeBoxShadow(maskImg image.Image, shadowColor color.Color, sigma float64, shadowMargin, offsetY int) image.Image { - w, h := maskImg.Bounds().Dx(), maskImg.Bounds().Dy() - cr, cg, cb, _ := shadowColor.RGBA() - cr8, cg8, cb8 := uint8(cr>>8), uint8(cg>>8), uint8(cb>>8) - - // New box image - backing := image.NewNRGBA(image.Rect(0, 0, w+shadowMargin*2, h+shadowMargin*2+offsetY)) - newImg := image.NewNRGBA(image.Rect(0, 0, w+shadowMargin*2, h+shadowMargin*2+offsetY)) - for x := 0; x < w+shadowMargin*2; x++ { - for y := 0; y < h+shadowMargin*2; y++ { - var c = color.NRGBA{R: 255, G: 255, B: 255, A: 0} - if x >= shadowMargin-4 && y >= shadowMargin-4 && x <= w+shadowMargin+4 && y <= h+shadowMargin+4 { - _, _, _, a := maskImg.At(x-shadowMargin, y-shadowMargin).RGBA() - c = color.NRGBA{R: cr8, G: cg8, B: cb8, A: uint8(a >> 8)} - } - backing.SetNRGBA(x, y, color.NRGBA{R: 255, G: 255, B: 255, A: 0}) - newImg.SetNRGBA(x, y+offsetY, c) - } - } - - // Blur - blurredImage := imaging.Blur(newImg, sigma) - backing = imaging.OverlayCenter(backing, blurredImage, 0.6) - - return backing -} diff --git a/services/imgedit/store.go b/services/imgedit/store.go deleted file mode 100644 index df3403a..0000000 --- a/services/imgedit/store.go +++ /dev/null @@ -1,70 +0,0 @@ -package imgedit - -import ( - "encoding/json" - "io" - "os" - "path/filepath" - - "lmika.dev/lmika/weiro/models" -) - -type sessionStore struct { - baseDir string -} - -func (ss *sessionStore) save(newSession *models.ImageEditSession) error { - sessionMeta, err := json.Marshal(newSession) - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Join(ss.baseDir, newSession.GUID), 0755); err != nil { - return err - } - if err := os.WriteFile(filepath.Join(ss.baseDir, newSession.GUID, "session.json"), sessionMeta, 0644); err != nil { - return err - } - return nil -} - -func (ss *sessionStore) get(guid string) (*models.ImageEditSession, error) { - sessionDataBts, err := os.ReadFile(filepath.Join(ss.baseDir, guid, "session.json")) - if err != nil { - return nil, err - } - - sessionData := models.ImageEditSession{} - if err := json.Unmarshal(sessionDataBts, &sessionData); err != nil { - return nil, err - } - - return &sessionData, nil -} - -func (ss *sessionStore) delete(guid string) { - os.RemoveAll(filepath.Join(ss.baseDir, guid)) -} - -func (ss *sessionStore) getImage(session *models.ImageEditSession, imageFilename string) (string, func() (io.ReadCloser, error), error) { - fullPath := filepath.Join(ss.baseDir, session.GUID, imageFilename) - if s, err := os.Stat(fullPath); err != nil { - return "", nil, err - } else if s.IsDir() { - return "", nil, os.ErrNotExist - } - - var mimeType string - switch filepath.Ext(imageFilename) { - case ".jpg", ".jpeg": - mimeType = "image/jpeg" - case ".png": - mimeType = "image/png" - default: - return "", nil, models.UnsupportedImageFormat - } - - return mimeType, func() (io.ReadCloser, error) { - return os.Open(fullPath) - }, nil -} diff --git a/services/obsimport/service.go b/services/obsimport/service.go deleted file mode 100644 index 0852031..0000000 --- a/services/obsimport/service.go +++ /dev/null @@ -1,229 +0,0 @@ -package obsimport - -import ( - "archive/zip" - "bufio" - "context" - "fmt" - "io" - "log" - "mime" - "os" - "path/filepath" - "strings" - "time" - - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/providers/db" - "lmika.dev/lmika/weiro/providers/uploadfiles" - "lmika.dev/lmika/weiro/services/publisher" -) - -type Service struct { - db *db.Provider - up *uploadfiles.Provider - publisher *publisher.Queue - scratchDir string -} - -func New(db *db.Provider, up *uploadfiles.Provider, publisher *publisher.Queue, scratchDir string) *Service { - return &Service{ - db: db, - up: up, - publisher: publisher, - scratchDir: scratchDir, - } -} - -type ImportResult struct { - PostsImported int - UploadsImported int -} - -func (s *Service) ImportZip(ctx context.Context, zipPath string) (ImportResult, error) { - site, ok := models.GetSite(ctx) - if !ok { - return ImportResult{}, models.SiteRequiredError - } - - zr, err := zip.OpenReader(zipPath) - if err != nil { - return ImportResult{}, fmt.Errorf("open zip: %w", err) - } - defer zr.Close() - - var result ImportResult - - for _, f := range zr.File { - if f.FileInfo().IsDir() { - continue - } - - ext := strings.ToLower(filepath.Ext(f.Name)) - if ext == ".md" || ext == ".markdown" { - if err := s.importNote(ctx, site, f); err != nil { - log.Printf("warn: skipping note %s: %v", f.Name, err) - continue - } - result.PostsImported++ - } else if isAttachment(ext) { - if err := s.importAttachment(ctx, site, f); err != nil { - log.Printf("warn: skipping attachment %s: %v", f.Name, err) - continue - } - result.UploadsImported++ - } - } - - s.publisher.Queue(site) - - return result, nil -} - -func (s *Service) importNote(ctx context.Context, site models.Site, f *zip.File) error { - rc, err := f.Open() - if err != nil { - return err - } - defer rc.Close() - - data, err := io.ReadAll(rc) - if err != nil { - return err - } - - body := stripFrontMatter(string(data)) - title := strings.TrimSuffix(filepath.Base(f.Name), filepath.Ext(f.Name)) - publishedAt := f.Modified - if publishedAt.IsZero() { - publishedAt = time.Now() - } - - renderTZ, err := time.LoadLocation(site.Timezone) - if err != nil { - renderTZ = time.UTC - } - publishedAt = publishedAt.In(renderTZ) - - post := &models.Post{ - SiteID: site.ID, - GUID: models.NewNanoID(), - State: models.StatePublished, - Title: title, - Body: body, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - PublishedAt: publishedAt, - } - post.Slug = post.BestSlug() - - return s.db.SavePost(ctx, post) -} - -func (s *Service) importAttachment(ctx context.Context, site models.Site, f *zip.File) error { - rc, err := f.Open() - if err != nil { - return err - } - defer rc.Close() - - // Write to a temp file in scratch dir - if err := os.MkdirAll(s.scratchDir, 0755); err != nil { - return err - } - - tmpFile, err := os.CreateTemp(s.scratchDir, "obsimport-*"+filepath.Ext(f.Name)) - if err != nil { - return err - } - tmpPath := tmpFile.Name() - - if _, err := io.Copy(tmpFile, rc); err != nil { - tmpFile.Close() - os.Remove(tmpPath) - return err - } - tmpFile.Close() - - filename := filepath.Base(f.Name) - mimeType := mime.TypeByExtension(filepath.Ext(filename)) - if mimeType == "" { - mimeType = "application/octet-stream" - } - - stat, err := os.Stat(tmpPath) - if err != nil { - os.Remove(tmpPath) - return err - } - - newUploadGUID := models.NewNanoID() - newTime := time.Now().UTC() - newSlug := filepath.Join( - fmt.Sprintf("%04d", newTime.Year()), - fmt.Sprintf("%02d", newTime.Month()), - newUploadGUID+filepath.Ext(filename), - ) - - newUpload := models.Upload{ - SiteID: site.ID, - GUID: models.NewNanoID(), - FileSize: stat.Size(), - MIMEType: mimeType, - Filename: filename, - CreatedAt: newTime, - Slug: newSlug, - } - if err := s.db.SaveUpload(ctx, &newUpload); err != nil { - os.Remove(tmpPath) - return err - } - - if err := s.up.AdoptFile(site, newUpload, tmpPath); err != nil { - os.Remove(tmpPath) - return err - } - - return nil -} - -// stripFrontMatter removes YAML front matter (delimited by ---) from markdown content. -func stripFrontMatter(content string) string { - scanner := bufio.NewScanner(strings.NewReader(content)) - - // Check if the first line is a front matter delimiter - if !scanner.Scan() { - return content - } - firstLine := strings.TrimSpace(scanner.Text()) - if firstLine != "---" { - return content - } - - // Skip until the closing --- - for scanner.Scan() { - if strings.TrimSpace(scanner.Text()) == "---" { - // Return everything after the closing delimiter - var rest strings.Builder - for scanner.Scan() { - rest.WriteString(scanner.Text()) - rest.WriteString("\n") - } - return strings.TrimLeft(rest.String(), "\n") - } - } - - // No closing delimiter found, return original content - return content -} - -var attachmentExts = map[string]bool{ - ".png": true, ".jpg": true, ".jpeg": true, ".gif": true, ".svg": true, ".webp": true, - ".bmp": true, ".ico": true, ".tiff": true, ".tif": true, - ".mp3": true, ".mp4": true, ".wav": true, ".ogg": true, ".webm": true, - ".pdf": true, ".doc": true, ".docx": true, ".xls": true, ".xlsx": true, -} - -func isAttachment(ext string) bool { - return attachmentExts[ext] -} diff --git a/services/obsimport/service_test.go b/services/obsimport/service_test.go deleted file mode 100644 index 51123de..0000000 --- a/services/obsimport/service_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package obsimport - -import "testing" - -func TestStripFrontMatter(t *testing.T) { - tests := []struct { - name string - input string - want string - }{ - { - name: "no front matter", - input: "Hello world\nThis is a note", - want: "Hello world\nThis is a note", - }, - { - name: "with front matter", - input: "---\ntitle: Test\ntags: [a, b]\n---\nHello world\nThis is a note\n", - want: "Hello world\nThis is a note\n", - }, - { - name: "only front matter", - input: "---\ntitle: Test\n---\n", - want: "", - }, - { - name: "unclosed front matter", - input: "---\ntitle: Test\nno closing delimiter", - want: "---\ntitle: Test\nno closing delimiter", - }, - { - name: "empty string", - input: "", - want: "", - }, - { - name: "front matter with leading newlines stripped", - input: "---\nkey: val\n---\n\n\nBody here\n", - want: "Body here\n", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := stripFrontMatter(tt.input) - if got != tt.want { - t.Errorf("stripFrontMatter() = %q, want %q", got, tt.want) - } - }) - } -} diff --git a/services/publisher/iter.go b/services/publisher/iter.go index d07d4fe..ea70616 100644 --- a/services/publisher/iter.go +++ b/services/publisher/iter.go @@ -9,10 +9,10 @@ import ( ) // postIter returns a post iterator which returns posts in reverse chronological order. -func (s *Publisher) publishedPostIter(ctx context.Context, site int64) iter.Seq[models.Maybe[*models.Post]] { +func (s *Publisher) postIter(ctx context.Context, site int64) iter.Seq[models.Maybe[*models.Post]] { return func(yield func(models.Maybe[*models.Post]) bool) { paging := db.PagingParams{Offset: 0, Limit: 50} - page, err := s.db.SelectPublishedPostsOfSite(ctx, site, paging) + page, err := s.db.SelectPostsOfSite(ctx, site, false, paging) if err != nil { yield(models.Maybe[*models.Post]{Err: err}) return @@ -45,7 +45,7 @@ func (s *Publisher) postIterByCategory(ctx context.Context, categoryID int64) it return func(yield func(models.Maybe[*models.Post]) bool) { paging := db.PagingParams{Offset: 0, Limit: 50} for { - page, err := s.db.SelectPublishedPostsOfCategory(ctx, categoryID, paging) + page, err := s.db.SelectPostsOfCategory(ctx, categoryID, paging) if err != nil { yield(models.Maybe[*models.Post]{Err: err}) return diff --git a/services/publisher/service.go b/services/publisher/service.go index a5072a5..adfcdd7 100644 --- a/services/publisher/service.go +++ b/services/publisher/service.go @@ -79,7 +79,7 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error { pubSite := pubmodel.Site{ Site: site, PostIter: func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]] { - return p.publishedPostIter(ctx, site.ID) + return p.postIter(ctx, site.ID) }, BaseURL: target.BaseURL, Uploads: uploads, diff --git a/services/services.go b/services/services.go index a79e903..852dea3 100644 --- a/services/services.go +++ b/services/services.go @@ -8,8 +8,6 @@ import ( "lmika.dev/lmika/weiro/providers/uploadfiles" "lmika.dev/lmika/weiro/services/auth" "lmika.dev/lmika/weiro/services/categories" - "lmika.dev/lmika/weiro/services/imgedit" - "lmika.dev/lmika/weiro/services/obsimport" "lmika.dev/lmika/weiro/services/pages" "lmika.dev/lmika/weiro/services/posts" "lmika.dev/lmika/weiro/services/publisher" @@ -25,10 +23,8 @@ type Services struct { Posts *posts.Service Sites *sites.Service Uploads *uploads.Service - ImageEdit *imgedit.Service Categories *categories.Service Pages *pages.Service - ObsImport *obsimport.Service } func New(cfg config.Config) (*Services, error) { @@ -45,10 +41,8 @@ func New(cfg config.Config) (*Services, error) { postService := posts.New(dbp, publisherQueue) siteService := sites.New(dbp) uploadService := uploads.New(dbp, ufp, filepath.Join(cfg.ScratchDir, "uploads", "pending")) - imageEditService := imgedit.New(uploadService, filepath.Join(cfg.ScratchDir, "imageedit")) categoriesService := categories.New(dbp, publisherQueue) pagesService := pages.New(dbp, publisherQueue) - obsImportService := obsimport.New(dbp, ufp, publisherQueue, filepath.Join(cfg.ScratchDir, "obsimport")) return &Services{ DB: dbp, @@ -58,10 +52,8 @@ func New(cfg config.Config) (*Services, error) { Posts: postService, Sites: siteService, Uploads: uploadService, - ImageEdit: imageEditService, Categories: categoriesService, Pages: pagesService, - ObsImport: obsImportService, }, nil } diff --git a/services/sites/services.go b/services/sites/services.go index 4585d03..86e34b2 100644 --- a/services/sites/services.go +++ b/services/sites/services.go @@ -9,7 +9,6 @@ import ( "github.com/gofiber/fiber/v3" "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db" - "lmika.dev/pkg/modash/moslice" ) type Service struct { @@ -26,22 +25,6 @@ func (s *Service) HasUsersAsSites(ctx context.Context) (bool, error) { return s.db.HasUsersAndSites(ctx) } -func (s *Service) ListSites(ctx context.Context) ([]models.Site, error) { - user, ok := models.GetUser(ctx) - if !ok { - return nil, models.UserRequiredError - } - - sites, err := s.db.SelectSitesOwnedByUser(ctx, user.ID) - if err != nil { - return nil, err - } else if len(sites) == 0 { - return nil, errors.New("no sites found") - } - - return sites, nil -} - func (s *Service) BestSite(ctx context.Context, user models.User) (models.Site, error) { sites, err := s.db.SelectSitesOwnedByUser(ctx, user.ID) if err != nil { @@ -53,20 +36,16 @@ func (s *Service) BestSite(ctx context.Context, user models.User) (models.Site, return sites[0], nil } -type CreateSiteParams struct { +type FirstRunRequest struct { + Username string `form:"username"` + Password1 string `form:"password1"` + Password2 string `form:"password2"` SiteName string `form:"siteName"` SiteURL string `form:"siteUrl"` NetlifySiteID string `form:"netlifySiteId"` NetlifyAPIKey string `form:"netlifyAPIToken"` } -type FirstRunRequest struct { - CreateSiteParams - Username string `form:"username"` - Password1 string `form:"password1"` - Password2 string `form:"password2"` -} - func (frr FirstRunRequest) Validate() error { return validation.ValidateStruct(&frr, validation.Field(&frr.Username, validation.Required, validation.Match(models.ValidUserName)), @@ -97,31 +76,16 @@ func (s *Service) FirstRun(ctx context.Context, req FirstRunRequest) (newUser mo return newUser, newSite, err } - ctx = models.WithUser(ctx, newUser) - newSite, err = s.CreateSite(ctx, req.CreateSiteParams) - if err != nil { - return newUser, newSite, err - } - - return newUser, newSite, nil -} - -func (s *Service) CreateSite(ctx context.Context, req CreateSiteParams) (newSite models.Site, _ error) { - user, ok := models.GetUser(ctx) - if !ok { - return newSite, models.UserRequiredError - } - newSite = models.Site{ Title: defaultIfEmpty(req.SiteName, "New Site"), GUID: models.NewNanoID(), - OwnerID: user.ID, + OwnerID: newUser.ID, Timezone: "UTC", PostsPerPage: 10, Created: time.Now(), } if err := s.db.SaveSite(ctx, &newSite); err != nil { - return newSite, err + return newUser, newSite, err } hasNetlifyConfig := req.SiteURL != "" && req.NetlifySiteID != "" && req.NetlifyAPIKey != "" @@ -136,11 +100,11 @@ func (s *Service) CreateSite(ctx context.Context, req CreateSiteParams) (newSite TargetKey: req.NetlifyAPIKey, } if err := s.db.SavePublishTarget(ctx, &target); err != nil { - return newSite, err + return newUser, newSite, err } } - return newSite, nil + return newUser, newSite, nil } func (s *Service) GetSiteByID(ctx context.Context, siteID int64) (models.Site, error) { @@ -202,17 +166,3 @@ func (s *Service) UpdateSiteSettings(ctx context.Context, params UpdateSiteSetti return site, nil } - -func (s *Service) BestPubTarget(ctx context.Context, site models.Site) (models.SitePublishTarget, error) { - pubTargets, err := s.db.SelectPublishTargetsOfSite(ctx, site.ID) - if err != nil { - return models.SitePublishTarget{}, err - } - - enabledPubTargets := moslice.Filter(pubTargets, func(pubTarget models.SitePublishTarget) bool { return pubTarget.Enabled }) - if len(enabledPubTargets) == 0 { - return models.SitePublishTarget{}, errors.New("no publish targets found") - } - - return enabledPubTargets[0], nil -} diff --git a/services/uploads/manage.go b/services/uploads/manage.go index 9cb24ea..32debac 100644 --- a/services/uploads/manage.go +++ b/services/uploads/manage.go @@ -6,10 +6,7 @@ import ( "html/template" "io" "log" - "os" - "path/filepath" "strings" - "time" "lmika.dev/lmika/weiro/models" ) @@ -70,75 +67,6 @@ func (s *Service) renderCopyTemplate(upload models.Upload) string { return sb.String() } -func (s *Service) ReplaceUploadFile(ctx context.Context, uploadID int64, srcPath string) (models.Upload, error) { - site, _, err := s.fetchSiteAndUser(ctx) - if err != nil { - return models.Upload{}, err - } - - upload, err := s.db.SelectUploadByID(ctx, uploadID) - if err != nil { - return models.Upload{}, err - } else if upload.SiteID != site.ID { - return models.Upload{}, models.NotFoundError - } - - if err := s.up.ReplaceFile(site, upload, srcPath); err != nil { - return models.Upload{}, err - } - - stat, err := os.Stat(srcPath) - if err != nil { - return models.Upload{}, err - } - upload.FileSize = stat.Size() - - if err := s.db.UpdateUploadFileSize(ctx, upload.ID, upload.FileSize); err != nil { - return models.Upload{}, err - } - - return upload, nil -} - -func (s *Service) CreateUploadFromFile(ctx context.Context, srcPath string, filename string, mimeType string) (models.Upload, error) { - site, _, err := s.fetchSiteAndUser(ctx) - if err != nil { - return models.Upload{}, err - } - - stat, err := os.Stat(srcPath) - if err != nil { - return models.Upload{}, err - } - - newUploadGUID := models.NewNanoID() - newTime := time.Now().UTC() - newSlug := filepath.Join( - fmt.Sprintf("%04d", newTime.Year()), - fmt.Sprintf("%02d", newTime.Month()), - newUploadGUID+filepath.Ext(filename), - ) - - newUpload := models.Upload{ - SiteID: site.ID, - GUID: models.NewNanoID(), - FileSize: stat.Size(), - MIMEType: mimeType, - Filename: filename, - CreatedAt: newTime, - Slug: newSlug, - } - if err := s.db.SaveUpload(ctx, &newUpload); err != nil { - return models.Upload{}, err - } - - if err := s.up.AdoptFile(site, newUpload, srcPath); err != nil { - return models.Upload{}, err - } - - return newUpload, nil -} - func (s *Service) ListUploads(ctx context.Context) (res []UploadWithURL, _ error) { site, _, err := s.fetchSiteAndUser(ctx) if err != nil { diff --git a/sql/queries/categories.sql b/sql/queries/categories.sql index b8e0e64..4b48506 100644 --- a/sql/queries/categories.sql +++ b/sql/queries/categories.sql @@ -17,7 +17,7 @@ INNER JOIN post_categories pc ON pc.category_id = c.id WHERE pc.post_id = ? ORDER BY c.name ASC; --- name: SelectPublishedPostsOfCategory :many +-- name: SelectPostsOfCategory :many SELECT p.* FROM posts p INNER JOIN post_categories pc ON pc.post_id = p.id WHERE pc.category_id = ? AND p.state = 0 AND p.deleted_at = 0 diff --git a/sql/queries/posts.sql b/sql/queries/posts.sql index feaae7f..5a4c18e 100644 --- a/sql/queries/posts.sql +++ b/sql/queries/posts.sql @@ -17,12 +17,6 @@ WHERE site_id = sqlc.arg(site_id) AND ( END ) ORDER BY created_at DESC LIMIT sqlc.arg(limit) OFFSET sqlc.arg(offset); --- name: SelectPublishedPostsOfSite :many -SELECT * -FROM posts -WHERE site_id = sqlc.arg(site_id) AND state = 0 AND deleted_at = 0 -ORDER BY published_at DESC LIMIT sqlc.arg(limit) OFFSET sqlc.arg(offset); - -- name: SelectPost :one SELECT * FROM posts WHERE id = ? LIMIT 1; diff --git a/sql/queries/uploads.sql b/sql/queries/uploads.sql index f661591..fc8b82d 100644 --- a/sql/queries/uploads.sql +++ b/sql/queries/uploads.sql @@ -7,7 +7,7 @@ SELECT * FROM uploads WHERE id = ? LIMIT 1; -- name: SelectUploadBySiteIDAndSlug :one SELECT * FROM uploads WHERE site_id = ? AND slug = ? LIMIT 1; --- name: InsertUpload :one +-- name: InsertUpload :exec INSERT INTO uploads ( site_id, guid, @@ -23,8 +23,5 @@ RETURNING id; -- name: UpdateUpload :exec UPDATE uploads SET alt = ? WHERE id = ?; --- name: UpdateUploadFileSize :exec -UPDATE uploads SET file_size = ? WHERE id = ?; - -- name: DeleteUpload :exec DELETE FROM uploads WHERE id = ?; \ No newline at end of file diff --git a/views/_common/nav.html b/views/_common/nav.html index 5005326..e9c0de7 100644 --- a/views/_common/nav.html +++ b/views/_common/nav.html @@ -29,25 +29,7 @@ Publishing... --> - - +
-
-
-
diff --git a/views/obsimport/result.html b/views/obsimport/result.html deleted file mode 100644 index 15ebe31..0000000 --- a/views/obsimport/result.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
Import Complete
-
-

Successfully imported {{ .result.PostsImported }} post(s) and {{ .result.UploadsImported }} upload(s).

-
- Go to Posts - Back to Settings -
-
diff --git a/views/pages/index.html b/views/pages/index.html index 6755409..3011c64 100644 --- a/views/pages/index.html +++ b/views/pages/index.html @@ -29,7 +29,7 @@ {{ else }}
-
📄
No pages yet.
+
No pages yet.
{{ end }}
diff --git a/views/posts/edit.html b/views/posts/edit.html index fbb94fa..b9f5ea7 100644 --- a/views/posts/edit.html +++ b/views/posts/edit.html @@ -1,5 +1,5 @@ {{ $isPublished := ne .post.State 1 }} -
+
-
-
-
- Import Obsidian - Import posts and attachments from an Obsidian vault zip file. -
-
\ No newline at end of file diff --git a/views/sitesettings/new.html b/views/sitesettings/new.html deleted file mode 100644 index b057c72..0000000 --- a/views/sitesettings/new.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
-

New Site

-
-
-
-

Enter the details of your blog if you know them.
All fields are optional and can be changed later.

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
\ No newline at end of file diff --git a/views/uploads/edit.html b/views/uploads/edit.html deleted file mode 100644 index a7b27ab..0000000 --- a/views/uploads/edit.html +++ /dev/null @@ -1,31 +0,0 @@ -
-
-
-
- {{ .upload.Upload.Alt }} -
-
-
- -
- -
-
-
-
-
- - -
-
\ No newline at end of file diff --git a/views/uploads/index.html b/views/uploads/index.html index 9900b43..3d89215 100644 --- a/views/uploads/index.html +++ b/views/uploads/index.html @@ -20,9 +20,5 @@ {{ end }} - {{ else }} -
-
🖼️
No uploads yet.
-
{{ end }}
\ No newline at end of file diff --git a/views/uploads/show.html b/views/uploads/show.html index 7b42a38..087c10f 100644 --- a/views/uploads/show.html +++ b/views/uploads/show.html @@ -5,10 +5,7 @@ data-show-upload-site-id-value="{{ .upload.Upload.SiteID }}" data-show-upload-upload-id-value="{{ .upload.Upload.ID }}"> - - Edit - - +