initial commit

This commit is contained in:
Krzysztof
2025-08-01 18:01:55 +02:00
commit 9af1af7f92
73 changed files with 6531 additions and 0 deletions

140
store/check_store.go Normal file
View File

@@ -0,0 +1,140 @@
package store
import (
"context"
"fmt"
"time"
"uptimemonitor"
"github.com/google/uuid"
)
func (s *Store) CreateCheck(ctx context.Context, check uptimemonitor.Check) (uptimemonitor.Check, error) {
stmt := `INSERT INTO checks(uuid, monitor_id, status_code, response_time_ms, created_at) VALUES(?, ?, ?, ?, ?)`
uuid := uuid.NewString()
if check.CreatedAt.IsZero() {
check.CreatedAt = time.Now()
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return check, err
}
defer tx.Rollback()
res, err := tx.ExecContext(ctx, stmt, uuid, check.MonitorID, check.StatusCode, check.ResponseTimeMs, check.CreatedAt)
if err != nil {
return check, err
}
id, _ := res.LastInsertId()
check.ID = id
stmt = `
SELECT uptime, avg_response_time_ms, n, incidents_count
FROM monitors
WHERE id = ?
`
var n int64
var uptime float32
var avgResponseTimeMs int64
var incidentsCount int64
err = tx.QueryRowContext(ctx, stmt, check.MonitorID).Scan(&uptime, &avgResponseTimeMs, &n, &incidentsCount)
if err != nil {
return check, err
}
if check.StatusCode >= 300 {
incidentsCount++
}
stmt = `
UPDATE monitors
SET uptime = ?, avg_response_time_ms = ?, n = ?, incidents_count = ?
WHERE id = ?
`
newIncidentCount := incidentsCount
newN := n + 1
newUptime := fmt.Sprintf("%.1f", float32(float32(newN-newIncidentCount)/float32(newN)*float32(100)))
newAvgResponseTimeMs := (avgResponseTimeMs*n + check.ResponseTimeMs) / newN
_, err = tx.ExecContext(ctx, stmt, newUptime, newAvgResponseTimeMs, newN, newIncidentCount, check.MonitorID)
if err != nil {
return check, err
}
tx.Commit()
return check, nil
}
func (s *Store) ListChecks(ctx context.Context, monitorID int64, limit int) ([]uptimemonitor.Check, error) {
stmt := `
SELECT checks.id, checks.uuid, checks.monitor_id, checks.created_at,
checks.status_code, checks.response_time_ms,
monitors.id, monitors.uuid, monitors.url, monitors.created_at
FROM checks
LEFT JOIN monitors ON monitors.id = checks.monitor_id
WHERE monitor_id = ?
ORDER BY checks.id DESC
LIMIT ?
`
rows, err := s.db.QueryContext(ctx, stmt, monitorID, limit)
if err != nil {
return []uptimemonitor.Check{}, err
}
defer rows.Close()
var checks []uptimemonitor.Check
for rows.Next() {
var c uptimemonitor.Check
if err := rows.Scan(
&c.ID, &c.Uuid, &c.MonitorID, &c.CreatedAt,
&c.StatusCode, &c.ResponseTimeMs,
&c.Monitor.ID, &c.Monitor.Uuid, &c.Monitor.Url, &c.Monitor.CreatedAt,
); err != nil {
return []uptimemonitor.Check{}, err
}
checks = append(checks, c)
}
if err = rows.Err(); err != nil {
return []uptimemonitor.Check{}, err
}
return checks, nil
}
func (s *Store) GetCheckByID(ctx context.Context, id int64) (uptimemonitor.Check, error) {
stmt := `
SELECT
checks.id, checks.uuid, checks.monitor_id, checks.status_code, checks.response_time_ms, checks.created_at,
monitors.id, monitors.url, monitors.uuid, monitors.http_method, monitors.http_headers, monitors.http_body, monitors.created_at
FROM checks
LEFT JOIN monitors ON monitors.id = checks.monitor_id
WHERE checks.id = ?
`
var ch uptimemonitor.Check
err := s.db.QueryRowContext(ctx, stmt, id).Scan(
&ch.ID, &ch.Uuid, &ch.MonitorID, &ch.StatusCode, &ch.ResponseTimeMs, &ch.CreatedAt,
&ch.Monitor.ID, &ch.Monitor.Url, &ch.Monitor.Uuid, &ch.Monitor.HttpMethod, &ch.Monitor.HttpHeaders, &ch.Monitor.HttpBody, &ch.Monitor.CreatedAt,
)
return ch, err
}
func (s *Store) DeleteOldChecks(ctx context.Context) error {
stmt := `DELETE FROM checks WHERE created_at < ?`
_, err := s.db.ExecContext(ctx, stmt, time.Now().Add(-time.Hour))
return err
}

