Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/labels: change scope to be a list of NSIDs

Signed-off-by: oppiliappan <me@oppi.li>

+196 -36
+13 -8
appview/db/db.go
··· 483 483 )), 484 484 value_format text not null default "any", 485 485 value_enum text, -- comma separated list 486 - scope text not null, 486 + scope text not null, -- comma separated list of nsid 487 487 color text, 488 488 multiple integer not null default 0, 489 489 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), ··· 996 996 } 997 997 } 998 998 999 - func FilterEq(key string, arg any) filter { return newFilter(key, "=", arg) } 1000 - func FilterNotEq(key string, arg any) filter { return newFilter(key, "<>", arg) } 1001 - func FilterGte(key string, arg any) filter { return newFilter(key, ">=", arg) } 1002 - func FilterLte(key string, arg any) filter { return newFilter(key, "<=", arg) } 1003 - func FilterIs(key string, arg any) filter { return newFilter(key, "is", arg) } 1004 - func FilterIsNot(key string, arg any) filter { return newFilter(key, "is not", arg) } 1005 - func FilterIn(key string, arg any) filter { return newFilter(key, "in", arg) } 999 + func FilterEq(key string, arg any) filter { return newFilter(key, "=", arg) } 1000 + func FilterNotEq(key string, arg any) filter { return newFilter(key, "<>", arg) } 1001 + func FilterGte(key string, arg any) filter { return newFilter(key, ">=", arg) } 1002 + func FilterLte(key string, arg any) filter { return newFilter(key, "<=", arg) } 1003 + func FilterIs(key string, arg any) filter { return newFilter(key, "is", arg) } 1004 + func FilterIsNot(key string, arg any) filter { return newFilter(key, "is not", arg) } 1005 + func FilterIn(key string, arg any) filter { return newFilter(key, "in", arg) } 1006 + func FilterLike(key string, arg any) filter { return newFilter(key, "like", arg) } 1007 + func FilterNotLike(key string, arg any) filter { return newFilter(key, "not like", arg) } 1008 + func FilterContains(key string, arg any) filter { 1009 + return newFilter(key, "like", fmt.Sprintf("%%%v%%", arg)) 1010 + } 1006 1011 1007 1012 func (f filter) Condition() string { 1008 1013 rv := reflect.ValueOf(f.arg)
+1 -1
appview/issues/issues.go
··· 95 95 labelDefs, err := db.GetLabelDefinitions( 96 96 rp.db, 97 97 db.FilterIn("at_uri", f.Repo.Labels), 98 - db.FilterEq("scope", tangled.RepoIssueNSID), 98 + db.FilterContains("scope", tangled.RepoIssueNSID), 99 99 ) 100 100 if err != nil { 101 101 log.Println("failed to fetch labels", err)
+146 -15
appview/repo/repo.go
··· 966 966 rp.pages.HxRefresh(w) 967 967 } 968 968 969 - func (rp *Repo) AddLabel(w http.ResponseWriter, r *http.Request) { 969 + func (rp *Repo) AddLabelDef(w http.ResponseWriter, r *http.Request) { 970 970 user := rp.oauth.GetUser(r) 971 971 l := rp.logger.With("handler", "AddLabel") 972 972 l = l.With("did", user.Did) ··· 989 989 concreteType := r.FormValue("valueType") 990 990 valueFormat := r.FormValue("valueFormat") 991 991 enumValues := r.FormValue("enumValues") 992 - scope := r.FormValue("scope") 992 + scope := r.Form["scope"] 993 993 color := r.FormValue("color") 994 994 multiple := r.FormValue("multiple") == "true" 995 995 ··· 998 998 if part = strings.TrimSpace(part); part != "" { 999 999 variants = append(variants, part) 1000 1000 } 1001 + } 1002 + 1003 + if concreteType == "" { 1004 + concreteType = "null" 1001 1005 } 1002 1006 1003 1007 format := db.ValueTypeFormatAny ··· 1020 1016 Rkey: tid.TID(), 1021 1017 Name: name, 1022 1018 ValueType: valueType, 1023 - Scope: syntax.NSID(scope), 1019 + Scope: scope, 1024 1020 Color: &color, 1025 1021 Multiple: multiple, 1026 1022 Created: time.Now(), ··· 1076 1072 Val: &repoRecord, 1077 1073 }, 1078 1074 }) 1075 + if err != nil { 1076 + fail("Failed to update labels for repo.", err) 1077 + return 1078 + } 1079 1079 1080 1080 tx, err := rp.db.BeginTx(r.Context(), nil) 1081 1081 if err != nil { ··· 1126 1118 rp.pages.HxRefresh(w) 1127 1119 } 1128 1120 1129 - func (rp *Repo) DeleteLabel(w http.ResponseWriter, r *http.Request) { 1121 + func (rp *Repo) DeleteLabelDef(w http.ResponseWriter, r *http.Request) { 1130 1122 user := rp.oauth.GetUser(r) 1131 1123 l := rp.logger.With("handler", "DeleteLabel") 1132 1124 l = l.With("did", user.Did) ··· 1237 1229 1238 1230 func (rp *Repo) SubscribeLabel(w http.ResponseWriter, r *http.Request) { 1239 1231 user := rp.oauth.GetUser(r) 1240 - l := rp.logger.With("handler", "DeleteLabel") 1232 + l := rp.logger.With("handler", "SubscribeLabel") 1241 1233 l = l.With("did", user.Did) 1242 1234 l = l.With("handle", user.Handle) 1243 1235 ··· 1247 1239 return 1248 1240 } 1249 1241 1250 - errorId := "label-operation" 1242 + errorId := "default-label-operation" 1251 1243 fail := func(msg string, err error) { 1252 1244 l.Error(msg, "err", err) 1253 1245 rp.pages.Notice(w, errorId, msg) ··· 1300 1292 1301 1293 func (rp *Repo) UnsubscribeLabel(w http.ResponseWriter, r *http.Request) { 1302 1294 user := rp.oauth.GetUser(r) 1303 - l := rp.logger.With("handler", "DeleteLabel") 1295 + l := rp.logger.With("handler", "UnsubscribeLabel") 1304 1296 l = l.With("did", user.Did) 1305 1297 l = l.With("handle", user.Handle) 1306 1298 ··· 1310 1302 return 1311 1303 } 1312 1304 1313 - errorId := "label-operation" 1305 + errorId := "default-label-operation" 1314 1306 fail := func(msg string, err error) { 1315 1307 l.Error(msg, "err", err) 1316 1308 rp.pages.Notice(w, errorId, msg) ··· 1367 1359 1368 1360 // everything succeeded 1369 1361 rp.pages.HxRefresh(w) 1362 + } 1363 + 1364 + func (rp *Repo) LabelPanel(w http.ResponseWriter, r *http.Request) { 1365 + l := rp.logger.With("handler", "LabelPanel") 1366 + 1367 + f, err := rp.repoResolver.Resolve(r) 1368 + if err != nil { 1369 + l.Error("failed to get repo and knot", "err", err) 1370 + return 1371 + } 1372 + 1373 + subjectStr := r.FormValue("subject") 1374 + subject, err := syntax.ParseATURI(subjectStr) 1375 + if err != nil { 1376 + l.Error("failed to get repo and knot", "err", err) 1377 + return 1378 + } 1379 + 1380 + labelDefs, err := db.GetLabelDefinitions( 1381 + rp.db, 1382 + db.FilterIn("at_uri", f.Repo.Labels), 1383 + db.FilterContains("scope", subject.Collection().String()), 1384 + ) 1385 + if err != nil { 1386 + log.Println("failed to fetch label defs", err) 1387 + return 1388 + } 1389 + 1390 + defs := make(map[string]*db.LabelDefinition) 1391 + for _, l := range labelDefs { 1392 + defs[l.AtUri().String()] = &l 1393 + } 1394 + 1395 + states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject)) 1396 + if err != nil { 1397 + log.Println("failed to build label state", err) 1398 + return 1399 + } 1400 + state := states[subject] 1401 + 1402 + user := rp.oauth.GetUser(r) 1403 + rp.pages.LabelPanel(w, pages.LabelPanelParams{ 1404 + LoggedInUser: user, 1405 + RepoInfo: f.RepoInfo(user), 1406 + Defs: defs, 1407 + Subject: subject.String(), 1408 + State: state, 1409 + }) 1410 + } 1411 + 1412 + func (rp *Repo) EditLabelPanel(w http.ResponseWriter, r *http.Request) { 1413 + l := rp.logger.With("handler", "EditLabelPanel") 1414 + 1415 + f, err := rp.repoResolver.Resolve(r) 1416 + if err != nil { 1417 + l.Error("failed to get repo and knot", "err", err) 1418 + return 1419 + } 1420 + 1421 + subjectStr := r.FormValue("subject") 1422 + subject, err := syntax.ParseATURI(subjectStr) 1423 + if err != nil { 1424 + l.Error("failed to get repo and knot", "err", err) 1425 + return 1426 + } 1427 + 1428 + labelDefs, err := db.GetLabelDefinitions( 1429 + rp.db, 1430 + db.FilterIn("at_uri", f.Repo.Labels), 1431 + db.FilterContains("scope", subject.Collection().String()), 1432 + ) 1433 + if err != nil { 1434 + log.Println("failed to fetch labels", err) 1435 + return 1436 + } 1437 + 1438 + defs := make(map[string]*db.LabelDefinition) 1439 + for _, l := range labelDefs { 1440 + defs[l.AtUri().String()] = &l 1441 + } 1442 + 1443 + states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject)) 1444 + if err != nil { 1445 + log.Println("failed to build label state", err) 1446 + return 1447 + } 1448 + state := states[subject] 1449 + 1450 + user := rp.oauth.GetUser(r) 1451 + rp.pages.EditLabelPanel(w, pages.EditLabelPanelParams{ 1452 + LoggedInUser: user, 1453 + RepoInfo: f.RepoInfo(user), 1454 + Defs: defs, 1455 + Subject: subject.String(), 1456 + State: state, 1457 + }) 1370 1458 } 1371 1459 1372 1460 func (rp *Repo) AddCollaborator(w http.ResponseWriter, r *http.Request) { ··· 1894 1790 return 1895 1791 } 1896 1792 1897 - labels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels)) 1793 + defaultLabels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", db.DefaultLabelDefs())) 1898 1794 if err != nil { 1899 1795 log.Println("failed to fetch labels", err) 1900 1796 rp.pages.Error503(w) 1901 1797 return 1902 1798 } 1903 1799 1800 + labels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels)) 1801 + if err != nil { 1802 + log.Println("failed to fetch labels", err) 1803 + rp.pages.Error503(w) 1804 + return 1805 + } 1806 + // remove default labels from the labels list, if present 1807 + defaultLabelMap := make(map[string]bool) 1808 + for _, dl := range defaultLabels { 1809 + defaultLabelMap[dl.AtUri().String()] = true 1810 + } 1811 + n := 0 1812 + for _, l := range labels { 1813 + if !defaultLabelMap[l.AtUri().String()] { 1814 + labels[n] = l 1815 + n++ 1816 + } 1817 + } 1818 + labels = labels[:n] 1819 + 1820 + subscribedLabels := make(map[string]struct{}) 1821 + for _, l := range f.Repo.Labels { 1822 + subscribedLabels[l] = struct{}{} 1823 + } 1824 + 1904 1825 rp.pages.RepoGeneralSettings(w, pages.RepoGeneralSettingsParams{ 1905 - LoggedInUser: user, 1906 - RepoInfo: f.RepoInfo(user), 1907 - Branches: result.Branches, 1908 - Labels: labels, 1909 - Tabs: settingsTabs, 1910 - Tab: "general", 1826 + LoggedInUser: user, 1827 + RepoInfo: f.RepoInfo(user), 1828 + Branches: result.Branches, 1829 + Labels: labels, 1830 + DefaultLabels: defaultLabels, 1831 + SubscribedLabels: subscribedLabels, 1832 + Tabs: settingsTabs, 1833 + Tab: "general", 1911 1834 }) 1912 1835 } 1913 1836
+36 -12
appview/validator/label.go
··· 18 18 // Color should be a valid hex color 19 19 colorRegex = regexp.MustCompile(`^#[a-fA-F0-9]{6}$`) 20 20 // You can only label issues and pulls presently 21 - validScopes = []syntax.NSID{tangled.RepoIssueNSID, tangled.RepoPullNSID} 21 + validScopes = []string{tangled.RepoIssueNSID, tangled.RepoPullNSID} 22 22 ) 23 23 24 24 func (v *Validator) ValidateLabelDefinition(label *db.LabelDefinition) error { ··· 36 36 } 37 37 38 38 if !label.ValueType.IsConcreteType() { 39 - return fmt.Errorf("invalid value type: %q (must be one of: null, boolean, integer, string)", label.ValueType) 39 + return fmt.Errorf("invalid value type: %q (must be one of: null, boolean, integer, string)", label.ValueType.Type) 40 40 } 41 41 42 - if label.ValueType.IsNull() && label.ValueType.IsEnumType() { 42 + // null type checks: cannot be enums, multiple or explicit format 43 + if label.ValueType.IsNull() && label.ValueType.IsEnum() { 43 44 return fmt.Errorf("null type cannot be used in conjunction with enum type") 45 + } 46 + if label.ValueType.IsNull() && label.Multiple { 47 + return fmt.Errorf("null type labels cannot be multiple") 48 + } 49 + if label.ValueType.IsNull() && !label.ValueType.IsAnyFormat() { 50 + return fmt.Errorf("format cannot be used in conjunction with null type") 51 + } 52 + 53 + // format checks: cannot be used with enum, or integers 54 + if !label.ValueType.IsAnyFormat() && label.ValueType.IsEnum() { 55 + return fmt.Errorf("enum types cannot be used in conjunction with format specification") 56 + } 57 + 58 + if !label.ValueType.IsAnyFormat() && !label.ValueType.IsString() { 59 + return fmt.Errorf("format specifications are only permitted on string types") 44 60 } 45 61 46 62 // validate scope (nsid format) 47 - if label.Scope == "" { 63 + if label.Scope == nil { 48 64 return fmt.Errorf("scope is required") 49 65 } 50 - if _, err := syntax.ParseNSID(string(label.Scope)); err != nil { 51 - return fmt.Errorf("failed to parse scope: %w", err) 52 - } 53 - if !slices.Contains(validScopes, label.Scope) { 54 - return fmt.Errorf("invalid scope: scope must be one of %q", validScopes) 66 + for _, s := range label.Scope { 67 + if _, err := syntax.ParseNSID(s); err != nil { 68 + return fmt.Errorf("failed to parse scope: %w", err) 69 + } 70 + if !slices.Contains(validScopes, s) { 71 + return fmt.Errorf("invalid scope: scope must be present in %q", validScopes) 72 + } 55 73 } 56 74 57 75 // validate color if provided ··· 134 116 func (v *Validator) validateOperandValue(labelDef *db.LabelDefinition, labelOp *db.LabelOp) error { 135 117 valueType := labelDef.ValueType 136 118 119 + // this is permitted, it "unsets" a label 120 + if labelOp.OperandValue == "" { 121 + labelOp.Operation = db.LabelOperationDel 122 + return nil 123 + } 124 + 137 125 switch valueType.Type { 138 126 case db.ConcreteTypeNull: 139 127 // For null type, value should be empty ··· 149 125 150 126 case db.ConcreteTypeString: 151 127 // For string type, validate enum constraints if present 152 - if valueType.IsEnumType() { 128 + if valueType.IsEnum() { 153 129 if !slices.Contains(valueType.Enum, labelOp.OperandValue) { 154 130 return fmt.Errorf("value %q is not in allowed enum values %v", labelOp.OperandValue, valueType.Enum) 155 131 } ··· 177 153 return fmt.Errorf("value %q is not a valid integer", labelOp.OperandValue) 178 154 } 179 155 180 - if valueType.IsEnumType() { 156 + if valueType.IsEnum() { 181 157 if !slices.Contains(valueType.Enum, labelOp.OperandValue) { 182 158 return fmt.Errorf("value %q is not in allowed enum values %v", labelOp.OperandValue, valueType.Enum) 183 159 } ··· 189 165 } 190 166 191 167 // validate enum constraints if present (though uncommon for booleans) 192 - if valueType.IsEnumType() { 168 + if valueType.IsEnum() { 193 169 if !slices.Contains(valueType.Enum, labelOp.OperandValue) { 194 170 return fmt.Errorf("value %q is not in allowed enum values %v", labelOp.OperandValue, valueType.Enum) 195 171 }