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 // if a.DBDir is empty return log "Set a path using storageDir in the config file"
21 return DiskDBfilepath
22}
23
24// ensureDBDir ensures that the database directory exists.
25func (a *App) ensureDBDir() error {
26 if _, err := os.Stat(a.DBDir); os.IsNotExist(err) {
27 if err := os.MkdirAll(a.DBDir, 0755); err != nil {
28 return err
29 }
30 }
31 return nil
32}
33
34// checkDBFile checks if the database file exists.
35func (a *App) checkDBFile(dbPath string) (bool, error) {
36 if _, err := os.Stat(dbPath); os.IsNotExist(err) {
37 return false, nil
38 } else if err != nil {
39 return false, err
40 }
41 return true, nil
42}
43
44// InitializeDB initializes the database
45func InitializeDB(a *App) (*sql.DB, error) {
46 if err := a.ensureDBDir(); err != nil {
47 return nil, fmt.Errorf("failed to ensure DB directory: %w", err)
48 }
49
50 dbPath := a.getDiskDBFilePath()
51
52 dbExists, err := a.checkDBFile(dbPath)
53 if err != nil {
54 return nil, fmt.Errorf("failed to check DB file: %w", err)
55 }
56
57 if !dbExists {
58 if err := bootstrapDB(dbPath, a); err != nil {
59 return nil, fmt.Errorf("failed to bootstrap DB: %w", err)
60 }
61 }
62
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 db, err := sql.Open("sqlite", dbPath)
82 if err != nil {
83 return err
84 }
85 defer db.Close()
86
87 tables := app.DBTables
88
89 if err := pkgsqlite.CreateTable(db, tables.User, "user"); err != nil {
90 return err
91 }
92 if err := pkgsqlite.CreateTable(db, tables.Account, "account"); err != nil {
93 return err
94 }
95 if err := pkgsqlite.CreateTable(db, tables.Sessions, "sessions"); err != nil {
96 return err
97 }
98 if err := pkgsqlite.CreateTable(db, tables.Provider, "provider"); err != nil {
99 return err
100 }
101
102 if err := pkgsqlite.CreateTable(db, tables.Clients, "clients"); err != nil {
103 return err
104 }
105
106 return nil
107}
108
109type DBTable interface {
110 ScanFromRows(rows *sql.Rows) error
111}
112
113type UserWrapper struct {
114 *db.User
115}
116
117type AccountWrapper struct {
118 *db.Account
119}
120
121type ProviderWrapper struct {
122 *db.Provider
123}
124
125type SessionsWrapper struct {
126 *db.Sessions
127}
128
129type ClientsWrapper struct {
130 *db.Clients
131}
132
133func populateTableFromDB(a *App, query string, table DBTable) error {
134 rows, err := a.DB.Query(query)
135 if err != nil {
136 return err
137 }
138 defer rows.Close()
139
140 for rows.Next() {
141 if err := table.ScanFromRows(rows); err != nil {
142 return err
143 }
144 }
145 return rows.Err()
146}
147
148// Implementation of DBTable for User, Account, Provider, Sessions
149func (u *UserWrapper) ScanFromRows(rows *sql.Rows) error {
150 return rows.Scan(&u.ID, &u.Name, &u.Email)
151}
152
153func (a *AccountWrapper) ScanFromRows(rows *sql.Rows) error {
154 return rows.Scan(&a.ID, &a.UserID)
155}
156
157func (p *ProviderWrapper) ScanFromRows(rows *sql.Rows) error {
158 return rows.Scan(&p.ID, &p.AccountID, &p.Name, &p.Login, &p.AvatarURL, &p.ProfileURL, &p.Email)
159}
160
161func (s *SessionsWrapper) ScanFromRows(rows *sql.Rows) error {
162 return rows.Scan(&s.ID, &s.AccountID, &s.AccessToken, &s.BrowserInfo, &s.Expires, &s.IsOnline)
163}
164
165func (c *ClientsWrapper) ScanFromRows(rows *sql.Rows) error {
166 return rows.Scan(&c.ID, &c.AccountID, &c.OS, &c.IP, &c.Hostname, &c.Expires)
167}
168
169// PopulateStructWithTables updates db struct with tables
170func PopulateStructWithTables(a *App) error {
171 tables := map[string]DBTable{
172 "SELECT id, name, email FROM user": &UserWrapper{&a.DBTables.User},
173 "SELECT id, user_id FROM account": &AccountWrapper{&a.DBTables.Account},
174 "SELECT id, account_id, name, login, avatar_url, profile_url, email FROM provider": &ProviderWrapper{&a.DBTables.Provider},
175 "SELECT id, account_id, access_token, browser_info, expires, is_online FROM sessions": &SessionsWrapper{&a.DBTables.Sessions},
176 "SELECT id, account_id, os, ip, hostname, expires FROM clients": &ClientsWrapper{&a.DBTables.Clients},
177 }
178
179 for query, table := range tables {
180 if err := populateTableFromDB(a, query, table); err != nil {
181 return err
182 }
183 }
184
185 return nil
186}