From 6396c160f237122a0daeedde53d355abb2bc3af5 Mon Sep 17 00:00:00 2001 From: Ivan Reshetnikov Date: Mon, 1 Apr 2024 23:35:46 +0500 Subject: [PATCH] feat: export and import The list of links can now be exported to a json file, and later imported back. --- assets/css/base.css | 11 +++++++++-- assets/css/import.css | 31 +++++++++++++++++++++++++++++ assets/css/settings.css | 15 ++++++++++++++ database/groups.go | 6 +++--- database/links.go | 10 +++++----- templates/import.html.tmpl | 27 +++++++++++++++++++++++++ templates/settings.html.tmpl | 13 +++++++++++- views/pages/export.go | 29 +++++++++++++++++++++++++++ views/pages/import.go | 38 ++++++++++++++++++++++++++++++++++++ views/routes.go | 5 +++++ 10 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 assets/css/import.css create mode 100644 templates/import.html.tmpl create mode 100644 views/pages/export.go create mode 100644 views/pages/import.go diff --git a/assets/css/base.css b/assets/css/base.css index 30d263d..826c2aa 100644 --- a/assets/css/base.css +++ b/assets/css/base.css @@ -19,7 +19,8 @@ body { } input, -button { +button, +textarea { font-size: inherit; font-family: inherit; padding: 8px 10px; @@ -35,11 +36,17 @@ button { cursor: pointer; } +textarea { + resize: vertical; +} + input:focus, input:hover, button:active, button:hover, -button:focus { +button:focus, +textarea:focus, +textarea:hover { border-color: #812abd; } diff --git a/assets/css/import.css b/assets/css/import.css new file mode 100644 index 0000000..d635e23 --- /dev/null +++ b/assets/css/import.css @@ -0,0 +1,31 @@ +body { + padding: 2em 1em; +} + +@media screen and (min-width: 800px) { + body { + padding: 2em 10em; + } +} + +p { + max-width: 400px; + margin-top: 2em; +} + +form { + width: 100%; + max-width: 400px; + margin-top: 2em; +} + +form textarea { + width: 100%; + margin-top: 10px; + margin-bottom: 10px; + min-height: 100px; +} + +form button { + width: 100%; +} diff --git a/assets/css/settings.css b/assets/css/settings.css index 9020098..3c833d4 100644 --- a/assets/css/settings.css +++ b/assets/css/settings.css @@ -2,6 +2,21 @@ body { padding: 2em 1em; } +.actions { + display: flex; + gap: 2em; +} + +.actions a { + display: flex; + align-items: center; + gap: 8px; +} + +.actions a img { + filter: invert(100%); +} + .row { display: flex; gap: 10px; diff --git a/database/groups.go b/database/groups.go index 3f744ea..ccb2e2e 100644 --- a/database/groups.go +++ b/database/groups.go @@ -1,9 +1,9 @@ package database type Group struct { - ID int - Name string - Links []Link + ID int `json:"id"` + Name string `json:"name"` + Links []Link `json:"links"` } func GetGroupsWithLinks() ([]Group, error) { diff --git a/database/links.go b/database/links.go index aed805c..5b898dd 100644 --- a/database/links.go +++ b/database/links.go @@ -1,11 +1,11 @@ package database type Link struct { - ID int - Name string - Href string - GroupID int - Icon *string + ID int `json:"id"` + Name string `json:"name"` + Href string `json:"href"` + GroupID int `json:"-"` + Icon *string `json:"icon,omitempty"` } func GetLinksFromGroup(groupID int) ([]Link, error) { diff --git a/templates/import.html.tmpl b/templates/import.html.tmpl new file mode 100644 index 0000000..8d0eadd --- /dev/null +++ b/templates/import.html.tmpl @@ -0,0 +1,27 @@ + + + + {{template "head" .}} + + + +

Import

+ + Settings + +

+ Importing does not erase existing links, but may create duplicates. +

+ +
+ + + +
+ + diff --git a/templates/settings.html.tmpl b/templates/settings.html.tmpl index b096583..4fcedf9 100644 --- a/templates/settings.html.tmpl +++ b/templates/settings.html.tmpl @@ -6,7 +6,18 @@

Settings

- Main page + +
+ + Main page + + + Export links + + + Import links + +
{{range .groups}}

Group "{{.Name}}"

diff --git a/views/pages/export.go b/views/pages/export.go new file mode 100644 index 0000000..e199917 --- /dev/null +++ b/views/pages/export.go @@ -0,0 +1,29 @@ +package pages + +import ( + "encoding/json" + "net/http" + + "github.com/ordinary-dev/phoenix/database" +) + +type ExportFile struct { + Groups []database.Group `json:"groups"` +} + +func Export(w http.ResponseWriter, _ *http.Request) { + groups, err := database.GetGroupsWithLinks() + if err != nil { + ShowError(w, http.StatusInternalServerError, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Disposition", "attachment; filename=phoenix.json") + w.WriteHeader(http.StatusOK) + + enc := json.NewEncoder(w) + enc.Encode(&ExportFile{ + Groups: groups, + }) +} diff --git a/views/pages/import.go b/views/pages/import.go new file mode 100644 index 0000000..8d19367 --- /dev/null +++ b/views/pages/import.go @@ -0,0 +1,38 @@ +package pages + +import ( + "encoding/json" + "net/http" + + "github.com/ordinary-dev/phoenix/database" +) + +func ImportPage(w http.ResponseWriter, _ *http.Request) { + Render("import.html.tmpl", w, map[string]any{}) +} + +func Import(w http.ResponseWriter, r *http.Request) { + var exportFile ExportFile + data := []byte(r.FormValue("exportFile")) + if err := json.Unmarshal(data, &exportFile); err != nil { + ShowError(w, http.StatusBadRequest, err) + return + } + + for _, g := range exportFile.Groups { + if err := database.CreateGroup(&g); err != nil { + ShowError(w, http.StatusInternalServerError, err) + return + } + + for _, l := range g.Links { + l.GroupID = g.ID + if err := database.CreateLink(&l); err != nil { + ShowError(w, http.StatusInternalServerError, err) + return + } + } + } + + http.Redirect(w, r, "/", http.StatusFound) +} diff --git a/views/routes.go b/views/routes.go index 768ab53..e51a882 100644 --- a/views/routes.go +++ b/views/routes.go @@ -53,6 +53,11 @@ func GetHttpServer() (*http.Server, error) { // Delete link. protectedMux.HandleFunc("POST /links/{id}/delete", pages.DeleteLink) + // Import-export + protectedMux.HandleFunc("GET /export", pages.Export) + protectedMux.HandleFunc("GET /import", pages.ImportPage) + protectedMux.HandleFunc("POST /import", pages.Import) + return &http.Server{ Addr: ":8080", Handler: middleware.LoggingMiddleware(