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 +}