package apps import ( "path/filepath" "strings" "codeberg.org/VARASYS/ZDDC/zddc/internal/zddc" ) // Folder name conventions that gate which tools are virtually available // at a given path. The names are case-sensitive; ZDDC convention uses // the capitalized forms. The full canonical list lives in // zddc/internal/zddc/special.go (SpecialFolderNames) — this file pulls // the relevant subsets from there to avoid duplication. var ( // Subset of zddc.AutoOwnFolderNames where classifier is virtually // available (the same three folders that grant mkdir auto-ownership). folderNamesIncomingWorkingStaging = zddc.AutoOwnFolderNames folderNamesWorking = []string{"Working"} folderNamesStaging = []string{"Staging"} ) // AppAvailableAt reports whether app's virtual HTML can be served at // requestDir. Rules: // // - archive: every directory (multi-project, project, archive, vendor) // - 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 // "Incoming", "Working", or "Staging" (the directories where // incoming/outgoing files get classified) // - mdedit: requestDir is, or descends from, a "Working" folder // (where markdown drafts are written and edited) // - 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. func AppAvailableAt(root, requestDir, app string) bool { root = filepath.Clean(root) requestDir = filepath.Clean(requestDir) switch app { case "archive": return true case "browse": return true case "landing": return requestDir == root case "classifier": return inAncestorWithName(root, requestDir, folderNamesIncomingWorkingStaging) case "mdedit": return inAncestorWithName(root, requestDir, folderNamesWorking) case "transmittal": return inAncestorWithName(root, requestDir, folderNamesStaging) } return false } // inAncestorWithName reports whether requestDir is, or has an ancestor // (not including root itself), named one of names. The match is on the // last segment of each directory in the chain root → requestDir. 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 part == n { return true } } } return false }