refactor javascript and fix minor bugs
This commit is contained in:
parent
545ade9c28
commit
2489406c08
17 changed files with 235 additions and 135 deletions
|
@ -7,3 +7,16 @@ services:
|
|||
POSTGRES_PASSWORD: example
|
||||
POSTGRES_USER: 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://
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
BINDADDR=127.0.0.1
|
||||
|
||||
# Port to bind to
|
||||
# default: 2345
|
||||
PORT=2345
|
||||
# default: 3000
|
||||
PORT=3000
|
||||
|
||||
# List of trusted proxy IPs separated by colons
|
||||
# default: 127.0.0.1,::1
|
||||
TRUSTEDPROXIES=127.0.0.1,::1
|
||||
# TRUSTEDPROXIES=127.0.0.1,::1
|
||||
|
||||
# Postgresql connection string
|
||||
# format: postgresql://<username>:<password>@<database_ip>/todos?sslmode=verify-ca
|
||||
|
|
|
@ -13,6 +13,10 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
|
||||
err := models.DB.NewSelect().Model(&links).Scan(context.Background())
|
||||
|
@ -23,7 +27,10 @@ func HandleLinkGetAll(c *fiber.Ctx) error {
|
|||
for _, link := range links {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -32,11 +39,10 @@ func HandleLinkGet(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")
|
||||
}
|
||||
|
||||
// TODO: Add API-Key Auth
|
||||
var newlink models.Link
|
||||
|
||||
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.Status(fiber.StatusCreated)
|
||||
c.JSON(newlink)
|
||||
|
||||
err = c.JSON(newlink)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -92,5 +102,12 @@ func HandleLinkDelete(c *fiber.Ctx) error {
|
|||
log.Println(err.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
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"log"
|
||||
"time"
|
||||
|
@ -34,7 +33,6 @@ func HandleUserPost(c *fiber.Ctx) error {
|
|||
salt := misc.RandomString(15)
|
||||
created := time.Now()
|
||||
hashbytes := sha256.Sum256([]byte(salt + newuser.Password))
|
||||
fmt.Printf("%x\n", hashbytes)
|
||||
|
||||
hash := hex.EncodeToString(hashbytes[:])
|
||||
|
||||
|
|
27
internal/db/apikey.go
Normal file
27
internal/db/apikey.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -12,6 +12,12 @@ import (
|
|||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
type ApiKey struct {
|
||||
bun.BaseModel `bun:"table:apikeys"`
|
||||
Key string `bun:"key,pk,type:uuid,default:gen_random_uuid()" json:"key,omitempty"`
|
||||
UserName string `bun:"username,notnull" json:"username"`
|
||||
Created time.Time `bun:"created,default:now()" json:"created"`
|
||||
UserName string `bun:"username,notnull" json:"username,omitempty"`
|
||||
Created time.Time `bun:"created,default:now()" json:"created,omitempty"`
|
||||
Description string `bun:"description,notnull" json:"description"`
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
</ul>
|
||||
</nav>
|
||||
<main class="container" style="padding: 0">
|
||||
<form action="javascript:HandleSubmit();" style="margin: 0">
|
||||
<form action="javascript:HandleLinkAddSubmit();" style="margin: 0">
|
||||
<fieldset id="form_fields">
|
||||
<hgroup>
|
||||
<h1>Add new shortlink</h1>
|
||||
|
@ -46,7 +46,7 @@
|
|||
<input type="text" name="linkname" id="linkname" placeholder="shortlink">
|
||||
|
||||
<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>
|
||||
<input type="text" name="description" id="description" placeholder="">
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<link rel="stylesheet" href="/admin/pico.min.css">
|
||||
<link rel="stylesheet" href="/admin/custom.css">
|
||||
<script src="/admin/link_add.js" defer></script>
|
||||
<script src="/admin/misc.js" defer></script>
|
||||
<script src="/admin/main.js" defer></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
|
107
views/links.tmpl
107
views/links.tmpl
|
@ -8,8 +8,7 @@
|
|||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<link rel="stylesheet" href="/admin/pico.min.css">
|
||||
<link rel="stylesheet" href="/admin/custom.css">
|
||||
<script src="/admin/links.js" defer></script>
|
||||
<script src="/admin/misc.js" defer></script>
|
||||
<script src="/admin/main.js" defer></script>
|
||||
|
||||
<style>
|
||||
td {
|
||||
|
@ -20,58 +19,58 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="container-fluid">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/" class="contrast"><strong>go-urlsh</strong></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/links/new" style="color: greenyellow;">Add new Shortlink</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/apikeys/">API Keys</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript: void(0)" >Users (coming soon)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript: Logout()" style="color: red;">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="container">
|
||||
<table class="n-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortlink</th>
|
||||
<th>URL</th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td><a href="javascript: HandleCopy('{{.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 style="text-align: right;">
|
||||
<a href="/admin/links/edit/{{.ID}}">
|
||||
Edit
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<a href="javascript: HandleDelete('{{.ID}}');">
|
||||
Delete
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
{{template "partials/footer" .}}
|
||||
<nav class="container-fluid">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/" class="contrast"><strong>go-urlsh</strong></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/links/new" style="color: greenyellow;">Add new Shortlink</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/apikeys/">API Keys</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript: void(0)" >Users (coming soon)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript: Logout()" style="color: red;">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="container">
|
||||
<table class="n-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortlink</th>
|
||||
<th>URL</th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<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 style="text-align: right;">
|
||||
<a href="/admin/links/edit/{{.ID}}">
|
||||
Edit
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<a href="javascript: HandleLinkIndexDelete('{{.ID}}');">
|
||||
Delete
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
{{template "partials/footer" .}}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
</ul>
|
||||
</nav>
|
||||
<main class="container" style="padding: 0">
|
||||
<form action="javascript:HandleSubmit();" style="margin: 0">
|
||||
<form action="javascript:HandleLinkAddSubmit();" style="margin: 0">
|
||||
<fieldset id="form_fields">
|
||||
<hgroup>
|
||||
<h1>Add new shortlink</h1>
|
||||
|
@ -46,7 +46,7 @@
|
|||
<input type="text" name="linkname" id="linkname" placeholder="shortlink">
|
||||
|
||||
<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>
|
||||
<input type="text" name="description" id="description" placeholder="">
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -8,8 +8,7 @@
|
|||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<link rel="stylesheet" href="/admin/pico.min.css">
|
||||
<link rel="stylesheet" href="/admin/custom.css">
|
||||
<script src="/admin/link_add.js" defer></script>
|
||||
<script src="/admin/misc.js" defer></script>
|
||||
<script src="/admin/main.js" defer></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<link rel="stylesheet" href="/admin/pico.min.css">
|
||||
<link rel="stylesheet" href="/admin/custom.css">
|
||||
<script src="/admin/links.js" defer></script>
|
||||
<script src="/admin/misc.js" defer></script>
|
||||
<script src="/admin/main.js" defer></script>
|
||||
|
||||
<style>
|
||||
td {
|
||||
|
@ -54,7 +53,7 @@
|
|||
<tbody>
|
||||
{{range .}}
|
||||
<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 style="text-align: right;">
|
||||
<a href="/admin/links/edit/{{.ID}}">
|
||||
|
@ -62,7 +61,7 @@
|
|||
</a>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<a href="javascript: HandleDelete('{{.ID}}');">
|
||||
<a href="javascript: HandleLinkIndexDelete('{{.ID}}');">
|
||||
Delete
|
||||
</a>
|
||||
</td>
|
||||
|
|
16
web/links.js
16
web/links.js
|
@ -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
99
web/main.js
Normal 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"
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
function Logout() {
|
||||
document.cookie = 'gourlsh_auth=; Max-Age=-1; path=/; domain=' + location.hostname;
|
||||
document.location = "/admin/login"
|
||||
}
|
Loading…
Reference in a new issue