8
store/embed.go Normal file
View File

@@ -0,0 +1,8 @@
package store
import "embed"
var (
//go:embed migrations/*.sql
FS embed.FS
)

301
store/incident_store.go Normal file
View File

@@ -0,0 +1,301 @@
package store
import (
"context"
"time"
"uptimemonitor"
"github.com/google/uuid"
)
func (s *Store) CreateIncident(ctx context.Context, incident uptimemonitor.Incident) (uptimemonitor.Incident, error) {
stmt := `INSERT INTO incidents (uuid, monitor_id, status_text, status_code, response_time_ms, body, headers, req_method, req_url, req_headers, req_body, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id`
if incident.CreatedAt.IsZero() {
incident.CreatedAt = time.Now()
}
incident.Uuid = uuid.NewString()
incident.StatusText = uptimemonitor.IncidentStatusOpen
res, err := s.db.ExecContext(ctx, stmt, incident.Uuid, incident.MonitorID, incident.StatusText, incident.StatusCode, incident.ResponseTimeMs, incident.Body, incident.Headers,
incident.ReqMethod, incident.ReqUrl, incident.ReqHeaders, incident.ReqBody, incident.CreatedAt)
if err != nil {
return uptimemonitor.Incident{}, err
}
id, err := res.LastInsertId()
incident.ID = id
return incident, err
}
func (s *Store) UpdateIncidentBodyAndHeaders(ctx context.Context, incident uptimemonitor.Incident, body, headers, reqMethod, reqUrl, reqHeaders, reqBody string) error {
stmt := `
UPDATE incidents
SET body = ?, headers = ?, req_method = ?, req_url = ?, req_headers = ?, req_body = ?
WHERE id = ?
`
_, err := s.db.ExecContext(ctx, stmt, body, headers, reqMethod, reqUrl, reqHeaders, reqBody, incident.ID)
return err
}
func (s *Store) LastIncidentByStatusCode(ctx context.Context, monitorID int64, status string, statusCode int) (uptimemonitor.Incident, error) {
stmt := `
SELECT id, uuid, monitor_id, status_text, status_code, response_time_ms, body, headers, created_at
FROM incidents
WHERE monitor_id = ? AND status_text = ? AND status_code = ?
ORDER BY id DESC
LIMIT 1
`
row := s.db.QueryRowContext(ctx, stmt, monitorID, status, statusCode)
var incident uptimemonitor.Incident
if err := row.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt,
); err != nil {
return uptimemonitor.Incident{}, err
}
return incident, nil
}
func (s *Store) LastOpenIncident(ctx context.Context, monitorID int64) (uptimemonitor.Incident, error) {
stmt := `
SELECT id, uuid, monitor_id, status_text, status_code, response_time_ms, body, headers, created_at
FROM incidents
WHERE monitor_id = ? AND status_text = ?
ORDER BY id DESC
LIMIT 1
`
row := s.db.QueryRowContext(ctx, stmt, monitorID, uptimemonitor.IncidentStatusOpen)
var incident uptimemonitor.Incident
if err := row.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt,
); err != nil {
return uptimemonitor.Incident{}, err
}
return incident, nil
}
func (s *Store) ListOpenIncidents(ctx context.Context) ([]uptimemonitor.Incident, error) {
stmt := `
SELECT incidents.id, incidents.uuid, incidents.monitor_id,
incidents.status_text, incidents.status_code, incidents.response_time_ms,
incidents.body, incidents.headers, incidents.created_at,
monitors.id, monitors.url, monitors.uuid, monitors.created_at
FROM incidents
JOIN monitors ON incidents.monitor_id = monitors.id
WHERE incidents.status_text = ?
ORDER BY incidents.id DESC
`
rows, err := s.db.QueryContext(ctx, stmt, uptimemonitor.IncidentStatusOpen)
if err != nil {
return nil, err
}
defer rows.Close()
var incidents []uptimemonitor.Incident
for rows.Next() {
var incident uptimemonitor.Incident
if err := rows.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt,
&incident.Monitor.ID, &incident.Monitor.Url, &incident.Monitor.Uuid, &incident.Monitor.CreatedAt,
); err != nil {
return nil, err
}
incidents = append(incidents, incident)
}
return incidents, nil
}
func (s *Store) ListMonitorIncidents(ctx context.Context, id int64) ([]uptimemonitor.Incident, error) {
stmt := `
SELECT incidents.id, incidents.uuid, incidents.monitor_id,
incidents.status_text, incidents.status_code, incidents.response_time_ms,
incidents.body, incidents.headers, incidents.created_at, incidents.resolved_at,
monitors.id, monitors.url, monitors.uuid, monitors.created_at
FROM incidents
JOIN monitors ON incidents.monitor_id = monitors.id
WHERE incidents.monitor_id = ?
ORDER BY incidents.id DESC
LIMIT 10
`
rows, err := s.db.QueryContext(ctx, stmt, id)
if err != nil {
return nil, err
}
defer rows.Close()
var incidents []uptimemonitor.Incident
for rows.Next() {
var incident uptimemonitor.Incident
if err := rows.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt, &incident.ResolvedAt,
&incident.Monitor.ID, &incident.Monitor.Url, &incident.Monitor.Uuid, &incident.Monitor.CreatedAt,
); err != nil {
return nil, err
}
incidents = append(incidents, incident)
}
return incidents, nil
}
func (s *Store) CountMonitorIncidents(ctx context.Context, id int64) int64 {
stmt := `
SELECT COUNT(*)
FROM incidents
WHERE monitor_id = ?
`
var count int64
s.db.QueryRowContext(ctx, stmt, id).Scan(&count)
return count
}
func (s *Store) ListMonitorOpenIncidents(ctx context.Context, id int64) ([]uptimemonitor.Incident, error) {
stmt := `
SELECT incidents.id, incidents.uuid, incidents.monitor_id,
incidents.status_text, incidents.status_code, incidents.response_time_ms,
incidents.body, incidents.headers, incidents.created_at,
monitors.id, monitors.url, monitors.uuid, monitors.created_at
FROM incidents
JOIN monitors ON incidents.monitor_id = monitors.id
WHERE incidents.monitor_id = ? AND incidents.status_text = ?
ORDER BY incidents.id DESC
`
rows, err := s.db.QueryContext(ctx, stmt, id, uptimemonitor.IncidentStatusOpen)
if err != nil {
return nil, err
}
defer rows.Close()
var incidents []uptimemonitor.Incident
for rows.Next() {
var incident uptimemonitor.Incident
if err := rows.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt,
&incident.Monitor.ID, &incident.Monitor.Url, &incident.Monitor.Uuid, &incident.Monitor.CreatedAt,
); err != nil {
return nil, err
}
incidents = append(incidents, incident)
}
return incidents, nil
}
func (s *Store) ResolveIncident(ctx context.Context, incident uptimemonitor.Incident) error {
stmt := `
UPDATE incidents SET status_text = ?, resolved_at = ? WHERE id = ?
`
_, err := s.db.ExecContext(ctx, stmt, uptimemonitor.IncidentStatusResolved, time.Now(), incident.ID)
return err
}
func (s *Store) ResolveMonitorIncidents(ctx context.Context, monitor uptimemonitor.Monitor) error {
stmt := `
UPDATE incidents SET status_text = ?, resolved_at = ? WHERE monitor_id = ?
`
_, err := s.db.ExecContext(ctx, stmt, uptimemonitor.IncidentStatusResolved, time.Now(), monitor.ID)
return err
}
func (s *Store) DeleteOldIncidents(ctx context.Context) error {
stmt := `DELETE FROM incidents WHERE created_at < ?`
_, err := s.db.ExecContext(ctx, stmt, time.Now().Add(-time.Hour*24*7))
return err
}
func (s *Store) DeleteIncident(ctx context.Context, id int64) error {
stmt := `DELETE FROM incidents WHERE id = ?`
_, err := s.db.ExecContext(ctx, stmt, id)
return err
}
func (s *Store) GetIncidentByUuid(ctx context.Context, uuid string) (uptimemonitor.Incident, error) {
stmt := `
SELECT
incidents.id, incidents.uuid, incidents.monitor_id,
incidents.status_text, incidents.status_code, incidents.response_time_ms,
incidents.body, incidents.headers, incidents.created_at, incidents.resolved_at,
incidents.req_method, incidents.req_url, incidents.req_headers, incidents.req_body,
monitors.id, monitors.url, monitors.uuid, monitors.created_at
FROM incidents
LEFT JOIN monitors ON monitors.id = incidents.monitor_id
WHERE incidents.uuid = ?
`
row := s.db.QueryRowContext(ctx, stmt, uuid)
var incident uptimemonitor.Incident
if err := row.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt, &incident.ResolvedAt,
&incident.ReqMethod, &incident.ReqUrl, &incident.ReqHeaders, &incident.ReqBody,
&incident.Monitor.ID, &incident.Monitor.Url, &incident.Monitor.Uuid, &incident.Monitor.CreatedAt,
); err != nil {
return incident, err
}
return incident, nil
}
func (s *Store) GetIncidentByID(ctx context.Context, id int64) (uptimemonitor.Incident, error) {
stmt := `
SELECT
incidents.id, incidents.uuid, incidents.monitor_id,
incidents.status_text, incidents.status_code, incidents.response_time_ms,
incidents.body, incidents.headers, incidents.created_at, incidents.resolved_at,
incidents.req_method, incidents.req_url, incidents.req_headers, incidents.req_body,
monitors.id, monitors.url, monitors.uuid, monitors.created_at
FROM incidents
LEFT JOIN monitors ON monitors.id = incidents.monitor_id
WHERE incidents.id = ?
`
row := s.db.QueryRowContext(ctx, stmt, id)
var incident uptimemonitor.Incident
if err := row.Scan(
&incident.ID, &incident.Uuid, &incident.MonitorID,
&incident.StatusText, &incident.StatusCode, &incident.ResponseTimeMs,
&incident.Body, &incident.Headers, &incident.CreatedAt, &incident.ResolvedAt,
&incident.ReqMethod, &incident.ReqUrl, &incident.ReqHeaders, &incident.ReqBody,
&incident.Monitor.ID, &incident.Monitor.Url, &incident.Monitor.Uuid, &incident.Monitor.CreatedAt,
); err != nil {
return incident, err
}
return incident, nil
}

