This commit is contained in:
Vicente Ferrari Smith 2025-09-25 07:52:10 +02:00
parent d88952fa70
commit 8b06cc1c2e
10 changed files with 342 additions and 192 deletions

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/web"
}
]
}

View File

@ -1,190 +0,0 @@
package main
import (
"context"
"log"
//"encoding/json"
"fmt"
"html/template"
"net/http"
"time"
"github.com/gorilla/websocket"
"github.com/jackc/pgx/v5"
)
type Message struct {
Type string `json:"type"`
Timestamp string `json:"timestamp"`
Random float64 `json:"random"`
}
var page_template = template.Must(template.ParseFiles("../../ui/index.html"))
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
}
func handler(w http.ResponseWriter, r *http.Request) {
data := struct {
Name string
}{
Name: "Visitor",
}
page_template.Execute(w, data)
}
func ws_handler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Upgrade error:", err)
return
}
defer conn.Close()
done := make(chan struct{})
go func() {
ticker := time.NewTicker(1 * time.Second);
for {
select {
case <-done:
return
case t := <-ticker.C:
msg := map[string]interface{}{
"type": "server_tick",
"timestamp": t.Format(time.RFC3339),
}
if err := conn.WriteJSON(msg); err != nil {
fmt.Println("Write error:", err)
return
}
}
}
}()
for {
var msg Message
err := conn.ReadJSON(&msg)
if err != nil {
fmt.Println("Read error:", err)
break
}
fmt.Println(msg)
// Send a response
// response := fmt.Sprintf("Server time: %s", time.Now())
// if err := conn.WriteMessage(websocket.TextMessage, []byte(response)); err != nil {
// fmt.Println("Write error:", err)
// break
// }
}
}
func redirect_to_HTTPS(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://localhost:8443" + r.URL.RequestURI(), http.StatusMovedPermanently)
}
func database_init(conn *pgx.Conn) {
sql := "DROP TABLE IF EXISTS account"
_, err := conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE TABLE account (
id bigserial primary KEY,
username varchar(20) NOT NULL,
password text NOT NULL,
firstname varchar(255) NOT NULL,
lastname varchar(255) NOT NULL,
email varchar(255) NOT NULL,
created timestamptz DEFAULT now() NOT NULL,
CONSTRAINT accounts_uc_email UNIQUE (email),
CONSTRAINT accounts_username_key UNIQUE (username))`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(25, 'vik', '$argon2id$v=19$m=65536,t=1,p=32$+dQ9uB7kKL7t7G3bI+TOMw$Wvic27W6SYH6Fx2Pp84irhVJ/blVh5qINlkv58bpgEc', 'Vicente', 'Ferrari Smith', 'vikhenzo@gmail.com', '2024-05-24 13:21:48.179');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(26, 'Al Orjales', '$argon2id$v=19$m=65536,t=1,p=1$rQODKJ0+mUZ6v6ChUAcr4Q$x0cDjym/QB9lFFq/77FPv7R90Ao5gldb9cuNprBpGAs', '', '', '', '2024-09-26 17:43:37.879');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(36, 'mkBflwkpe', '$argon2id$v=19$m=65536,t=1,p=1$BWcWdp8bhgS84LWqUCb2IA$DGF/FzQbSNHnfZraE9F2qvfdBGf5XB81+w00QgY/jG0', 'zWkxKTNolTgJwO', 'OahedOBLSo', 'bellrebekaou@gmail.com', '2024-11-25 03:00:18.211');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(29, 'TzBeIMeRjxrfsM', '$argon2id$v=19$m=65536,t=1,p=1$ZIpNaO6RPeGncWe9cw8Iog$di0qjf8G0HlcZE8Hl+krNDlBeMrtfuGMwFWAlAnEMNs', 'ZBbLOWXqQlr', 'pIomimIQ', 'denielkgb21@gmail.com', '2024-11-19 13:20:33.177');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(38, 'TeAOvwIdnfqpxy', '$argon2id$v=19$m=65536,t=1,p=1$GRL555iybL1S8GjPq8jlOg$YnuwBxHAT8/I+cU548CYuhwaJdcEXe+R2PQbYpV7UKQ', 'RzqsaECTYrz', 'zreWiEtZGOeRNI', 'aolelonnum@yahoo.com', '2024-11-26 01:19:37.780');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(32, 'XIdQMFvxmT', '$argon2id$v=19$m=65536,t=1,p=1$PIcX7GObR0wlgRTmcMRUug$Kpc7SAv5K1PRxBtqioV4uoCZlkvGebkBmYyXCwoTgmM', 'ayZKALohBYmBx', 'smILWvtOvb', 'heilwoodsf@gmail.com', '2024-11-23 00:41:59.471');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(34, 'yIQvINFS', '$argon2id$v=19$m=65536,t=1,p=1$SJxMxkotEr+lfGeaCs7vlA$fffslijaMuPy+XPBEdv9iPqLbt66H/qJbGUHGrbpdz0', 'nKXBOwRbtXmMedo', 'KvMechDKtPPMgM', 'cortezzavira@gmail.com', '2024-11-24 06:29:58.378');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(40, 'HEjcEByjlPjtaGE', '$argon2id$v=19$m=65536,t=1,p=1$Hu2qnPxi0nz0OHx97h5j5Q$xh7HwKBIi9mp+WWU7rS9MfnDohtJqrv0EUrF2mVpnto', 'yJrQikhSHRYNgdu', 'GttOfPxSPoVWWl', 'jacobsheizitn4963@gmail.com', '2024-11-27 00:07:53.617');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(42, 'DggewDRjXu', '$argon2id$v=19$m=65536,t=1,p=1$MzFSqUr5J5IRC930empHrw$y7kogPP715NOtazsXqjR56LQGF2Eaes9CyAoPjRm3xk', 'uMezBkaQcfBbD', 'YoaQNBvZgHvTaFf', 'kinharrispa@gmail.com', '2024-11-27 21:55:53.037');
INSERT INTO account
(id, username, "password", firstname, lastname, email, created)
VALUES(44, 'yyZGFAsmPEBoSf', '$argon2id$v=19$m=65536,t=1,p=1$vLVm5ol6GQkXPU0BPut92g$Kyvp7/dl3lGUszXRiXfgsgB/IY0EKulZVpVttXQaDDU', 'svVjyPUaAkN', 'mdngeHlf', 'giyahyd4141@gmail.com', '2024-11-28 19:02:32.806');`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
}
func main() {
conn, err := pgx.Connect(context.Background(), "postgres://party:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com:5432/party")
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
database_init(conn)
http.HandleFunc("/", handler)
http.HandleFunc("/ws", ws_handler)
go func() {
err := http.ListenAndServe(":8080", http.HandlerFunc(redirect_to_HTTPS))
if err != nil {
panic(err)
}
}()
log.Println("Starting server on :8443")
// Start HTTPS server (requires cert.pem and key.pem in current dir)
err = http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil)
if err != nil {
panic(err)
}
}

