refactor javascript and fix minor bugs

This commit is contained in:
Adora Laura Kalb 2023-05-05 15:16:54 +02:00
parent 545ade9c28
commit 2489406c08
Signed by: adoralaura
GPG key ID: 7A4552166FC8C056
17 changed files with 235 additions and 135 deletions

View file

@ -6,4 +6,17 @@ services:
environment: environment:
POSTGRES_PASSWORD: example POSTGRES_PASSWORD: example
POSTGRES_USER: go-urlsh POSTGRES_USER: go-urlsh
POSTGRES_DB: go-urlsh POSTGRES_DB: go-urlsh
volumes:
- ./postgres-data:/var/lib/postgresql/data
go-urlsh:
image: codeberg.org/lauralani/go-urlsh:0.1
restart: always
ports:
- 127.0.0.1:3000:3000
depends_on:
- db
environment:
# format: postgresql://<username>:<password>@<database_ip>/<database>
DATABASE_URL: postgres://

View file

@ -3,12 +3,12 @@
BINDADDR=127.0.0.1 BINDADDR=127.0.0.1
# Port to bind to # Port to bind to
# default: 2345 # default: 3000
PORT=2345 PORT=3000
# List of trusted proxy IPs separated by colons # List of trusted proxy IPs separated by colons
# default: 127.0.0.1,::1 # default: 127.0.0.1,::1
TRUSTEDPROXIES=127.0.0.1,::1 # TRUSTEDPROXIES=127.0.0.1,::1
# Postgresql connection string # Postgresql connection string
# format: postgresql://<username>:<password>@<database_ip>/todos?sslmode=verify-ca # format: postgresql://<username>:<password>@<database_ip>/todos?sslmode=verify-ca

View file

@ -13,6 +13,10 @@ import (
) )
func HandleLinkGetAll(c *fiber.Ctx) error { func HandleLinkGetAll(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(misc.CookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
}
var links []models.Link var links []models.Link
err := models.DB.NewSelect().Model(&links).Scan(context.Background()) err := models.DB.NewSelect().Model(&links).Scan(context.Background())
@ -23,7 +27,10 @@ func HandleLinkGetAll(c *fiber.Ctx) error {
for _, link := range links { for _, link := range links {
log.Printf("id: %v, url: %v", link.ID, link.URL) log.Printf("id: %v, url: %v", link.ID, link.URL)
} }
c.JSON(links) err = c.JSON(links)
if err != nil {
log.Println(err)
}
return nil return nil
} }
@ -32,11 +39,10 @@ func HandleLinkGet(c *fiber.Ctx) error {
} }
func HandleLinkPost(c *fiber.Ctx) error { func HandleLinkPost(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(misc.CookieName, "")) { if !db.IsCookieValid(c.Cookies(misc.CookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized") return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
} }
// TODO: Add API-Key Auth
var newlink models.Link var newlink models.Link
err := json.Unmarshal(c.Body(), &newlink) err := json.Unmarshal(c.Body(), &newlink)
@ -61,7 +67,11 @@ func HandleLinkPost(c *fiber.Ctx) error {
c.Append("Location", c.BaseURL()+"/api/v1/links/"+newlink.ID) c.Append("Location", c.BaseURL()+"/api/v1/links/"+newlink.ID)
c.Status(fiber.StatusCreated) c.Status(fiber.StatusCreated)
c.JSON(newlink)
err = c.JSON(newlink)
if err != nil {
log.Println(err)
}
return nil return nil
} }
@ -92,5 +102,12 @@ func HandleLinkDelete(c *fiber.Ctx) error {
log.Println(err.Error()) log.Println(err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
} }
c.Status(fiber.StatusNoContent)
err = c.SendString("204 No Content")
if err != nil {
log.Println(err)
}
return nil return nil
} }

View file