View File

@@ -0,0 +1,68 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at DATETIME NOT NULL
);
CREATE TABLE sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
user_id INTEGER,
created_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
CREATE TABLE monitors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
`url` TEXT NOT NULL,
http_method TEXT NOT NULL,
http_headers TEXT NOT NULL,
http_body TEXT NOT NULL,
webhook_url TEXT NOT NULL,
webhook_method TEXT NOT NULL,
webhook_headers TEXT NOT NULL,
webhook_body TEXT NOT NULL,
uptime FLOAT DEFAULT 0,
avg_response_time_ms INTEGER DEFAULT 0,
incidents_count INTEGER DEFAULT 0,
n INTEGER DEFAULT 0,
created_at DATETIME NOT NULL
);
CREATE TABLE checks(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
monitor_id INTEGER,
created_at DATETIME NOT NULL,
status_code INTEGER NOT NULL,
response_time_ms INTEGER NOT NULL,
FOREIGN KEY(monitor_id) REFERENCES monitors(id) ON DELETE CASCADE
);
CREATE TABLE incidents(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
monitor_id INTEGER,
status_text TEXT NOT NULL,
status_code INTEGER NOT NULL,
response_time_ms INTEGER NOT NULL,
body TEXT,
headers TEXT,
req_method TEXT,
req_url TEXT,
req_headers TEXT,
req_body TEXT,
created_at DATETIME NOT NULL,
resolved_at DATETIME,
FOREIGN KEY(monitor_id) REFERENCES monitors(id) ON DELETE CASCADE
);
-- +goose StatementEnd

