package admin import ( "context" _ "embed" "fmt" "html/template" "net/http" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/pkglib/httpx" "github.com/tkw1536/pkglib/httpx/form/field" "github.com/julienschmidt/httprouter" "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) //go:embed "html/instance_users.html" var instanceUsersHTML []byte var instanceUsersTemplate = templating.Parse[instanceUsersContext]( "instance_users.html", instanceUsersHTML, nil, templating.Assets(assets.AssetsAdmin), ) type instanceUsersContext struct { templating.RuntimeFlags Error string instance *wisski.WissKI Instance models.Instance // current instance Users []status.DrupalUser // drupal users Usernames []string // unuused distillery usernames Grants []models.Grant // grants that exist for the user } func (admin *Admin) instanceUsers(context.Context) http.Handler { tpl := instanceUsersTemplate.Prepare( admin.dependencies.Templating, templating.Crumbs( menuAdmin, menuInstances, menuInstance, menuGrants, ), ) return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (instanceUsersContext, []templating.FlagFunc, error) { if r.Method == http.MethodGet { return admin.getGrantsUsers(r) } else { return admin.postInstanceUsers(r) } }) } func (admin *Admin) getGrantsUsers(r *http.Request) (gc instanceUsersContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") funcs, err = gc.use(r, slug, admin) if err != nil { return gc, nil, err } if err := gc.useUsers(r, admin); err != nil { return gc, nil, err } return gc, funcs, nil } func (admin *Admin) postInstanceUsers(r *http.Request) (gc instanceUsersContext, funcs []templating.FlagFunc, err error) { // parse the form if err := r.ParseForm(); err != nil { return gc, nil, err } // read out the form values var ( slug = r.PostFormValue("slug") delete = r.PostFormValue("action") == "delete" distilleryUser = r.PostFormValue("distillery-user") drupalUser = r.PostFormValue("drupal-user") adminRole = r.PostFormValue("admin") == field.CheckboxChecked ) // set the common fields funcs, err = gc.use(r, slug, admin) if err != nil { return gc, nil, err } if delete { // delete the user grant err := admin.dependencies.Policy.Remove(r.Context(), distilleryUser, slug) if err != nil { return gc, nil, err } } else { // update the grant err := admin.dependencies.Policy.Set(r.Context(), models.Grant{ User: distilleryUser, Slug: slug, DrupalUsername: drupalUser, DrupalAdminRole: adminRole, }) if err != nil { gc.Error = fmt.Sprintf("Unable to update grant for user %s: %s", distilleryUser, err.Error()) } } // fetch the grants for the instance if err := gc.useUsers(r, admin); err != nil { return gc, nil, err } return gc, funcs, nil } func (gc *instanceUsersContext) use(r *http.Request, slug string, admin *Admin) (funcs []templating.FlagFunc, err error) { // find the instance itself gc.instance, err = admin.dependencies.Instances.WissKI(r.Context(), slug) if err == instances.ErrWissKINotFound { return nil, httpx.ErrNotFound } if err != nil { return nil, err } gc.Instance = gc.instance.Instance // replace the functions funcs = []templating.FlagFunc{ templating.ReplaceCrumb(menuInstance, component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}), templating.ReplaceCrumb(menuGrants, component.MenuItem{Title: "Users & Grants", Path: template.URL("/admin/instance/" + slug + "/users")}), templating.Title(gc.Instance.Slug + " - Users & Grants"), admin.instanceTabs(slug, "users"), } return funcs, nil } func (gc *instanceUsersContext) useUsers(r *http.Request, admin *Admin) (err error) { gc.Grants, err = admin.dependencies.Policy.Instance(r.Context(), gc.Instance.Slug) if err != nil { return err } users, err := admin.dependencies.Auth.Users(r.Context()) if err != nil { return err } // create a namemap of users, but not those already taken userNameMap := make(map[string]struct{}, len(users)) for _, user := range users { userNameMap[user.User.User] = struct{}{} } for _, grant := range gc.Grants { delete(userNameMap, grant.User) } // setup the usernames gc.Usernames = maps.Keys(userNameMap) slices.Sort(gc.Usernames) // get the drupal user data gc.Users, err = gc.instance.Users().All(r.Context(), nil) if err != nil { return err } slices.SortFunc(gc.Users, func(a, b status.DrupalUser) int { return int(a.UID) - int(b.UID) }) return nil }