1package server
2
3import (
4 "database/sql"
5 "fmt"
6 "os"
7 "path/filepath"
8
9 "github.com/bnema/gordon/internal/db"
10 pkgsqlite "github.com/bnema/gordon/pkg/sqlite"
11 _ "modernc.org/sqlite"
12)
13
14const (
15 DBFilename = "sqlite.db"
16)
17
18func (a *App) getDiskDBFilePath() string {
19 DiskDBfilepath := filepath.Join(a.DBDir, a.DBFilename)
20 return DiskDBfilepath
21}
22
23// ensureDBDir ensures that the database directory exists.
24func (a *App) ensureDBDir() error {
25 if _, err := os.Stat(a.DBDir); os.IsNotExist(err) {
26 if err := os.MkdirAll(a.DBDir, 0755); err != nil {
27 return err
28 }
29 }
30 return nil
31}
32
33// checkDBFile checks if the database file exists.
34func (a *App) checkDBFile(dbPath string) (bool, error) {
35 if _, err := os.Stat(dbPath); os.IsNotExist(err) {
36 return false, nil
37 } else if err != nil {
38 return false, err
39 }
40 return true, nil
41}
42
43// InitializeDB initializes the database
44func InitializeDB(a *App) (*sql.DB, error) {
45 if err := a.ensureDBDir(); err != nil {
46 return nil, fmt.Errorf("failed to ensure DB directory: %w", err)
47 }
48
49 dbPath := a.getDiskDBFilePath()
50
51 dbExists, err := a.checkDBFile(dbPath)
52 if err != nil {
53 return nil, fmt.Errorf("failed to check DB file: %w", err)
54 }
55
56 if !dbExists {
57 if err := bootstrapDB(dbPath, a); err != nil {
58 return nil, fmt.Errorf("failed to bootstrap DB: %w", err)
59 }
60 }
61
62 // Change here from "sqlite3" to "sqlite"
63 db, err := sql.Open("sqlite", dbPath)
64 if err != nil {
65 return nil, fmt.Errorf("failed to open disk DB: %w", err)
66 }
67
68 a.DB = db
69
70 if dbExists {
71 // Populate the struct tables from the database
72 if err := PopulateStructWithTables(a); err != nil {
73 return nil, err
74 }
75 }
76
77 return db, nil
78}
79
80func bootstrapDB(dbPath string, app *App) error {
81 // Change here from "sqlite3" to "sqlite"
82 db, err := sql.Open("sqlite", dbPath)
83 if err != nil {
84 return err
85 }
86 defer db.Close()
87
88 tables := app.DBTables
89
90 if err := pkgsqlite.CreateTable(db, tables.User, "user"); err != nil {
91 return err
92 }
93 if err := pkgsqlite.CreateTable(db, tables.Account, "account"); err != nil {
94 return err
95 }
96 if err := pkgsqlite.CreateTable(db, tables.Sessions, "sessions"); err != nil {
97 return err
98 }
99 if err := pkgsqlite.CreateTable(db, tables.Provider, "provider"); err != nil {
100 return err
101 }
102
103 if err := pkgsqlite.CreateTable(db, tables.Clients, "clients"); err != nil {
104 return err
105 }
106
107 return nil
108}
109
110type DBTable interface {
111 ScanFromRows(rows *sql.Rows) error
112}
113
114type UserWrapper struct {
115 *db.User
116}
117
118type AccountWrapper struct {
119 *db.Account
120}
121
122type ProviderWrapper struct {
123 *db.Provider
124}
125
126type SessionsWrapper struct {
127 *db.Sessions
128}
129
130type ClientsWrapper struct {
131 *db.Clients
132}
133
134func populateTableFromDB(a *App, query string, table DBTable) error {
135 rows, err := a.DB.Query(query)
136 if err != nil {
137 return err
138 }
139 defer rows.Close()
140
141 for rows.Next() {
142 if err := table.ScanFromRows(rows); err != nil {
143 return err
144 }
145 }
146 return rows.Err()
147}
148
149// Implementation of DBTable for User, Account, Provider, Sessions
150func (u *UserWrapper) ScanFromRows(rows *sql.Rows) error {
151 return rows.Scan(&u.ID, &u.Name, &u.Email)
152}
153
154func (a *AccountWrapper) ScanFromRows(rows *sql.Rows) error {
155 return rows.Scan(&a.ID, &a.UserID)
156}
157
158func (p *ProviderWrapper) ScanFromRows(rows *sql.Rows) error {
159 return rows.Scan(&p.ID, &p.AccountID, &p.Name, &p.Login, &p.AvatarURL, &p.ProfileURL, &p.Email)
160}
161
162func (s *SessionsWrapper) ScanFromRows(rows *sql.Rows) error {
163 return rows.Scan(&s.ID, &s.AccountID, &s.AccessToken, &s.BrowserInfo, &s.Expires, &s.IsOnline)
164}
165
166func (c *ClientsWrapper) ScanFromRows(rows *sql.Rows) error {
167 return rows.Scan(&c.ID, &c.AccountID, &c.OS, &c.IP, &c.Hostname, &c.Expires)
168}
169
170// PopulateStructWithTables updates db struct with tables
171func PopulateStructWithTables(a *App) error {
172 tables := map[string]DBTable{
173 "SELECT id, name, email FROM user": &UserWrapper{&a.DBTables.User},
174 "SELECT id, user_id FROM account": &AccountWrapper{&a.DBTables.Account},
175 "SELECT id, account_id, name, login, avatar_url, profile_url, email FROM provider": &ProviderWrapper{&a.DBTables.Provider},
176 "SELECT id, account_id, access_token, browser_info, expires, is_online FROM sessions": &SessionsWrapper{&a.DBTables.Sessions},
177 "SELECT id, account_id, os, ip, hostname, expires FROM clients": &ClientsWrapper{&a.DBTables.Clients},
178 }
179
180 for query, table := range tables {
181 if err := populateTableFromDB(a, query, table); err != nil {
182 return err
183 }
184 }
185
186 return nil
187}