158
store/monitor_store.go Normal file
View File

@@ -0,0 +1,158 @@
package store
import (
"context"
"time"
"uptimemonitor"
"github.com/google/uuid"
)
func (s *Store) CountMonitors(ctx context.Context) int {
stmt := `SELECT COUNT(*) FROM monitors`
var count int
s.db.QueryRowContext(ctx, stmt).Scan(&count)
return count
}
func (s *Store) CreateMonitor(ctx context.Context, monitor uptimemonitor.Monitor) (uptimemonitor.Monitor, error) {
stmt := `
INSERT INTO
monitors(
url, uuid,
http_method, http_headers, http_body,
webhook_url, webhook_method, webhook_headers, webhook_body,
created_at
)
VALUES(?,?,?,?,?,?,?,?,?,?)
`
monitor.CreatedAt = time.Now()
uuid := uuid.NewString()
res, err := s.db.ExecContext(
ctx, stmt,
monitor.Url, uuid, monitor.HttpMethod, monitor.HttpHeaders, monitor.HttpBody,
monitor.WebhookUrl, monitor.WebhookMethod, monitor.WebhookHeaders, monitor.WebhookBody,
monitor.CreatedAt,
)
if err != nil {
return monitor, err
}
id, _ := res.LastInsertId()
monitor.ID = id
monitor.Uuid = uuid
return monitor, nil
}
func (s *Store) ListMonitors(ctx context.Context) ([]uptimemonitor.Monitor, error) {
stmt := `
SELECT
id, url, uuid, created_at,
http_method, http_body, http_headers,
webhook_method, webhook_url, webhook_headers, webhook_body
FROM monitors
ORDER BY created_at DESC
`
rows, err := s.db.QueryContext(ctx, stmt)
if err != nil {
return []uptimemonitor.Monitor{}, err
}
defer rows.Close()
var monitors []uptimemonitor.Monitor
for rows.Next() {
var m uptimemonitor.Monitor
if err := rows.Scan(
&m.ID, &m.Url, &m.Uuid, &m.CreatedAt, &m.HttpMethod, &m.HttpBody, &m.HttpHeaders,
&m.WebhookMethod, &m.WebhookUrl, &m.WebhookHeaders, &m.WebhookBody,
); err != nil {
return monitors, err
}
monitors = append(monitors, m)
}
if err = rows.Err(); err != nil {
return monitors, err
}
return monitors, nil
}
func (s *Store) GetMonitorByID(ctx context.Context, id int) (uptimemonitor.Monitor, error) {
stmt := `
SELECT
id, url, uuid, http_method, http_headers, http_body,
webhook_url, webhook_method, webhook_headers, webhook_body,
uptime, avg_response_time_ms, n, incidents_count,
created_at
FROM monitors
WHERE id = ?
LIMIT 1
`
var m uptimemonitor.Monitor
err := s.db.QueryRowContext(ctx, stmt, id).
Scan(
&m.ID, &m.Url, &m.Uuid, &m.HttpMethod, &m.HttpHeaders, &m.HttpBody,
&m.WebhookUrl, &m.WebhookMethod, &m.WebhookHeaders, &m.WebhookBody,
&m.Uptime, &m.AvgResponseTimeMs, &m.N, &m.IncidentsCount,
&m.CreatedAt,
)
return m, err
}
func (s *Store) GetMonitorByUuid(ctx context.Context, uuid string) (uptimemonitor.Monitor, error) {
stmt := `
SELECT
id, url, uuid, http_method, http_headers, http_body,
webhook_url, webhook_method, webhook_headers, webhook_body,
uptime, avg_response_time_ms, n, incidents_count,
created_at
FROM monitors
WHERE uuid = ?
LIMIT 1
`
var m uptimemonitor.Monitor
err := s.db.QueryRowContext(ctx, stmt, uuid).
Scan(
&m.ID, &m.Url, &m.Uuid, &m.HttpMethod, &m.HttpHeaders, &m.HttpBody,
&m.WebhookUrl, &m.WebhookMethod, &m.WebhookHeaders, &m.WebhookBody,
&m.Uptime, &m.AvgResponseTimeMs, &m.N, &m.IncidentsCount,
&m.CreatedAt,
)
return m, err
}
func (s *Store) UpdateMonitor(ctx context.Context, monitor uptimemonitor.Monitor) error {
stmt := `
UPDATE monitors SET
url = ?, http_method = ?, http_headers = ?, http_body = ?,
webhook_url = ?, webhook_method = ?, webhook_headers = ?, webhook_body = ?
WHERE id = ?
`
_, err := s.db.ExecContext(
ctx, stmt, monitor.Url, monitor.HttpMethod, monitor.HttpHeaders, monitor.HttpBody,
monitor.WebhookUrl, monitor.WebhookMethod, monitor.WebhookHeaders, monitor.WebhookBody,
monitor.ID,
)
if err != nil {
return err
}
return nil
}
func (s *Store) DeleteMonitor(ctx context.Context, id int64) error {
stmt := `DELETE FROM monitors WHERE id = ?`
_, err := s.db.ExecContext(ctx, stmt, id)
return err
}

