1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5package main
6
7import (
8 "errors"
9 "fmt"
10 "log"
11 "net/http"
12 "os"
13 "sync"
14
15 "git.sr.ht/~amolith/willow/db"
16 "git.sr.ht/~amolith/willow/project"
17 "git.sr.ht/~amolith/willow/ws"
18
19 "github.com/BurntSushi/toml"
20 flag "github.com/spf13/pflag"
21)
22
23type (
24 Config struct {
25 Server server
26 CSVLocation string
27 DBConn string
28 // TODO: Make cache location configurable
29 // CacheLocation string
30 FetchInterval int
31 }
32
33 server struct {
34 Listen string
35 }
36)
37
38var (
39 flagConfig = flag.StringP("config", "c", "config.toml", "Path to config file")
40 flagAddUser = flag.StringP("add", "a", "", "Username of account to add")
41 flagDeleteUser = flag.StringP("deleteuser", "d", "", "Username of account to delete")
42 flagCheckAuthorised = flag.StringP("validatecredentials", "v", "", "Username of account to check")
43 flagListUsers = flag.BoolP("listusers", "l", false, "List all users")
44 config Config
45 req = make(chan struct{})
46 res = make(chan []project.Project)
47 manualRefresh = make(chan struct{})
48)
49
50//goland:noinspection GoUnusedFunction
51func main() {
52 flag.Parse()
53
54 err := checkConfig()
55 if err != nil {
56 log.Fatalln(err)
57 }
58
59 fmt.Println("Opening database at", config.DBConn)
60
61 dbConn, err := db.Open(config.DBConn)
62 if err != nil {
63 fmt.Println("Error opening database:", err)
64 os.Exit(1)
65 }
66
67 fmt.Println("Verifying database schema")
68 err = db.VerifySchema(dbConn)
69 if err != nil {
70 fmt.Println("Error verifying database schema:", err)
71 fmt.Println("Attempting to load schema")
72 err = db.LoadSchema(dbConn)
73 if err != nil {
74 fmt.Println("Error loading schema:", err)
75 os.Exit(1)
76 }
77 }
78 fmt.Println("Database schema verified")
79
80 if len(*flagAddUser) > 0 && len(*flagDeleteUser) == 0 && !*flagListUsers && len(*flagCheckAuthorised) == 0 {
81 createUser(dbConn, *flagAddUser)
82 os.Exit(0)
83 } else if len(*flagAddUser) == 0 && len(*flagDeleteUser) > 0 && !*flagListUsers && len(*flagCheckAuthorised) == 0 {
84 deleteUser(dbConn, *flagDeleteUser)
85 os.Exit(0)
86 } else if len(*flagAddUser) == 0 && len(*flagDeleteUser) == 0 && *flagListUsers && len(*flagCheckAuthorised) == 0 {
87 listUsers(dbConn)
88 os.Exit(0)
89 } else if len(*flagAddUser) == 0 && len(*flagDeleteUser) == 0 && !*flagListUsers && len(*flagCheckAuthorised) > 0 {
90 checkAuthorised(dbConn, *flagCheckAuthorised)
91 os.Exit(0)
92 }
93
94 fmt.Println("Starting refresh loop")
95 go project.RefreshLoop(dbConn, config.FetchInterval, &manualRefresh, &req, &res)
96
97 var mutex sync.Mutex
98
99 wsHandler := ws.Handler{
100 DbConn: dbConn,
101 Mutex: &mutex,
102 Req: &req,
103 Res: &res,
104 ManualRefresh: &manualRefresh,
105 }
106
107 mux := http.NewServeMux()
108 mux.HandleFunc("/", wsHandler.RootHandler)
109 mux.HandleFunc("/static", ws.StaticHandler)
110 mux.HandleFunc("/new", wsHandler.NewHandler)
111 mux.HandleFunc("/login", wsHandler.LoginHandler)
112 mux.HandleFunc("/logout", wsHandler.LogoutHandler)
113
114 httpServer := &http.Server{
115 Addr: config.Server.Listen,
116 Handler: mux,
117 }
118
119 fmt.Println("Starting web server on", config.Server.Listen)
120 if err := httpServer.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
121 fmt.Println("Web server closed")
122 os.Exit(0)
123 } else {
124 fmt.Println(err)
125 os.Exit(1)
126 }
127}
128
129func checkConfig() error {
130 file, err := os.Open(*flagConfig)
131 if err != nil {
132 if os.IsNotExist(err) {
133 file, err = os.Create(*flagConfig)
134 if err != nil {
135 return err
136 }
137 defer file.Close()
138
139 _, err = file.WriteString(`# Path to SQLite database
140DBConn = "willow.sqlite"
141# How often to fetch new releases in seconds
142FetchInterval = 3600
143
144[Server]
145# Address to listen on
146Listen = "127.0.0.1:1313"
147 `)
148 if err != nil {
149 return err
150 }
151
152 fmt.Println("Config file created at", *flagConfig)
153 fmt.Println("Please edit it and restart the server")
154 os.Exit(0)
155 } else {
156 return err
157 }
158 }
159 defer file.Close()
160
161 _, err = toml.DecodeFile(*flagConfig, &config)
162 if err != nil {
163 return err
164 }
165
166 if config.FetchInterval < 10 {
167 fmt.Println("Fetch interval is set to", config.FetchInterval, "seconds, but the minimum is 10, using 10")
168 config.FetchInterval = 10
169 }
170
171 if config.Server.Listen == "" {
172 fmt.Println("No listen address specified, using 127.0.0.1:1313")
173 config.Server.Listen = "127.0.0.1:1313"
174 }
175
176 if config.DBConn == "" {
177 fmt.Println("No SQLite path specified, using \"willow.sqlite\"")
178 config.DBConn = "willow.sqlite"
179 }
180
181 return nil
182}