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.Slice(result, func(i, j int) bool {
70 return result[i].UpdatedTime.After(result[j].UpdatedTime)
71 })
72 if len(result) > 50 {
73 result = result[:50]
74 }
75 return result, nil
76} // todo clean up paths
77
78func getUserFiles(user string) ([]*File, error) {
79 result := []*File{}
80 files, err := ioutil.ReadDir(path.Join(c.FilesDirectory, user))
81 if err != nil {
82 return nil, err
83 }
84 for _, file := range files {
85 result = append(result, &File{
86 Name: file.Name(),
87 Creator: user,
88 UpdatedTime: file.ModTime(),
89 })
90 }
91 return result, nil
92}
93
94func createTablesIfDNE() {
95 _, err := DB.Exec(`CREATE TABLE IF NOT EXISTS user (
96 id INTEGER PRIMARY KEY NOT NULL,
97 username TEXT NOT NULL UNIQUE,
98 email TEXT NOT NULL UNIQUE,
99 password_hash TEXT NOT NULL,
100 active boolean NOT NULL DEFAULT false,
101 admin 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 defer rows.Close()
117 if err != nil {
118 log.Fatal(err)
119 }
120 if rows.Next() {
121 var cookie []byte
122 err := rows.Scan(&cookie)
123 if err != nil {
124 log.Fatal(err)
125 }
126 return cookie
127 } else {
128 k := make([]byte, 32)
129 _, err := io.ReadFull(rand.Reader, k)
130 if err != nil {
131 log.Fatal(err)
132 }
133 _, err = DB.Exec("insert into cookie_key values ($1)", k)
134 if err != nil {
135 log.Fatal(err)
136 }
137 return k
138 }
139}
140
141func main() {
142 configPath := flag.String("c", "flounder.toml", "path to config file") // doesnt work atm
143 if len(os.Args) < 2 {
144 fmt.Println("expected 'admin' or 'serve' subcommand")
145 os.Exit(1)
146 }
147 flag.Parse()
148
149 var err error
150 c, err = getConfig(*configPath)
151 if err != nil {
152 log.Fatal(err)
153 }
154 logFile, err := os.OpenFile(c.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
155 if err != nil {
156 panic(err)
157 }
158 mw := io.MultiWriter(os.Stdout, logFile)
159 log.SetOutput(mw)
160
161 // Generate self signed cert if does not exist. This is not suitable for production.
162 _, err1 := os.Stat(c.TLSCertFile)
163 _, err2 := os.Stat(c.TLSKeyFile)
164 if os.IsNotExist(err1) || os.IsNotExist(err2) {
165 log.Println("Keyfile or certfile does not exist.")
166 }
167
168 // Generate session cookie key if does not exist
169 DB, err = sql.Open("sqlite3", c.DBFile)
170 if err != nil {
171 log.Fatal(err)
172 }
173
174 createTablesIfDNE()
175 cookie := generateCookieKeyIfDNE()
176 SessionStore = sessions.NewCookieStore(cookie)
177
178 switch os.Args[1] {
179 case "serve":
180 wg := new(sync.WaitGroup)
181 wg.Add(2)
182 go func() {
183 runHTTPServer()
184 wg.Done()
185 }()
186 go func() {
187 runGeminiServer()
188 wg.Done()
189 }()
190 wg.Wait()
191 case "admin":
192 runAdminCommand()
193 }
194}