diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fb58120 --- /dev/null +++ b/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go deleted file mode 100644 index 015ae9d..0000000 --- a/cmd/server/main.go +++ /dev/null @@ -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) - } -} diff --git a/cmd/server/cert.pem b/cmd/web/cert.pem similarity index 100% rename from cmd/server/cert.pem rename to cmd/web/cert.pem diff --git a/cmd/web/db.go b/cmd/web/db.go new file mode 100644 index 0000000..570fd82 --- /dev/null +++ b/cmd/web/db.go @@ -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) + } +} diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go new file mode 100644 index 0000000..8c530f6 --- /dev/null +++ b/cmd/web/handlers.go @@ -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) +} diff --git a/cmd/server/key.pem b/cmd/web/key.pem similarity index 100% rename from cmd/server/key.pem rename to cmd/web/key.pem diff --git a/cmd/web/main.go b/cmd/web/main.go new file mode 100644 index 0000000..03ee7dc --- /dev/null +++ b/cmd/web/main.go @@ -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) + } +} diff --git a/ui/index.html b/ui/html/base.layout.tmpl similarity index 68% rename from ui/index.html rename to ui/html/base.layout.tmpl index f211d2f..fab842f 100644 --- a/ui/index.html +++ b/ui/html/base.layout.tmpl @@ -1,8 +1,9 @@ +{{define "base"}} - Ping Demo + {{template "title" .}} + + + {{template "body" .}} +

Hello, {{.Name}}!

-

This page sends a ping to the server every second.

+

This is THE PARTY.

+{{end}} diff --git a/ui/html/home.page.tmpl b/ui/html/home.page.tmpl new file mode 100644 index 0000000..4670334 --- /dev/null +++ b/ui/html/home.page.tmpl @@ -0,0 +1,7 @@ +{{template "base" .}} + +{{define "title"}}Home{{end}} + +{{define "body"}} +

The Party?

+{{end}} diff --git a/ui/static/style.css b/ui/static/style.css new file mode 100644 index 0000000..aa8c771 --- /dev/null +++ b/ui/static/style.css @@ -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; +}