Add Obsidian vault import feature (#8)

- New 'Import Obsidian' action on site settings page
- Upload a zip file of an Obsidian vault to import all notes as posts
- Markdown notes imported with title from filename, published date from
  file timestamp, and body with front-matter stripped
- Images and other attachments saved as Upload records
- New obsimport service handles zip traversal and import logic
- Unit tests for front-matter stripping

Co-authored-by: Shelley <shelley@exe.dev>

Co-authored-by: exe.dev user <exedev@kernel-leviathan.exe.xyz>
Reviewed-on: #8
This commit is contained in:
lmika 2026-04-09 11:40:52 +00:00
parent d21aeadd56
commit a3197f9b11
8 changed files with 376 additions and 0 deletions

50
handlers/obsimport.go Normal file
View file

@ -0,0 +1,50 @@
package handlers
import (
"fmt"
"os"
"path/filepath"
"github.com/gofiber/fiber/v3"
"lmika.dev/lmika/weiro/models"
"lmika.dev/lmika/weiro/services/obsimport"
)
type ObsImportHandler struct {
ObsImportService *obsimport.Service
ScratchDir string
}
func (h ObsImportHandler) Form(c fiber.Ctx) error {
return c.Render("obsimport/form", fiber.Map{})
}
func (h ObsImportHandler) Upload(c fiber.Ctx) error {
site := c.Locals("site").(models.Site)
fileHeader, err := c.FormFile("zipfile")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "no file provided")
}
// Save uploaded file to scratch dir
if err := os.MkdirAll(h.ScratchDir, 0755); err != nil {
return err
}
dstPath := filepath.Join(h.ScratchDir, models.NewNanoID()+".zip")
if err := c.SaveFile(fileHeader, dstPath); err != nil {
return err
}
defer os.Remove(dstPath)
result, err := h.ObsImportService.ImportZip(c.Context(), dstPath)
if err != nil {
return err
}
return c.Render("obsimport/result", fiber.Map{
"result": result,
"siteURL": fmt.Sprintf("/sites/%v/posts", site.ID),
})
}