all repos — flounder @ 758676c26d0a87c9354a795aed370bc06b91be6b

A small site builder for the Gemini protocol

db.go (view raw)

  1package main
  2
  3import (
  4	"crypto/rand"
  5	"database/sql"
  6	"golang.org/x/crypto/bcrypt"
  7	"io"
  8	"io/ioutil"
  9	"log"
 10	"os"
 11	"path"
 12	"path/filepath"
 13	"sort"
 14	"time"
 15)
 16
 17var DB *sql.DB
 18
 19func initializeDB() {
 20	var err error
 21	DB, err = sql.Open("sqlite3", c.DBFile)
 22	if err != nil {
 23		log.Fatal(err)
 24	}
 25	createTablesIfDNE()
 26}
 27
 28func getAnalyticsDB() (*sql.DB, error) {
 29	db, err := sql.Open("sqlite3", c.AnalyticsDBFile)
 30	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS log (
 31  id INTEGER PRIMARY KEY NOT NULL,
 32  timestamp TEXT NOT NULL,
 33  protocol TEXT NOT NULL,
 34  request_ip TEXT,
 35  request_user TEXT,
 36  status INTEGER,
 37  destination_host TEXT,
 38  path TEXT,
 39  method TEXT,
 40  referer TEXT
 41);`)
 42	return db, err
 43}
 44
 45type File struct { // also folders
 46	Creator     string
 47	Name        string // includes folder
 48	UpdatedTime time.Time
 49	TimeAgo     string
 50	IsText      bool
 51	Children    []File
 52	Host        string
 53}
 54
 55func fileFromPath(fullPath string) File {
 56	info, _ := os.Stat(fullPath)
 57	creatorFolder := getCreator(fullPath)
 58	isText := isTextFile(fullPath)
 59	updatedTime := info.ModTime()
 60	return File{
 61		Name:        getLocalPath(fullPath),
 62		Creator:     path.Base(creatorFolder),
 63		UpdatedTime: updatedTime,
 64		IsText:      isText,
 65		TimeAgo:     timeago(&updatedTime),
 66		Host:        c.Host,
 67	}
 68
 69}
 70
 71type User struct {
 72	Username      string
 73	Email         string
 74	Active        bool
 75	Admin         bool
 76	CreatedAt     int // timestamp
 77	Reference     string
 78	Domain        string
 79	DomainEnabled bool
 80}
 81
 82func getActiveUserNames() ([]string, error) {
 83	rows, err := DB.Query(`SELECT username from user WHERE active is true order by username`)
 84	if err != nil {
 85		return nil, err
 86	}
 87	var users []string
 88	for rows.Next() {
 89		var user string
 90		err = rows.Scan(&user)
 91		if err != nil {
 92			return nil, err
 93		}
 94		users = append(users, user)
 95	}
 96
 97	return users, nil
 98}
 99
100var domains map[string]string
101
102func refreshDomainMap() error {
103	domains = make(map[string]string)
104	rows, err := DB.Query(`SELECT domain, username from user WHERE domain != ""`)
105	if err != nil {
106		log.Println(err)
107		return err
108	}
109	for rows.Next() {
110		var domain string
111		var username string
112		err = rows.Scan(&domain, &username)
113		if err != nil {
114			return err
115		}
116		domains[domain] = username
117	}
118	return nil
119}
120
121func getUserByName(username string) (*User, error) {
122	var user User
123	row := DB.QueryRow(`SELECT username, email, active, admin, created_at, reference, domain, domain_enabled from user WHERE username = ?`, username)
124	err := row.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference, &user.Domain, &user.DomainEnabled)
125	if err != nil {
126		return nil, err
127	}
128	return &user, nil
129}
130
131func getUsers() ([]User, error) {
132	rows, err := DB.Query(`SELECT username, email, active, admin, created_at, reference, domain from user ORDER BY created_at DESC`)
133	if err != nil {
134		return nil, err
135	}
136	var users []User
137	for rows.Next() {
138		var user User
139		err = rows.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference, &user.Domain)
140		if err != nil {
141			return nil, err
142		}
143		users = append(users, user)
144	}
145	return users, nil
146}
147
148func getIndexFiles(admin bool) ([]*File, error) { // cache this function
149	result := []*File{}
150	err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
151		if err != nil {
152			log.Printf("Failure accessing a path %q: %v\n", thepath, err)
153			return err // think about
154		}
155		if !admin && info.IsDir() && info.Name() == HiddenFolder {
156			return filepath.SkipDir
157		}
158		// make this do what it should
159		if !info.IsDir() && info.Name() != followingFile {
160			res := fileFromPath(thepath)
161			result = append(result, &res)
162		}
163		return nil
164	})
165	if err != nil {
166		return nil, err
167	}
168	sort.Slice(result, func(i, j int) bool {
169		return result[i].UpdatedTime.After(result[j].UpdatedTime)
170	})
171	if len(result) > 50 {
172		result = result[:50]
173	}
174	return result, nil
175} // todo clean up paths
176
177func getMyFilesRecursive(p string, creator string) ([]File, error) {
178	result := []File{}
179	files, err := ioutil.ReadDir(p)
180	if err != nil {
181		return nil, err
182	}
183	for _, file := range files {
184		fullPath := path.Join(p, file.Name())
185		f := fileFromPath(fullPath)
186		if file.IsDir() {
187			f.Children, err = getMyFilesRecursive(path.Join(p, file.Name()), creator)
188		}
189		result = append(result, f)
190	}
191	return result, nil
192}
193
194func createTablesIfDNE() {
195	_, err := DB.Exec(`CREATE TABLE user (
196  id INTEGER PRIMARY KEY NOT NULL,
197  username TEXT NOT NULL UNIQUE,
198  email TEXT NOT NULL UNIQUE,
199  password_hash TEXT NOT NULL,
200  reference TEXT NOT NULL default "",
201  active boolean NOT NULL DEFAULT false,
202  admin boolean NOT NULL DEFAULT false,
203  created_at INTEGER DEFAULT (strftime('%s', 'now')),
204  domain TEXT NOT NULL default "",
205  domain_enabled BOOLEAN NOT NULL DEFAULT false
206);`)
207	if err == nil {
208		// on first creation, create admin user with pw admin
209		hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin"), 8) // TODO handle error
210		if err != nil {
211			log.Fatal(err)
212		}
213		_, err = DB.Exec(`INSERT OR IGNORE INTO user (username, email, password_hash, admin) values ('admin', 'default@flounder.local', ?, true)`, hashedPassword)
214		activateUser("admin")
215		if err != nil {
216			log.Fatal(err)
217		}
218	}
219
220	_, err = DB.Exec(`CREATE TABLE IF NOT EXISTS cookie_key (
221  value TEXT NOT NULL
222);`)
223	if err != nil {
224		log.Fatal(err)
225	}
226}
227
228// Generate a cryptographically secure key for the cookie store
229func generateCookieKeyIfDNE() []byte {
230	rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
231	defer rows.Close()
232	if err != nil {
233		log.Fatal(err)
234	}
235	if rows.Next() {
236		var cookie []byte
237		err := rows.Scan(&cookie)
238		if err != nil {
239			log.Fatal(err)
240		}
241		return cookie
242	} else {
243		k := make([]byte, 32)
244		_, err := io.ReadFull(rand.Reader, k)
245		if err != nil {
246			log.Fatal(err)
247		}
248		_, err = DB.Exec("insert into cookie_key values (?)", k)
249		if err != nil {
250			log.Fatal(err)
251		}
252		return k
253	}
254}