added url on/off and css styles (#66)
* added basic styles css
* added toggle on/off for URL
* added loggedinuser check
* minor changes
* minor changes v2
* minor changes v3
* chore: stylistic fixes
---------
Co-authored-by: Giorgi Lekveishvili <lekva@gl-mbp-m1-max.local>
diff --git a/apps/url-shortener/index.html b/apps/url-shortener/index.html
index 15eee68..5861daf 100644
--- a/apps/url-shortener/index.html
+++ b/apps/url-shortener/index.html
@@ -1,42 +1,59 @@
<!DOCTYPE html>
<html lang="en">
-
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL Shortener</title>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>
-
<body>
<h1>URL Shortener</h1>
-
<form action="/" method="post">
<label for="address">Address:</label>
<input type="text" id="address" name="address" required>
-
<label for="custom">Custom Name (optional):</label>
<input type="text" id="custom" name="custom">
-
<button type="submit">Shorten URL</button>
</form>
-
<h2>Named Addresses:</h2>
<table>
<tr>
<th>Name</th>
<th>Address</th>
- <th>Owner</th>
<th>Active</th>
</tr>
- {{ range .NamedAddresses }}
+ {{- range .NamedAddresses -}}
<tr>
<td><a href="{{ .Name }}" target="_blank">{{ .Name }}</a></td>
<td>{{ .Address }}</td>
- <td>{{ .OwnerId }}</td>
- <td>{{ .Active }}</td>
+ <td>
+ <input type="checkbox" role="switch" {{ if .Active }}checked{{ end }} onclick="toggle('{{ .Name }}', {{ not .Active }});">
+ </td>
</tr>
- {{ end }}
+ {{- end -}}
</table>
</body>
-
+<script type="application/javascript">
+ function toggle(name, status) {
+ const data = {
+ "name": name,
+ "active": status,
+ };
+ fetch("/api/update/", {
+ method: "POST",
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ })
+ .then(response => {
+ if (response.ok) {
+ window.location.reload();
+ }
+ })
+ .catch((error) => {
+ console.error('Error:', error);
+ });
+ }
+</script>
</html>
diff --git a/apps/url-shortener/main.go b/apps/url-shortener/main.go
index 58c022b..8f2af45 100644
--- a/apps/url-shortener/main.go
+++ b/apps/url-shortener/main.go
@@ -3,6 +3,7 @@
import (
"database/sql"
"embed"
+ "encoding/json"
"flag"
"fmt"
"html/template"
@@ -29,8 +30,7 @@
type Store interface {
Create(addr NamedAddress) error
Get(name string) (NamedAddress, error)
- Activate(name string) error
- Deactivate(name string) error
+ UpdateStatus(name string, active bool) error
ChangeOwner(name, ownerId string) error
List(ownerId string) ([]NamedAddress, error)
}
@@ -107,13 +107,12 @@
return namedAddress, nil
}
-func (s *SQLiteStore) Activate(name string) error {
+func (s *SQLiteStore) UpdateStatus(name string, active bool) error {
//TODO
- return nil
-}
-
-func (s *SQLiteStore) Deactivate(name string) error {
- //TODO
+ _, err := s.db.Exec("UPDATE named_addresses SET active = ? WHERE name = ?", active, name)
+ if err != nil {
+ return err
+ }
return nil
}
@@ -151,16 +150,27 @@
}
}
+func getLoggedInUser(r *http.Request) (string, error) {
+ // TODO(dato): should make a request to get loggedin user
+ return "tabo", nil
+}
+
type Server struct {
store Store
}
func (s *Server) Start() {
http.HandleFunc("/", s.handler)
+ http.HandleFunc("/api/update/", s.toggleHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
+ loggedInUser, err := getLoggedInUser(r)
+ if err != nil {
+ http.Error(w, "User Not Logged In", http.StatusUnauthorized)
+ return
+ }
if r.Method == http.MethodPost {
customName := r.PostFormValue("custom")
address := r.PostFormValue("address")
@@ -173,11 +183,10 @@
if cn == "" {
cn = generateRandomURL()
}
- // check if custom exists
namedAddress := NamedAddress{
Name: cn,
Address: address,
- OwnerId: "tabo", //TODO. Owner ID should be taken from http header
+ OwnerId: loggedInUser,
Active: true,
}
if err := s.store.Create(namedAddress); err == nil {
@@ -201,17 +210,18 @@
if err != nil {
return
}
- // Redirect to the address
+ if !namedAddress.Active {
+ http.Error(w, "address not found", http.StatusNotFound)
+ return
+ }
http.Redirect(w, r, namedAddress.Address, http.StatusSeeOther)
return
}
- // Retrieve named addresses for the owner
- namedAddresses, err := s.store.List("tabo")
+ namedAddresses, err := s.store.List(loggedInUser)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
- // Combine data for rendering
pageVariables := PageVariables{
NamedAddresses: namedAddresses,
}
@@ -228,6 +238,42 @@
renderHTML(w, r, tmpl, pageVariables)
}
+type UpdateRequest struct {
+ Name string `json:"name"`
+ Active bool `json:"active"`
+}
+
+func (s *Server) toggleHandler(w http.ResponseWriter, r *http.Request) {
+ var data UpdateRequest
+ if r.Method == http.MethodPost {
+ loggedInUser, err := getLoggedInUser(r)
+ if err != nil {
+ http.Error(w, "User Not Logged In", http.StatusUnauthorized)
+ return
+ }
+ if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
+ http.Error(w, "Failed to decode JSON data", http.StatusBadRequest)
+ return
+ }
+ namedAddress, err := s.store.Get(data.Name)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Failed to get named_address for name %s", data.Name), http.StatusInternalServerError)
+ return
+ }
+ if namedAddress.OwnerId != loggedInUser {
+ http.Error(w, "Invalid owner ID", http.StatusUnauthorized)
+ return
+ }
+ if err := s.store.UpdateStatus(data.Name, data.Active); err != nil {
+ http.Error(w, fmt.Sprintf("Failed to update status for name %s", data.Name), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
+ return
+ }
+}
+
func main() {
flag.Parse()
db, err := NewSQLiteStore(*dbPath)