@ -7,7 +7,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"log" "log"
"time" "time"
@ -34,7 +33,6 @@ func HandleUserPost(c *fiber.Ctx) error {
salt := misc.RandomString(15) salt := misc.RandomString(15)
created := time.Now() created := time.Now()
hashbytes := sha256.Sum256([]byte(salt + newuser.Password)) hashbytes := sha256.Sum256([]byte(salt + newuser.Password))
fmt.Printf("%x\n", hashbytes)
hash := hex.EncodeToString(hashbytes[:]) hash := hex.EncodeToString(hashbytes[:])

27
internal/db/apikey.go Normal file
View file

@ -0,0 +1,27 @@
package db
import (
"codeberg.org/lauralani/go-urlsh/models"
"context"
"log"
)
// IsApiKeyValid checks the database if ApiKey val is valid.
//
// Returns true if it's valid, false if not.
func IsApiKeyValid(val string) bool {
if val == "" {
return false
}
count, err := models.DB.NewSelect().Model((*models.ApiKey)(nil)).Where("key = ?", val).Count(context.Background())
if err != nil {
log.Printf("Error checking apikey validity for key %v\n", val)
return false
}
if count < 1 {
return false
} else {
return true
}
}

View file

@ -12,6 +12,12 @@ import (
) )
func HandleAdminLinkNewGet(c *fiber.Ctx) error { func HandleAdminLinkNewGet(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(misc.CookieName, "")) {
c.Location("/admin/")
c.Status(fiber.StatusSeeOther)
return nil
}
return c.Render("add_link", nil) return c.Render("add_link", nil)
} }

View file

