all repos — flounder @ d8647e6a087a23309305a5a0c7075c7e7ddf9ed4

A small site builder for the Gemini protocol

db.go (view raw)

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