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