@ -8,6 +8,7 @@ import (
type ApiKey struct { type ApiKey struct {
bun.BaseModel `bun:"table:apikeys"` bun.BaseModel `bun:"table:apikeys"`
Key string `bun:"key,pk,type:uuid,default:gen_random_uuid()" json:"key,omitempty"` Key string `bun:"key,pk,type:uuid,default:gen_random_uuid()" json:"key,omitempty"`
UserName string `bun:"username,notnull" json:"username"` UserName string `bun:"username,notnull" json:"username,omitempty"`
Created time.Time `bun:"created,default:now()" json:"created"` Created time.Time `bun:"created,default:now()" json:"created,omitempty"`
Description string `bun:"description,notnull" json:"description"`
} }

View file

@ -36,7 +36,7 @@
</ul> </ul>
</nav> </nav>
<main class="container" style="padding: 0"> <main class="container" style="padding: 0">
<form action="javascript:HandleSubmit();" style="margin: 0"> <form action="javascript:HandleLinkAddSubmit();" style="margin: 0">
<fieldset id="form_fields"> <fieldset id="form_fields">
<hgroup> <hgroup>
<h1>Add new shortlink</h1> <h1>Add new shortlink</h1>
@ -46,7 +46,7 @@
<input type="text" name="linkname" id="linkname" placeholder="shortlink"> <input type="text" name="linkname" id="linkname" placeholder="shortlink">
<label for="link">Link <i style="color: red;">*</i></label> <label for="link">Link <i style="color: red;">*</i></label>
<input type="text" name="link" id="link" placeholder="https://" onchange="HandleChange()"> <input type="text" name="link" id="link" placeholder="https://" onchange="HandleLinkFieldChange()">
<label for="description">Description</label> <label for="description">Description</label>
<input type="text" name="description" id="description" placeholder=""> <input type="text" name="description" id="description" placeholder="">

View file

@ -8,8 +8,7 @@
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel="stylesheet" href="/admin/pico.min.css"> <link rel="stylesheet" href="/admin/pico.min.css">
<link rel="stylesheet" href="/admin/custom.css"> <link rel="stylesheet" href="/admin/custom.css">
<script src="/admin/link_add.js" defer></script> <script src="/admin/main.js" defer></script>
<script src="/admin/misc.js" defer></script>
</head> </head>

View file

@ -8,8 +8,7 @@
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel="stylesheet" href="/admin/pico.min.css"> <link rel="stylesheet" href="/admin/pico.min.css">
<link rel="stylesheet" href="/admin/custom.css"> <link rel="stylesheet" href="/admin/custom.css">
<script src="/admin/links.js" defer></script> <script src="/admin/main.js" defer></script>
<script src="/admin/misc.js" defer></script>
<style> <style>
td { td {
@ -20,58 +19,58 @@
</head> </head>
<body> <body>
<nav class="container-fluid"> <nav class="container-fluid">
<ul> <ul>
<li> <li>
<a href="/admin/" class="contrast"><strong>go-urlsh</strong></a> <a href="/admin/" class="contrast"><strong>go-urlsh</strong></a>
</li> </li>
</ul> </ul>
<ul> <ul>
<li> <li>
<a href="/admin/links/new" style="color: greenyellow;">Add new Shortlink</a> <a href="/admin/links/new" style="color: greenyellow;">Add new Shortlink</a>
</li> </li>
<li> <li>
<a href="/admin/apikeys/">API Keys</a> <a href="/admin/apikeys/">API Keys</a>
</li> </li>
<li> <li>
<a href="javascript: void(0)" >Users (coming soon)</a> <a href="javascript: void(0)" >Users (coming soon)</a>
</li> </li>
<li> <li>
<a href="javascript: Logout()" style="color: red;">Logout</a> <a href="javascript: Logout()" style="color: red;">Logout</a>
</li> </li>
</ul> </ul>
</nav> </nav>
<main class="container"> <main class="container">
<table class="n-table"> <table class="n-table">
<thead> <thead>
<tr> <tr>
<th>Shortlink</th> <th>Shortlink</th>
<th>URL</th> <th>URL</th>
<th> </th> <th> </th>
<th> </th> <th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{range .}} {{range .}}
<tr> <tr>
<td><a href="javascript: HandleCopy('{{.ID}}');" data-tooltip="click to copy">{{.ID}}</a></td> <td><a href="javascript: HandleLinkIndexCopy('{{.ID}}');" data-tooltip="click to copy">{{.ID}}</a></td>
<td><a href="{{.URL}}" target="_blank" {{if .Description}}data-tooltip="{{.Description}}"{{end}}>{{.URL}}</a></td> <td><a href="{{.URL}}" target="_blank" {{if .Description}}data-tooltip="{{.Description}}"{{end}}>{{.URL}}</a></td>
<td style="text-align: right;"> <td style="text-align: right;">
<a href="/admin/links/edit/{{.ID}}"> <a href="/admin/links/edit/{{.ID}}">
Edit Edit
</a> </a>
</td> </td>
<td style="text-align: right;"> <td style="text-align: right;">
<a href="javascript: HandleDelete('{{.ID}}');"> <a href="javascript: HandleLinkIndexDelete('{{.ID}}');">
Delete Delete
</a> </a>
</td> </td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>
</table> </table>
</main> </main>
{{template "partials/footer" .}} {{template "partials/footer" .}}
</body> </body>
</html> </html>

View file

@ -36,7 +36,7 @@
</ul> </ul>
</nav> </nav>
<main class="container" style="padding: 0"> <main class="container" style="padding: 0">
<form action="javascript:HandleSubmit();" style="margin: 0"> <form action="javascript:HandleLinkAddSubmit();" style="margin: 0">
<fieldset id="form_fields"> <fieldset id="form_fields">
<hgroup> <hgroup>
<h1>Add new shortlink</h1> <h1>Add new shortlink</h1>
@ -46,7 +46,7 @@
<input type="text" name="linkname" id="linkname" placeholder="shortlink"> <input type="text" name="linkname" id="linkname" placeholder="shortlink">
<label for="link">Link <i style="color: red;">*</i></label> <label for="link">Link <i style="color: red;">*</i></label>
<input type="text" name="link" id="link" placeholder="https://" onchange="HandleChange()"> <input type="text" name="link" id="link" placeholder="https://" onchange="HandleLinkFieldChange()">
<label for="description">Description</label> <label for="description">Description</label>
<input type="text" name="description" id="description" placeholder=""> <input type="text" name="description" id="description" placeholder="">

View file

@ -1,37 +0,0 @@
async function HandleSubmit() {
document.getElementById("submit").active = false
let slug = document.getElementById("linkname").value
let url = document.getElementById("link").value
let description = document.getElementById("description").value
let body = {
"id" : slug,
"url" : url,
"description" : description
}
let response = await fetch("/api/v1/links", {
credentials: "include",
body: JSON.stringify(body),
mode: "same-origin",
method: "POST"
});
if (!response.ok) {
document.getElementById("dialog-heading").textContent = "Error"
document.getElementById("dialog-text").textContent = "The following error occured during the request: " + response.statusText
document.getElementById('dialog-info').showModal()
document.getElementById("submit").active = true
}
document.location = "/admin/"
}
async function HandleChange() {
console.log("HandleChange")
let buttonactive = true
if (document.getElementById("link").value === "")
{
buttonactive = false
}
document.getElementById("submit").active = buttonactive
}

View file

@ -8,8 +8,7 @@
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel="stylesheet" href="/admin/pico.min.css"> <link rel="stylesheet" href="/admin/pico.min.css">
<link rel="stylesheet" href="/admin/custom.css"> <link rel="stylesheet" href="/admin/custom.css">
<script src="/admin/link_add.js" defer></script> <script src="/admin/main.js" defer></script>
<script src="/admin/misc.js" defer></script>
</head> </head>

View file

@ -8,8 +8,7 @@
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel="stylesheet" href="/admin/pico.min.css"> <link rel="stylesheet" href="/admin/pico.min.css">
<link rel="stylesheet" href="/admin/custom.css"> <link rel="stylesheet" href="/admin/custom.css">
<script src="/admin/links.js" defer></script> <script src="/admin/main.js" defer></script>
<script src="/admin/misc.js" defer></script>
<style> <style>
td { td {
@ -54,7 +53,7 @@
<tbody> <tbody>
{{range .}} {{range .}}
<tr> <tr>
<td><a href="javascript: HandleCopy('{{.ID}}');" data-tooltip="click to copy">{{.ID}}</a></td> <td><a href="javascript: HandleLinkIndexCopy('{{.ID}}');" data-tooltip="click to copy">{{.ID}}</a></td>
<td><a href="{{.URL}}" target="_blank" {{if .Description}}data-tooltip="{{.Description}}"{{end}}>{{.URL}}</a></td> <td><a href="{{.URL}}" target="_blank" {{if .Description}}data-tooltip="{{.Description}}"{{end}}>{{.URL}}</a></td>
<td style="text-align: right;"> <td style="text-align: right;">
<a href="/admin/links/edit/{{.ID}}"> <a href="/admin/links/edit/{{.ID}}">
@ -62,7 +61,7 @@
</a> </a>
</td> </td>
<td style="text-align: right;"> <td style="text-align: right;">
<a href="javascript: HandleDelete('{{.ID}}');"> <a href="javascript: HandleLinkIndexDelete('{{.ID}}');">
Delete Delete
</a> </a>
</td> </td>

View file

@ -1,16 +0,0 @@
async function HandleDelete(id){
let response = await fetch("/api/v1/links/" + id, {
credentials: "include",
mode: "same-origin",
method: "DELETE"
});
if (!response.ok) {
console.log("error deleting " + id + ": " + response.statusText)
}
document.location = "/admin/"
}
async function HandleCopy(id) {
let host = window.location.protocol + "//" + window.location.host;
await navigator.clipboard.writeText(host + "/" + id);
}

99
web/main.js Normal file
View file

@ -0,0 +1,99 @@
// Link overview
async function HandleLinkIndexDelete(id){
let response = await fetch("/api/v1/links/" + id, {
credentials: "include",
mode: "same-origin",
method: "DELETE"
});
if (!response.ok) {
console.log("error deleting " + id + ": " + response.statusText)
}
document.location = "/admin/"
}
async function HandleLinkIndexCopy(id) {
let host = window.location.protocol + "//" + window.location.host;
await navigator.clipboard.writeText(host + "/" + id);
}
// Link Add
async function HandleLinkAddSubmit() {
document.getElementById("submit").active = false
let slug = document.getElementById("linkname").value
let url = document.getElementById("link").value
let description = document.getElementById("description").value
let body = {
"id" : slug,
"url" : url,
"description" : description
}
let response = await fetch("/api/v1/links", {
credentials: "include",
body: JSON.stringify(body),
mode: "same-origin",
method: "POST"
});
if (!response.ok) {
document.getElementById("dialog-heading").textContent = "Error"
document.getElementById("dialog-text").textContent = "The following error occured during the request: " + response.statusText
document.getElementById('dialog-info').showModal()
document.getElementById("submit").active = true
}
document.location = "/admin/"
}
async function HandleLinkFieldChange() {
console.log("HandleChange")
let buttonactive = true
if (document.getElementById("link").value === "")
{
buttonactive = false
}
document.getElementById("submit").active = buttonactive
}
// ApiKey Add
async function HandleApiKeyNewSubmit() {
let button = document.getElementById("submit")
let description = document.getElementById("description")
button.active = false
button.setAttribute("aria-busy", "true")
let body = {
"description" : description
}
let response = await fetch("/api/v1/apikeys", {
credentials: "include",
body: JSON.stringify(body),
mode: "same-origin",
method: "POST"
});
if (response.ok) {
let data = await response.json()
document.getElementById("dialog-heading").textContent = "New API-Key"
document.getElementById("dialog-text").textContent = "Here is your new API Key. Copy it NOW, it won't be shown again."
document.getElementById("dialog-apikey").textContent = data.key
document.getElementById('dialog-info').showModal()
}
}
async function HandleApiKeyModalClose() {
let modal = document.getElementById('dialog-info');
modal.close()
}
// General
function Logout() {
document.cookie = 'gourlsh_auth=; Max-Age=-1; path=/; domain=' + location.hostname;
document.location = "/admin/login"
}

View file

@ -1,4 +0,0 @@
function Logout() {
document.cookie = 'gourlsh_auth=; Max-Age=-1; path=/; domain=' + location.hostname;
document.location = "/admin/login"
}