Files
go-socket/database.go
T
2026-04-12 19:29:10 +02:00

215 lines
6.2 KiB
Go

package main
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
var dbConn *pgxpool.Pool
func DbInit(ctx context.Context) {
var err error
dbConn, err = pgxpool.New(ctx, "postgres://master:secret@localhost:5432") // TODO change to env in production
if err != nil {
panic(err)
}
_, err = dbConn.Exec(ctx, `CREATE EXTENSION IF NOT EXISTS "pgcrypto";`)
if err != nil {
panic(err)
}
_, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL,
pass_hash TEXT NOT NULL,
pronouns TEXT DEFAULT NULL,
color_red SMALLINT DEFAULT NULL,
color_green SMALLINT DEFAULT NULL,
color_blue SMALLINT DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)
`)
if err != nil {
panic(err)
}
_, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS user_connections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
requestor_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
recipient_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
state SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)
`)
if err != nil {
panic(err)
}
_, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS direct_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
content TEXT NOT NULL
)
`)
if err != nil {
panic(err)
}
}
func DbUserSave(ctx context.Context, user *User) error {
err := dbConn.QueryRow(ctx, `
INSERT INTO users (name, pass_hash, pronouns, color_red, color_green, color_blue, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id
`, user.Name, user.PasswordHash, user.Pronouns, user.Color[0], user.Color[1], user.Color[2], user.CreatedAt).
Scan(&user.Id)
return err
}
func DbUserDelete(ctx context.Context, id uuid.UUID) error {
_, err := dbConn.Exec(ctx, `
DELETE FROM users WHERE id = $1
`, id)
return err
}
func DbUserGetStandardInfoByName(ctx context.Context, user *User) error {
err := dbConn.QueryRow(ctx, `
SELECT id, name, pass_hash, COALESCE(pronouns, ''), color_red, color_green, color_blue, created_at FROM users WHERE name = $1
`, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &user.Color[0], &user.Color[1], &user.Color[2], &user.CreatedAt)
return err
}
func DbUserGetById(ctx context.Context, user *User) error {
err := dbConn.QueryRow(ctx, `
SELECT name, pass_hash, COALESCE(pronouns, ''), color_red, color_green, color_blue, created_at FROM users WHERE id = $1
`, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &user.Color[0], &user.Color[1], &user.Color[2], &user.CreatedAt)
return err
}
func DbUserSetColor(ctx context.Context, user *User) error {
_, err := dbConn.Exec(ctx, `
UPDATE users SET color_red = $1, color_green = $2, color_blue = $3 WHERE id = $4
`, user.Color[0], user.Color[1], user.Color[2], user.Id)
return err
}
func DbUserSetPronouns(ctx context.Context, user *User) error {
_, err := dbConn.Exec(ctx, `
UPDATE users SET pronouns = $1 WHERE id = $2
`, user.Pronouns, user.Id)
return err
}
func DbGetWholeUser(ctx context.Context, user *User) error {
if err := DbUserGetById(ctx, user); err != nil {
return err
}
if err := DbConnectionsGetBelongingToUser(ctx, user); err != nil {
return err
}
CacheSaveUser(user)
return nil
}
func DbConnectionSave(ctx context.Context, conn *Connection) error {
return dbConn.QueryRow(ctx, `
INSERT INTO user_connections (requestor_id, recipient_id, state, created_at) VALUES ($1, $2, $3, $4)
RETURNING id
`, conn.RequestorId, conn.RecipientId, conn.State, conn.CreatedAt).Scan(&conn.Id)
}
func DbConnectionDelete(ctx context.Context, conn *Connection) error {
_, err := dbConn.Exec(ctx, `
DELETE FROM user_connections WHERE id = $1
`, conn.Id)
return err
}
func DbConnectionsGetBelongingToUser(ctx context.Context, user *User) error {
rows, err := dbConn.Query(ctx, `
SELECT id, requestor_id, recipient_id, state, created_at
FROM user_connections
WHERE requestor_id = $1 OR recipient_id = $1
`, user.Id)
if err != nil {
return err
}
defer rows.Close()
if user.Connections == nil {
user.Connections = make(map[uuid.UUID]*Connection)
}
for rows.Next() {
conn := &Connection{}
if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil {
return fmt.Errorf("scanning connection row: %w", err)
}
user.Connections[conn.Id] = conn
}
return rows.Err()
}
func DbConnectionUpdateState(ctx context.Context, conn *Connection) error {
_, err := dbConn.Exec(ctx, `
UPDATE user_connections SET state = $1 WHERE id = $2
`, conn.State, conn.Id)
return err
}
func DbConnectionMessageSave(ctx context.Context, message *Message) error {
if message.Id != (uuid.UUID{}) {
_, err := dbConn.Exec(ctx, `
INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4, $5)
`, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content)
return err
}
return dbConn.QueryRow(ctx, `
INSERT INTO direct_messages (sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4)
RETURNING id
`, message.Sender, message.Receiver, message.CreatedAt, message.Content).Scan(&message.Id)
}
func DbConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*Message, error) {
rows, err := dbConn.Query(ctx, `
SELECT id, sender_id, receiver_id, created_at, content
FROM (
SELECT id, sender_id, receiver_id, created_at, content
FROM direct_messages
WHERE receiver_id = $1
AND created_at < $2
ORDER BY created_at DESC
LIMIT $3
) sub
ORDER BY created_at ASC
`, connection, before, cap)
if err != nil {
return nil, err
}
defer rows.Close()
messages := make([]*Message, 0, cap)
for rows.Next() {
msg := &Message{}
if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content); err != nil {
return nil, err
}
messages = append(messages, msg)
}
return messages, rows.Err()
}