all repos — flounder @ 2e4c18b8bddc9120c9fcac5cdf1f103205abd3b5

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