all repos — flounder @ c26272ec81129523df8496118cc4432e56a334ab

A small site builder for the Gemini protocol

main.go (view raw)

  1package main
  2
  3import (
  4	"crypto/rand"
  5	"database/sql"
  6	"flag"
  7	"github.com/gorilla/sessions"
  8	"io"
  9	"io/ioutil"
 10	"log"
 11	"os"
 12	"path"
 13	"path/filepath"
 14	"sort"
 15	"sync"
 16	"time"
 17)
 18
 19var c Config // global var to hold static configuration
 20
 21type File struct {
 22	Creator     string
 23	Name        string
 24	UpdatedTime time.Time
 25	TimeAgo     string
 26}
 27
 28func getUsers() ([]string, error) {
 29	rows, err := DB.Query(`SELECT username from user`)
 30	if err != nil {
 31		return nil, err
 32	}
 33	var users []string
 34	for rows.Next() {
 35		var user string
 36		err = rows.Scan(&user)
 37		if err != nil {
 38			return nil, err
 39		}
 40		users = append(users, user)
 41	}
 42	return users, nil
 43}
 44
 45func getIndexFiles() ([]*File, error) { // cache this function
 46	result := []*File{}
 47	err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
 48		if err != nil {
 49			log.Printf("Failure accessing a path %q: %v\n", thepath, err)
 50			return err // think about
 51		}
 52		// make this do what it should
 53		if !info.IsDir() {
 54			creatorFolder, _ := path.Split(thepath)
 55			updatedTime := info.ModTime()
 56			result = append(result, &File{
 57				Name:        info.Name(),
 58				Creator:     path.Base(creatorFolder),
 59				UpdatedTime: updatedTime,
 60				TimeAgo:     timeago(&updatedTime),
 61			})
 62		}
 63		return nil
 64	})
 65	if err != nil {
 66		return nil, err
 67	}
 68	// sort
 69	// truncate
 70	sort.Slice(result, func(i, j int) bool {
 71		return result[i].UpdatedTime.Before(result[j].UpdatedTime)
 72	})
 73	if len(result) > 50 {
 74		result = result[:50]
 75	}
 76	return result, nil
 77} // todo clean up paths
 78
 79func getUserFiles(user string) ([]*File, error) {
 80	result := []*File{}
 81	files, err := ioutil.ReadDir(path.Join(c.FilesDirectory, user))
 82	if err != nil {
 83		return nil, err
 84	}
 85	for _, file := range files {
 86		result = append(result, &File{
 87			Name:        file.Name(),
 88			Creator:     user,
 89			UpdatedTime: file.ModTime(),
 90		})
 91	}
 92	return result, nil
 93}
 94
 95func createTablesIfDNE() {
 96	_, err := DB.Exec(`CREATE TABLE IF NOT EXISTS user (
 97  id INTEGER PRIMARY KEY NOT NULL,
 98  username TEXT NOT NULL UNIQUE,
 99  email TEXT NOT NULL UNIQUE,
100  password_hash TEXT NOT NULL,
101  approved boolean NOT NULL DEFAULT false,
102  created_at INTEGER DEFAULT (strftime('%s', 'now'))
103);
104
105CREATE TABLE IF NOT EXISTS cookie_key (
106  value TEXT NOT NULL
107);`)
108	if err != nil {
109		log.Fatal(err)
110	}
111}
112
113// Generate a cryptographically secure key for the cookie store
114func generateCookieKeyIfDNE() []byte {
115	rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
116	if err != nil {
117		log.Fatal(err)
118	}
119	if rows.Next() {
120		var cookie []byte
121		err := rows.Scan(&cookie)
122		if err != nil {
123			log.Fatal(err)
124		}
125		return cookie
126	} else {
127		k := make([]byte, 32)
128		_, err := io.ReadFull(rand.Reader, k)
129		if err != nil {
130			log.Fatal(err)
131		}
132		_, err = DB.Exec("insert into cookie_key values ($1)", k)
133		if err != nil {
134			log.Fatal(err)
135		}
136		return k
137	}
138}
139
140func main() {
141	configPath := flag.String("c", "flounder.toml", "path to config file")
142	var err error
143	c, err = getConfig(*configPath)
144	if err != nil {
145		log.Fatal(err)
146	}
147
148	// Generate self signed cert if does not exist. This is not suitable for production.
149	_, err1 := os.Stat(c.TLSCertFile)
150	_, err2 := os.Stat(c.TLSKeyFile)
151	if os.IsNotExist(err1) || os.IsNotExist(err2) {
152		log.Println("Keyfile or certfile does not exist.")
153	}
154
155	// Generate session cookie key if does not exist
156	DB, err = sql.Open("sqlite3", c.DBFile)
157	if err != nil {
158		log.Fatal(err)
159	}
160
161	createTablesIfDNE()
162	cookie := generateCookieKeyIfDNE()
163	SessionStore = sessions.NewCookieStore(cookie)
164	wg := new(sync.WaitGroup)
165	wg.Add(2)
166	go func() {
167		runHTTPServer()
168		wg.Done()
169	}()
170	go func() {
171		runGeminiServer()
172		wg.Done()
173	}()
174	wg.Wait()
175}