diff --git a/.air.toml b/.air.toml index 1d0892a..1a1d829 100644 --- a/.air.toml +++ b/.air.toml @@ -1,7 +1,6 @@ root = "." testdata_dir = "testdata" tmp_dir = "build/tmp" -env_files = [".env"] [build] args_bin = [] @@ -11,7 +10,7 @@ env_files = [".env"] exclude_dir = ["static", "build", "node_modules"] exclude_file = [] exclude_regex = ["_test.go"] - exclude_unchanged = false + exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] diff --git a/.gitignore b/.gitignore index c8b64a2..9117a62 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ node_modules/ static/assets/ # Local Netlify folder .netlify -.env diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 95faeea..0000000 --- a/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -# Build stage -FROM golang:1.25-alpine AS builder - -WORKDIR /app - -# Copy go mod files -COPY go.mod go.sum ./ -RUN go mod download - -# Install the build dependencies -RUN apk update && \ - apk add nodejs npm make gcc libc-dev - -# Copy source code -COPY . . - -# Build the frontend -RUN make frontend - -# Build the application -RUN CGO_ENABLED=1 GOOS=linux go build -o weiro . - -# Runtime stage -FROM alpine:latest - -RUN apk --no-cache add ca-certificates -RUN mkdir -p /data - -WORKDIR /root/ - -# Copy the binary from builder -COPY --from=builder /app/weiro . -COPY --from=builder /app/static ./static -COPY --from=builder /app/views ./views - -ENV DATA_DIR=/data - -EXPOSE 3000 - -CMD ["./weiro"] diff --git a/_test-site/posts/2026/02/23-another-post-to.md b/_test-site/posts/2026/02/23-another-post-to.md deleted file mode 100644 index aaa3b4a..0000000 --- a/_test-site/posts/2026/02/23-another-post-to.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: X-fIs5JROC49 -title: "" -date: 2026-02-23T10:12:47Z -tags: [] -slug: /2026/02/23/another-post-to ---- -Another post to delete. \ No newline at end of file diff --git a/_test-site/posts/2026/02/23-be-a-comma.md b/_test-site/posts/2026/02/23-be-a-comma.md deleted file mode 100644 index 102c87c..0000000 --- a/_test-site/posts/2026/02/23-be-a-comma.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -id: Uk11zptnUi3A -title: "" -date: 2026-02-23T10:28:37Z -tags: [] -slug: /2026/02/23/be-a-comma ---- -Be a comma than a full stop. - -Also, this will be deleted soon. \ No newline at end of file diff --git a/_test-site/posts/2026/02/23-i-should-soft.md b/_test-site/posts/2026/02/23-i-should-soft.md deleted file mode 100644 index f53706a..0000000 --- a/_test-site/posts/2026/02/23-i-should-soft.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: hTF0-vhojyR7 -title: "" -date: 2026-02-23T10:16:19Z -tags: [] -slug: /2026/02/23/i-should-soft ---- -I should soft delete. \ No newline at end of file diff --git a/_test-site/posts/2026/02/23-this-is-to.md b/_test-site/posts/2026/02/23-this-is-to.md deleted file mode 100644 index f4aedf5..0000000 --- a/_test-site/posts/2026/02/23-this-is-to.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: EWhQUasFRLfJ -title: "" -date: 2026-02-23T10:12:00Z -tags: [] -slug: /2026/02/23/this-is-to ---- -This is to be deleted. \ No newline at end of file diff --git a/_test-site/posts/2026/02/24-it-may-have.md b/_test-site/posts/2026/02/24-it-may-have.md deleted file mode 100644 index a5cb835..0000000 --- a/_test-site/posts/2026/02/24-it-may-have.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -id: Ed6vIR86gspx -title: "" -date: 2026-02-24T10:55:39Z -tags: [] -slug: /2026/02/24/it-may-have ---- -It may have been an issue with the call to air? Hmm. Will need to make sure to check that is working correctly. - -I am now updating this. I am also updating this too. \ No newline at end of file diff --git a/_test-site/posts/2026/02/24-it-will-even.md b/_test-site/posts/2026/02/24-it-will-even.md deleted file mode 100644 index 6abd989..0000000 --- a/_test-site/posts/2026/02/24-it-will-even.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: bzAVD55SB2LE -title: "" -date: 2026-02-24T11:20:23Z -tags: [] -slug: /2026/02/24/it-will-even ---- -It will even do it for new posts. \ No newline at end of file diff --git a/_test-site/posts/2026/02/24-this-is-a.md b/_test-site/posts/2026/02/24-this-is-a.md deleted file mode 100644 index 6497e96..0000000 --- a/_test-site/posts/2026/02/24-this-is-a.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: MFHzBhJwJCQ3 -title: "" -date: 2026-02-24T11:20:11Z -tags: [] -slug: /2026/02/24/this-is-a ---- -This is a new post, and will be saved as a draft. - -It's still a draft. But the minute I publish it, it will be updated as an updated post. You see? I'm still writing in this. - -But the minute I press enter, it will always publish. \ No newline at end of file diff --git a/_test-site/posts/2026/02/24-this-was-a.md b/_test-site/posts/2026/02/24-this-was-a.md deleted file mode 100644 index 00439fe..0000000 --- a/_test-site/posts/2026/02/24-this-was-a.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -id: -sU1lmmL7i56 -title: "" -date: 2026-02-24T11:20:44Z -tags: [] -slug: /2026/02/24/this-was-a ---- -This was a draft. - -But now it's a published post. \ No newline at end of file diff --git a/assets/js/controllers/firstrun.js b/assets/js/controllers/firstrun.js deleted file mode 100644 index 400d86d..0000000 --- a/assets/js/controllers/firstrun.js +++ /dev/null @@ -1,66 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { showToast } from "../services/toast"; - -export default class FirstRunController extends Controller { - static targets = ['pages']; - - connect() { - this.pagesTargets.forEach((x) => x.classList.add('d-none')); - this.pagesTargets[0].classList.remove('d-none'); - this.element.querySelector('input[name="username"]').focus(); - } - - nextPage(ev) { - ev.preventDefault(); - - const currentIndex = this.pagesTargets.findIndex(x => !x.classList.contains('d-none')); - if (currentIndex === -1) { - return; - } - if (!this._validate(currentIndex)) { - return; - } - - let nextPage = currentIndex + 1; - if (nextPage >= this.pagesTargets.length) { - this.element.querySelector('form').submit(); - return; - } - - this.pagesTargets[currentIndex].classList.add('d-none'); - this.pagesTargets[nextPage].classList.remove('d-none'); - - if (nextPage === 1) { - this.element.querySelector('input[name="siteName"]').focus(); - } - } - - _validate(pageNumber) { - let newUsername = this.element.querySelector('input[name="username"]'); - let newPassword1 = this.element.querySelector('input[name="password1"]'); - let newPassword2 = this.element.querySelector('input[name="password2"]'); - - if (newUsername.value === '') { - alert('Please enter a username'); - newUsername.focus(); - return false; - } - if (!newUsername.value.match(/^[a-zA-Z0-9_-]+$/)) { - alert('Please enter a username with letters, numbers, underscores, and dashes only'); - newUsername.focus(); - newUsername.select(); - return false; - } - if (newPassword1.value === '') { - alert('Please enter a password'); - newPassword1.focus(); - return false; - } - if (newPassword2.value !== newPassword1.value) { - alert('Passwords do not match'); - newPassword2.focus(); - return false; - } - return true; - } -} \ No newline at end of file diff --git a/assets/js/controllers/logout.js b/assets/js/controllers/logout.js deleted file mode 100644 index 385570f..0000000 --- a/assets/js/controllers/logout.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class LogoutController extends Controller { - async logout(ev) { - ev.preventDefault(); - await fetch(`/logout`, { method: 'POST' }); - window.location.href = '/login'; - } -} \ No newline at end of file diff --git a/assets/js/controllers/postedit.js b/assets/js/controllers/postedit.js deleted file mode 100644 index 54679c5..0000000 --- a/assets/js/controllers/postedit.js +++ /dev/null @@ -1,80 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { showToast } from "../services/toast"; - -export default class PosteditController extends Controller { - static targets = ['bodyTextEdit']; - static values = { - saveAction: String, - }; - - connect() { - this.bodyTextEditTarget.focus(); - } - - async save(ev) { - ev.preventDefault(); - - try { - await this._postForm(this.saveActionValue); - - showToast({ - title: "๐Ÿ’พ Post Saved", - body: (this.saveActionValue === "Save Draft") ? "Post saved as draft." : "Post updated.", - }); - } catch (e) { - console.error(e); - showToast({ - title: "โŒ Error", - body: "Unable to save post. Please try again later.", - }); - } - } - - async publish(ev) { - ev.preventDefault(); - - try { - await this._postForm("Publish"); - - window.location.href = this.element.getAttribute("action"); - } catch (e) { - console.error(e); - showToast({ - title: "โŒ Error", - body: "Unable to publish post. Please try again later.", - }); - } - } - - async _postForm(action) { - if (this._isPosting) { - return; - } - this._isPosting = true; - - try { - const formData = new FormData(this.element); - let data = Object.fromEntries(formData.entries()); - data = {...data, action: action || 'save'}; - - const response = await fetch(this.element.getAttribute("action"), { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - return response.json(); - } finally { - this._isPosting = false; - } - } - - -} \ No newline at end of file diff --git a/assets/js/controllers/postlist.js b/assets/js/controllers/postlist.js deleted file mode 100644 index 82a319f..0000000 --- a/assets/js/controllers/postlist.js +++ /dev/null @@ -1,76 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { showToast } from "../services/toast"; - -export default class PostlistController extends Controller { - static values = { - siteId: Number, - postId: Number, - nanoSummary: String, - }; - - async deletePost(ev) { - ev.preventDefault(); - - let isHardDelete = ev.params && ev.params.hardDelete; - if (isHardDelete) { - if (!confirm("Are you sure you want to delete this post?")) { - return; - } - } - - try { - let deleteQuery = isHardDelete ? '?hard=true' : ''; - this.element.remove(); - - await fetch(`/sites/${this.siteIdValue}/posts/${this.postIdValue}${deleteQuery}`, { - method: 'DELETE', - headers: { 'Accept': 'application/json' }, - }); - - if (isHardDelete) { - showToast({ - title: "๐Ÿ”ฅ Post Delete", - body: this.nanoSummaryValue, - }); - } else { - showToast({ - title: "๐Ÿ—‘๏ธ Sent To Trash", - body: this.nanoSummaryValue, - }); - } - } catch (error) { - showToast({ - title: "โŒ Error", - body: "Failed to delete post. Please try again later.", - }); - } - } - - async restorePost(ev) { - ev.preventDefault(); - - try { - this.element.remove(); - await fetch(`/sites/${this.siteIdValue}/posts/${this.postIdValue}`, { - method: 'PATCH', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - action: 'restore' - }) - }); - - showToast({ - title: "๐Ÿ—‘๏ธ Restored From Trash", - body: this.nanoSummaryValue, - }); - } catch (error) { - showToast({ - title: "โŒ Error", - body: "Failed to rstore post. Please try again later.", - }); - } - } -} diff --git a/assets/js/controllers/toast.js b/assets/js/controllers/toast.js deleted file mode 100644 index 1655dbf..0000000 --- a/assets/js/controllers/toast.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Toast } from 'bootstrap/dist/js/bootstrap.js'; -import { Controller } from "@hotwired/stimulus" - -export default class ToastController extends Controller { - static targets = ['title', 'body']; - - initialize() { - this._toast = new Toast(this.element); - } - - showToast(ev) { - let toastDetails = ev.detail; - if (!toastDetails) { - return; - } - - this.titleTarget.innerText = toastDetails.title || "Title"; - this.bodyTarget.innerText = toastDetails.body || "Body"; - - if (!this._toast.isShown()) { - this._toast.show(); - } - } -} diff --git a/assets/js/main.js b/assets/js/main.js deleted file mode 100644 index 6bca555..0000000 --- a/assets/js/main.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Application } from "@hotwired/stimulus"; - -import ToastController from "./controllers/toast"; -import PostlistController from "./controllers/postlist"; -import PosteditController from "./controllers/postedit"; -import LogoutController from "./controllers/logout"; -import FirstRunController from "./controllers/firstrun"; - -window.Stimulus = Application.start() -Stimulus.register("toast", ToastController); -Stimulus.register("postlist", PostlistController); -Stimulus.register("postedit", PosteditController); -Stimulus.register("logout", LogoutController); -Stimulus.register("first-run", FirstRunController); \ No newline at end of file diff --git a/assets/js/services/toast.js b/assets/js/services/toast.js deleted file mode 100644 index 7f810ec..0000000 --- a/assets/js/services/toast.js +++ /dev/null @@ -1,6 +0,0 @@ -export function showToast(details) { - let event = new CustomEvent('weiroToast', { - detail: details - }); - window.dispatchEvent(event); -} \ No newline at end of file diff --git a/cmds/pubtargets.go b/cmds/pubtargets.go deleted file mode 100644 index bb74e3b..0000000 --- a/cmds/pubtargets.go +++ /dev/null @@ -1,118 +0,0 @@ -package cmds - -import ( - "context" - "fmt" - "log" - "os" - "text/tabwriter" - - "github.com/spf13/cobra" - "lmika.dev/lmika/weiro/config" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services" -) - -func PubTargetsAdd() *cobra.Command { - var ( - siteGUID string - targetType string - targetRef string - targetKey string - baseURL string - enabled bool - ) - - cmd := &cobra.Command{ - Use: "add", - Short: "Add a publication target", - Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.LoadConfig() - if err != nil { - log.Fatal(err) - } - - svcs, err := services.New(cfg) - if err != nil { - log.Fatal(err) - } - defer svcs.Close() - - ctx := context.Background() - site, err := svcs.DB.SelectSiteByGUID(ctx, siteGUID) - if err != nil { - log.Fatal(err) - } - - target := &models.SitePublishTarget{ - SiteID: site.ID, - GUID: models.NewNanoID(), - Enabled: enabled, - BaseURL: baseURL, - TargetType: targetType, - TargetRef: targetRef, - TargetKey: targetKey, - } - - if err := svcs.DB.SavePublishTarget(ctx, target); err != nil { - log.Fatal(err) - } - - fmt.Printf("Added publish target %s\n", target.GUID) - }, - } - - cmd.Flags().StringVarP(&siteGUID, "site", "s", "", "Site GUID") - cmd.Flags().StringVarP(&targetType, "type", "t", "", "Target type (localfs, netlify)") - cmd.Flags().StringVarP(&targetRef, "ref", "r", "", "Target reference") - cmd.Flags().StringVarP(&targetKey, "key", "k", "", "Target key") - cmd.Flags().StringVarP(&baseURL, "url", "u", "", "Base URL") - cmd.Flags().BoolVar(&enabled, "enabled", true, "Enable target") - - cmd.MarkFlagRequired("site") - cmd.MarkFlagRequired("type") - cmd.MarkFlagRequired("ref") - cmd.MarkFlagRequired("url") - - return cmd -} - -func PubTargets() *cobra.Command { - cmd := &cobra.Command{ - Use: "pubtargets ", - Short: "Manage publication targets", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.LoadConfig() - if err != nil { - log.Fatal(err) - } - - svcs, err := services.New(cfg) - if err != nil { - log.Fatal(err) - } - defer svcs.Close() - - ctx := context.Background() - site, err := svcs.DB.SelectSiteByGUID(ctx, args[0]) - if err != nil { - log.Fatal(err) - } - - targets, err := svcs.DB.SelectPublishTargetsOfSite(ctx, site.ID) - if err != nil { - log.Fatal(err) - } - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "GUID\tTARGET_TYPE\tENABLED\tTARGET_REF") - for _, target := range targets { - fmt.Fprintf(w, "%s\t%s\t%v\t%s\n", target.GUID, target.TargetType, target.Enabled, target.TargetRef) - } - w.Flush() - }, - } - cmd.AddCommand(PubTargetsAdd()) - return cmd -} diff --git a/cmds/server.go b/cmds/server.go deleted file mode 100644 index ccf11f7..0000000 --- a/cmds/server.go +++ /dev/null @@ -1,139 +0,0 @@ -package cmds - -import ( - "context" - "html" - "html/template" - "log" - "path/filepath" - "strings" - "time" - - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/extractors" - "github.com/gofiber/fiber/v3/middleware/session" - "github.com/gofiber/fiber/v3/middleware/static" - "github.com/gofiber/storage/sqlite3/v2" - fiber_html "github.com/gofiber/template/html/v3" - "github.com/gofiber/utils/v2" - "github.com/spf13/cobra" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" - "lmika.dev/lmika/weiro/config" - "lmika.dev/lmika/weiro/handlers" - "lmika.dev/lmika/weiro/handlers/middleware" - "lmika.dev/lmika/weiro/services" -) - -func Root() *cobra.Command { - cmd := &cobra.Command{ - Use: "weiro", - Short: "Weiro is a simple blogging platform", - Long: `Weiro is a simple blogging platform. - -Starting weiro without any arguments will start the server. -`, - Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.LoadConfig() - if err != nil { - log.Fatal(err) - } - - svcs, err := services.New(cfg) - if err != nil { - log.Fatal(err) - } - defer svcs.Close() - - svcs.PublisherQueue.Start(context.Background()) - - fiberTemplate := fiber_html.New("./views", ".html") - fiberTemplate.Funcmap["sub"] = func(x, y int) int { return x - y } - fiberTemplate.Funcmap["markdown"] = func() func(s string) template.HTML { - mdParser := goldmark.New( - goldmark.WithExtensions(extension.GFM), - ) - return func(s string) template.HTML { - var sb strings.Builder - if err := mdParser.Convert([]byte(s), &sb); err != nil { - return template.HTML("Markdown error: " + html.EscapeString(err.Error())) - } - return template.HTML(sb.String()) - } - }() - - // Initialize custom config - store := sqlite3.New(sqlite3.Config{ - Database: filepath.Join(cfg.DataDir, "./fiber.db"), - Table: "fiber_storage", - Reset: false, - GCInterval: 10 * time.Second, - MaxOpenConns: 100, - MaxIdleConns: 100, - ConnMaxLifetime: 1 * time.Second, - }) - - app := fiber.New(fiber.Config{ - Views: fiberTemplate, - ViewsLayout: "layouts/main", - PassLocalsToViews: true, - }) - app.Use(session.New(session.Config{ - // Storage - Storage: store, - - // Security - CookieSecure: cfg.IsProd(), - CookieSameSite: "Lax", - - // Session Management - IdleTimeout: 24 * time.Hour, // Inactivity timeout - AbsoluteTimeout: 7 * 24 * time.Hour, // Maximum session duration - - // Cookie Settings - CookiePath: "/", - CookieDomain: cfg.SiteDomain, - CookieSessionOnly: false, // Persist across browser restarts - - // Session ID - Extractor: extractors.FromCookie("__wro-session_id"), - KeyGenerator: utils.SecureToken, - - // Error Handling - ErrorHandler: func(c fiber.Ctx, err error) { - log.Printf("Session error: %v", err) - }, - })) - - ih := handlers.IndexHandler{SiteService: svcs.Sites} - lh := handlers.LoginHandler{Config: cfg, AuthService: svcs.Auth} - ph := handlers.PostsHandler{PostService: svcs.Posts} - - app.Get("/login", lh.Login) - app.Post("/login", lh.DoLogin) - app.Post("/logout", lh.Logout) - - siteGroup := app.Group("/sites/:siteID", middleware.RequireUser(svcs.Auth), middleware.RequiresSite(svcs.Sites)) - - siteGroup.Get("/posts", ph.Index) - siteGroup.Get("/posts/new", ph.New) - siteGroup.Get("/posts/:postID", ph.Edit) - siteGroup.Post("/posts", ph.Update) - siteGroup.Patch("/posts/:postID", ph.Patch) - siteGroup.Delete("/posts/:postID", ph.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) - } - }, - } - cmd.AddCommand(Sites()) - cmd.AddCommand(PubTargets()) - return cmd -} diff --git a/cmds/sites.go b/cmds/sites.go deleted file mode 100644 index 8e7dfea..0000000 --- a/cmds/sites.go +++ /dev/null @@ -1,46 +0,0 @@ -package cmds - -import ( - "context" - "fmt" - "log" - "os" - "text/tabwriter" - - "github.com/spf13/cobra" - "lmika.dev/lmika/weiro/config" - "lmika.dev/lmika/weiro/services" -) - -func Sites() *cobra.Command { - cmd := &cobra.Command{ - Use: "sites", - Short: "Manage sites", - Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.LoadConfig() - if err != nil { - log.Fatal(err) - } - - svcs, err := services.New(cfg) - if err != nil { - log.Fatal(err) - } - defer svcs.Close() - - ctx := context.Background() - sites, err := svcs.Sites.ListAllSitesWithOwners(ctx) - if err != nil { - log.Fatal(err) - } - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "GUID\tOWNER\tNAME") - for _, site := range sites { - fmt.Fprintf(w, "%s\t%s\t%s\n", site.GUID, site.Username, site.Title) - } - w.Flush() - }, - } - return cmd -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 38cde39..0000000 --- a/config/config.go +++ /dev/null @@ -1,31 +0,0 @@ -package config - -import ( - "fmt" - "path/filepath" - - "github.com/Netflix/go-env" -) - -type Config struct { - DataDir string `env:"DATA_DIR"` - SiteDomain string `env:"SITE_DOMAIN"` - LoginLocked bool `env:"LOGIN_LOCKED,default=false"` - Env string `env:"ENV,default=prod"` -} - -func LoadConfig() (Config, error) { - cfg := Config{} - if _, err := env.UnmarshalFromEnviron(&cfg); err != nil { - return Config{}, fmt.Errorf("failed to load config: %w", err) - } - return cfg, nil -} - -func (c Config) IsProd() bool { - return c.Env != "dev" -} - -func (c Config) DBName() string { - return filepath.Join(c.DataDir, "weiro.db") -} diff --git a/esbuild.mjs b/esbuild.mjs index 2aabc8e..188a9b1 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -1,16 +1,9 @@ import * as esbuild from 'esbuild' import {sassPlugin} from 'esbuild-sass-plugin' -await Promise.all([ - esbuild.build({ - entryPoints: ['./assets/css/main.scss'], - bundle: true, - plugins: [sassPlugin()], - outfile: './static/assets/main.css', - }), - esbuild.build({ - entryPoints: ['./assets/js/main.js'], - bundle: true, - outfile: './static/assets/main.js', - }) -]); \ No newline at end of file +await esbuild.build({ + entryPoints: ['./assets/css/main.scss'], + bundle: true, + plugins: [sassPlugin()], + outfile: './static/assets/main.css', +}); \ No newline at end of file diff --git a/go.mod b/go.mod index c6e73c1..2db351c 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( github.com/Azure/go-autorest/autorest/date v0.2.0 // indirect github.com/Azure/go-autorest/logger v0.1.0 // indirect github.com/Azure/go-autorest/tracing v0.5.0 // indirect - github.com/Netflix/go-env v0.1.2 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/andybalholm/brotli v1.2.0 // indirect @@ -37,26 +36,22 @@ require ( github.com/go-openapi/strfmt v0.19.11 // indirect github.com/go-openapi/swag v0.19.12 // indirect github.com/go-openapi/validate v0.20.0 // indirect - github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/gofiber/fiber/v3 v3.1.0 // indirect - github.com/gofiber/schema v1.7.0 // indirect - github.com/gofiber/storage/sqlite3/v2 v2.2.3 // indirect + github.com/gofiber/fiber/v3 v3.0.0 // indirect + github.com/gofiber/schema v1.6.0 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/template/html/v3 v3.0.2 // indirect github.com/gofiber/template/v2 v2.1.0 // indirect - github.com/gofiber/utils/v2 v2.0.2 // indirect + github.com/gofiber/utils/v2 v2.0.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.4 // indirect + github.com/klauspost/compress v1.18.3 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/matoous/go-nanoid/v2 v2.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.33 // indirect github.com/mitchellh/mapstructure v1.4.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/netlify/open-api/v2 v2.49.1 // indirect @@ -66,19 +61,17 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rsc/goversion v1.2.0 // indirect github.com/sirupsen/logrus v1.6.0 // indirect - github.com/spf13/cobra v1.10.2 // indirect - github.com/spf13/pflag v1.0.10 // indirect github.com/tinylib/msgp v1.6.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.69.0 // indirect go.mongodb.org/mongo-driver v1.4.4 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.48.0 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 6f783dd..1cf5ed2 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,6 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Netflix/go-env v0.1.2 h1:0DRoLR9lECQ9Zqvkswuebm3jJ/2enaDX6Ei8/Z+EnK0= -github.com/Netflix/go-env v0.1.2/go.mod h1:WlIhYi++8FlKNJtrop1mjXYAJMzv1f43K4MqCoh0yGE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -73,7 +71,6 @@ github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -179,8 +176,6 @@ github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0 github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= github.com/go-openapi/validate v0.20.0 h1:pzutNCCBZGZlE+u8HD3JZyWdc/TVbtVwlWUp8/vgUKk= github.com/go-openapi/validate v0.20.0/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -212,14 +207,8 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gofiber/fiber/v3 v3.0.0 h1:GPeCG8X60L42wLKrzgeewDHBr6pE6veAvwaXsqD3Xjk= github.com/gofiber/fiber/v3 v3.0.0/go.mod h1:kVZiO/AwyT5Pq6PgC8qRCJ+j/BHrMy5jNw1O9yH38aY= -github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY= -github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU= github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY= github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s= -github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg= -github.com/gofiber/schema v1.7.0/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk= -github.com/gofiber/storage/sqlite3/v2 v2.2.3 h1:m3n80wUewnB5ruAV3Qq0mzIS+bwBrYYETo4N+fvBoow= -github.com/gofiber/storage/sqlite3/v2 v2.2.3/go.mod h1:F1w9BpQtU7BD5cCjlQnFIEjWHUaAcm9Hh5fuCpfG/OE= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= github.com/gofiber/template/html/v3 v3.0.2 h1:/Fh8UcEsB4uhf1QWNbYaAOwXxSORebJ2zXkb5tgG/TI= @@ -228,8 +217,6 @@ github.com/gofiber/template/v2 v2.1.0 h1:vrLY6uEW2HdioJm6J5FGUpYZuapVQhHciNz21XQ github.com/gofiber/template/v2 v2.1.0/go.mod h1:ohgpR/Ng90nJbK+IyNzrgR/XpnBNt862/oTF5G7SAmE= github.com/gofiber/utils/v2 v2.0.0 h1:SCC3rpsEDWupFSHtc0RKxg/BKgV0s1qKfZg9Jv6D0sM= github.com/gofiber/utils/v2 v2.0.0/go.mod h1:xF9v89FfmbrYqI/bQUGN7gR8ZtXot2jxnZvmAUtiavE= -github.com/gofiber/utils/v2 v2.0.2 h1:ShRRssz0F3AhTlAQcuEj54OEDtWF7+HJDwEi/aa6QLI= -github.com/gofiber/utils/v2 v2.0.2/go.mod h1:+9Ub4NqQ+IaJoTliq5LfdmOJAA/Hzwf4pXOxOa3RrJ0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -296,8 +283,6 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= @@ -317,8 +302,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= -github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= @@ -352,8 +335,6 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= -github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -411,7 +392,6 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rsc/goversion v1.2.0 h1:zVF4y5ciA/rw779S62bEAq4Yif1cBc/UwRkXJ2xZyT4= github.com/rsc/goversion v1.2.0/go.mod h1:Tf/O0TQyfRvp7NelXAyfXYRKUO+LX3KNgXc8ALRUv4k= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -430,15 +410,10 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= -github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -489,7 +464,6 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -507,8 +481,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -563,8 +535,6 @@ golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -612,8 +582,6 @@ golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -627,8 +595,6 @@ golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -663,7 +629,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/handlers/accepts.go b/handlers/accepts.go deleted file mode 100644 index f4dd3bf..0000000 --- a/handlers/accepts.go +++ /dev/null @@ -1,35 +0,0 @@ -package handlers - -import ( - "github.com/gofiber/fiber/v3" -) - -type acceptor struct { - canAccept func(ctx fiber.Ctx) bool - acceptFn func(ctx fiber.Ctx) error -} - -func accepts(ctx fiber.Ctx, acceptors ...acceptor) error { - for _, a := range acceptors { - if a.canAccept(ctx) { - return a.acceptFn(ctx) - } - } - return fiber.ErrNotFound -} - -func json(fn func() any) acceptor { - return acceptor{ - canAccept: func(ctx fiber.Ctx) bool { return ctx.AcceptsJSON() && !ctx.AcceptsHTML() }, - acceptFn: func(ctx fiber.Ctx) error { - return ctx.Status(fiber.StatusOK).JSON(fn()) - }, - } -} - -func html(fn func(ctx fiber.Ctx) error) acceptor { - return acceptor{ - canAccept: func(ctx fiber.Ctx) bool { return ctx.AcceptsHTML() }, - acceptFn: fn, - } -} diff --git a/handlers/index.go b/handlers/index.go deleted file mode 100644 index 6062237..0000000 --- a/handlers/index.go +++ /dev/null @@ -1,77 +0,0 @@ -package handlers - -import ( - "fmt" - "net/url" - "regexp" - - "emperror.dev/errors" - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/middleware/session" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services/sites" -) - -var sitePath = regexp.MustCompile(`^/sites/([0-9]+)`) - -type IndexHandler struct { - SiteService *sites.Service -} - -func (h IndexHandler) Index(c fiber.Ctx) error { - hasUserAndSites, err := h.SiteService.HasUsersAsSites(c.Context()) - if err != nil { - return err - } else if !hasUserAndSites { - return c.Redirect().To("/first-run") - } - - user, hasUser := models.GetUser(c.Context()) - if !hasUser { - return c.Redirect().To("/login") - } - - if refUrl, rerr := url.Parse(c.Get("Referer")); rerr == nil { - if parts := sitePath.FindStringSubmatch(refUrl.Path); len(parts) == 2 { - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", parts[1])) - } - } - - site, err := h.SiteService.BestSite(c.Context(), user) - if err != nil { - return err - } - - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", site.ID)) -} - -func (h IndexHandler) FirstRun(c fiber.Ctx) error { - hasUserAndSites, err := h.SiteService.HasUsersAsSites(c.Context()) - if err != nil { - return err - } else if hasUserAndSites { - return errors.New("you already have a site") - } - - return c.Render("index/first-run", fiber.Map{}, "layouts/bare_with_scripts") -} - -func (h IndexHandler) FirstRunSubmit(c fiber.Ctx) error { - var req sites.FirstRunRequest - if err := c.Bind().Body(&req); err != nil { - return errors.Wrap(err, "failed to parse first run request") - } - - sess := session.FromContext(c) - if err := sess.Regenerate(); err != nil { - return c.Status(fiber.StatusInternalServerError).SendString("Failed to login") - } - - user, site, err := h.SiteService.FirstRun(c.Context(), req) - if err != nil { - return err - } - - sess.Set("user_id", user.ID) - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", site.ID)) -} diff --git a/handlers/login.go b/handlers/login.go deleted file mode 100644 index 30ed0b4..0000000 --- a/handlers/login.go +++ /dev/null @@ -1,72 +0,0 @@ -package handlers - -import ( - "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/middleware/session" - "lmika.dev/lmika/weiro/config" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services/auth" -) - -type LoginHandler struct { - Config config.Config - AuthService *auth.Service -} - -func (lh *LoginHandler) Login(c fiber.Ctx) error { - if lh.Config.LoginLocked { - return c.Status(fiber.StatusForbidden).SendString("Login is locked") - } - - loginChallenge := models.NewNanoID() - - sess := session.FromContext(c) - sess.Set("_login_challenge", loginChallenge) - - c.Render("login/login", fiber.Map{ - "challenge": loginChallenge, - }, "layouts/bare") - return nil -} - -func (lh *LoginHandler) Logout(c fiber.Ctx) error { - sess := session.FromContext(c) - sess.Destroy() - return c.Redirect().To("/login") -} - -func (lh *LoginHandler) DoLogin(c fiber.Ctx) error { - var req struct { - 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") - } - - if req.Username == "" || req.Password == "" { - return c.Status(fiber.StatusBadRequest).SendString("Username and password are required") - } - - 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") - } - - if err := sess.Regenerate(); err != nil { - return c.Status(fiber.StatusInternalServerError).SendString("Failed to login") - } - - sess.Set("user_id", user.ID) - sess.Delete("_login_challenge") - - return c.Redirect().To("/") -} diff --git a/handlers/middleware/site.go b/handlers/middleware/site.go index 54211bc..0cc2b7a 100644 --- a/handlers/middleware/site.go +++ b/handlers/middleware/site.go @@ -3,14 +3,12 @@ package middleware import ( "strconv" - "emperror.dev/errors" "github.com/gofiber/fiber/v3" "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db" - "lmika.dev/lmika/weiro/services/sites" ) -func RequiresSite(sites *sites.Service) func(c fiber.Ctx) error { +func RequiresSite(db *db.Provider) func(c fiber.Ctx) error { return func(c fiber.Ctx) error { siteIDStr := c.Params("siteID") if siteIDStr == "" { @@ -22,15 +20,18 @@ func RequiresSite(sites *sites.Service) func(c fiber.Ctx) error { return fiber.ErrBadRequest } - site, err := sites.GetSiteByID(c.Context(), siteID) + user, ok := models.GetUser(c.Context()) + if !ok { + return fiber.ErrUnauthorized + } + + site, err := db.SelectSiteByID(c.Context(), siteID) if err != nil { - if errors.Is(err, models.UserRequiredError) { - return fiber.ErrForbidden - } else if errors.Is(err, models.PermissionError) || db.ErrorIsNoRows(err) { - return fiber.ErrNotFound - } else if errors.Is(err, models.NotFoundError) || db.ErrorIsNoRows(err) { - return err - } + return fiber.ErrNotFound + } + + if site.OwnerID != user.ID { + return fiber.ErrForbidden } c.Locals("site", site) diff --git a/handlers/middleware/user.go b/handlers/middleware/user.go index 8391b85..d36891d 100644 --- a/handlers/middleware/user.go +++ b/handlers/middleware/user.go @@ -1,47 +1,23 @@ package middleware import ( + "log" + "github.com/gofiber/fiber/v3" - "github.com/gofiber/fiber/v3/middleware/session" "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/services/auth" ) -func OptionalUser(auth *auth.Service) func(c fiber.Ctx) error { +func AuthUser() func(c fiber.Ctx) error { return func(c fiber.Ctx) error { - sess := session.FromContext(c) - userID, _ := sess.Get("user_id").(int64) - if userID == 0 { - return c.Next() - } - - user, err := auth.GetUser(c.Context(), userID) - if err != nil { - return c.Next() - } - - c.Locals("user", user) - c.SetContext(models.WithUser(c.Context(), user)) - - return c.Next() - } -} - -func RequireUser(auth *auth.Service) func(c fiber.Ctx) error { - return func(c fiber.Ctx) error { - sess := session.FromContext(c) - userID, _ := sess.Get("user_id").(int64) - if userID == 0 { - return c.Redirect().To("/login") - } - - user, err := auth.GetUser(c.Context(), userID) - if err != nil { - return c.Redirect().To("/login") + // TEMP - Actually do the auth here + user := models.User{ + ID: 1, + Username: "testuser", } c.Locals("user", user) c.SetContext(models.WithUser(c.Context(), user)) + log.Printf("User %s authenticated", user.Username) return c.Next() } diff --git a/handlers/posts.go b/handlers/posts.go index 041ec9d..82d9aa0 100644 --- a/handlers/posts.go +++ b/handlers/posts.go @@ -2,7 +2,6 @@ package handlers import ( "fmt" - "log" "strconv" "github.com/gofiber/fiber/v3" @@ -15,32 +14,19 @@ type PostsHandler struct { } func (ph PostsHandler) Index(c fiber.Ctx) error { - var req struct { - Filter string `query:"filter"` - } - if err := c.Bind().Query(&req); err != nil { - return fiber.ErrBadRequest - } - - posts, err := ph.PostService.ListPosts(c.Context(), req.Filter == "deleted") + posts, err := ph.PostService.ListPosts(c.Context()) if err != nil { return err } - return accepts(c, json(func() any { - return posts - }), html(func(c fiber.Ctx) error { - return c.Render("posts/index", fiber.Map{ - "req": req, - "posts": posts, - }) - })) + return c.Render("posts/index", fiber.Map{ + "posts": posts, + }) } func (ph PostsHandler) New(c fiber.Ctx) error { p := models.Post{ - GUID: models.NewNanoID(), - State: models.StateDraft, + GUID: models.NewNanoID(), } return c.Render("posts/edit", fiber.Map{ @@ -63,13 +49,9 @@ func (ph PostsHandler) Edit(c fiber.Ctx) error { return err } - return accepts(c, json(func() any { - return post - }), html(func(c fiber.Ctx) error { - return c.Render("posts/edit", fiber.Map{ - "post": post, - }) - })) + return c.Render("posts/edit", fiber.Map{ + "post": post, + }) } func (ph PostsHandler) Update(c fiber.Ctx) error { @@ -78,82 +60,10 @@ func (ph PostsHandler) Update(c fiber.Ctx) error { return err } - post, err := ph.PostService.UpdatePost(c.Context(), req) + post, err := ph.PostService.PublishPost(c.Context(), req) if err != nil { return err } - return accepts(c, json(func() any { - // TODO: should be created if brand new - return post - }), html(func(c fiber.Ctx) error { - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", post.SiteID)) - })) -} - -func (ph PostsHandler) Patch(c fiber.Ctx) error { - log.Println("PATCH") - - postIDStr := c.Params("postID") - if postIDStr == "" { - return fiber.ErrBadRequest - } - postID, err := strconv.ParseInt(postIDStr, 10, 64) - if err != nil { - return fiber.ErrBadRequest - } - - var req struct { - Action string `json:"action"` - } - if err := c.Bind().Body(&req); err != nil { - return err - } - - log.Println("Request") - - switch req.Action { - case "restore": - if err := ph.PostService.RestorePost(c.Context(), postID); err != nil { - return err - } - default: - return fiber.ErrBadRequest - } - - return accepts(c, json(func() any { - return struct{}{} - }), html(func(c fiber.Ctx) error { - - return c.Redirect().To(fmt.Sprintf("/sites/%v/posts")) - })) -} - -func (ph PostsHandler) Delete(c fiber.Ctx) error { - postIDStr := c.Params("postID") - if postIDStr == "" { - return fiber.ErrBadRequest - } - - postID, err := strconv.ParseInt(postIDStr, 10, 64) - if err != nil { - return fiber.ErrBadRequest - } - - var req struct { - Hard bool `query:"hard"` - } - if err := c.Bind().Query(&req); err != nil { - return err - } - - if err := ph.PostService.DeletePost(c.Context(), postID, req.Hard); err != nil { - return err - } - - return accepts(c, json(func() any { - return fiber.Map{} - }), html(func(c fiber.Ctx) error { - return c.Redirect().To("/sites") - })) + return c.Redirect().To(fmt.Sprintf("/sites/%v/posts", post.SiteID)) } diff --git a/main.go b/main.go index 823bff5..fbd9c31 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,106 @@ package main import ( - "os" + "html" + "html/template" + "log" + "strings" - "lmika.dev/lmika/weiro/cmds" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/static" + fiber_html "github.com/gofiber/template/html/v3" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "lmika.dev/lmika/weiro/handlers" + "lmika.dev/lmika/weiro/handlers/middleware" + "lmika.dev/lmika/weiro/providers/db" + "lmika.dev/lmika/weiro/services/posts" + "lmika.dev/lmika/weiro/services/publisher" _ "modernc.org/sqlite" ) func main() { - if err := cmds.Root().Execute(); err != nil { - os.Exit(1) + dbp, err := db.New("build/weiro.db") + if err != nil { + log.Fatal(err) } + defer dbp.Close() + + publisherSvc := publisher.New(dbp) + + postService := posts.New(dbp, publisherSvc) + + //user, err := dbp.SelectUserByUsername(context.Background(), "testuser") + //if err != nil { + // user = models.User{ + // Username: "testuser", + // PasswordHashed: []byte("changeme"), + // } + // if err := dbp.SaveUser(context.Background(), &user); err != nil { + // log.Fatal(err) + // } + //} + + fiberTemplate := fiber_html.New("./views", ".html") + fiberTemplate.Funcmap["markdown"] = func() func(s string) template.HTML { + mdParser := goldmark.New( + goldmark.WithExtensions(extension.GFM), + ) + return func(s string) template.HTML { + var sb strings.Builder + if err := mdParser.Convert([]byte(s), &sb); err != nil { + return template.HTML("Markdown error: " + html.EscapeString(err.Error())) + } + return template.HTML(sb.String()) + } + }() + + app := fiber.New(fiber.Config{ + Views: fiberTemplate, + ViewsLayout: "layouts/main", + PassLocalsToViews: true, + }) + + siteGroup := app.Group("/sites/:siteID", middleware.AuthUser(), middleware.RequiresSite(dbp)) + + ph := handlers.PostsHandler{PostService: postService} + + siteGroup.Get("/posts", ph.Index) + siteGroup.Get("/posts/new", ph.New) + siteGroup.Get("/posts/:postID/edit", ph.Edit) + siteGroup.Post("/posts", ph.Update) + + app.Get("/", func(c fiber.Ctx) error { + return c.Redirect().To("/sites/1/posts") + }) + app.Get("/static/*", static.New("./static")) + + // TEMP + // + /* + dbp.SaveUser(context.Background(), &models.User{Username: "testuser"}) + + ctx := models.WithUser(context.Background(), models.User{ID: 1}) + site, err := importer.New(dbp).Import(ctx, "_test-site") + if err != nil { + log.Fatal(err) + } + + target := models.SitePublishTarget{ + SiteID: site.ID, + BaseURL: "https://jolly-boba-9e2486.netlify.app", + TargetType: "netlify", + TargetRef: "55c878a7-189e-42cf-aa02-5c60908143f3", + TargetKey: os.Getenv("NETLIFY_AUTH_TOKEN"), + } + + if err := dbp.SavePublishTarget(ctx, &target); err != nil { + log.Fatal(err) + } + */ + //if err := publisherSvc.Publish(ctx, site.ID); err != nil { + // log.Fatal(err) + //} + + log.Fatal(app.Listen(":3000")) } diff --git a/models/errors.go b/models/errors.go index 997a952..4a23c08 100644 --- a/models/errors.go +++ b/models/errors.go @@ -6,4 +6,3 @@ var UserRequiredError = errors.New("user required") var PermissionError = errors.New("permission denied") 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") diff --git a/models/ids.go b/models/ids.go index c5c1cbd..58dd928 100644 --- a/models/ids.go +++ b/models/ids.go @@ -3,6 +3,6 @@ package models import "github.com/matoous/go-nanoid/v2" func NewNanoID() string { - id, _ := gonanoid.New(16) + id, _ := gonanoid.New(12) return id } diff --git a/models/posts.go b/models/posts.go index e129a57..16184d6 100644 --- a/models/posts.go +++ b/models/posts.go @@ -8,36 +8,15 @@ import ( "unicode" ) -const ( - StatePublished = iota - StateDraft -) - type Post struct { - ID int64 `json:"id"` - SiteID int64 `json:"site_id"` - State int `json:"state"` - GUID string `json:"guid"` - Title string `json:"title"` - Body string `json:"body"` - Slug string `json:"slug"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - DeletedAt time.Time `json:"deleted_at,omitempty"` - PublishedAt time.Time `json:"published_at,omitempty"` -} - -func (p *Post) NanoSummary() string { - if p.Title != "" { - return p.Title - } - firstWords := firstNWords(p.Body, 7, wordForSummary) - if firstWords == "" { - firstWords = "(no content)" - } else if len(firstWords) < len(p.Body) { - return firstWords + "..." - } - return firstWords + ID int64 + SiteID int64 + GUID string + Title string + Body string + Slug string + CreatedAt time.Time + PublishedAt time.Time } func (p *Post) BestSlug() string { @@ -67,10 +46,6 @@ func (p *Post) BestSlug() string { return fmt.Sprintf("/%s/%s", datePart, slugPath) } -func wordForSummary(word string) string { - return word -} - func wordForSlug(word string) string { var sb strings.Builder for _, c := range word { diff --git a/models/sites.go b/models/sites.go index 42b8a3c..d83d126 100644 --- a/models/sites.go +++ b/models/sites.go @@ -1,7 +1,5 @@ package models -import "time" - type PublishTargetType int const ( @@ -10,31 +8,18 @@ const ( PublishTargetTypeNetlify PublishTargetType = 2 ) -func ParsePublishTargetType(s string) (PublishTargetType, error) { - switch s { - case "localfs": - return PublishTargetTypeLocalFS, nil - case "netlify": - return PublishTargetTypeNetlify, nil - default: - return PublishTargetTypeNone, nil - } -} - type Site struct { ID int64 OwnerID int64 - GUID string - Created time.Time - Title string Tagline string + //Meta SiteMeta + //Posts []*Post } type SitePublishTarget struct { ID int64 SiteID int64 - GUID string Enabled bool BaseURL string @@ -42,3 +27,24 @@ type SitePublishTarget struct { TargetRef string TargetKey string } + +/* +type SiteMeta struct { + Title string `yaml:"title"` + Tagline string `yaml:"tagline"` + BaseURL string `yaml:"base_url"` +} + +type PostMeta struct { + ID string `yaml:"id"` + Title string `yaml:"title"` + Date time.Time `yaml:"date"` + Tags []string `yaml:"tags"` + Slug string `yaml:"slug"` +} + +type Post struct { + Meta PostMeta + Content string +} +*/ diff --git a/models/users.go b/models/users.go index f901ac9..13c6452 100644 --- a/models/users.go +++ b/models/users.go @@ -1,49 +1,7 @@ package models -import ( - "regexp" - "time" - - "golang.org/x/crypto/bcrypt" -) - -var ValidUserName = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) - type User struct { ID int64 Username string PasswordHashed []byte - TimeZone string - Created time.Time -} - -func (u *User) SetPassword(pwd string) { - bcrypted, _ := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost) - u.PasswordHashed = bcrypted -} - -func (u User) CheckPassword(pwd string) bool { - err := bcrypt.CompareHashAndPassword(u.PasswordHashed, []byte(pwd)) - return err == nil -} - -func (u User) FormatTime(t time.Time) string { - if loc := getLocation(u.TimeZone); loc != nil { - return t.In(loc).Format("2006-01-02 15:04:05") - } - return t.Format("2006-01-02 15:04:05") -} - -var loadedLocation = map[string]*time.Location{} - -func getLocation(tz string) *time.Location { - if loc, ok := loadedLocation[tz]; ok { - return loc - } - loc, err := time.LoadLocation(tz) - if err != nil { - loc = time.Local - } - loadedLocation[tz] = loc - return loc } diff --git a/package-lock.json b/package-lock.json index c4f391c..ffe8353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "packages": { "": { "dependencies": { - "@hotwired/stimulus": "^3.2.2", "bootstrap": "^5.3.8", "esbuild-sass-plugin": "^3.6.0" }, @@ -436,12 +435,6 @@ "node": ">=18" } }, - "node_modules/@hotwired/stimulus": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz", - "integrity": "sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==", - "license": "MIT" - }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", diff --git a/package.json b/package.json index 64e6fca..34b5b2f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "esbuild": "0.27.3" }, "dependencies": { - "@hotwired/stimulus": "^3.2.2", "bootstrap": "^5.3.8", "esbuild-sass-plugin": "^3.6.0" } diff --git a/providers/db/gen/sqlgen/models.go b/providers/db/gen/sqlgen/models.go index 5317ef2..5d8f3b5 100644 --- a/providers/db/gen/sqlgen/models.go +++ b/providers/db/gen/sqlgen/models.go @@ -7,21 +7,17 @@ package sqlgen type Post struct { ID int64 SiteID int64 - State int64 Guid string Title string Body string Slug string CreatedAt int64 - UpdatedAt int64 PublishedAt int64 - DeletedAt int64 } type PublishTarget struct { ID int64 SiteID int64 - Guid string TargetType string Enabled int64 BaseUrl string @@ -30,17 +26,14 @@ type PublishTarget struct { } type Site struct { - ID int64 - OwnerID int64 - Guid string - Title string - Tagline string - CreatedAt int64 + ID int64 + OwnerID int64 + Title string + Tagline string } type User struct { - ID int64 - Username string - Password string - CreatedAt int64 + ID int64 + Username string + Password string } diff --git a/providers/db/gen/sqlgen/posts.sql.go b/providers/db/gen/sqlgen/posts.sql.go index e4b00f0..adeefde 100644 --- a/providers/db/gen/sqlgen/posts.sql.go +++ b/providers/db/gen/sqlgen/posts.sql.go @@ -9,73 +9,46 @@ import ( "context" ) -const hardDeletePost = `-- name: HardDeletePost :exec -DELETE FROM posts WHERE id = ? -` - -func (q *Queries) HardDeletePost(ctx context.Context, id int64) error { - _, err := q.db.ExecContext(ctx, hardDeletePost, id) - return err -} - const insertPost = `-- name: InsertPost :one INSERT INTO posts ( site_id, - state, guid, title, body, slug, created_at, - updated_at, - published_at, - deleted_at -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + published_at +) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id ` type InsertPostParams struct { SiteID int64 - State int64 Guid string Title string Body string Slug string CreatedAt int64 - UpdatedAt int64 PublishedAt int64 - DeletedAt int64 } func (q *Queries) InsertPost(ctx context.Context, arg InsertPostParams) (int64, error) { row := q.db.QueryRowContext(ctx, insertPost, arg.SiteID, - arg.State, arg.Guid, arg.Title, arg.Body, arg.Slug, arg.CreatedAt, - arg.UpdatedAt, arg.PublishedAt, - arg.DeletedAt, ) var id int64 err := row.Scan(&id) return id, err } -const restorePost = `-- name: RestorePost :exec -UPDATE posts SET deleted_at = 0 WHERE id = ? -` - -func (q *Queries) RestorePost(ctx context.Context, id int64) error { - _, err := q.db.ExecContext(ctx, restorePost, id) - return err -} - const selectPost = `-- name: SelectPost :one -SELECT id, site_id, state, guid, title, body, slug, created_at, updated_at, published_at, deleted_at FROM posts WHERE id = ? LIMIT 1 +SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE id = ? LIMIT 1 ` func (q *Queries) SelectPost(ctx context.Context, id int64) (Post, error) { @@ -84,21 +57,18 @@ func (q *Queries) SelectPost(ctx context.Context, id int64) (Post, error) { err := row.Scan( &i.ID, &i.SiteID, - &i.State, &i.Guid, &i.Title, &i.Body, &i.Slug, &i.CreatedAt, - &i.UpdatedAt, &i.PublishedAt, - &i.DeletedAt, ) return i, err } const selectPostByGUID = `-- name: SelectPostByGUID :one -SELECT id, site_id, state, guid, title, body, slug, created_at, updated_at, published_at, deleted_at FROM posts WHERE guid = ? LIMIT 1 +SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE guid = ? LIMIT 1 ` func (q *Queries) SelectPostByGUID(ctx context.Context, guid string) (Post, error) { @@ -107,37 +77,22 @@ func (q *Queries) SelectPostByGUID(ctx context.Context, guid string) (Post, erro err := row.Scan( &i.ID, &i.SiteID, - &i.State, &i.Guid, &i.Title, &i.Body, &i.Slug, &i.CreatedAt, - &i.UpdatedAt, &i.PublishedAt, - &i.DeletedAt, ) return i, err } const selectPostsOfSite = `-- name: SelectPostsOfSite :many -SELECT id, site_id, state, guid, title, body, slug, created_at, updated_at, published_at, deleted_at -FROM posts -WHERE site_id = ? AND ( - CASE CAST (?2 AS TEXT) - WHEN 'deleted' THEN deleted_at > 0 - ELSE deleted_at = 0 - END -) ORDER BY created_at DESC LIMIT 10 +SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE site_id = ? ORDER BY created_at DESC LIMIT 10 ` -type SelectPostsOfSiteParams struct { - SiteID int64 - PostFilter string -} - -func (q *Queries) SelectPostsOfSite(ctx context.Context, arg SelectPostsOfSiteParams) ([]Post, error) { - rows, err := q.db.QueryContext(ctx, selectPostsOfSite, arg.SiteID, arg.PostFilter) +func (q *Queries) SelectPostsOfSite(ctx context.Context, siteID int64) ([]Post, error) { + rows, err := q.db.QueryContext(ctx, selectPostsOfSite, siteID) if err != nil { return nil, err } @@ -148,15 +103,12 @@ func (q *Queries) SelectPostsOfSite(ctx context.Context, arg SelectPostsOfSitePa 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 } @@ -171,52 +123,29 @@ func (q *Queries) SelectPostsOfSite(ctx context.Context, arg SelectPostsOfSitePa return items, nil } -const softDeletePost = `-- name: SoftDeletePost :exec -UPDATE posts SET deleted_at = ? WHERE id = ? -` - -type SoftDeletePostParams struct { - DeletedAt int64 - ID int64 -} - -func (q *Queries) SoftDeletePost(ctx context.Context, arg SoftDeletePostParams) error { - _, err := q.db.ExecContext(ctx, softDeletePost, arg.DeletedAt, arg.ID) - return err -} - const updatePost = `-- name: UpdatePost :exec UPDATE posts SET title = ?, - state = ?, body = ?, slug = ?, - updated_at = ?, - published_at = ?, - deleted_at = ? + published_at = ? WHERE id = ? ` type UpdatePostParams struct { Title string - State int64 Body string Slug string - UpdatedAt int64 PublishedAt int64 - DeletedAt int64 ID int64 } func (q *Queries) UpdatePost(ctx context.Context, arg UpdatePostParams) error { _, err := q.db.ExecContext(ctx, updatePost, arg.Title, - arg.State, arg.Body, arg.Slug, - arg.UpdatedAt, arg.PublishedAt, - arg.DeletedAt, arg.ID, ) return err diff --git a/providers/db/gen/sqlgen/pubtargets.sql.go b/providers/db/gen/sqlgen/pubtargets.sql.go index cd5cfa6..49ca66a 100644 --- a/providers/db/gen/sqlgen/pubtargets.sql.go +++ b/providers/db/gen/sqlgen/pubtargets.sql.go @@ -12,19 +12,17 @@ import ( const insertPublishTarget = `-- name: InsertPublishTarget :one INSERT INTO publish_targets ( site_id, - guid, target_type, enabled, base_url, target_ref, target_key -) VALUES (?, ?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?) RETURNING id ` type InsertPublishTargetParams struct { SiteID int64 - Guid string TargetType string Enabled int64 BaseUrl string @@ -35,7 +33,6 @@ type InsertPublishTargetParams struct { func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTargetParams) (int64, error) { row := q.db.QueryRowContext(ctx, insertPublishTarget, arg.SiteID, - arg.Guid, arg.TargetType, arg.Enabled, arg.BaseUrl, @@ -48,7 +45,7 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg } const selectPublishTargetsOfSite = `-- name: SelectPublishTargetsOfSite :many -SELECT id, site_id, guid, target_type, enabled, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ? +SELECT id, site_id, target_type, enabled, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ? ` func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) ([]PublishTarget, error) { @@ -63,7 +60,6 @@ func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) if err := rows.Scan( &i.ID, &i.SiteID, - &i.Guid, &i.TargetType, &i.Enabled, &i.BaseUrl, diff --git a/providers/db/gen/sqlgen/sites.sql.go b/providers/db/gen/sqlgen/sites.sql.go index fd3d3c6..136cb38 100644 --- a/providers/db/gen/sqlgen/sites.sql.go +++ b/providers/db/gen/sqlgen/sites.sql.go @@ -7,116 +7,32 @@ package sqlgen import ( "context" - "database/sql" ) -const hasUsersAndSites = `-- name: HasUsersAndSites :one -SELECT (SELECT COUNT(*) FROM users) > 0 AND (SELECT COUNT(*) FROM sites) > 0 AS has_users_and_sites -` - -func (q *Queries) HasUsersAndSites(ctx context.Context) (sql.NullBool, error) { - row := q.db.QueryRowContext(ctx, hasUsersAndSites) - var has_users_and_sites sql.NullBool - err := row.Scan(&has_users_and_sites) - return has_users_and_sites, err -} - const insertSite = `-- name: InsertSite :one INSERT INTO sites ( owner_id, - guid, title, - tagline, - created_at -) VALUES (?, ?, ?, ?, ?) + tagline +) VALUES (?, ?, ?) RETURNING id ` type InsertSiteParams struct { - OwnerID int64 - Guid string - Title string - Tagline string - CreatedAt int64 + OwnerID int64 + Title string + Tagline string } func (q *Queries) InsertSite(ctx context.Context, arg InsertSiteParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertSite, - arg.OwnerID, - arg.Guid, - arg.Title, - arg.Tagline, - arg.CreatedAt, - ) + row := q.db.QueryRowContext(ctx, insertSite, arg.OwnerID, arg.Title, arg.Tagline) var id int64 err := row.Scan(&id) return id, err } -const selectAllSitesWithOwners = `-- name: SelectAllSitesWithOwners :many -SELECT s.id, s.guid, s.title, s.owner_id, u.username -FROM sites s -JOIN users u ON s.owner_id = u.id -ORDER BY s.title ASC -` - -type SelectAllSitesWithOwnersRow struct { - ID int64 - Guid string - Title string - OwnerID int64 - Username string -} - -func (q *Queries) SelectAllSitesWithOwners(ctx context.Context) ([]SelectAllSitesWithOwnersRow, error) { - rows, err := q.db.QueryContext(ctx, selectAllSitesWithOwners) - if err != nil { - return nil, err - } - defer rows.Close() - var items []SelectAllSitesWithOwnersRow - for rows.Next() { - var i SelectAllSitesWithOwnersRow - if err := rows.Scan( - &i.ID, - &i.Guid, - &i.Title, - &i.OwnerID, - &i.Username, - ); 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 selectSiteByGUID = `-- name: SelectSiteByGUID :one -SELECT id, owner_id, guid, title, tagline, created_at FROM sites WHERE guid = ? -` - -func (q *Queries) SelectSiteByGUID(ctx context.Context, guid string) (Site, error) { - row := q.db.QueryRowContext(ctx, selectSiteByGUID, guid) - var i Site - err := row.Scan( - &i.ID, - &i.OwnerID, - &i.Guid, - &i.Title, - &i.Tagline, - &i.CreatedAt, - ) - return i, err -} - const selectSiteByID = `-- name: SelectSiteByID :one -SELECT id, owner_id, guid, title, tagline, created_at FROM sites WHERE id = ? +SELECT id, owner_id, title, tagline FROM sites WHERE id = ? ` func (q *Queries) SelectSiteByID(ctx context.Context, id int64) (Site, error) { @@ -125,16 +41,14 @@ func (q *Queries) SelectSiteByID(ctx context.Context, id int64) (Site, error) { err := row.Scan( &i.ID, &i.OwnerID, - &i.Guid, &i.Title, &i.Tagline, - &i.CreatedAt, ) return i, err } const selectSitesOwnedByUser = `-- name: SelectSitesOwnedByUser :many -SELECT id, owner_id, guid, title, tagline, created_at FROM sites WHERE owner_id = ? ORDER BY title ASC +SELECT id, owner_id, title, tagline FROM sites WHERE owner_id = ? ` func (q *Queries) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([]Site, error) { @@ -149,10 +63,8 @@ func (q *Queries) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([] if err := rows.Scan( &i.ID, &i.OwnerID, - &i.Guid, &i.Title, &i.Tagline, - &i.CreatedAt, ); err != nil { return nil, err } diff --git a/providers/db/gen/sqlgen/users.sql.go b/providers/db/gen/sqlgen/users.sql.go index a70a3bf..48b1e53 100644 --- a/providers/db/gen/sqlgen/users.sql.go +++ b/providers/db/gen/sqlgen/users.sql.go @@ -10,51 +10,29 @@ import ( ) const insertUser = `-- name: InsertUser :one -INSERT INTO users (username, password, created_at) VALUES (?, ?, ?) RETURNING id +INSERT INTO users (username, password) VALUES (?, ?) RETURNING id ` type InsertUserParams struct { - Username string - Password string - CreatedAt int64 + Username string + Password string } func (q *Queries) InsertUser(ctx context.Context, arg InsertUserParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertUser, arg.Username, arg.Password, arg.CreatedAt) + row := q.db.QueryRowContext(ctx, insertUser, arg.Username, arg.Password) var id int64 err := row.Scan(&id) return id, err } -const selectUserByID = `-- name: SelectUserByID :one -SELECT id, username, password, created_at FROM users WHERE id = ? LIMIT 1 -` - -func (q *Queries) SelectUserByID(ctx context.Context, id int64) (User, error) { - row := q.db.QueryRowContext(ctx, selectUserByID, id) - var i User - err := row.Scan( - &i.ID, - &i.Username, - &i.Password, - &i.CreatedAt, - ) - return i, err -} - const selectUserByUsername = `-- name: SelectUserByUsername :one -SELECT id, username, password, created_at FROM users WHERE username = ? LIMIT 1 +SELECT id, username, password FROM users WHERE username = ? ` func (q *Queries) SelectUserByUsername(ctx context.Context, username string) (User, error) { row := q.db.QueryRowContext(ctx, selectUserByUsername, username) var i User - err := row.Scan( - &i.ID, - &i.Username, - &i.Password, - &i.CreatedAt, - ) + err := row.Scan(&i.ID, &i.Username, &i.Password) return i, err } diff --git a/providers/db/posts.go b/providers/db/posts.go index 04a7a3e..57dfbe8 100644 --- a/providers/db/posts.go +++ b/providers/db/posts.go @@ -8,16 +8,8 @@ import ( "lmika.dev/lmika/weiro/providers/db/gen/sqlgen" ) -func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64, showDeleted bool) ([]*models.Post, error) { - var filter = "" - if showDeleted { - filter = "deleted" - } - - rows, err := db.queries.SelectPostsOfSite(ctx, sqlgen.SelectPostsOfSiteParams{ - SiteID: siteID, - PostFilter: filter, - }) +func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64) ([]*models.Post, error) { + rows, err := db.queries.SelectPostsOfSite(ctx, siteID) if err != nil { return nil, err } @@ -51,15 +43,12 @@ func (db *Provider) SavePost(ctx context.Context, post *models.Post) error { if post.ID == 0 { newID, err := db.queries.InsertPost(ctx, sqlgen.InsertPostParams{ SiteID: post.SiteID, - State: int64(post.State), Guid: post.GUID, Title: post.Title, Body: post.Body, Slug: post.Slug, - CreatedAt: timeToInt(post.CreatedAt), - UpdatedAt: timeToInt(post.UpdatedAt), - PublishedAt: timeToInt(post.PublishedAt), - DeletedAt: timeToInt(post.DeletedAt), + CreatedAt: post.CreatedAt.Unix(), + PublishedAt: post.PublishedAt.Unix(), }) if err != nil { return err @@ -70,13 +59,10 @@ func (db *Provider) SavePost(ctx context.Context, post *models.Post) error { return db.queries.UpdatePost(ctx, sqlgen.UpdatePostParams{ ID: post.ID, - State: int64(post.State), Title: post.Title, Body: post.Body, Slug: post.Slug, - UpdatedAt: timeToInt(post.UpdatedAt), - PublishedAt: timeToInt(post.PublishedAt), - DeletedAt: timeToInt(post.DeletedAt), + PublishedAt: post.PublishedAt.Unix(), }) } @@ -84,21 +70,11 @@ func dbPostToPost(row sqlgen.Post) *models.Post { return &models.Post{ ID: row.ID, SiteID: row.SiteID, - State: int(row.State), GUID: row.Guid, Title: row.Title, Body: row.Body, Slug: row.Slug, CreatedAt: time.Unix(row.CreatedAt, 0).UTC(), - UpdatedAt: time.Unix(row.UpdatedAt, 0).UTC(), PublishedAt: time.Unix(row.PublishedAt, 0).UTC(), - DeletedAt: time.Unix(row.DeletedAt, 0).UTC(), } } - -func timeToInt(t time.Time) int64 { - if t.IsZero() { - return 0 - } - return t.Unix() -} diff --git a/providers/db/provider.go b/providers/db/provider.go index eda0513..b061b32 100644 --- a/providers/db/provider.go +++ b/providers/db/provider.go @@ -3,7 +3,6 @@ package db import ( "context" "database/sql" - "time" "github.com/Southclaws/fault" "lmika.dev/lmika/weiro/providers/db/gen/sqlgen" @@ -39,18 +38,3 @@ func New(dbFile string) (*Provider, error) { func (db *Provider) Close() error { return db.drvr.Close() } - -func (db *Provider) SoftDeletePost(ctx context.Context, postID int64) error { - return db.queries.SoftDeletePost(ctx, sqlgen.SoftDeletePostParams{ - DeletedAt: time.Now().Unix(), - ID: postID, - }) -} - -func (db *Provider) HardDeletePost(ctx context.Context, postID int64) error { - return db.queries.HardDeletePost(ctx, postID) -} - -func (db *Provider) RestorePost(ctx context.Context, postID int64) error { - return db.queries.RestorePost(ctx, postID) -} diff --git a/providers/db/provider_test.go b/providers/db/provider_test.go index 4781d61..f4788cd 100644 --- a/providers/db/provider_test.go +++ b/providers/db/provider_test.go @@ -158,7 +158,7 @@ func TestProvider_Posts(t *testing.T) { require.NoError(t, err) assert.NotZero(t, post.ID) - posts, err := p.SelectPostsOfSite(ctx, site.ID, false) + posts, err := p.SelectPostsOfSite(ctx, site.ID) require.NoError(t, err) require.Len(t, posts, 1) assert.Equal(t, post.ID, posts[0].ID) @@ -205,7 +205,7 @@ func TestProvider_Posts(t *testing.T) { require.NoError(t, p.SavePost(ctx, post1)) require.NoError(t, p.SavePost(ctx, post2)) - posts, err := p.SelectPostsOfSite(ctx, site2.ID, false) + posts, err := p.SelectPostsOfSite(ctx, site2.ID) require.NoError(t, err) require.Len(t, posts, 2) assert.Equal(t, "New Post", posts[0].Title) @@ -220,7 +220,7 @@ func TestProvider_Posts(t *testing.T) { } require.NoError(t, p.SaveSite(ctx, emptySite)) - posts, err := p.SelectPostsOfSite(ctx, emptySite.ID, false) + posts, err := p.SelectPostsOfSite(ctx, emptySite.ID) require.NoError(t, err) assert.Empty(t, posts) }) @@ -248,7 +248,6 @@ func TestProvider_PublishTargets(t *testing.T) { target := &models.SitePublishTarget{ SiteID: site.ID, TargetType: "netlify", - GUID: "target-001", BaseURL: "https://example.netlify.app", TargetRef: "netlify-site-123", TargetKey: "secret-key", diff --git a/providers/db/pubtargets.go b/providers/db/pubtargets.go index dcfb25f..74b0b26 100644 --- a/providers/db/pubtargets.go +++ b/providers/db/pubtargets.go @@ -18,7 +18,6 @@ func (db *Provider) SelectPublishTargetsOfSite(ctx context.Context, siteID int64 targets[i] = models.SitePublishTarget{ ID: row.ID, SiteID: row.SiteID, - GUID: row.Guid, Enabled: row.Enabled != 0, TargetType: row.TargetType, BaseURL: row.BaseUrl, @@ -39,7 +38,6 @@ func (db *Provider) SavePublishTarget(ctx context.Context, target *models.SitePu newID, err := db.queries.InsertPublishTarget(ctx, sqlgen.InsertPublishTargetParams{ SiteID: target.SiteID, TargetType: target.TargetType, - Guid: target.GUID, Enabled: enabled, BaseUrl: target.BaseURL, TargetRef: target.TargetRef, diff --git a/providers/db/sites.go b/providers/db/sites.go index f878e45..eaf61fb 100644 --- a/providers/db/sites.go +++ b/providers/db/sites.go @@ -2,7 +2,6 @@ package db import ( "context" - "time" "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db/gen/sqlgen" @@ -14,16 +13,12 @@ func (db *Provider) SelectSiteByID(ctx context.Context, id int64) (models.Site, return models.Site{}, err } - return dbSiteToSite(row), nil -} - -func (db *Provider) SelectSiteByGUID(ctx context.Context, guid string) (models.Site, error) { - row, err := db.queries.SelectSiteByGUID(ctx, guid) - if err != nil { - return models.Site{}, err - } - - return dbSiteToSite(row), nil + return models.Site{ + ID: row.ID, + OwnerID: row.OwnerID, + Title: row.Title, + Tagline: row.Tagline, + }, nil } func (db *Provider) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([]models.Site, error) { @@ -34,7 +29,12 @@ func (db *Provider) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ( sites := make([]models.Site, len(rows)) for i, row := range rows { - sites[i] = dbSiteToSite(row) + sites[i] = models.Site{ + ID: row.ID, + OwnerID: row.OwnerID, + Title: row.Title, + Tagline: row.Tagline, + } } return sites, nil } @@ -42,11 +42,9 @@ func (db *Provider) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ( func (db *Provider) SaveSite(ctx context.Context, site *models.Site) error { if site.ID == 0 { newID, err := db.queries.InsertSite(ctx, sqlgen.InsertSiteParams{ - OwnerID: site.OwnerID, - Guid: site.GUID, - Title: site.Title, - Tagline: site.Tagline, - CreatedAt: timeToInt(site.Created), + OwnerID: site.OwnerID, + Title: site.Title, + Tagline: site.Tagline, }) if err != nil { return err @@ -58,49 +56,3 @@ func (db *Provider) SaveSite(ctx context.Context, site *models.Site) error { // No update query defined in sqlgen yet return nil } - -func (db *Provider) HasUsersAndSites(ctx context.Context) (bool, error) { - nullBool, err := db.queries.HasUsersAndSites(ctx) - if err != nil { - return false, err - } - return nullBool.Valid && nullBool.Bool, nil -} - -type SiteWithOwner struct { - ID int64 - GUID string - Title string - OwnerID int64 - Username string -} - -func (db *Provider) SelectAllSitesWithOwners(ctx context.Context) ([]SiteWithOwner, error) { - rows, err := db.queries.SelectAllSitesWithOwners(ctx) - if err != nil { - return nil, err - } - - sites := make([]SiteWithOwner, len(rows)) - for i, row := range rows { - sites[i] = SiteWithOwner{ - ID: row.ID, - GUID: row.Guid, - Title: row.Title, - OwnerID: row.OwnerID, - Username: row.Username, - } - } - return sites, nil -} - -func dbSiteToSite(row sqlgen.Site) models.Site { - return models.Site{ - ID: row.ID, - OwnerID: row.OwnerID, - GUID: row.Guid, - Title: row.Title, - Tagline: row.Tagline, - Created: time.Unix(row.CreatedAt, 0).UTC(), - } -} diff --git a/providers/db/users.go b/providers/db/users.go index 18e0bc6..73b3590 100644 --- a/providers/db/users.go +++ b/providers/db/users.go @@ -3,7 +3,6 @@ package db import ( "context" "encoding/base64" - "time" "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db/gen/sqlgen" @@ -15,16 +14,16 @@ func (db *Provider) SelectUserByUsername(ctx context.Context, username string) ( return models.User{}, err } - return dbUserToUser(res) -} - -func (db *Provider) SelectUserByID(ctx context.Context, userID int64) (models.User, error) { - res, err := db.queries.SelectUserByID(ctx, userID) + pwdBytes, err := base64.StdEncoding.DecodeString(res.Password) if err != nil { return models.User{}, err } - return dbUserToUser(res) + return models.User{ + ID: res.ID, + Username: res.Username, + PasswordHashed: pwdBytes, + }, nil } func (db *Provider) SaveUser(ctx context.Context, user *models.User) error { @@ -32,9 +31,8 @@ func (db *Provider) SaveUser(ctx context.Context, user *models.User) error { if user.ID == 0 { newID, err := db.queries.InsertUser(ctx, sqlgen.InsertUserParams{ - Username: user.Username, - Password: hashedPassword, - CreatedAt: timeToInt(user.Created), + Username: user.Username, + Password: hashedPassword, }) if err != nil { return err @@ -49,17 +47,3 @@ func (db *Provider) SaveUser(ctx context.Context, user *models.User) error { Password: hashedPassword, }) } - -func dbUserToUser(res sqlgen.User) (models.User, error) { - pwdBytes, err := base64.StdEncoding.DecodeString(res.Password) - if err != nil { - return models.User{}, err - } - - return models.User{ - ID: res.ID, - Username: res.Username, - PasswordHashed: pwdBytes, - Created: time.Unix(res.CreatedAt, 0).UTC(), - }, nil -} diff --git a/services/auth/service.go b/services/auth/service.go deleted file mode 100644 index bb958e3..0000000 --- a/services/auth/service.go +++ /dev/null @@ -1,46 +0,0 @@ -package auth - -import ( - "context" - - "emperror.dev/errors" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/providers/db" -) - -type Service struct { - db *db.Provider -} - -func New(db *db.Provider) *Service { - return &Service{ - db: db, - } -} - -func (s *Service) Login(ctx context.Context, username, password string) (models.User, error) { - user, err := s.db.SelectUserByUsername(ctx, username) - if err != nil { - return models.User{}, err - } - - if !user.CheckPassword(password) { - return models.User{}, errors.New("invalid password") - } - - return user, nil -} - -func (s *Service) GetUser(ctx context.Context, userID int64) (models.User, error) { - return s.db.SelectUserByID(ctx, userID) -} - -func (s *Service) SetPassword(ctx context.Context, username, password string) (models.User, error) { - user, err := s.db.SelectUserByUsername(ctx, username) - if err != nil { - return models.User{}, err - } - - user.SetPassword(password) - return user, s.db.SaveUser(ctx, &user) -} diff --git a/services/posts/create.go b/services/posts/create.go index 1dc69a1..191dbbc 100644 --- a/services/posts/create.go +++ b/services/posts/create.go @@ -2,7 +2,6 @@ package posts import ( "context" - "strings" "time" "lmika.dev/lmika/weiro/models" @@ -10,13 +9,12 @@ import ( ) type CreatePostParams struct { - GUID string `form:"guid" json:"guid"` - Title string `form:"title" json:"title"` - Body string `form:"body" json:"body"` - Action string `form:"action" json:"action"` + GUID string `form:"guid" json:"guid"` + Title string `form:"title" json:"title"` + Body string `form:"body" json:"body"` } -func (s *Service) UpdatePost(ctx context.Context, params CreatePostParams) (*models.Post, error) { +func (s *Service) PublishPost(ctx context.Context, params CreatePostParams) (*models.Post, error) { site, ok := models.GetSite(ctx) if !ok { return nil, models.SiteRequiredError @@ -29,27 +27,16 @@ func (s *Service) UpdatePost(ctx context.Context, params CreatePostParams) (*mod post.Title = params.Title post.Body = params.Body - post.UpdatedAt = time.Now() + post.PublishedAt = time.Now() post.Slug = post.BestSlug() - oldState := post.State - - switch strings.ToLower(params.Action) { - case "publish": - post.State = models.StatePublished - post.PublishedAt = time.Now() - case "save draft": - post.State = models.StateDraft - post.PublishedAt = time.Time{} - default: - // Leave unchanged - } if err := s.db.SavePost(ctx, post); err != nil { return nil, err } - if oldState != post.State || post.State == models.StatePublished { - s.publisher.Queue(site) + // TODO: do on separate thread + if err := s.publisher.Publish(ctx, site); err != nil { + return nil, err } return post, nil @@ -72,7 +59,6 @@ func (s *Service) fetchOrCreatePost(ctx context.Context, site models.Site, param GUID: params.GUID, Title: params.Title, Body: params.Body, - State: models.StateDraft, CreatedAt: time.Now(), } return post, nil diff --git a/services/posts/delete.go b/services/posts/delete.go deleted file mode 100644 index df220ef..0000000 --- a/services/posts/delete.go +++ /dev/null @@ -1,67 +0,0 @@ -package posts - -import ( - "context" - "time" - - "lmika.dev/lmika/weiro/models" -) - -const ( - deleteDebounce = 2 * time.Second -) - -func (s *Service) DeletePost(ctx context.Context, pid int64, hardDelete bool) error { - post, site, err := s.fetchPostAndSite(ctx, pid) - if err != nil { - return err - } - - if hardDelete && post.DeletedAt.Unix() > 0 { - delta := time.Now().Sub(post.DeletedAt) - if delta < deleteDebounce { - return models.DeleteDebounceError - } - - if err := s.db.HardDeletePost(ctx, post.ID); err != nil { - return err - } - } else { - if err := s.db.SoftDeletePost(ctx, post.ID); err != nil { - return err - } - } - - s.publisher.Queue(site) - - return nil -} - -func (s *Service) RestorePost(ctx context.Context, pid int64) error { - post, site, err := s.fetchPostAndSite(ctx, pid) - if err != nil { - return err - } - - if err := s.db.RestorePost(ctx, post.ID); err != nil { - return err - } - - s.publisher.Queue(site) - return nil -} - -func (s *Service) fetchPostAndSite(ctx context.Context, pid int64) (*models.Post, models.Site, error) { - site, ok := models.GetSite(ctx) - if !ok { - return nil, models.Site{}, models.SiteRequiredError - } - - post, err := s.db.SelectPost(ctx, pid) - if err != nil { - return nil, models.Site{}, err - } else if post.SiteID != site.ID { - return nil, models.Site{}, models.NotFoundError - } - return post, site, nil -} diff --git a/services/posts/list.go b/services/posts/list.go index e94b24b..ba0d7a7 100644 --- a/services/posts/list.go +++ b/services/posts/list.go @@ -6,13 +6,13 @@ import ( "lmika.dev/lmika/weiro/models" ) -func (s *Service) ListPosts(ctx context.Context, showDeleted bool) ([]*models.Post, error) { +func (s *Service) ListPosts(ctx context.Context) ([]*models.Post, error) { site, ok := models.GetSite(ctx) if !ok { return nil, models.SiteRequiredError } - posts, err := s.db.SelectPostsOfSite(ctx, site.ID, showDeleted) + posts, err := s.db.SelectPostsOfSite(ctx, site.ID) if err != nil { return nil, err } @@ -21,9 +21,16 @@ func (s *Service) ListPosts(ctx context.Context, showDeleted bool) ([]*models.Po } func (s *Service) GetPost(ctx context.Context, pid int64) (*models.Post, error) { - post, _, err := s.fetchPostAndSite(ctx, pid) + site, ok := models.GetSite(ctx) + if !ok { + return nil, models.SiteRequiredError + } + + post, err := s.db.SelectPost(ctx, pid) if err != nil { return nil, err + } else if post.SiteID != site.ID { + return nil, models.NotFoundError } return post, nil diff --git a/services/posts/service.go b/services/posts/service.go index b7bb433..c0b19d7 100644 --- a/services/posts/service.go +++ b/services/posts/service.go @@ -7,10 +7,10 @@ import ( type Service struct { db *db.Provider - publisher *publisher.Queue + publisher *publisher.Publisher } -func New(db *db.Provider, publisher *publisher.Queue) *Service { +func New(db *db.Provider, publisher *publisher.Publisher) *Service { return &Service{ db: db, publisher: publisher, diff --git a/services/publisher/pqueue.go b/services/publisher/pqueue.go deleted file mode 100644 index 38608f3..0000000 --- a/services/publisher/pqueue.go +++ /dev/null @@ -1,44 +0,0 @@ -package publisher - -import ( - "context" - "log" - - "lmika.dev/lmika/weiro/models" -) - -type Queue struct { - publisher *Publisher - pending chan models.Site -} - -func NewQueue(publisher *Publisher) *Queue { - return &Queue{ - publisher: publisher, - pending: make(chan models.Site, 1), - } -} - -func (q *Queue) Queue(site models.Site) bool { - select { - case q.pending <- site: - return true - default: - return false - } -} - -func (q *Queue) Start(ctx context.Context) { - go func() { - for { - select { - case site := <-q.pending: - if err := q.publisher.Publish(ctx, site); err != nil { - log.Printf("error publishing site: %v", err) - } - case <-ctx.Done(): - return - } - } - }() -} diff --git a/services/publisher/service.go b/services/publisher/service.go index 2cc47c8..fe43854 100644 --- a/services/publisher/service.go +++ b/services/publisher/service.go @@ -35,7 +35,7 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error { } // Fetch all content of site - posts, err := p.db.SelectPostsOfSite(ctx, site.ID, false) + posts, err := p.db.SelectPostsOfSite(ctx, site.ID) if err != nil { return err } diff --git a/services/services.go b/services/services.go deleted file mode 100644 index edf52cd..0000000 --- a/services/services.go +++ /dev/null @@ -1,47 +0,0 @@ -package services - -import ( - "path/filepath" - - "lmika.dev/lmika/weiro/config" - "lmika.dev/lmika/weiro/providers/db" - "lmika.dev/lmika/weiro/services/auth" - "lmika.dev/lmika/weiro/services/posts" - "lmika.dev/lmika/weiro/services/publisher" - "lmika.dev/lmika/weiro/services/sites" -) - -type Services struct { - DB *db.Provider - Auth *auth.Service - Publisher *publisher.Publisher - PublisherQueue *publisher.Queue - Posts *posts.Service - Sites *sites.Service -} - -func New(cfg config.Config) (*Services, error) { - dbp, err := db.New(filepath.Join(cfg.DataDir, "weiro.db")) - if err != nil { - return nil, err - } - - authSvc := auth.New(dbp) - publisherSvc := publisher.New(dbp) - publisherQueue := publisher.NewQueue(publisherSvc) - postService := posts.New(dbp, publisherQueue) - siteService := sites.New(dbp) - - return &Services{ - DB: dbp, - Auth: authSvc, - Publisher: publisherSvc, - PublisherQueue: publisherQueue, - Posts: postService, - Sites: siteService, - }, nil -} - -func (s *Services) Close() error { - return s.DB.Close() -} diff --git a/services/sites/services.go b/services/sites/services.go deleted file mode 100644 index 22e3916..0000000 --- a/services/sites/services.go +++ /dev/null @@ -1,128 +0,0 @@ -package sites - -import ( - "context" - "time" - - "emperror.dev/errors" - "github.com/go-ozzo/ozzo-validation/v4" - "github.com/gofiber/fiber/v3" - "lmika.dev/lmika/weiro/models" - "lmika.dev/lmika/weiro/providers/db" -) - -type Service struct { - db *db.Provider -} - -func New(dbp *db.Provider) *Service { - return &Service{ - db: dbp, - } -} - -func (s *Service) HasUsersAsSites(ctx context.Context) (bool, error) { - return s.db.HasUsersAndSites(ctx) -} - -func (s *Service) BestSite(ctx context.Context, user models.User) (models.Site, error) { - sites, err := s.db.SelectSitesOwnedByUser(ctx, user.ID) - if err != nil { - return models.Site{}, err - } else if len(sites) == 0 { - return models.Site{}, errors.New("no sites found") - } - - return sites[0], nil -} - -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"` -} - -func (frr FirstRunRequest) Validate() error { - return validation.ValidateStruct(&frr, - validation.Field(&frr.Username, validation.Required, validation.Match(models.ValidUserName)), - validation.Field(&frr.Password1, validation.Required), - validation.Field(&frr.Password2, validation.Required, validation.By(stringEquals(frr.Password1))), - ) -} - -func (s *Service) FirstRun(ctx context.Context, req FirstRunRequest) (newUser models.User, newSite models.Site, _ error) { - if err := req.Validate(); err != nil { - return newUser, newSite, err - } - - hasSite, err := s.db.HasUsersAndSites(ctx) - if err != nil { - return newUser, newSite, err - } else if hasSite { - return newUser, newSite, errors.New("user and site already exists") - } - - newUser = models.User{ - Username: req.Username, - TimeZone: "UTC", - Created: time.Now(), - } - newUser.SetPassword(req.Password1) - if err := s.db.SaveUser(ctx, &newUser); err != nil { - return newUser, newSite, err - } - - newSite = models.Site{ - Title: defaultIfEmpty(req.SiteName, "New Site"), - GUID: models.NewNanoID(), - OwnerID: newUser.ID, - Created: time.Now(), - } - if err := s.db.SaveSite(ctx, &newSite); err != nil { - return newUser, newSite, err - } - - hasNetlifyConfig := req.SiteURL != "" && req.NetlifySiteID != "" && req.NetlifyAPIKey != "" - if hasNetlifyConfig { - target := models.SitePublishTarget{ - SiteID: newSite.ID, - Enabled: true, - GUID: models.NewNanoID(), - BaseURL: req.SiteURL, - TargetType: "netlify", - TargetRef: req.NetlifySiteID, - TargetKey: req.NetlifyAPIKey, - } - if err := s.db.SavePublishTarget(ctx, &target); err != nil { - return newUser, newSite, err - } - } - - return newUser, newSite, nil -} - -func (s *Service) GetSiteByID(ctx context.Context, siteID int64) (models.Site, error) { - user, ok := models.GetUser(ctx) - if !ok { - return models.Site{}, models.UserRequiredError - } - - site, err := s.db.SelectSiteByID(ctx, siteID) - if err != nil { - return models.Site{}, err - } - - if site.OwnerID != user.ID { - return models.Site{}, fiber.ErrForbidden - } - - return site, nil -} - -func (s *Service) ListAllSitesWithOwners(ctx context.Context) ([]db.SiteWithOwner, error) { - return s.db.SelectAllSitesWithOwners(ctx) -} diff --git a/services/sites/utils.go b/services/sites/utils.go deleted file mode 100644 index af0af63..0000000 --- a/services/sites/utils.go +++ /dev/null @@ -1,23 +0,0 @@ -package sites - -import ( - "emperror.dev/errors" - validation "github.com/go-ozzo/ozzo-validation/v4" -) - -func stringEquals(str string) validation.RuleFunc { - return func(value interface{}) error { - s, _ := value.(string) - if s != str { - return errors.New("unexpected string") - } - return nil - } -} - -func defaultIfEmpty(value string, defaultValue string) string { - if value == "" { - return defaultValue - } - return value -} diff --git a/sql/queries/posts.sql b/sql/queries/posts.sql index 3f740bf..2a95e08 100644 --- a/sql/queries/posts.sql +++ b/sql/queries/posts.sql @@ -1,12 +1,5 @@ -- name: SelectPostsOfSite :many -SELECT * -FROM posts -WHERE site_id = ? AND ( - CASE CAST (sqlc.arg(post_filter) AS TEXT) - WHEN 'deleted' THEN deleted_at > 0 - ELSE deleted_at = 0 - END -) ORDER BY created_at DESC LIMIT 10; +SELECT * FROM posts WHERE site_id = ? ORDER BY created_at DESC LIMIT 10; -- name: SelectPost :one SELECT * FROM posts WHERE id = ? LIMIT 1; @@ -17,34 +10,19 @@ SELECT * FROM posts WHERE guid = ? LIMIT 1; -- name: InsertPost :one INSERT INTO posts ( site_id, - state, guid, title, body, slug, created_at, - updated_at, - published_at, - deleted_at -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + published_at +) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id; -- name: UpdatePost :exec UPDATE posts SET title = ?, - state = ?, body = ?, slug = ?, - updated_at = ?, - published_at = ?, - deleted_at = ? -WHERE id = ?; - --- name: SoftDeletePost :exec -UPDATE posts SET deleted_at = ? WHERE id = ?; - --- name: RestorePost :exec -UPDATE posts SET deleted_at = 0 WHERE id = ?; - --- name: HardDeletePost :exec -DELETE FROM posts WHERE id = ?; \ No newline at end of file + published_at = ? +WHERE id = ?; \ No newline at end of file diff --git a/sql/queries/pubtargets.sql b/sql/queries/pubtargets.sql index f3fdabd..e77ef8b 100644 --- a/sql/queries/pubtargets.sql +++ b/sql/queries/pubtargets.sql @@ -4,11 +4,10 @@ SELECT * FROM publish_targets WHERE site_id = ?; -- name: InsertPublishTarget :one INSERT INTO publish_targets ( site_id, - guid, target_type, enabled, base_url, target_ref, target_key -) VALUES (?, ?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?) RETURNING id; \ No newline at end of file diff --git a/sql/queries/sites.sql b/sql/queries/sites.sql index 92e7ccb..0ea7567 100644 --- a/sql/queries/sites.sql +++ b/sql/queries/sites.sql @@ -1,27 +1,13 @@ -- name: SelectSitesOwnedByUser :many -SELECT * FROM sites WHERE owner_id = ? ORDER BY title ASC; +SELECT * FROM sites WHERE owner_id = ?; -- name: SelectSiteByID :one SELECT * FROM sites WHERE id = ?; --- name: SelectSiteByGUID :one -SELECT * FROM sites WHERE guid = ?; - -- name: InsertSite :one INSERT INTO sites ( owner_id, - guid, title, - tagline, - created_at -) VALUES (?, ?, ?, ?, ?) -RETURNING id; - --- name: HasUsersAndSites :one -SELECT (SELECT COUNT(*) FROM users) > 0 AND (SELECT COUNT(*) FROM sites) > 0 AS has_users_and_sites; - --- name: SelectAllSitesWithOwners :many -SELECT s.id, s.guid, s.title, s.owner_id, u.username -FROM sites s -JOIN users u ON s.owner_id = u.id -ORDER BY s.title ASC; \ No newline at end of file + tagline +) VALUES (?, ?, ?) +RETURNING id; \ No newline at end of file diff --git a/sql/queries/users.sql b/sql/queries/users.sql index 1fd590c..ec4c69d 100644 --- a/sql/queries/users.sql +++ b/sql/queries/users.sql @@ -1,11 +1,8 @@ -- name: SelectUserByUsername :one -SELECT * FROM users WHERE username = ? LIMIT 1; - --- name: SelectUserByID :one -SELECT * FROM users WHERE id = ? LIMIT 1; +SELECT * FROM users WHERE username = ?; -- name: InsertUser :one -INSERT INTO users (username, password, created_at) VALUES (?, ?, ?) RETURNING id; +INSERT INTO users (username, password) VALUES (?, ?) RETURNING id; -- name: UpdateUser :exec UPDATE users SET username = ?, password = ? WHERE id = ?; \ No newline at end of file diff --git a/sql/schema/01_init.up.sql b/sql/schema/01_init.up.sql index cc76a5b..0c0e1e2 100644 --- a/sql/schema/01_init.up.sql +++ b/sql/schema/01_init.up.sql @@ -1,49 +1,40 @@ CREATE TABLE users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL, - password TEXT NOT NULL, - created_at INTEGER NOT NULL + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL, + password TEXT NOT NULL ); CREATE UNIQUE INDEX idx_users_username ON users (username); CREATE TABLE sites ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - owner_id INTEGER NOT NULL, - guid TEXT NOT NULL, - title TEXT NOT NULL, - tagline TEXT NOT NULL, - created_at INTEGER NOT NULL, + id INTEGER PRIMARY KEY AUTOINCREMENT, + owner_id INTEGER NOT NULL, + title TEXT NOT NULL, + tagline TEXT NOT NULL, FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE ); CREATE INDEX idx_site_owner ON sites (owner_id); -CREATE UNIQUE INDEX idx_site_guid ON sites (guid); CREATE TABLE publish_targets ( id INTEGER PRIMARY KEY AUTOINCREMENT, site_id INTEGER NOT NULL, - guid TEXT NOT NULL, - target_type TEXT NOT NULL, - enabled INT NOT NULL, + target_type TEXT NOT NULL, + enabled INT NOT NULL, base_url TEXT NOT NULL, target_ref TEXT NOT NULL, target_key TEXT NOT NULL ); CREATE INDEX idx_publish_targets_site ON publish_targets (site_id); -CREATE UNIQUE INDEX idx_publish_targets_guid ON publish_targets (guid); CREATE TABLE posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, site_id INTEGER NOT NULL, - state INTEGER NOT NULL, guid TEXT NOT NULL, title TEXT NOT NULL, body TEXT NOT NULL, slug TEXT NOT NULL, created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - published_at INTEGER NOT NULL, - deleted_at INTEGER NOT NULL + published_at INTEGER NOT NULL ); CREATE INDEX idx_post_site ON posts (site_id); CREATE UNIQUE INDEX idx_post_guid ON posts (guid); \ No newline at end of file diff --git a/views/_common/nav.html b/views/_common/nav.html index 8729c09..9446f20 100644 --- a/views/_common/nav.html +++ b/views/_common/nav.html @@ -1,6 +1,6 @@