diff --git a/app/config.go b/app/config.go index 3cbff0c..85c1007 100644 --- a/app/config.go +++ b/app/config.go @@ -37,32 +37,40 @@ func NewConfigProvider(files []string, devfiles []string) *ConfigProvider { func (c *ConfigProvider) Read() error { c.Config = &Config{} + c.Config = readDefaults(c.Config) + + for _, file := range c.DevFiles { + conf, err := readSettingsFile(file) + if err == nil { + c.Config = conf + } + } for _, file := range c.Files { conf, err := readSettingsFile(file) if err == nil { c.Config = conf - } else { - panic(err) } } - for _, file := range c.DevFiles { - conf, err := readSettingsFile(file) - if c.Debug { - if err == nil { - c.Config = conf - } else { - panic(err) - } - } - } c.Config = readSettingsEnv(c.Config) - c.Config = readDefaults(c.Config) + c.Validate() return nil } func (c *ConfigProvider) Validate() error { + if c.AllowTestLogin { + slog.Info("Test login is enabled") + } else { + slog.Info("Test login is disabled") + } + + if c.Debug { + slog.Info("Debug mode is enabled") + } else { + slog.Info("Debug mode is disabled") + } + return nil } diff --git a/app/pb.go b/app/pb.go index e173aae..45bd0a9 100644 --- a/app/pb.go +++ b/app/pb.go @@ -7,7 +7,6 @@ import ( "github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels" "github.com/Theodor-Springmann-Stiftung/musenalm/templating" "github.com/Theodor-Springmann-Stiftung/musenalm/views" - "github.com/fsnotify/fsnotify" "github.com/mattn/go-sqlite3" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" @@ -20,6 +19,9 @@ const ( ROUTES_DIR = "./views/routes" ) +type ServeFunc = func(e *core.ServeEvent) error +type BootFunc = func(e *core.BootstrapEvent) error + // INFO: this is the main application that mainly is a pocketbase wrapper type App struct { PB *pocketbase.PocketBase @@ -60,7 +62,7 @@ func New(config Config) App { } app.createPBInstance() - app.setupTestuser() + app.Bootstrap() return app } @@ -74,8 +76,12 @@ func (app *App) createPBInstance() { }) } -func (app *App) setupTestuser() { - app.PB.OnServe().BindFunc(func(e *core.ServeEvent) error { +func (app *App) setupTestuser() BootFunc { + return func(e *core.BootstrapEvent) error { + if err := e.Next(); err != nil { + return err + } + superusersCol, err := e.App.FindCachedCollectionByNameOrId(core.CollectionNameSuperusers) if err != nil { return fmt.Errorf("Failed to fetch %q collection: %w.", core.CollectionNameSuperusers, err) @@ -93,7 +99,7 @@ func (app *App) setupTestuser() { return fmt.Errorf("Failed to delete superuser account: %w.", err) } - return e.Next() + return nil } superuser.SetEmail(TEST_SUPERUSER_MAIL) @@ -104,10 +110,30 @@ func (app *App) setupTestuser() { } return e.Next() - }) + } +} + +func (app *App) Bootstrap() error { + app.PB.OnBootstrap().BindFunc(app.setupTestuser()) + return nil } func (app *App) Serve() error { + engine, err := app.createEngine() + if err != nil { + panic(err) + } + + if app.MAConfig.Debug { + app.setWatchers(engine) + } + + // INFO: we use OnServe, but here is also OnBootstrap + app.PB.OnServe().BindFunc(app.bindPages(engine)) + return app.PB.Start() +} + +func (app *App) createEngine() (*templating.Engine, error) { engine := templating.NewEngine(&views.LayoutFS, &views.RoutesFS) engine.Globals(map[string]interface{}{ "isDev": app.MAConfig.Debug, @@ -118,34 +144,29 @@ func (app *App) Serve() error { "desc": "Bibliographie deutscher Almanache des 18. und 19. Jahrhunderts", }}) + return engine, nil +} + +func (app *App) setWatchers(engine *templating.Engine) { // INFO: hot reloading for poor people - if app.MAConfig.Debug { - watcher, err := EngineWatcher(engine) + watcher, err := EngineWatcher(engine) + if err != nil { + app.PB.Logger().Error("Failed to create watcher, continuing without", "error", err) + } else { + watcher.AddRecursive(LAYOUT_DIR) + watcher.AddRecursive(ROUTES_DIR) + engine.Debug() + rwatcher, err := RefreshWatcher(engine) if err != nil { app.PB.Logger().Error("Failed to create watcher, continuing without", "error", err) } else { - watcher.AddRecursive(LAYOUT_DIR) - watcher.AddRecursive(ROUTES_DIR) - engine.Debug() - rwatcher, err := RefreshWatcher(engine) - if err != nil { - app.PB.Logger().Error("Failed to create watcher, continuing without", "error", err) - } else { - rwatcher.Add("./views/assets") - } + rwatcher.Add("./views/assets") } - } +} - app.PB.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error { - if err := e.Next(); err != nil { - return err - } - - return nil - }) - - app.PB.OnServe().BindFunc(func(e *core.ServeEvent) error { +func (app *App) bindPages(engine *templating.Engine) ServeFunc { + return func(e *core.ServeEvent) error { e.Router.GET("/assets/{path...}", apis.Static(views.StaticFS, true)) // INFO: we put this here, to make sure all migrations are done for _, page := range pages { @@ -163,23 +184,5 @@ func (app *App) Serve() error { } return e.Next() - }) - return app.PB.Start() -} - -func (app *App) watchFN(watcher *fsnotify.Watcher, engine *templating.Engine) { - for { - select { - case _, ok := <-watcher.Events: - if !ok { - return - } - engine.Reload() - case err, ok := <-watcher.Errors: - if !ok { - return - } - fmt.Println("error:", err) - } } } diff --git a/dbmodels/agent.go b/dbmodels/agent.go index 0552968..fbc844b 100644 --- a/dbmodels/agent.go +++ b/dbmodels/agent.go @@ -142,3 +142,15 @@ func (a *Agent) SetEditState(editState string) { func (a *Agent) Comment() string { return a.GetString(COMMENT_FIELD) } + +func (a *Agent) SetComment(comments string) { + a.Set(COMMENT_FIELD, comments) +} + +func (a *Agent) Editor() string { + return a.GetString(EDITOR_FIELD) +} + +func (a *Agent) SetEditor(editor string) { + a.Set(EDITOR_FIELD, editor) +} diff --git a/dbmodels/content.go b/dbmodels/content.go index 077616e..94975a3 100644 --- a/dbmodels/content.go +++ b/dbmodels/content.go @@ -212,3 +212,11 @@ func (c *Content) Comment() string { func (c *Content) SetComment(comment string) { c.Set(COMMENT_FIELD, comment) } + +func (c *Content) Editor() string { + return c.GetString(EDITOR_FIELD) +} + +func (c *Content) SetEditor(editor string) { + c.Set(EDITOR_FIELD, editor) +} diff --git a/dbmodels/dbdata.go b/dbmodels/dbdata.go index 1d76f6a..96d2945 100644 --- a/dbmodels/dbdata.go +++ b/dbmodels/dbdata.go @@ -387,6 +387,12 @@ var MUSENALM_MIME_TYPES = []string{ "image/svg+xml", } +var USER_ROLES = []string{ + "Admin", + "Editor", + "User", +} + var AGENT_RELATIONS = []string{ "Schöpfer", "Autor:in", @@ -507,4 +513,7 @@ const ( USERS_TABLE = "users" USERS_SETTINGS_FIELD = "settings" + USERS_NAME_FIELD = "name" + USERS_ROLE_FIELD = "role" + USERS_AVATAR_FIELD = "avatar" ) diff --git a/dbmodels/entry.go b/dbmodels/entry.go index e6782b2..302d920 100644 --- a/dbmodels/entry.go +++ b/dbmodels/entry.go @@ -235,3 +235,11 @@ func (e *Entry) Comment() string { func (e *Entry) SetComment(comment string) { e.Set(COMMENT_FIELD, comment) } + +func (e *Entry) Editor() string { + return e.GetString(EDITOR_FIELD) +} + +func (e *Entry) SetEditor(editor string) { + e.Set(EDITOR_FIELD, editor) +} diff --git a/dbmodels/item.go b/dbmodels/item.go index 47118b4..6940f04 100644 --- a/dbmodels/item.go +++ b/dbmodels/item.go @@ -121,3 +121,11 @@ func (a *Item) Comment() string { func (a *Item) SetComment(comments string) { a.Set(COMMENT_FIELD, comments) } + +func (a *Item) Editor() string { + return a.GetString(EDITOR_FIELD) +} + +func (a *Item) SetEditor(editor string) { + a.Set(EDITOR_FIELD, editor) +} diff --git a/dbmodels/place.go b/dbmodels/place.go index 9f0f9bb..656d6d3 100644 --- a/dbmodels/place.go +++ b/dbmodels/place.go @@ -81,3 +81,11 @@ func (p *Place) Comment() string { func (p *Place) SetComment(comment string) { p.Set(COMMENT_FIELD, comment) } + +func (p *Place) Editor() string { + return p.GetString(EDITOR_FIELD) +} + +func (p *Place) SetEditor(editor string) { + p.Set(EDITOR_FIELD, editor) +} diff --git a/dbmodels/series.go b/dbmodels/series.go index de1c003..41d3105 100644 --- a/dbmodels/series.go +++ b/dbmodels/series.go @@ -87,3 +87,11 @@ func (s *Series) Frequency() string { func (s *Series) SetFrequency(frequency string) { s.Set(SERIES_FREQUENCY_FIELD, frequency) } + +func (s *Series) Editor() string { + return s.GetString(EDITOR_FIELD) +} + +func (s *Series) SetEditor(editor string) { + s.Set(EDITOR_FIELD, editor) +} diff --git a/dbmodels/sessions.go b/dbmodels/sessions.go new file mode 100644 index 0000000..2e962ab --- /dev/null +++ b/dbmodels/sessions.go @@ -0,0 +1,86 @@ +package dbmodels + +import ( + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tools/types" +) + +var _ core.RecordProxy = (*Place)(nil) + +type Session struct { + core.BaseRecordProxy +} + +func NewSession(record *core.Record) *Session { + i := &Session{} + i.SetProxyRecord(record) + return i +} + +func (u *Session) TableName() string { + return USERS_TABLE +} + +func (u *Session) Token() string { + return u.GetString(SESSIONS_TOKEN_FIELD) +} + +func (u *Session) SetToken(token string) { + u.Set(SESSIONS_TOKEN_FIELD, token) +} + +func (u *Session) User() string { + return u.GetString(SESSIONS_USER_FIELD) +} + +func (u *Session) SetUser(userId string) { + u.Set(SESSIONS_USER_FIELD, userId) +} + +func (u *Session) Created() string { + return u.GetString(CREATED_FIELD) +} + +func (u *Session) Updated() string { + return u.GetString(UPDATED_FIELD) +} + +func (u *Session) Expires() types.DateTime { + return u.GetDateTime(SESSIONS_EXPIRES_FIELD) +} + +func (u *Session) SetExpires(expires types.DateTime) { + u.Set(SESSIONS_EXPIRES_FIELD, expires) +} + +func (u *Session) IP() string { + return u.GetString(SESSIONS_IP_FIELD) +} + +func (u *Session) SetIP(ip string) { + u.Set(SESSIONS_IP_FIELD, ip) +} + +func (u *Session) UserAgent() string { + return u.GetString(SESSIONS_USER_AGENT_FIELD) +} + +func (u *Session) SetUserAgent(userAgent string) { + u.Set(SESSIONS_USER_AGENT_FIELD, userAgent) +} + +func (u *Session) LastAccess() types.DateTime { + return u.GetDateTime(SESSIONS_LAST_ACCESS_FIELD) +} + +func (u *Session) SetLastAccess(lastAccess types.DateTime) { + u.Set(SESSIONS_LAST_ACCESS_FIELD, lastAccess) +} + +func (u *Session) Persist() bool { + return u.GetBool(SESSIONS_PERSIST_FIELD) +} + +func (u *Session) SetPersist(persist bool) { + u.Set(SESSIONS_PERSIST_FIELD, persist) +} diff --git a/dbmodels/user.go b/dbmodels/user.go new file mode 100644 index 0000000..80a1d75 --- /dev/null +++ b/dbmodels/user.go @@ -0,0 +1,61 @@ +package dbmodels + +import ( + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tools/filesystem" + "github.com/pocketbase/pocketbase/tools/types" +) + +var _ core.RecordProxy = (*Place)(nil) + +type User struct { + core.BaseRecordProxy +} + +func NewUser(record *core.Record) *User { + i := &User{} + i.SetProxyRecord(record) + return i +} + +func (u *User) TableName() string { + return USERS_TABLE +} + +// INFO: Email is already set on the core.Record +// TODO: We need to create a settings struct as soon as we have settings +func (u *User) Name() string { + return u.GetString(USERS_NAME_FIELD) +} + +func (u *User) SetName(name string) { + u.Set(USERS_NAME_FIELD, name) +} + +func (u *User) Created() types.DateTime { + return u.GetDateTime(CREATED_FIELD) +} + +func (u *User) Updated() types.DateTime { + return u.GetDateTime(UPDATED_FIELD) +} + +func (u *User) Role() string { + return u.GetString(USERS_ROLE_FIELD) +} + +func (u *User) SetRole(role string) { + u.Set(USERS_ROLE_FIELD, role) +} + +func (u *User) Avatar() string { + av := u.GetString(USERS_AVATAR_FIELD) + if av != "" { + return "/api/files/" + u.TableName() + "/" + u.Id + "/" + av + } + return av +} + +func (u *User) SetAvatar(avatar *filesystem.File) { + u.Set(USERS_AVATAR_FIELD, avatar) +} diff --git a/migrations/1747860355_users.go b/migrations/1747860355_users.go index 467f39c..66e0184 100644 --- a/migrations/1747860355_users.go +++ b/migrations/1747860355_users.go @@ -22,7 +22,16 @@ func init() { Presentable: false, } + roleField := &core.SelectField{ + Name: dbmodels.USERS_ROLE_FIELD, + Required: true, + Presentable: true, + MaxSelect: 1, + Values: []string{"admin", "editor", "viewer"}, + } + collection.Fields.Add(settingsField) + collection.Fields.Add(roleField) app.Logger().Info("Adding 'settings' JSON field to 'users' collection", "collectionId", collection.Id) @@ -40,6 +49,7 @@ func init() { } collection.Fields.RemoveByName(dbmodels.USERS_SETTINGS_FIELD) + collection.Fields.RemoveByName(dbmodels.USERS_ROLE_FIELD) if err := app.Save(collection); err != nil { app.Logger().Warn("Failed to remove 'settings' field during rollback (it might not exist)", "collectionId", collection.Id,