feat: personal pins on materials
A per-user jz_bm_material_pins table and PUT/DELETE /materials/{id}/pin
on the student API and its token twins: 204 for a material the caller
may see, idempotent, 404 otherwise. List rows, course rows and the
detail carry pinned. The plain list lists the caller's pins first
(newest pin first); with q the relevance order stays. A pin writes only
the pins table and never changes the material's revision.
This commit is contained in:
@@ -77,6 +77,35 @@ func TestMaterialJSONCarriesPrivacy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMaterialJSONCarriesPinned checks that pinned is always present in a
|
||||
// material row, false unless the caller set it, and that the detail carries
|
||||
// the row's value.
|
||||
func TestMaterialJSONCarriesPinned(t *testing.T) {
|
||||
row := newMaterialListJSON(models.Material{ID: 1, Title: "Materiał", Type: "notatka"}, 0)
|
||||
b, err := json.Marshal(row)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(b), `"pinned":false`) {
|
||||
t.Errorf("material JSON %s does not contain pinned false", b)
|
||||
}
|
||||
row.Pinned = true
|
||||
b, err = json.Marshal(row)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(b), `"pinned":true`) {
|
||||
t.Errorf("pinned material JSON %s does not contain pinned true", b)
|
||||
}
|
||||
b, err = json.Marshal(materialDetailJSON{materialListJSON: row})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(b), `"pinned":true`) {
|
||||
t.Errorf("pinned material detail JSON %s does not contain pinned true", b)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVisibleMaterialSQL checks the visibility fragment: public, the author,
|
||||
// or a share of the caller, with the caller id bound twice.
|
||||
func TestVisibleMaterialSQL(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user