1package main
2
3import (
4 "context"
5 "encoding/hex"
6 "errors"
7 "os"
8 "os/signal"
9
10 "github.com/fasthttp/router"
11 "github.com/sirupsen/logrus"
12 flag "github.com/spf13/pflag"
13 "github.com/valyala/fasthttp"
14 "gorm.io/driver/mysql"
15 "gorm.io/driver/postgres"
16 "gorm.io/driver/sqlite"
17 "gorm.io/gorm"
18
19 "git.sr.ht/~gabrielgio/img/pkg/components/auth"
20 "git.sr.ht/~gabrielgio/img/pkg/components/filesystem"
21 "git.sr.ht/~gabrielgio/img/pkg/components/media"
22 "git.sr.ht/~gabrielgio/img/pkg/database/localfs"
23 "git.sr.ht/~gabrielgio/img/pkg/database/sql"
24 "git.sr.ht/~gabrielgio/img/pkg/ext"
25 "git.sr.ht/~gabrielgio/img/pkg/view"
26 "git.sr.ht/~gabrielgio/img/pkg/worker"
27)
28
29func main() {
30 var (
31 key = flag.String("aes-key", "", "AES key, either 16, 24, or 32 bytes string to select AES-128, AES-192, or AES-256")
32 dbType = flag.String("db-type", "sqlite", "Database to be used. Choose either mysql, psql or sqlite")
33 dbCon = flag.String("db-con", "main.db", "Database string connection for given database type. Ref: https://gorm.io/docs/connecting_to_the_database.html")
34 logLevel = flag.String("log-level", "error", "Log level: Choose either trace, debug, info, warning, error, fatal or panic")
35
36 // TODO: this will later be replaced by user specific root folder
37 root = flag.String("root", "", "root folder for the whole application. All the workers will use it as working directory")
38 )
39
40 flag.Parse()
41
42 l, err := logrus.ParseLevel(*logLevel)
43 if err != nil {
44 panic("failed to parse log level" + err.Error())
45 }
46 logger := logrus.New()
47 logger.SetLevel(l)
48
49 d, err := OpenDatabase(*dbType, *dbCon)
50 if err != nil {
51 panic("failed to parse database strings" + err.Error())
52 }
53
54 db, err := gorm.Open(d, &gorm.Config{
55 Logger: ext.Wraplog(logger.WithField("context", "sql")),
56 })
57 if err != nil {
58 panic("failed to connect database: " + err.Error())
59 }
60
61 if err = sql.Migrate(db); err != nil {
62 panic("failed to migrate database: " + err.Error())
63 }
64
65 hexKey, err := hex.DecodeString(*key)
66 if err != nil {
67 panic("failed to decode key database: " + err.Error())
68 }
69
70 r := router.New()
71 r.ServeFiles("/static/{filepath:*}", "./static")
72 r.NotFound = ext.NotFoundHTML
73
74 authMiddleware := ext.NewAuthMiddleware(hexKey, logger.WithField("context", "auth"))
75 logMiddleware := ext.NewLogMiddleare(logger.WithField("context", "http"))
76
77 extRouter := ext.NewRouter(r)
78 extRouter.AddMiddleware(logMiddleware.HTTP)
79 extRouter.AddMiddleware(authMiddleware.LoggedIn)
80 extRouter.AddMiddleware(ext.HTML)
81
82 scheduler := worker.NewScheduler(10)
83
84 // repository
85 var (
86 userRepository = sql.NewUserRepository(db)
87 settingsRepository = sql.NewSettingsRespository(db)
88 fileSystemRepository = localfs.NewFileSystemRepository(*root)
89 mediaRepository = sql.NewMediaRepository(db)
90 )
91
92 //TODO: remove later
93 userRepository.EnsureAdmin(context.Background())
94
95 // controller
96 var (
97 userController = auth.NewController(userRepository, hexKey)
98 fileSystemController = filesystem.NewController(fileSystemRepository)
99 )
100
101 // view
102 for _, v := range []view.View{
103 view.NewAuthView(userController),
104 view.NewFileSystemView(*fileSystemController, settingsRepository),
105 view.NewSettingsView(settingsRepository),
106 view.NewMediaView(mediaRepository),
107 } {
108 v.SetMyselfIn(extRouter)
109 }
110
111 // worker
112 var (
113 serverWorker = worker.NewServerWorker(&fasthttp.Server{Handler: r.Handler})
114 fileScanner = worker.NewFileScanner(*root, mediaRepository)
115 exifScanner = worker.NewEXIFScanner(mediaRepository)
116 )
117
118 pool := worker.NewWorkerPool()
119 pool.AddWorker("http server", serverWorker)
120 pool.AddWorker("exif scanner", worker.NewWorkerFromListProcessor[*media.Media](exifScanner, scheduler))
121 pool.AddWorker("file scanner", worker.NewWorkerFromChanProcessor[string](fileScanner, scheduler))
122
123 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
124 defer stop()
125
126 pool.Start(ctx)
127 pool.Wait()
128}
129
130func OpenDatabase(dbType string, dbConn string) (gorm.Dialector, error) {
131 switch dbType {
132 case "sqlite":
133 return sqlite.Open(dbConn), nil
134 case "psql":
135 return postgres.Open(dbConn), nil
136 case "mysql":
137 return mysql.Open(dbConn), nil
138 default:
139 return nil, errors.New("No valid db type given")
140 }
141}