package apps import ( "path/filepath" "strings" ) // AppAvailableAt reports whether app's virtual HTML can be served at // requestDir. Rules (case-insensitive on canonical folder names): // // - archive: every directory (multi-project, project, archive, party) // - browse: every directory (generic file listing — also the default // served at folder URLs without an index.html; see directory.go) // - classifier: requestDir is, or descends from, a folder named // "working", "staging", or "incoming" (the directories where // in-flight files get classified) // - mdedit: requestDir is, or descends from, a "working" folder // (where markdown drafts are written and edited, including review // responses drafted in working//) // - transmittal: requestDir is, or descends from, a "staging" folder // (where outgoing transmittals are prepared) // - landing: only at the deployment root (the project picker) // // Operators can always drop a real .html file at any path to // override — that path is served by the static handler regardless of // this function's result. AppAvailableAt is consulted only when no // real file exists. // // In the canonical layout, "incoming" only appears at // archive//incoming/, so checking "any ancestor named incoming" // is equivalent to checking "under a per-party incoming folder." func AppAvailableAt(root, requestDir, app string) bool { root = filepath.Clean(root) requestDir = filepath.Clean(requestDir) switch app { case "archive", "browse": return true case "landing": return requestDir == root case "classifier": return inAncestorWithName(root, requestDir, "working", "staging", "incoming") case "mdedit": return inAncestorWithName(root, requestDir, "working") case "transmittal": return inAncestorWithName(root, requestDir, "staging") } return false } // inAncestorWithName reports whether requestDir is, or has an ancestor // (not including root itself), whose last segment case-folds to one // of names. Match is on segment names, case-insensitively. func inAncestorWithName(root, requestDir string, names ...string) bool { if requestDir == root { return false } rel, err := filepath.Rel(root, requestDir) if err != nil || strings.HasPrefix(rel, "..") { return false } for _, part := range strings.Split(rel, string(filepath.Separator)) { for _, n := range names { if strings.EqualFold(part, n) { return true } } } return false }