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 // includes folder
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
83// get the user-reltaive local path from the filespath
84// NOTE -- dont use on unsafe input ( I think )
85func getLocalPath(filesPath string) string {
86 l := len(strings.Split(c.FilesDirectory, "/"))
87 return strings.Join(strings.Split(filesPath, "/")[l:], "/")
88}
89
90func getIndexFiles() ([]*File, error) { // cache this function
91 result := []*File{}
92 err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
93 if err != nil {
94 log.Printf("Failure accessing a path %q: %v\n", thepath, err)
95 return err // think about
96 }
97 // make this do what it should
98 if !info.IsDir() {
99 creatorFolder := strings.Split(thepath, "/")[1]
100 updatedTime := info.ModTime()
101 result = append(result, &File{
102 Name: getLocalPath(thepath),
103 Creator: path.Base(creatorFolder),
104 UpdatedTime: updatedTime,
105 TimeAgo: timeago(&updatedTime),
106 })
107 }
108 return nil
109 })
110 if err != nil {
111 return nil, err
112 }
113 sort.Slice(result, func(i, j int) bool {
114 return result[i].UpdatedTime.After(result[j].UpdatedTime)
115 })
116 if len(result) > 50 {
117 result = result[:50]
118 }
119 return result, nil
120} // todo clean up paths
121
122func getFiles(p string) ([]*File, error) {
123 result := []*File{}
124 files, err := ioutil.ReadDir(p)
125 if err != nil {
126 return nil, err
127 }
128 for _, file := range files {
129 isText := strings.HasPrefix(mime.TypeByExtension(path.Ext(file.Name())), "text")
130 fullPath := path.Join(p, file.Name())
131 localPath := getLocalPath(fullPath)
132 f := &File{
133 Name: localPath,
134 // Creator: strings.Split(p, "/")[0],
135 UpdatedTime: file.ModTime(),
136 IsText: isText,
137 }
138 if file.IsDir() {
139 f.Children, err = getFiles(path.Join(p, f.Name))
140 }
141 result = append(result, f)
142 }
143 return result, nil
144}
145
146func createTablesIfDNE() {
147 _, err := DB.Exec(`CREATE TABLE IF NOT EXISTS user (
148 id INTEGER PRIMARY KEY NOT NULL,
149 username TEXT NOT NULL UNIQUE,
150 email TEXT NOT NULL UNIQUE,
151 password_hash TEXT NOT NULL,
152 active boolean NOT NULL DEFAULT false,
153 admin boolean NOT NULL DEFAULT false,
154 created_at INTEGER DEFAULT (strftime('%s', 'now'))
155);
156
157CREATE TABLE IF NOT EXISTS cookie_key (
158 value TEXT NOT NULL
159);`)
160 if err != nil {
161 log.Fatal(err)
162 }
163}
164
165// Generate a cryptographically secure key for the cookie store
166func generateCookieKeyIfDNE() []byte {
167 rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
168 defer rows.Close()
169 if err != nil {
170 log.Fatal(err)
171 }
172 if rows.Next() {
173 var cookie []byte
174 err := rows.Scan(&cookie)
175 if err != nil {
176 log.Fatal(err)
177 }
178 return cookie
179 } else {
180 k := make([]byte, 32)
181 _, err := io.ReadFull(rand.Reader, k)
182 if err != nil {
183 log.Fatal(err)
184 }
185 _, err = DB.Exec("insert into cookie_key values ($1)", k)
186 if err != nil {
187 log.Fatal(err)
188 }
189 return k
190 }
191}
192
193func main() {
194 configPath := flag.String("c", "flounder.toml", "path to config file") // doesnt work atm
195 flag.Parse()
196 args := flag.Args()
197 if len(args) < 1 {
198 fmt.Println("expected 'admin' or 'serve' subcommand")
199 os.Exit(1)
200 }
201
202 var err error
203 c, err = getConfig(*configPath)
204 if err != nil {
205 log.Fatal(err)
206 }
207 logFile, err := os.OpenFile(c.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
208 if err != nil {
209 panic(err)
210 }
211 mw := io.MultiWriter(os.Stdout, logFile)
212 log.SetOutput(mw)
213
214 if c.HttpsEnabled {
215 _, err1 := os.Stat(c.TLSCertFile)
216 _, err2 := os.Stat(c.TLSKeyFile)
217 if os.IsNotExist(err1) || os.IsNotExist(err2) {
218 log.Fatal("Keyfile or certfile does not exist.")
219 }
220 }
221
222 // Generate session cookie key if does not exist
223 DB, err = sql.Open("sqlite3", c.DBFile)
224 if err != nil {
225 log.Fatal(err)
226 }
227
228 createTablesIfDNE()
229 cookie := generateCookieKeyIfDNE()
230 SessionStore = sessions.NewCookieStore(cookie)
231
232 switch args[0] {
233 case "serve":
234 wg := new(sync.WaitGroup)
235 wg.Add(2)
236 go func() {
237 runHTTPServer()
238 wg.Done()
239 }()
240 go func() {
241 runGeminiServer()
242 wg.Done()
243 }()
244 wg.Wait()
245 case "admin":
246 runAdminCommand()
247 }
248}