This commit is contained in:
Vicente Ferrari Smith 2025-10-09 07:44:38 +02:00
parent 0069aeaa24
commit 943408255c
9 changed files with 403 additions and 388 deletions

30
.vscode/launch.json vendored
View File

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

View File

@ -1,141 +1,141 @@
package main package main
import ( import (
"context" "context"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"log" "log"
) )
func database_init(conn *pgx.Conn) { func database_init(conn *pgx.Conn) {
sql := `DROP TABLE IF EXISTS vote; sql := `DROP TABLE IF EXISTS vote;
DROP TABLE IF EXISTS vote_token; DROP TABLE IF EXISTS vote_token;
DROP TABLE IF EXISTS option; DROP TABLE IF EXISTS option;
DROP TABLE IF EXISTS issue; DROP TABLE IF EXISTS issue;
DROP TABLE IF EXISTS account;` DROP TABLE IF EXISTS account;`
_, err := conn.Exec(context.Background(), sql) _, err := conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `CREATE TABLE account ( sql = `CREATE TABLE account (
id BIGINT primary key generated always as identity, id BIGINT primary key generated always as identity,
username VARCHAR(50) UNIQUE NOT NULL, username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,
first_name TEXT not null, first_name TEXT not null,
last_name TEXT not null, last_name TEXT not null,
created TIMESTAMPTZ DEFAULT now() NOT NULL, created TIMESTAMPTZ DEFAULT now() NOT NULL,
last_login TIMESTAMPTZ last_login TIMESTAMPTZ
)` )`
_, err = conn.Exec(context.Background(), sql) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `INSERT INTO account sql = `INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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'); 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 INSERT INTO account
(username, password_hash, first_name, last_name, email, created) (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');` 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) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `CREATE TABLE issue ( sql = `CREATE TABLE issue (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
description TEXT, description TEXT,
start_time TIMESTAMPTZ NOT NULL, start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ NOT NULL, end_time TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now() created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)` )`
_, err = conn.Exec(context.Background(), sql) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `CREATE TABLE option ( sql = `CREATE TABLE option (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
issue_id BIGINT NOT NULL REFERENCES issue(id) ON DELETE CASCADE, issue_id BIGINT NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
label VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now() created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)` )`
_, err = conn.Exec(context.Background(), sql) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `CREATE TABLE vote_token ( sql = `CREATE TABLE vote_token (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
issue_id BIGINT NOT NULL REFERENCES issue(id) ON DELETE CASCADE, issue_id BIGINT NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(), token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
used BOOLEAN NOT NULL DEFAULT FALSE, used BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now() created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)` )`
_, err = conn.Exec(context.Background(), sql) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `CREATE TABLE vote ( sql = `CREATE TABLE vote (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
token UUID NOT NULL UNIQUE REFERENCES vote_token(token) ON DELETE CASCADE, token UUID NOT NULL UNIQUE REFERENCES vote_token(token) ON DELETE CASCADE,
option_id BIGINT NOT NULL REFERENCES option(id) ON DELETE CASCADE, option_id BIGINT NOT NULL REFERENCES option(id) ON DELETE CASCADE,
voted_at TIMESTAMPTZ NOT NULL DEFAULT now() voted_at TIMESTAMPTZ NOT NULL DEFAULT now()
)` )`
_, err = conn.Exec(context.Background(), sql) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sql = `CREATE INDEX idx_votes_option_id ON vote(option_id); 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_vote_tokens_issue_id ON vote_token(issue_id);
CREATE INDEX idx_options_issue_id ON option(issue_id);` CREATE INDEX idx_options_issue_id ON option(issue_id);`
_, err = conn.Exec(context.Background(), sql) _, err = conn.Exec(context.Background(), sql)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

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

View File

@ -1,95 +1,85 @@
package main package main
import ( import (
"context" "context"
"log" "log"
//"html/template" //"html/template"
"net/http" "net/http"
"flag" "flag"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
) )
import ( import (
"fmt" "fmt"
"os" "os"
"time" "time"
) )
const version = "1.0.0" const version = "1.0.0"
type config struct { type config struct {
port int port int
env string env string
} }
type application struct { type application struct {
config config config config
logger *log.Logger logger *log.Logger
} }
type Message struct { type Message struct {
Type string `json:"type"` Type string `json:"type"`
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
Random float64 `json:"random"` Random float64 `json:"random"`
} }
//var page_template = template.Must(template.ParseFiles("../../ui/html/index.html")) //var page_template = template.Must(template.ParseFiles("../../ui/html/index.html"))
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
} }
func main() { func main() {
var cfg config
var cfg config
flag.IntVar(&cfg.port, "port", 4000, "API server port")
flag.IntVar(&cfg.port, "port", 4000, "API server port") flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)") //addr := flag.String("addr", ":8443", "HTTP network address")
//addr := flag.String("addr", ":8443", "HTTP network address") flag.Parse()
flag.Parse()
logger := log.New(os.Stdout, "", log.Ldate | log.Ltime)
logger := log.New(os.Stdout, "", log.Ldate | log.Ltime) app := &application{
config: cfg,
app := &application{ logger: logger,
config: cfg, }
logger: logger,
} conn, err := pgx.Connect(context.Background(), "postgres://party:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com:5432/party")
if err != nil {
conn, err := pgx.Connect(context.Background(), "postgres://party:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com:5432/party") log.Fatal(err)
if err != nil { }
log.Fatal(err)
} defer conn.Close(context.Background())
defer conn.Close(context.Background()) database_init(conn)
database_init(conn) srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.port),
fileServer := http.FileServer(http.Dir("ui/static")) Handler: app.routes(),
IdleTimeout: time.Minute,
mux := http.NewServeMux() ReadTimeout: 10 * time.Second,
mux.HandleFunc("/", home) WriteTimeout: 30 * time.Second,
mux.HandleFunc("/ws", ws) }
mux.Handle("/static/", http.StripPrefix("/static", fileServer))
mux.HandleFunc("/v1/healthcheck", app.healthcheckHandler) logger.Printf("starting %s server on %s", cfg.env, srv.Addr);
srv := &http.Server{ // Start HTTPS server (requires cert.pem and key.pem in current dir)
Addr: fmt.Sprintf(":%d", cfg.port), err = srv.ListenAndServe()
Handler: mux, if err != nil {
IdleTimeout: time.Minute, panic(err)
ReadTimeout: 10 * time.Second, }
WriteTimeout: 30 * time.Second, }
}
logger.Printf("starting %s server on %s", cfg.env, srv.Addr);
// Start HTTPS server (requires cert.pem and key.pem in current dir)
err = srv.ListenAndServe()
if err != nil {
panic(err)
}
}

22
cmd/api/routes.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func (app *application) routes() *httprouter.Router {
router := httprouter.New()
fileServer := http.FileServer(http.Dir("ui/static"))
router.HandlerFunc(http.MethodGet, "/", home)
router.HandlerFunc(http.MethodGet, "/ws", ws)
router.Handler(http.MethodGet, "/static/", http.StripPrefix("/static", fileServer))
router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler)
// router.HandlerFunc(http.MethodPost, "/v1/movies", app.createMovieHandler)
// router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.showMovieHandler)
return router
}

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.24.2
require ( require (
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/jackc/pgx/v5 v5.7.6 github.com/jackc/pgx/v5 v5.7.6
github.com/julienschmidt/httprouter v1.3.0
) )
require ( require (

2
go.sum
View File

@ -11,6 +11,8 @@ github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -1,38 +1,38 @@
{{define "base"}} {{define "base"}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{template "title" .}}</title> <title>{{template "title" .}}</title>
<script> <script>
const ws = new WebSocket("wss://localhost:8443/ws"); const ws = new WebSocket("wss://localhost:8443/ws");
ws.onopen = () => console.log("Connected") ws.onopen = () => console.log("Connected")
ws.onmessage = (event) => console.log(event.data) ws.onmessage = (event) => console.log(event.data)
ws.onclose = () => console.log("Closed") ws.onclose = () => console.log("Closed")
setInterval(() => { setInterval(() => {
if (ws.readyState === WebSocket.OPEN) { if (ws.readyState === WebSocket.OPEN) {
const message = { const message = {
type: "ping", type: "ping",
timestamp: Date(), timestamp: Date(),
random: Math.random() random: Math.random()
}; };
ws.send(JSON.stringify(message)) ws.send(JSON.stringify(message))
} }
}, 1000) }, 1000)
</script> </script>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap');
</style> </style>
<link rel="stylesheet" href="/static/style.css"/> <link rel="stylesheet" href="/static/style.css"/>
</head> </head>
<body> <body>
{{template "body" .}} {{template "body" .}}
<h1>Hello, {{.Name}}!</h1> <h1>Hello, {{.Name}}!</h1>
<h1>This is THE PARTY.</h1> <h1>This is THE PARTY.</h1>
</body> </body>
</html> </html>
{{end}} {{end}}

View File

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