141
cmd/web/db.go Normal file
View File

@ -0,0 +1,141 @@
package main
import (
"context"
"github.com/jackc/pgx/v5"
"log"
)
func database_init(conn *pgx.Conn) {
sql := `DROP TABLE IF EXISTS vote;
DROP TABLE IF EXISTS vote_token;
DROP TABLE IF EXISTS option;
DROP TABLE IF EXISTS issue;
DROP TABLE IF EXISTS account;`
_, err := conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE TABLE account (
id BIGINT primary key generated always as identity,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
first_name TEXT not null,
last_name TEXT not null,
created TIMESTAMPTZ DEFAULT now() NOT NULL,
last_login TIMESTAMPTZ
)`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('vik', '$argon2id$v=19$m=65536,t=1,p=32$+dQ9uB7kKL7t7G3bI+TOMw$Wvic27W6SYH6Fx2Pp84irhVJ/blVh5qINlkv58bpgEc', 'Vicente', 'Ferrari Smith', 'vikhenzo@gmail.com', '2024-05-24 13:21:48.179');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('Al Orjales', '$argon2id$v=19$m=65536,t=1,p=1$rQODKJ0+mUZ6v6ChUAcr4Q$x0cDjym/QB9lFFq/77FPv7R90Ao5gldb9cuNprBpGAs', '', '', '', '2024-09-26 17:43:37.879');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('mkBflwkpe', '$argon2id$v=19$m=65536,t=1,p=1$BWcWdp8bhgS84LWqUCb2IA$DGF/FzQbSNHnfZraE9F2qvfdBGf5XB81+w00QgY/jG0', 'zWkxKTNolTgJwO', 'OahedOBLSo', 'bellrebekaou@gmail.com', '2024-11-25 03:00:18.211');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('TzBeIMeRjxrfsM', '$argon2id$v=19$m=65536,t=1,p=1$ZIpNaO6RPeGncWe9cw8Iog$di0qjf8G0HlcZE8Hl+krNDlBeMrtfuGMwFWAlAnEMNs', 'ZBbLOWXqQlr', 'pIomimIQ', 'denielkgb21@gmail.com', '2024-11-19 13:20:33.177');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('TeAOvwIdnfqpxy', '$argon2id$v=19$m=65536,t=1,p=1$GRL555iybL1S8GjPq8jlOg$YnuwBxHAT8/I+cU548CYuhwaJdcEXe+R2PQbYpV7UKQ', 'RzqsaECTYrz', 'zreWiEtZGOeRNI', 'aolelonnum@yahoo.com', '2024-11-26 01:19:37.780');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('XIdQMFvxmT', '$argon2id$v=19$m=65536,t=1,p=1$PIcX7GObR0wlgRTmcMRUug$Kpc7SAv5K1PRxBtqioV4uoCZlkvGebkBmYyXCwoTgmM', 'ayZKALohBYmBx', 'smILWvtOvb', 'heilwoodsf@gmail.com', '2024-11-23 00:41:59.471');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('yIQvINFS', '$argon2id$v=19$m=65536,t=1,p=1$SJxMxkotEr+lfGeaCs7vlA$fffslijaMuPy+XPBEdv9iPqLbt66H/qJbGUHGrbpdz0', 'nKXBOwRbtXmMedo', 'KvMechDKtPPMgM', 'cortezzavira@gmail.com', '2024-11-24 06:29:58.378');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('HEjcEByjlPjtaGE', '$argon2id$v=19$m=65536,t=1,p=1$Hu2qnPxi0nz0OHx97h5j5Q$xh7HwKBIi9mp+WWU7rS9MfnDohtJqrv0EUrF2mVpnto', 'yJrQikhSHRYNgdu', 'GttOfPxSPoVWWl', 'jacobsheizitn4963@gmail.com', '2024-11-27 00:07:53.617');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('DggewDRjXu', '$argon2id$v=19$m=65536,t=1,p=1$MzFSqUr5J5IRC930empHrw$y7kogPP715NOtazsXqjR56LQGF2Eaes9CyAoPjRm3xk', 'uMezBkaQcfBbD', 'YoaQNBvZgHvTaFf', 'kinharrispa@gmail.com', '2024-11-27 21:55:53.037');
INSERT INTO account
(username, password_hash, first_name, last_name, email, created)
VALUES('yyZGFAsmPEBoSf', '$argon2id$v=19$m=65536,t=1,p=1$vLVm5ol6GQkXPU0BPut92g$Kyvp7/dl3lGUszXRiXfgsgB/IY0EKulZVpVttXQaDDU', 'svVjyPUaAkN', 'mdngeHlf', 'giyahyd4141@gmail.com', '2024-11-28 19:02:32.806');`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE TABLE issue (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE TABLE option (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
issue_id BIGINT NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
label VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE TABLE vote_token (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
issue_id BIGINT NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
used BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE TABLE vote (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
token UUID NOT NULL UNIQUE REFERENCES vote_token(token) ON DELETE CASCADE,
option_id BIGINT NOT NULL REFERENCES option(id) ON DELETE CASCADE,
voted_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
sql = `CREATE INDEX idx_votes_option_id ON vote(option_id);
CREATE INDEX idx_vote_tokens_issue_id ON vote_token(issue_id);
CREATE INDEX idx_options_issue_id ON option(issue_id);`
_, err = conn.Exec(context.Background(), sql)
if err != nil {
log.Fatal(err)
}
}

92
cmd/web/handlers.go Normal file
View File

@ -0,0 +1,92 @@
package main
import (
"fmt"
"net/http"
"time"
"log"
"html/template"
)
func home(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Println(r.URL.Path)
ts, err := template.ParseFiles("../../ui/html/home.page.tmpl", "../../ui/html/base.layout.tmpl")
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, nil)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
// data := struct {
// Name string
// }{
// Name: "Vicente",
// }
// page_template.Execute(w, data)
}
func ws(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Upgrade error:", err)
return
}
defer conn.Close()
done := make(chan struct{})
go func() {
ticker := time.NewTicker(1 * time.Second);
for {
select {
case <-done:
return
case t := <-ticker.C:
msg := map[string]interface{}{
"type": "server_tick",
"timestamp": t.Format(time.RFC3339),
}
if err := conn.WriteJSON(msg); err != nil {
fmt.Println("Write error:", err)
return
}
}
}
}()
for {
var msg Message
err := conn.ReadJSON(&msg)
if err != nil {
fmt.Println("Read error:", err)
break
}
fmt.Println(msg)
// Send a response
// response := fmt.Sprintf("Server time: %s", time.Now())
// if err := conn.WriteMessage(websocket.TextMessage, []byte(response)); err != nil {
// fmt.Println("Write error:", err)
// break
// }
}
}
func redirectToHTTPS(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://localhost:8443" + r.URL.RequestURI(), http.StatusMovedPermanently)
}

62
cmd/web/main.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"context"
"log"
//"html/template"
"net/http"
"flag"
"github.com/gorilla/websocket"
"github.com/jackc/pgx/v5"
)
type Message struct {
Type string `json:"type"`
Timestamp string `json:"timestamp"`
Random float64 `json:"random"`
}
//var page_template = template.Must(template.ParseFiles("../../ui/html/index.html"))
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
}
func main() {
addr := flag.String("addr", ":8443", "HTTP network address")
flag.Parse()
conn, err := pgx.Connect(context.Background(), "postgres://party:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com:5432/party")
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
database_init(conn)
mux := http.NewServeMux()
fileServer := http.FileServer(http.Dir("../../ui/static"))
mux.HandleFunc("/", home)
mux.HandleFunc("/ws", ws)
mux.Handle("/static/", http.StripPrefix("/static", fileServer))
go func() {
err := http.ListenAndServe(":8080", http.HandlerFunc(redirectToHTTPS))
if err != nil {
panic(err)
}
}()
log.Println("Starting server on", *addr)
// Start HTTPS server (requires cert.pem and key.pem in current dir)
err = http.ListenAndServeTLS(*addr, "cert.pem", "key.pem", mux)
if err != nil {
panic(err)
}
}

View File

@ -1,8 +1,9 @@
{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ping Demo</title>
<title>{{template "title" .}}</title>
<script>
const ws = new WebSocket("wss://localhost:8443/ws");
@ -22,9 +23,16 @@
}
}, 1000)
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap');
</style>
<link rel="stylesheet" href="/static/style.css"/>
</head>
<body>
{{template "body" .}}
<h1>Hello, {{.Name}}!</h1>
<p>This page sends a ping to the server every second.</p>
<h1>This is THE PARTY.</h1>
</body>
</html>
{{end}}

7
ui/html/home.page.tmpl Normal file
View File

@ -0,0 +1,7 @@
{{template "base" .}}
{{define "title"}}Home{{end}}
{{define "body"}}
<h1>The Party?</h1>
{{end}}

14
ui/static/style.css Normal file
View File

@ -0,0 +1,14 @@
* {
font-family: "Alfa Slab One", serif;
font-weight: 400;
font-style: normal;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}