From 2aab1bcdd26b7c1a27a228c032f986cadcdb08f9 Mon Sep 17 00:00:00 2001 From: Tueem Date: Wed, 27 May 2026 00:59:25 +0200 Subject: [PATCH 1/5] feat(template): add multi page upload capability --- internal/database/database.go | 2 + internal/database/svgdb.go | 50 ++++++-------- internal/database/svgpagedb.go | 118 +++++++++++++++++++++++++++++++++ internal/routes/upload.go | 50 +++++++++----- pkg/format/formatconverter.go | 6 +- pkg/svg/actions/delete.go | 12 +++- pkg/svg/actions/page.go | 60 +++++++++++++++++ pkg/svg/actions/template.go | 2 +- pkg/svg/actions/upload.go | 34 ++++------ pkg/svg/storage.go | 35 ++++++---- pkg/svg/templates.go | 26 +++++++- 11 files changed, 306 insertions(+), 89 deletions(-) create mode 100644 internal/database/svgpagedb.go create mode 100644 pkg/svg/actions/page.go diff --git a/internal/database/database.go b/internal/database/database.go index f8144a8..5b71854 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -25,6 +25,8 @@ func OpenSQLite(basepath string) error { func InitDB() { _, err := database.Exec(TOKENTABLECREATE) _, err = database.Exec(SVGTABLECREATE) + _, err = database.Exec(SVGPAGETABLECREATE) + if err != nil { log.Fatal("Failed to init database:\n", err) } diff --git a/internal/database/svgdb.go b/internal/database/svgdb.go index d2dcaaf..b256259 100644 --- a/internal/database/svgdb.go +++ b/internal/database/svgdb.go @@ -1,32 +1,23 @@ package database import ( - "encoding/json" - "log" - "tomatentum.net/svg-templater/pkg/svg" ) const SVGTABLECREATE string = ` CREATE TABLE IF NOT EXISTS svg ( id varchar(16) PRIMARY KEY NOT NULL, - name varchar(32), - templatekeys longtext NOT NULL + name varchar(32) );` -const INSERTSVGSQL string = "INSERT INTO svg VALUES (?, ?, ?);" +const INSERTSVGSQL string = "INSERT INTO svg VALUES (?, ?);" const GETSPECIFICSVGSQL string = "SELECT * FROM svg WHERE id = ?;" const GETSVGSQL string = "SELECT * FROM svg;" const DELETESVGSQL string = "DELETE FROM svg WHERE id = ?;" const RENAMESVGSQL string = "UPDATE svg SET name = ? WHERE id = ?;" func InsertSVG(data *svg.TemplateData) error { - json, err := json.Marshal(data.TemplateKeys) - if err != nil { - return err - } - - if _, err := database.Exec(INSERTSVGSQL, data.Id, data.Name, string(json)); err != nil { + if _, err := database.Exec(INSERTSVGSQL, data.Id, data.Name); err != nil { return err } @@ -42,18 +33,19 @@ func GetSVG() ([]svg.TemplateData, error) { templates := make([]svg.TemplateData, 0) for result.Next() { var ( - id string - name string - keysjson []byte - keys []string + id string + name string ) - if err := result.Scan(&id, &name, &keysjson); err != nil { + if err := result.Scan(&id, &name); err != nil { return nil, err } - if err := json.Unmarshal(keysjson, &keys); err != nil { + pages, err := GetSVGPages(id) + + if err != nil { return nil, err } - templates = append(templates, svg.TemplateData{Id: id, Name: name, TemplateKeys: keys}) + + templates = append(templates, svg.TemplateData{Id: id, Name: name, Pages: pages}) } if err := result.Err(); err != nil { @@ -65,18 +57,17 @@ func GetSVG() ([]svg.TemplateData, error) { func GetSpecificSVG(id string) (svg.TemplateData, error) { result := database.QueryRow(GETSPECIFICSVGSQL, id) - var ( - name string - keysjson []byte - keys []string - ) - if err := result.Scan(&id, &name, &keysjson); err != nil { + var name string + if err := result.Scan(&id, &name); err != nil { return svg.TemplateData{}, err } - if err := json.Unmarshal(keysjson, &keys); err != nil { + + pages, err := GetSVGPages(id) + + if err != nil { return svg.TemplateData{}, err } - return svg.TemplateData{Id: id, Name: name, TemplateKeys: keys}, nil + return svg.TemplateData{Id: id, Name: name, Pages: pages}, nil } func DeleteSvg(id string) (bool, error) { @@ -92,12 +83,11 @@ func DeleteSvg(id string) (bool, error) { if num == 0 { return false, nil } - return true, nil + return DeleteAllSVGPages(id) } func RenameSvg(id string, name string) error { - res, err := database.Exec(RENAMESVGSQL, name, id) - log.Println(id, name, res) + _, err := database.Exec(RENAMESVGSQL, name, id) if err != nil { return err } diff --git a/internal/database/svgpagedb.go b/internal/database/svgpagedb.go new file mode 100644 index 0000000..b40cf6c --- /dev/null +++ b/internal/database/svgpagedb.go @@ -0,0 +1,118 @@ +package database + +import ( + "encoding/json" + + "tomatentum.net/svg-templater/pkg/svg" +) + +func AddPage(page svg.TemplatePage) { + +} + +func DeletePage(templateID string, page int) { + +} + +const SVGPAGETABLECREATE string = ` + CREATE TABLE IF NOT EXISTS svgpage ( + id varchar(16) NOT NULL, + page int NOT NULL, + templatekeys longtext NOT NULL, + PRIMARY KEY (id, page) + );` + +const INSERTSVGPAGESQL string = "INSERT INTO svgpage VALUES (?, ?, ?);" +const GETSPECIFICSVGPAGESSQL string = "SELECT * FROM svgpage WHERE id = ?;" +const DELETESVGPAGESQL string = "DELETE FROM svgpage WHERE id = ? AND page = ?;" +const DELETEALLSVGPAGESQL string = "DELETE FROM svgpage WHERE id = ?;" +const COUNTSVGPAGESQL string = "SELECT COUNT(*) FROM svgpage WHERE id = ?;" + +func InsertSVGPage(data *svg.TemplatePage) (int, error) { + json, err := json.Marshal(data.TemplateKeys) + if err != nil { + return 0, err + } + + count, err := GetPageCount(data.TemplateId) + + if err != nil { + return 0, err + } + + if _, err := database.Exec(INSERTSVGPAGESQL, data.TemplateId, count+1, string(json)); err != nil { + return 0, err + } + + return count + 1, nil +} + +func GetSVGPages(id string) ([]svg.TemplatePage, error) { + res, err := database.Query(GETSPECIFICSVGPAGESSQL, id) + + if err != nil { + return nil, err + } + defer res.Close() + pages := make([]svg.TemplatePage, 0) + for res.Next() { + var ( + id string + page int + keysjson []byte + keys []string + ) + if err := res.Scan(&id, &page, &keysjson); err != nil { + return nil, err + } + if err := json.Unmarshal(keysjson, &keys); err != nil { + return nil, err + } + pages = append(pages, svg.TemplatePage{TemplateId: id, Page: page, TemplateKeys: keys}) + } + return pages, nil +} + +func DeleteSVGPage(id string, page int) (bool, error) { + res, err := database.Exec(DELETESVGPAGESQL, id, page) + if err != nil { + return false, err + } + num, err := res.RowsAffected() + if err != nil { + return false, err + } + + if num == 0 { + return false, nil + } + return true, nil +} + +func DeleteAllSVGPages(id string) (bool, error) { + res, err := database.Exec(DELETEALLSVGPAGESQL, id) + if err != nil { + return false, err + } + num, err := res.RowsAffected() + if err != nil { + return false, err + } + + if num == 0 { + return false, nil + } + return true, nil +} + +func GetPageCount(id string) (int, error) { + res := database.QueryRow(COUNTSVGPAGESQL, id) + + var count int + + if err := res.Scan(&count); err != nil { + return 0, err + } + + return count, nil +} diff --git a/internal/routes/upload.go b/internal/routes/upload.go index 0674310..0340276 100644 --- a/internal/routes/upload.go +++ b/internal/routes/upload.go @@ -10,39 +10,53 @@ import ( "tomatentum.net/svg-templater/pkg/svg/actions" ) -func CreateSVG(writer http.ResponseWriter, r *http.Request) { +func CreateSVG(w http.ResponseWriter, r *http.Request) { - contentType := r.Header.Get("Content-Type") - if contentType != "image/svg+xml" { - http.Error(writer, "Incorrect Content-Type. Needs image/svg+xml.", http.StatusUnsupportedMediaType) + if err := r.ParseMultipartForm(128000000); err != nil { + http.Error(w, "Couldn't parse form data.", http.StatusBadRequest) return } - readsvg, err := io.ReadAll(r.Body) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - log.Println("Error while reading Body\n", err) - return - } + fileheaders := r.MultipartForm.File["files"] - if ok, err := validateSVG(readsvg); err != nil || !ok { - http.Error(writer, err.Error(), http.StatusUnsupportedMediaType) - log.Println("Wrong Media Type was uploaded\n", err) - return + files := make([][]byte, len(fileheaders)) + + for i, fileh := range fileheaders { + file, err := fileh.Open() + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + buf, err := io.ReadAll(file) + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if ok, err := validateSVG(buf); err != nil || !ok { + http.Error(w, err.Error(), http.StatusUnsupportedMediaType) + log.Println("Wrong Media Type was uploaded\n", err) + return + } + + files[i] = buf } name := r.URL.Query().Get("name") - data, err := actions.Create(readsvg, name) + data, err := actions.Create(files, name) if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusInternalServerError) log.Println("Failed creating SVG template\n", err) return } - writer.Header().Add("Content-Type", "application/json") - json.NewEncoder(writer).Encode(data) + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(data) } func validateSVG(svgbuf []byte) (bool, error) { diff --git a/pkg/format/formatconverter.go b/pkg/format/formatconverter.go index 1610752..c632d29 100644 --- a/pkg/format/formatconverter.go +++ b/pkg/format/formatconverter.go @@ -55,10 +55,8 @@ func convertPNG(input string, param ConversionParameters) (io.ReadCloser, error) var args []string var outExt string - fontsdir, err := svg.Storage.GetFontsDir() - if err != nil { - return nil, err - } + fontsdir := svg.Storage.GetFontsDir() + args = append(args, "--skip-system-fonts") args = append(args, "--use-fonts-dir", fontsdir) diff --git a/pkg/svg/actions/delete.go b/pkg/svg/actions/delete.go index e63597e..db0c3b0 100644 --- a/pkg/svg/actions/delete.go +++ b/pkg/svg/actions/delete.go @@ -2,6 +2,7 @@ package actions import ( "log" + "strings" "tomatentum.net/svg-templater/internal/database" "tomatentum.net/svg-templater/pkg/svg" @@ -12,9 +13,18 @@ func Delete(id string) (bool, error) { return success, err } - if err := svg.Storage.Delete(id); err != nil { + entries, err := svg.Storage.List() + + if err != nil { return false, err } + + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), id) { + svg.Storage.Delete(entry.Name()) + } + } + log.Println("Deleted SVG Template " + id) return true, nil } diff --git a/pkg/svg/actions/page.go b/pkg/svg/actions/page.go new file mode 100644 index 0000000..c8a95c9 --- /dev/null +++ b/pkg/svg/actions/page.go @@ -0,0 +1,60 @@ +package actions + +import ( + "bytes" + "fmt" + "log" + "regexp" + "strconv" + + "tomatentum.net/svg-templater/internal/database" + "tomatentum.net/svg-templater/pkg/svg" +) + +const FILEFORMAT string = "%s-%d" + +func AddPage(id string, svgbuf []byte) (svg.TemplatePage, error) { + data := svg.TemplatePage{TemplateId: id} + populateKeys(&data, svgbuf) + + page, err := database.InsertSVGPage(&data) + + data.Page = page + + if err != nil { + return svg.TemplatePage{}, err + } + + _, err = svg.Storage.Create(fmt.Sprintf(FILEFORMAT, id, page), bytes.NewReader(svgbuf)) + if err != nil { + return svg.TemplatePage{}, err + } + + log.Println("Created SVG Template " + data.TemplateId + " Page " + strconv.Itoa(page)) + return data, nil +} + +func DeletePage(id string, page int) (bool, error) { + if success, err := database.DeleteSVGPage(id, page); err != nil || !success { + return success, err + } + + if err := svg.Storage.Delete(fmt.Sprintf(FILEFORMAT, id, page)); err != nil { + return false, err + } + log.Println("Deleted SVG Template " + id + " page " + strconv.Itoa(page)) + return true, nil +} + +func populateKeys(data *svg.TemplatePage, svgblob []byte) { + regex := regexp.MustCompile(svg.KeyRegex) + result := regex.FindAllSubmatch(svgblob, -1) + templateKeys := make([]string, len(result)) + + for i, matches := range result { + varname := matches[1] // first capture group + templateKeys[i] = string(varname) + } + data.TemplateKeys = templateKeys + log.Println("Found keys:\n", templateKeys) +} diff --git a/pkg/svg/actions/template.go b/pkg/svg/actions/template.go index 029d4ca..fc74c81 100644 --- a/pkg/svg/actions/template.go +++ b/pkg/svg/actions/template.go @@ -57,7 +57,7 @@ func verifyTemplate(id string, keys []string) (bool, error) { return false, err } - for _, key := range data.TemplateKeys { + for _, key := range data.AllKeys() { if !slices.Contains(keys, key) { return false, nil } diff --git a/pkg/svg/actions/upload.go b/pkg/svg/actions/upload.go index d7ed00e..66b0751 100644 --- a/pkg/svg/actions/upload.go +++ b/pkg/svg/actions/upload.go @@ -1,25 +1,30 @@ package actions import ( - "bytes" "crypto/rand" "encoding/hex" "log" - "regexp" "tomatentum.net/svg-templater/internal/database" "tomatentum.net/svg-templater/pkg/svg" ) -func Create(svgbuf []byte, name string) (svg.TemplateData, error) { - data := svg.TemplateData{Id: generateId(), Name: name, TemplateKeys: nil} - populateKeys(&data, svgbuf) +func Create(svgbufs [][]byte, name string) (svg.TemplateData, error) { + id := generateId() - _, err := svg.Storage.Create(data.Id, bytes.NewReader(svgbuf)) - if err != nil { - return svg.TemplateData{}, err + pages := make([]svg.TemplatePage, len(svgbufs)) + for i, pagebuf := range svgbufs { + page, err := AddPage(id, pagebuf) + + if err != nil { + return svg.TemplateData{}, err + } + + pages[i] = page } + data := svg.TemplateData{Id: id, Name: name, Pages: pages} + if err := database.InsertSVG(&data); err != nil { return svg.TemplateData{}, err } @@ -28,19 +33,6 @@ func Create(svgbuf []byte, name string) (svg.TemplateData, error) { return data, nil } -func populateKeys(data *svg.TemplateData, svgblob []byte) { - regex := regexp.MustCompile(svg.KeyRegex) - result := regex.FindAllSubmatch(svgblob, -1) - templateKeys := make([]string, len(result)) - - for i, matches := range result { - varname := matches[1] // first capture group - templateKeys[i] = string(varname) - } - data.TemplateKeys = templateKeys - log.Println("Found keys:\n", templateKeys) -} - func generateId() string { token := make([]byte, 16) if _, err := rand.Read(token); err != nil { diff --git a/pkg/svg/storage.go b/pkg/svg/storage.go index 51fdaa9..966338a 100644 --- a/pkg/svg/storage.go +++ b/pkg/svg/storage.go @@ -13,12 +13,13 @@ import ( ) type SvgStorage interface { - Create(id string, svg io.Reader) (string, error) - Get(id string) (io.ReadCloser, error) - Delete(id string) error + Create(name string, svg io.Reader) (string, error) + Get(name string) (io.ReadCloser, error) + Delete(name string) error + List() ([]os.DirEntry, error) AddFont(reader io.Reader, format string) error GetFonts() ([]string, error) - GetFontsDir() (string, error) + GetFontsDir() string CreatePublic(data io.Reader, filetype string) (string, error) GetPublic(path string) (io.ReadCloser, error) GetPublicDir() http.Dir @@ -44,8 +45,8 @@ func NewFileStorage(path, publicSubPath, fontssubpath string) *FileSvgStorage { return &FileSvgStorage{path, publicSubPath, fontssubpath} } -func (f FileSvgStorage) Create(id string, svg io.Reader) (string, error) { - file, err := os.Create(filepath.Join(f.basepath, id+".svg")) +func (f FileSvgStorage) Create(name string, svg io.Reader) (string, error) { + file, err := os.Create(filepath.Join(f.basepath, name+".svg")) if err != nil { return "", err @@ -61,20 +62,30 @@ func (f FileSvgStorage) Create(id string, svg io.Reader) (string, error) { return file.Name(), nil } -func (f FileSvgStorage) Get(id string) (io.ReadCloser, error) { - file, err := os.Open(filepath.Join(f.basepath, id+".svg")) +func (f FileSvgStorage) Get(name string) (io.ReadCloser, error) { + file, err := os.Open(filepath.Join(f.basepath, name+".svg")) if err != nil { return nil, err } return file, nil } -func (f FileSvgStorage) Delete(id string) error { - path := filepath.Join(f.basepath, id+".svg") +func (f FileSvgStorage) Delete(name string) error { + var path string + if strings.HasSuffix(name, ".svg") { + path = filepath.Join(f.basepath, name) + } else { + path = filepath.Join(f.basepath, name+".svg") + + } defer log.Println("Deleted File: " + path) return os.Remove(path) } +func (f FileSvgStorage) List() ([]os.DirEntry, error) { + return os.ReadDir(f.basepath) +} + func (f FileSvgStorage) CreatePublic(data io.Reader, filetype string) (string, error) { path := filepath.Join(f.basepath, f.publicSubPath) if err := os.MkdirAll(path, 0755); err != nil { @@ -164,8 +175,8 @@ func (f FileSvgStorage) GetFonts() ([]string, error) { return fonts, nil } -func (f FileSvgStorage) GetFontsDir() (string, error) { - return filepath.Join(f.basepath, f.fontssubpath), nil +func (f FileSvgStorage) GetFontsDir() string { + return filepath.Join(f.basepath, f.fontssubpath) } func getFontName(svgblob []byte) (string, error) { diff --git a/pkg/svg/templates.go b/pkg/svg/templates.go index 998c143..b2e42e3 100644 --- a/pkg/svg/templates.go +++ b/pkg/svg/templates.go @@ -1,11 +1,33 @@ package svg +import "slices" + const KeyRegex string = `\{\{\s*(.*?)\s*\}\}` type TemplateData struct { - Id string - Name string + Id string + Name string + Pages []TemplatePage +} + +type TemplatePage struct { + TemplateId string + Page int TemplateKeys []string } var Storage SvgStorage + +func (d TemplateData) AllKeys() []string { + keys := make([]string, 0) + + for _, page := range d.Pages { + for _, key := range page.TemplateKeys { + if !slices.Contains(keys, key) { + keys = append(keys, key) + } + } + } + + return keys +} From ba2332ecb630667ae9e3113193bc049ee3a3022f Mon Sep 17 00:00:00 2001 From: Tueem Date: Wed, 27 May 2026 22:02:11 +0200 Subject: [PATCH 2/5] feat(template): add multiple page support to templates --- internal/routes/download.go | 19 ++++++++++----- pkg/svg/actions/download.go | 26 ++++++++++++++------ pkg/svg/actions/page.go | 13 ++++++++++ pkg/svg/actions/template.go | 47 +++++++++++++++++++++---------------- pkg/svg/storage.go | 7 +++--- 5 files changed, 76 insertions(+), 36 deletions(-) diff --git a/internal/routes/download.go b/internal/routes/download.go index cd26063..c03d048 100644 --- a/internal/routes/download.go +++ b/internal/routes/download.go @@ -17,7 +17,7 @@ type downloadRequest struct { } type downloadResponse struct { - Url string + Urls []string } func DownloadSVG(w http.ResponseWriter, r *http.Request) { @@ -73,16 +73,23 @@ func DownloadSVG(w http.ResponseWriter, r *http.Request) { Keys: request.TemplateKeys, } - filename, err := actions.ProvideFile(&templateParam, &convParam) + filenames, err := actions.ProvideFile(&templateParam, &convParam) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - urlparsed := getPublicUrl(r, filename) - log.Printf("Made %s available as %s\n", id, urlparsed) - response := downloadResponse{ - Url: urlparsed, + + urlsparsed := make([]string, len(filenames)) + for i, filename := range filenames { + urlsparsed[i] = getPublicUrl(r, filename) + log.Printf("Made %s page %d available as %s\n", id, i, urlsparsed[i]) + } + + response := downloadResponse{ + Urls: urlsparsed, + } + w.Header().Add("Content-Type", "application/json") json, err := json.Marshal(response) if err != nil { diff --git a/pkg/svg/actions/download.go b/pkg/svg/actions/download.go index 28c977c..47951c2 100644 --- a/pkg/svg/actions/download.go +++ b/pkg/svg/actions/download.go @@ -3,20 +3,32 @@ package actions import ( "bytes" "log" + "strconv" "tomatentum.net/svg-templater/pkg/format" "tomatentum.net/svg-templater/pkg/svg" ) -func ProvideFile(r *TemplateParameters, conversion *format.ConversionParameters) (string, error) { - templatedSvgblob, err := Template(r) +func ProvideFile(r *TemplateParameters, conversion *format.ConversionParameters) ([]string, error) { + templatedPages, err := Template(r) if err != nil { - return "", err + return nil, err } log.Printf("Converting %s to format %s (w=%d,h=%d)", r.Id, conversion.Format, conversion.Width, conversion.Height) - result, err := format.ConvertByte(templatedSvgblob, *conversion) - if err != nil { - return "", err + filenames := make([]string, len(templatedPages)) + for i, pagedata := range templatedPages { + result, err := format.ConvertByte(pagedata, *conversion) + if err != nil { + return nil, err + } + filenames[i], err = svg.Storage.CreatePublic(bytes.NewReader(result), strconv.Itoa(i+1), conversion.Format) + + if err != nil { + return nil, err + } + log.Printf("Converted %s page %d to format %s (w=%d,h=%d)", r.Id, i, conversion.Format, conversion.Width, conversion.Height) + } - return svg.Storage.CreatePublic(bytes.NewReader(result), conversion.Format) + + return filenames, nil } diff --git a/pkg/svg/actions/page.go b/pkg/svg/actions/page.go index c8a95c9..f427272 100644 --- a/pkg/svg/actions/page.go +++ b/pkg/svg/actions/page.go @@ -3,6 +3,7 @@ package actions import ( "bytes" "fmt" + "io" "log" "regexp" "strconv" @@ -34,6 +35,18 @@ func AddPage(id string, svgbuf []byte) (svg.TemplatePage, error) { return data, nil } +func GetPage(id string, page int) ([]byte, error) { + file, err := svg.Storage.Get(fmt.Sprintf(FILEFORMAT, id, page)) + + if err != nil { + return nil, err + } + + defer file.Close() + + return io.ReadAll(file) +} + func DeletePage(id string, page int) (bool, error) { if success, err := database.DeleteSVGPage(id, page); err != nil || !success { return success, err diff --git a/pkg/svg/actions/template.go b/pkg/svg/actions/template.go index fc74c81..91a7bb6 100644 --- a/pkg/svg/actions/template.go +++ b/pkg/svg/actions/template.go @@ -2,7 +2,6 @@ package actions import ( "errors" - "io" "log" "maps" "regexp" @@ -18,29 +17,42 @@ type TemplateParameters struct { Keys map[string]string } -func Template(r *TemplateParameters) ([]byte, error) { - mapkeys := slices.Collect(maps.Keys(r.Keys)) - ok, err := verifyTemplate(r.Id, mapkeys) +func Template(r *TemplateParameters) ([][]byte, error) { + + data, err := database.GetSpecificSVG(r.Id) + if err != nil { return nil, err } + + mapkeys := slices.Collect(maps.Keys(r.Keys)) + ok, err := verifyTemplate(data, mapkeys) + + if err != nil { + return nil, err + } + if !ok { return nil, errors.New("Template does not exist.") } log.Printf("Replacing keys of %s template\n", r.Id) - reader, err := svg.Storage.Get(r.Id) - if err != nil { - return nil, err + pagesdata := make([][]byte, len(data.Pages)) + + for _, page := range data.Pages { + pagedata, err := GetPage(page.TemplateId, page.Page) + + if err != nil { + return nil, err + } + + replaceAll(&pagedata, r.Keys) + pagesdata[page.Page-1] = pagedata + log.Printf("Finished replacing keys of %s page %d\n", r.Id, page.Page) + } - defer reader.Close() - svgblob, err := io.ReadAll(reader) - if err != nil { - return nil, err - } - replaceAll(&svgblob, r.Keys) log.Printf("Finished replacing keys of %s template\n", r.Id) - return svgblob, nil + return pagesdata, nil } func replaceAll(svgblob *[]byte, keys map[string]string) { @@ -51,12 +63,7 @@ func replaceAll(svgblob *[]byte, keys map[string]string) { })) } -func verifyTemplate(id string, keys []string) (bool, error) { - data, err := database.GetSpecificSVG(id) - if err != nil { - return false, err - } - +func verifyTemplate(data svg.TemplateData, keys []string) (bool, error) { for _, key := range data.AllKeys() { if !slices.Contains(keys, key) { return false, nil diff --git a/pkg/svg/storage.go b/pkg/svg/storage.go index 966338a..5f2aba6 100644 --- a/pkg/svg/storage.go +++ b/pkg/svg/storage.go @@ -2,6 +2,7 @@ package svg import ( "errors" + "fmt" "io" "log" "net/http" @@ -20,7 +21,7 @@ type SvgStorage interface { AddFont(reader io.Reader, format string) error GetFonts() ([]string, error) GetFontsDir() string - CreatePublic(data io.Reader, filetype string) (string, error) + CreatePublic(data io.Reader, suffix string, filetype string) (string, error) GetPublic(path string) (io.ReadCloser, error) GetPublicDir() http.Dir } @@ -86,13 +87,13 @@ func (f FileSvgStorage) List() ([]os.DirEntry, error) { return os.ReadDir(f.basepath) } -func (f FileSvgStorage) CreatePublic(data io.Reader, filetype string) (string, error) { +func (f FileSvgStorage) CreatePublic(data io.Reader, suffix string, filetype string) (string, error) { path := filepath.Join(f.basepath, f.publicSubPath) if err := os.MkdirAll(path, 0755); err != nil { return "", err } - file, err := os.CreateTemp(path, "*."+filetype) + file, err := os.CreateTemp(path, fmt.Sprintf("*-%s."+filetype, suffix)) if err != nil { return "", err } From ae65322aa0eeea05bceadf1bd53e3b1eae6314c1 Mon Sep 17 00:00:00 2001 From: Tueem Date: Wed, 27 May 2026 22:53:21 +0200 Subject: [PATCH 3/5] fix(page): move pages back when one is deleted inbetween. --- internal/database/svgpagedb.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/database/svgpagedb.go b/internal/database/svgpagedb.go index b40cf6c..ca52379 100644 --- a/internal/database/svgpagedb.go +++ b/internal/database/svgpagedb.go @@ -27,6 +27,7 @@ const GETSPECIFICSVGPAGESSQL string = "SELECT * FROM svgpage WHERE id = ?;" const DELETESVGPAGESQL string = "DELETE FROM svgpage WHERE id = ? AND page = ?;" const DELETEALLSVGPAGESQL string = "DELETE FROM svgpage WHERE id = ?;" const COUNTSVGPAGESQL string = "SELECT COUNT(*) FROM svgpage WHERE id = ?;" +const UPDATEPAGESQL string = "UPDATE svgpage SET page = page - 1 WHERE page > ?;" func InsertSVGPage(data *svg.TemplatePage) (int, error) { json, err := json.Marshal(data.TemplateKeys) @@ -86,6 +87,11 @@ func DeleteSVGPage(id string, page int) (bool, error) { if num == 0 { return false, nil } + + if err := movePages(id, page); err != nil { + return false, err + } + return true, nil } @@ -116,3 +122,8 @@ func GetPageCount(id string) (int, error) { return count, nil } + +func movePages(id string, deletedPage int) error { + _, err := database.Exec(UPDATEPAGESQL, deletedPage) + return err +} From 664ae1f7d39baf628332d81d15f3adbbfaf45bef Mon Sep 17 00:00:00 2001 From: Tueem Date: Wed, 27 May 2026 23:07:08 +0200 Subject: [PATCH 4/5] fix(page): fix AddPage not checking if the template actually existed. --- internal/database/svgdb.go | 13 +++++++++++++ pkg/svg/actions/page.go | 12 ++++++++++++ pkg/svg/actions/upload.go | 14 +++++++------- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/internal/database/svgdb.go b/internal/database/svgdb.go index b256259..d5ac42d 100644 --- a/internal/database/svgdb.go +++ b/internal/database/svgdb.go @@ -15,6 +15,7 @@ const GETSPECIFICSVGSQL string = "SELECT * FROM svg WHERE id = ?;" const GETSVGSQL string = "SELECT * FROM svg;" const DELETESVGSQL string = "DELETE FROM svg WHERE id = ?;" const RENAMESVGSQL string = "UPDATE svg SET name = ? WHERE id = ?;" +const EXISTSSVGSQL string = "SELECT COUNT(*) FROM svg WHERE id = ?;" func InsertSVG(data *svg.TemplateData) error { if _, err := database.Exec(INSERTSVGSQL, data.Id, data.Name); err != nil { @@ -93,3 +94,15 @@ func RenameSvg(id string, name string) error { } return nil } + +func Exists(id string) (bool, error) { + res := database.QueryRow(EXISTSSVGSQL, id) + + var count int + + if err := res.Scan(&count); err != nil { + return false, err + } + + return count > 0, nil +} diff --git a/pkg/svg/actions/page.go b/pkg/svg/actions/page.go index f427272..33cc32a 100644 --- a/pkg/svg/actions/page.go +++ b/pkg/svg/actions/page.go @@ -2,6 +2,7 @@ package actions import ( "bytes" + "errors" "fmt" "io" "log" @@ -15,6 +16,17 @@ import ( const FILEFORMAT string = "%s-%d" func AddPage(id string, svgbuf []byte) (svg.TemplatePage, error) { + + exists, err := database.Exists(id) + + if err != nil { + return svg.TemplatePage{}, err + } + + if !exists { + return svg.TemplatePage{}, errors.New("Template does not exist.") + } + data := svg.TemplatePage{TemplateId: id} populateKeys(&data, svgbuf) diff --git a/pkg/svg/actions/upload.go b/pkg/svg/actions/upload.go index 66b0751..efbc864 100644 --- a/pkg/svg/actions/upload.go +++ b/pkg/svg/actions/upload.go @@ -10,11 +10,15 @@ import ( ) func Create(svgbufs [][]byte, name string) (svg.TemplateData, error) { - id := generateId() + data := svg.TemplateData{Id: generateId(), Name: name} + + if err := database.InsertSVG(&data); err != nil { + return svg.TemplateData{}, err + } pages := make([]svg.TemplatePage, len(svgbufs)) for i, pagebuf := range svgbufs { - page, err := AddPage(id, pagebuf) + page, err := AddPage(data.Id, pagebuf) if err != nil { return svg.TemplateData{}, err @@ -23,11 +27,7 @@ func Create(svgbufs [][]byte, name string) (svg.TemplateData, error) { pages[i] = page } - data := svg.TemplateData{Id: id, Name: name, Pages: pages} - - if err := database.InsertSVG(&data); err != nil { - return svg.TemplateData{}, err - } + data.Pages = pages log.Println("Created SVG Template " + data.Id) return data, nil From 70e14ec63ce170a6a3b4db71c0a1d53a2fd25be3 Mon Sep 17 00:00:00 2001 From: Tueem Date: Wed, 27 May 2026 23:07:35 +0200 Subject: [PATCH 5/5] feat(page): add Page action endpoints --- internal/routes/pages.go | 80 ++++++++++++++++++++++++++++++++++++++++ internal/server/http.go | 16 ++++++++ 2 files changed, 96 insertions(+) create mode 100644 internal/routes/pages.go diff --git a/internal/routes/pages.go b/internal/routes/pages.go new file mode 100644 index 0000000..689d5c7 --- /dev/null +++ b/internal/routes/pages.go @@ -0,0 +1,80 @@ +package routes + +import ( + "encoding/json" + "io" + "net/http" + "strconv" + + "tomatentum.net/svg-templater/pkg/svg" + "tomatentum.net/svg-templater/pkg/svg/actions" +) + +func AddPage(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(128000000); err != nil { + http.Error(w, "Couldn't parse form data.", http.StatusBadRequest) + return + } + + fileheaders := r.MultipartForm.File["files"] + + id := r.PathValue("id") + + pages := make([]svg.TemplatePage, len(fileheaders)) + + for i, fileh := range fileheaders { + file, err := fileh.Open() + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + buf, err := io.ReadAll(file) + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + page, err := actions.AddPage(id, buf) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + pages[i] = page + } + + err := json.NewEncoder(w).Encode(pages) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func DeletePage(w http.ResponseWriter, r *http.Request) { + + id, pagestring := r.PathValue("id"), r.PathValue("page") + + page, err := strconv.Atoi(pagestring) + + if err != nil { + http.Error(w, "Page must be a number", http.StatusBadRequest) + return + } + + ok, err := actions.DeletePage(id, page) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if !ok { + http.Error(w, "Could not delete page", http.StatusInternalServerError) + return + } +} diff --git a/internal/server/http.go b/internal/server/http.go index 4ef5bc6..a5baaef 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -43,6 +43,22 @@ func PrepareHTTP() { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) }) + + registerAuthorizedFunc("/svg/{id}/page/", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + routes.AddPage(w, r) + }) + + registerAuthorizedFunc("/svg/{id}/page/{page}", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "DELETE" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + routes.DeletePage(w, r) + }) registerAuthorizedFunc("/font/", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET":