package bm import ( "context" "embed" "io/fs" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/bonfire" "git.golem15.com/golem15/summercms/modules/lagoon" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" "git.golem15.com/jakub/sm-bm-plugin/console" "git.golem15.com/jakub/sm-bm-plugin/controllers" "github.com/go-gormigrate/gormigrate/v2" "gorm.io/gorm" ) var ( _ pact.HasConfig = (*Plugin)(nil) _ pact.HasLang = (*Plugin)(nil) _ pact.HasMailTemplates = (*Plugin)(nil) _ pact.HasModels = (*Plugin)(nil) _ pact.HasMigrations = (*Plugin)(nil) _ pact.HasCommands = (*Plugin)(nil) _ pact.HasJobs = (*Plugin)(nil) _ pact.HasAdminControllers = (*Plugin)(nil) _ pact.AdminAssets = (*Plugin)(nil) _ pact.HasPermissions = (*Plugin)(nil) _ pact.HasNavigation = (*Plugin)(nil) ) //go:embed config var configFS embed.FS //go:embed lang var langFS embed.FS //go:embed views/mail var mailFS embed.FS //go:embed controllers/*/*.yaml models/*/*.yaml var adminFS embed.FS // Plugin is the jz.bm plugin: the study dashboard backend of the BM // (Bezpieczeństwo międzynarodowe) master's programme. type Plugin struct { app *backpack.App // publicFS is the tree of the student app. Nil means the embedded // public/ directory; tests plant their own. publicFS fs.FS } func (p *Plugin) ID() string { return "jz.bm" } // Requires golem15.user: its users table holds the students, and its // jwt.auth and user.api_token middleware guard the APIs. func (p *Plugin) Requires() []string { return []string{"golem15.user"} } func (p *Plugin) Register(app *backpack.App) error { p.app = app return nil } // Boot keeps the application, so route handlers and commands can reach its // services, such as the database, when they run. The management API uses the // shared user.api_token middleware owned by golem15.user. func (p *Plugin) Boot(app *backpack.App) error { p.app = app return nil } func (p *Plugin) ConfigFS() fs.FS { return configFS } func (p *Plugin) LangFS() fs.FS { return langFS } func (p *Plugin) MailTemplatesFS() fs.FS { return mailFS } func (p *Plugin) MailTemplates() []string { return nil } func (p *Plugin) MailLayouts() map[string]string { return nil } func (p *Plugin) Models() []any { return generatedModels() } func (p *Plugin) Migrations() []*gormigrate.Migration { return generatedMigrations() } func (p *Plugin) Jobs() []pact.Job { return generatedJobs() } func (p *Plugin) AdminControllers() []pact.AdminController { return generatedAdminControllers() } // Commands returns the generated commands plus bm:seed. API tokens are // managed application-wide by golem15.user. func (p *Plugin) Commands() []bonfire.Command { return append(generatedCommands(), console.SeedCommand(p.withDB)) } // AdminFS is the admin YAML the controllers read. func (p *Plugin) AdminFS() fs.FS { return adminFS } // Permissions replaces registerPermissions(). func (p *Plugin) Permissions() []pact.Permission { tab := "jz.bm::lang.plugin.name" return []pact.Permission{ {Code: controllers.PermCourses, Tab: tab, Label: "jz.bm::lang.permissions.access_courses"}, {Code: controllers.PermSchedule, Tab: tab, Label: "jz.bm::lang.permissions.access_schedule"}, {Code: controllers.PermMaterials, Tab: tab, Label: "jz.bm::lang.permissions.access_materials"}, } } // Navigation replaces registerNavigation(): one "Studia BM" group with a // side item per screen. func (p *Plugin) Navigation() []pact.NavigationItem { return []pact.NavigationItem{{ Code: "bm", Label: "jz.bm::lang.plugin.name", Icon: "icon-graduation-cap", Permissions: []string{controllers.PermCourses, controllers.PermSchedule, controllers.PermMaterials}, Order: 300, Controller: "jz.bm.courses", SideMenu: []pact.NavigationItem{ {Code: "courses", Label: "jz.bm::lang.navigation.courses", Icon: "icon-book", Permissions: []string{controllers.PermCourses}, Controller: "jz.bm.courses"}, {Code: "readings", Label: "jz.bm::lang.navigation.readings", Icon: "icon-bookmark", Permissions: []string{controllers.PermCourses}, Controller: "jz.bm.readings"}, {Code: "meetings", Label: "jz.bm::lang.navigation.meetings", Icon: "icon-calendar", Permissions: []string{controllers.PermSchedule}, Controller: "jz.bm.meetings"}, {Code: "sessions", Label: "jz.bm::lang.navigation.sessions", Icon: "icon-clock-o", Permissions: []string{controllers.PermSchedule}, Controller: "jz.bm.class_sessions"}, {Code: "deadlines", Label: "jz.bm::lang.navigation.deadlines", Icon: "icon-flag", Permissions: []string{controllers.PermSchedule}, Controller: "jz.bm.deadlines"}, {Code: "materials", Label: "jz.bm::lang.navigation.materials", Icon: "icon-file-text-o", Permissions: []string{controllers.PermMaterials}, Controller: "jz.bm.materials"}, {Code: "tags", Label: "jz.bm::lang.navigation.tags", Icon: "icon-tags", Permissions: []string{controllers.PermMaterials}, Controller: "jz.bm.tags"}, }, }} } // withDB runs fn with the application's database: the one the serve command // published, or, when a console command runs, one opened from the config for // the duration of fn. func (p *Plugin) withDB(ctx context.Context, fn func(*gorm.DB) error) error { if gdb, ok := p.app.Lookup[*gorm.DB](); ok && gdb != nil { return fn(gdb) } sqlDB, gdb, err := lagoon.OpenFromApp(ctx, p.app) if err != nil { return err } defer sqlDB.Close() return fn(gdb) } func init() { party.Register(&Plugin{}) }