Show available and pending apps in Home directory
4 files changed,
+67
−1
internal/home/directory.go+1 −1internal/home/directory_catalog.go+17 −0internal/home/directory_catalog_test.go+36 −0internal/home/templates/directory.html+13 −0
CI passed — run details
diff --git a/internal/home/directory.go b/internal/home/directory.go| index fe25f09..6f1a734 100644 |
| --- a/internal/home/directory.go |
| +++ b/internal/home/directory.go |
| @@ -79,7 +79,7 @@ func (a *App) directoryRouter() http.Handler { |
| }) |
| r.With(a.sessions.Require).Get("/", func(w http.ResponseWriter, r *http.Request) { |
| w.Header().Set("Cache-Control", "no-store") |
| - a.render(w, 200, "directory.html", map[string]any{}) |
| + a.render(w, 200, "directory.html", map[string]any{"Apps": directoryCatalog()}) |
| }) |
| r.Post("/signout", func(w http.ResponseWriter, r *http.Request) { |
| a.sessions.SignOut(w, r) |
diff --git a/internal/home/directory_catalog.go b/internal/home/directory_catalog.go| new file mode 100644 |
| index 0000000..282488f |
| --- /dev/null |
| +++ b/internal/home/directory_catalog.go |
| @@ -0,0 +1,17 @@ |
| +package home |
| + |
| +type catalogApp struct { |
| + Name, Href, Status string |
| +} |
| + |
| +// This display catalog grants no integration or provisioning capability. |
| +// Those remain governed by directoryApps and the destination checks. |
| +func directoryCatalog() []catalogApp { |
| + return []catalogApp{ |
| + {Name: "Docs", Href: docsEntryPath, Status: "Choose a team"}, |
| + {Name: "Sheets", Status: "Not connected to Home yet"}, |
| + {Name: "Slides", Status: "Not connected to Home yet"}, |
| + {Name: "Calendar", Status: "Not connected to Home yet"}, |
| + {Name: "Meet", Status: "Coming soon"}, |
| + } |
| +} |
diff --git a/internal/home/directory_catalog_test.go b/internal/home/directory_catalog_test.go| new file mode 100644 |
| index 0000000..11b7c49 |
| --- /dev/null |
| +++ b/internal/home/directory_catalog_test.go |
| @@ -0,0 +1,36 @@ |
| +package home |
| + |
| +import ( |
| + "strings" |
| + "testing" |
| +) |
| + |
| +func TestDirectoryCatalogRequiresSigninAndDoesNotInventLaunchDestinations(t *testing.T) { |
| + f := newMountedChooser(t) |
| + anonymous := f.request("GET", "/", "", "", nil) |
| + if anonymous.Code != 303 || !strings.HasPrefix(anonymous.Header().Get("Location"), "/auth/start") || strings.Contains(anonymous.Body.String(), "Choose a team") { |
| + t.Fatal("anonymous directory exposed catalog", anonymous.Code, anonymous.Header()) |
| + } |
| + cookie := f.session(t, chooserPerson) |
| + page := f.request("GET", "/", "", "", cookie) |
| + if page.Code != 200 { |
| + t.Fatal(page.Code) |
| + } |
| + noStore(t, page) |
| + body := page.Body.String() |
| + for _, name := range []string{"Docs", "Sheets", "Slides", "Calendar", "Meet"} { |
| + if !strings.Contains(body, ">"+name+"<") { |
| + t.Fatal("missing app", name) |
| + } |
| + } |
| + if strings.Count(body, "<a ") != 1 || !strings.Contains(body, `href="/apps/docs"`) || !strings.Contains(body, "Choose a team") || strings.Contains(body, "<script") { |
| + t.Fatal("catalog advertised unsupported launch", body) |
| + } |
| + if strings.Count(body, "Not connected to Home yet") != 3 || strings.Count(body, "Coming soon") != 1 { |
| + t.Fatal("catalog readiness missing", body) |
| + } |
| + entry := f.request("GET", docsEntryPath, "", "", cookie) |
| + if entry.Code != 200 || !strings.Contains(entry.Body.String(), "This account has no team with Docs available.") { |
| + t.Fatal("catalog bypassed team selection", entry.Code) |
| + } |
| +} |
diff --git a/internal/home/templates/directory.html b/internal/home/templates/directory.html| index 7c27538..10bebfb 100644 |
| --- a/internal/home/templates/directory.html |
| +++ b/internal/home/templates/directory.html |
| @@ -2,5 +2,18 @@ |
| {{define "content"}} |
| <h1>Oficina</h1> |
| <p>You are signed in.</p> |
| +<section aria-labelledby="apps-heading"> |
| +<h2 id="apps-heading">Apps</h2> |
| +<div rst-list> |
| +{{range .Apps}} |
| +<div rst-row> |
| +<div rst-row-main> |
| +{{if .Href}}<a href="{{.Href}}">{{.Name}}</a>{{else}}<strong>{{.Name}}</strong>{{end}} |
| +<span rst-row-sub>{{.Status}}</span> |
| +</div> |
| +</div> |
| +{{end}} |
| +</div> |
| +</section> |
| <form method="post" action="/signout" rst-form><button type="submit" rst-btn>Sign out</button></form> |
| {{end}} |