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