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
29type User struct {
30 Username string
31 Email string
32 Active bool
33 Admin bool
34 CreatedAt int // timestamp
35}
36
37func getActiveUserNames() ([]string, error) {
38 rows, err := DB.Query(`SELECT username from user WHERE active is true`)
39 if err != nil {
40 return nil, err
41 }
42 var users []string
43 for rows.Next() {
44 var user string
45 err = rows.Scan(&user)
46 if err != nil {
47 return nil, err
48 }
49 users = append(users, user)
50 }
51 return users, nil
52}
53
54func getUsers() ([]User, error) {
55 rows, err := DB.Query(`SELECT username, email, active, admin, created_at from user ORDER BY created_at DESC`)
56 if err != nil {
57 return nil, err
58 }
59 var users []User
60 for rows.Next() {
61 var user User
62 err = rows.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt)
63 if err != nil {
64 return nil, err
65 }
66 users = append(users, user)
67 }
68 return users, nil
69}
70
71func getIndexFiles() ([]*File, error) { // cache this function
72 result := []*File{}
73 err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
74 if err != nil {
75 log.Printf("Failure accessing a path %q: %v\n", thepath, err)
76 return err // think about
77 }
78 // make this do what it should
79 if !info.IsDir() {
80 creatorFolder, _ := path.Split(thepath)
81 updatedTime := info.ModTime()
82 result = append(result, &File{
83 Name: info.Name(),
84 Creator: path.Base(creatorFolder),
85 UpdatedTime: updatedTime,
86 TimeAgo: timeago(&updatedTime),
87 })
88 }
89 return nil
90 })
91 if err != nil {
92 return nil, err
93 }
94 sort.Slice(result, func(i, j int) bool {
95 return result[i].UpdatedTime.After(result[j].UpdatedTime)
96 })
97 if len(result) > 50 {
98 result = result[:50]
99 }
100 return result, nil
101} // todo clean up paths
102
103func getUserFiles(user string) ([]*File, error) {
104 result := []*File{}
105 files, err := ioutil.ReadDir(path.Join(c.FilesDirectory, user))
106 if err != nil {
107 return nil, err
108 }
109 for _, file := range files {
110 result = append(result, &File{
111 Name: file.Name(),
112 Creator: user,
113 UpdatedTime: file.ModTime(),
114 })
115 }
116 return result, nil
117}
118
119func createTablesIfDNE() {
120 _, err := DB.Exec(`CREATE TABLE IF NOT EXISTS user (
121 id INTEGER PRIMARY KEY NOT NULL,
122 username TEXT NOT NULL UNIQUE,
123 email TEXT NOT NULL UNIQUE,
124 password_hash TEXT NOT NULL,
125 active boolean NOT NULL DEFAULT false,
126 admin boolean NOT NULL DEFAULT false,
127 created_at INTEGER DEFAULT (strftime('%s', 'now'))
128);
129
130CREATE TABLE IF NOT EXISTS cookie_key (
131 value TEXT NOT NULL
132);`)
133 if err != nil {
134 log.Fatal(err)
135 }
136}
137
138// Generate a cryptographically secure key for the cookie store
139func generateCookieKeyIfDNE() []byte {
140 rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
141 defer rows.Close()
142 if err != nil {
143 log.Fatal(err)
144 }
145 if rows.Next() {
146 var cookie []byte
147 err := rows.Scan(&cookie)
148 if err != nil {
149 log.Fatal(err)
150 }
151 return cookie
152 } else {
153 k := make([]byte, 32)
154 _, err := io.ReadFull(rand.Reader, k)
155 if err != nil {
156 log.Fatal(err)
157 }
158 _, err = DB.Exec("insert into cookie_key values ($1)", k)
159 if err != nil {
160 log.Fatal(err)
161 }
162 return k
163 }
164}
165
166func main() {
167 configPath := flag.String("c", "flounder.toml", "path to config file") // doesnt work atm
168 if len(os.Args) < 2 {
169 fmt.Println("expected 'admin' or 'serve' subcommand")
170 os.Exit(1)
171 }
172 flag.Parse()
173
174 var err error
175 c, err = getConfig(*configPath)
176 if err != nil {
177 log.Fatal(err)
178 }
179 logFile, err := os.OpenFile(c.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
180 if err != nil {
181 panic(err)
182 }
183 mw := io.MultiWriter(os.Stdout, logFile)
184 log.SetOutput(mw)
185
186 // Generate self signed cert if does not exist. This is not suitable for production.
187 _, err1 := os.Stat(c.TLSCertFile)
188 _, err2 := os.Stat(c.TLSKeyFile)
189 if os.IsNotExist(err1) || os.IsNotExist(err2) {
190 log.Println("Keyfile or certfile does not exist.")
191 }
192
193 // Generate session cookie key if does not exist
194 DB, err = sql.Open("sqlite3", c.DBFile)
195 if err != nil {
196 log.Fatal(err)
197 }
198
199 createTablesIfDNE()
200 cookie := generateCookieKeyIfDNE()
201 SessionStore = sessions.NewCookieStore(cookie)
202
203 switch os.Args[1] {
204 case "serve":
205 wg := new(sync.WaitGroup)
206 wg.Add(2)
207 go func() {
208 runHTTPServer()
209 wg.Done()
210 }()
211 go func() {
212 runGeminiServer()
213 wg.Done()
214 }()
215 wg.Wait()
216 case "admin":
217 runAdminCommand()
218 }
219}