feat: export and import

The list of links can now be exported to a json file,
and later imported back.
This commit is contained in:
Ivan R. 2024-04-01 23:35:46 +05:00
parent 9a9a1af63e
commit 6396c160f2
No known key found for this signature in database
GPG key ID: 56C7BAAE859B302C
10 changed files with 174 additions and 11 deletions

View file

@ -19,7 +19,8 @@ body {
} }
input, input,
button { button,
textarea {
font-size: inherit; font-size: inherit;
font-family: inherit; font-family: inherit;
padding: 8px 10px; padding: 8px 10px;
@ -35,11 +36,17 @@ button {
cursor: pointer; cursor: pointer;
} }
textarea {
resize: vertical;
}
input:focus, input:focus,
input:hover, input:hover,
button:active, button:active,
button:hover, button:hover,
button:focus { button:focus,
textarea:focus,
textarea:hover {
border-color: #812abd; border-color: #812abd;
} }

31
assets/css/import.css vendored Normal file
View file

@ -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%;
}

View file

@ -2,6 +2,21 @@ body {
padding: 2em 1em; padding: 2em 1em;
} }
.actions {
display: flex;
gap: 2em;
}
.actions a {
display: flex;
align-items: center;
gap: 8px;
}
.actions a img {
filter: invert(100%);
}
.row { .row {
display: flex; display: flex;
gap: 10px; gap: 10px;

View file

@ -1,9 +1,9 @@
package database package database
type Group struct { type Group struct {
ID int ID int `json:"id"`
Name string Name string `json:"name"`
Links []Link Links []Link `json:"links"`
} }
func GetGroupsWithLinks() ([]Group, error) { func GetGroupsWithLinks() ([]Group, error) {

View file

@ -1,11 +1,11 @@
package database package database
type Link struct { type Link struct {
ID int ID int `json:"id"`
Name string Name string `json:"name"`
Href string Href string `json:"href"`
GroupID int GroupID int `json:"-"`
Icon *string Icon *string `json:"icon,omitempty"`
} }
func GetLinksFromGroup(groupID int) ([]Link, error) { func GetLinksFromGroup(groupID int) ([]Link, error) {

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{template "head" .}}
<link rel="stylesheet" href="assets/css/import.css" />
</head>
<body>
<h1>Import</h1>
<a href="/settings">Settings</a>
<p>
Importing does not erase existing links, but may create duplicates.
</p>
<form action="/import" method="POST">
<label for="exportFile">JSON data</label>
<textarea
id="exportFile"
name="exportFile"
placeholder="{ groups: [] }"
required
></textarea>
<button type="submit">Submit</button>
</form>
</body>
</html>

View file

@ -6,7 +6,18 @@
</head> </head>
<body> <body>
<h1>Settings</h1> <h1>Settings</h1>
<a href="/">Main page</a>
<div class="actions">
<a href="/">
<img src="/assets/icons/solid/house.svg" width="20" height="20" /> Main page
</a>
<a href="/export">
<img src="/assets/icons/solid/file-export.svg" width="20" height="20" /> Export links
</a>
<a href="/import">
<img src="/assets/icons/solid/file-import.svg" width="20" height="20" /> Import links
</a>
</div>
{{range .groups}} {{range .groups}}
<h2 id="group-{{.ID}}">Group "{{.Name}}"</h2> <h2 id="group-{{.ID}}">Group "{{.Name}}"</h2>

29
views/pages/export.go Normal file
View file

@ -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,
})
}

38
views/pages/import.go Normal file
View file

@ -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)
}

View file

@ -53,6 +53,11 @@ func GetHttpServer() (*http.Server, error) {
// Delete link. // Delete link.
protectedMux.HandleFunc("POST /links/{id}/delete", pages.DeleteLink) 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{ return &http.Server{
Addr: ":8080", Addr: ":8080",
Handler: middleware.LoggingMiddleware( Handler: middleware.LoggingMiddleware(