54
store/session_store.go Normal file
View File

@@ -0,0 +1,54 @@
package store
import (
"context"
"time"
"uptimemonitor"
"github.com/google/uuid"
)
func (s *Store) CreateSession(ctx context.Context, session uptimemonitor.Session) (uptimemonitor.Session, error) {
stmt := `INSERT INTO sessions (uuid, user_id, created_at, expires_at) VALUES(?, ?, ?, ?)`
uuid := uuid.NewString()
session.CreatedAt = time.Now()
res, err := s.db.ExecContext(ctx, stmt, uuid, session.UserID, session.CreatedAt, session.ExpiresAt)
if err != nil {
return session, err
}
id, _ := res.LastInsertId()
session.ID = id
session.Uuid = uuid
return session, nil
}
func (s *Store) GetSessionByUuid(ctx context.Context, uuid string) (uptimemonitor.Session, error) {
stmt := `
SELECT sessions.id, sessions.user_id, sessions.created_at, sessions.expires_at,
users.id, users.name, users.email, users.created_at
FROM sessions
LEFT JOIN users ON users.id = sessions.user_id
WHERE uuid = ?
LIMIT 1
`
var session uptimemonitor.Session
err := s.db.QueryRowContext(ctx, stmt, uuid).Scan(
&session.ID, &session.UserID, &session.CreatedAt, &session.ExpiresAt,
&session.User.ID, &session.User.Name, &session.User.Email, &session.User.CreatedAt,
)
return session, err
}
func (s *Store) RemoveSessionByID(ctx context.Context, id int64) error {
stmt := `
DELETE FROM sessions WHERE id = ?
`
_, err := s.db.ExecContext(ctx, stmt, id)
return err
}

