feat: the author edits a material in the app
- PUT /_bm/api/v1/materials/{id} (jwt.auth only, no token twin): every
key of the edit body required, revision checked on the row locked FOR
UPDATE, 409 with the student detail of the server version
- editableMaterialForCaller: 404 for what the caller cannot see, 403 for
what they see but may not manage
- tags from the dictionary only (at most 4), http(s) urls, the body
normalised as bm-notatka push does; course, author and publication kept
- loadMaterialDetail shared by GET /materials/{id} and the edit answers
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -208,27 +209,41 @@ func (h *Handlers) Material(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
id, ok := surf.IntParam(r, "id")
|
||||
if !ok {
|
||||
if !ok || id <= 0 {
|
||||
writeNotFound(w)
|
||||
return
|
||||
}
|
||||
var m models.Material
|
||||
res := preloadMaterial(visibleMaterials(db, h.now(), userID)).Where("jz_bm_materials.id = ?", id).Limit(1).Find(&m)
|
||||
if res.Error != nil {
|
||||
out, found, err := h.loadMaterialDetail(r.Context(), db, userID, uint(id))
|
||||
if err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
if !found {
|
||||
writeNotFound(w)
|
||||
return
|
||||
}
|
||||
writeData(w, materialView(out, viaMCP(r), includeBody(r)))
|
||||
}
|
||||
|
||||
// loadMaterialDetail builds the student detail of material id as userID
|
||||
// sees it: the published, visible row with its course and tags, its public
|
||||
// files, can_manage and the revision. found is false when the caller cannot
|
||||
// see it. GET /materials/{id} and the author's routes answer with it.
|
||||
func (h *Handlers) loadMaterialDetail(ctx context.Context, db *gorm.DB, userID, id uint) (materialDetailJSON, bool, error) {
|
||||
var m models.Material
|
||||
res := preloadMaterial(visibleMaterials(db, h.now(), userID)).Where("jz_bm_materials.id = ?", id).Limit(1).Find(&m)
|
||||
if res.Error != nil {
|
||||
return materialDetailJSON{}, false, res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return materialDetailJSON{}, false, nil
|
||||
}
|
||||
var files []attach.File
|
||||
err := db.Where("attachment_type = ? AND attachment_id = ? AND field = ?", m.MorphName(), strconv.FormatUint(uint64(m.ID), 10), models.MaterialFilesField).
|
||||
Order("sort_order, id").
|
||||
Find(&files).Error
|
||||
if err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
return materialDetailJSON{}, false, err
|
||||
}
|
||||
out := materialDetailJSON{
|
||||
materialListJSON: newMaterialListJSON(m, int64(len(files))),
|
||||
@@ -245,14 +260,13 @@ func (h *Handlers) Material(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
out.FilesCount = int64(len(out.Files))
|
||||
if m.AuthorUserID == nil || *m.AuthorUserID == userID {
|
||||
manager, err := h.managesMaterials(r.Context(), db, userID)
|
||||
manager, err := h.managesMaterials(ctx, db, userID)
|
||||
if err != nil {
|
||||
writeOpaque500(w)
|
||||
return
|
||||
return materialDetailJSON{}, false, err
|
||||
}
|
||||
out.CanManage = canManageMaterial(m, userID, manager)
|
||||
}
|
||||
writeData(w, materialView(out, viaMCP(r), includeBody(r)))
|
||||
return out, true, nil
|
||||
}
|
||||
|
||||
// publishedMaterialsOfCourse lists a course's published materials that
|
||||
|
||||
Reference in New Issue
Block a user