all repos — flounder @ 58a76b756308ca8920f3091d35caf8220605f3da

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