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