51
store/store.go Normal file
View File

@@ -0,0 +1,51 @@
package store
import (
"database/sql"
"fmt"
"github.com/pressly/goose/v3"
_ "modernc.org/sqlite"
)
type Store struct {
db *sql.DB
}
func New(dsn string) *Store {
db, err := sql.Open("sqlite", dsn)
if err != nil {
panic(err)
}
if err := db.Ping(); err != nil {
panic(err)
}
_, err = db.Exec("PRAGMA journal_mode=WAL;")
if err != nil {
panic(fmt.Sprintf("Failed to enable WAL mode: %v", err))
}
db.Exec("PRAGMA foreign_keys = ON;")
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(0)
goose.SetBaseFS(FS)
if err := goose.SetDialect("sqlite"); err != nil {
panic(err)
}
if err := goose.Up(db, "migrations"); err != nil {
panic(err)
}
return &Store{
db: db,
}
}
func (s *Store) DB() *sql.DB {
return s.db
}

48
store/user_store.go Normal file
View File

@@ -0,0 +1,48 @@
package store
import (
"context"
"time"
"uptimemonitor"
)
func (s *Store) CountUsers(ctx context.Context) (int, error) {
stmt := `SELECT COUNT(*) FROM users`
var count int
if err := s.db.QueryRowContext(ctx, stmt).Scan(&count); err != nil {
return 0, err
}
return count, nil
}
func (s *Store) CreateUser(ctx context.Context, user uptimemonitor.User) (uptimemonitor.User, error) {
stmt := `INSERT INTO users (name, email, password_hash, created_at) VALUES (?, ?, ?, ?) RETURNING id`
user.CreatedAt = time.Now()
res, err := s.db.ExecContext(ctx, stmt, user.Name, user.Email, user.PasswordHash, user.CreatedAt)
if err != nil {
return uptimemonitor.User{}, err
}
id, _ := res.LastInsertId()
user.ID = id
return user, nil
}
func (s *Store) GetUserByEmail(ctx context.Context, email string) (uptimemonitor.User, error) {
stmt := `SELECT id, name, email, password_hash, created_at FROM users WHERE email = ? LIMIT 1`
row := s.db.QueryRowContext(ctx, stmt, email)
var user uptimemonitor.User
if err := row.Scan(
&user.ID, &user.Name, &user.Email, &user.PasswordHash, &user.CreatedAt,
); err != nil {
return user, err
}
return user, nil
}