@klikkn
Software Engineer

Как спроектировать RESTful API?

Есть следущая структура данных:

Mongo models: User, Course, Lesson
User roles: admin, teacher, student

Отношения будут например такие:
User: {
    name: 'Teacher',
    role: 'teacher',
    courses: [{
        title: 'Course',
        lessons: [{
            title: 'Lesson'
        }]
    }]
}


teacher - может CRUD свои курсы и уроки
admin - может CRUD все что угодно
student - может READ любые уроки, в идеале бы только те, к которым у него есть доступ

Для распределения прав доступа хочу использовать ABAC access control
Пример:
admin { courses: 'read:any' } - может read любые курсы
teacher { courses: 'read:own' } - может read только свои курсы
student - { courses: 'read:any' } - может read любые курсы

Вопрос 1. Каким образом давать доступ пользователю к конкретному уроку в ABAC стиле?

В кратце о CRUD
POST -     Create
GET -       Read
PUT -       Update
DELETE - Delete

Как я вижу организацию архитектуры:
CRUD для /users - тут вроде все понятно
GET, POST /users
PUT, GET, DELETE /users/:uuid

Ничего сложного ;)

Теперь рассмотрим создание пользовательских ресурсов:
P.S. Мы используем passport и у нас всегда есть текущий пользователь в req.user
CREATE для courses
Первый подход:
request:
POST users/:userUuid/courses - req.body { title } // создаем курс у пользователя

handler:
userUuid === req.user.uuid // проверяем права доступа
course = Course.create({ title })
user = User.find({ uuid: userUuid })
user.courses.push(course._id).save()

Чтобы получить курсы пользователя будем делать так:
request:
GET /courses/:courseUuid
handler:
course = Course.findOne({ uuid: courseUuid })
user = User.findOne({ 'courses': course._id})
user.uuid === req.user.uuid // проверяем права доступа, если true возвращаем курс

но это какой-то странный restful,
POST user/:userUuid/course
но
GET /course/:courseUuid


Далее создание lesson:
request:
POST /courses/:courseUuid/lessons
handler:
course = Course.findOne({ uuid: courseUuid })
user = User.findOne({ courses: course._id})
user.uuid === req.user.uuid // проверяем права доступа, если true - можно создавать урок

lesson = Lesson.create({ title })
course.lessons.push(lesson._id)


И чтение
request:
GET /lesson/:lessonUuid
handler:
lesson = Lesson.findOne({ uuid: lessonUuid })
course = Course.findOne({ lessons: lesson._id })
user = User.findOne({ courses: course._id})
user.uuid === req.user.uuid // проверяем права доступа, если true - можно вернуть урок

А если мы захотим добавить к lesson какой-нибудь quiz, то это снова будет цепочка предпроверок владельца.

Как-то все не оптимально и на RESTful не оч похоже, посоветуйте пожалуйста, как это вообще организовывают обычно?
  • Вопрос задан
  • 307 просмотров
Пригласить эксперта
Ответы на вопрос 2
DmitriyEntelis
@DmitriyEntelis
Думаю за деньги

Как-то все не оптимально и на RESTful не оч похоже, посоветуйте пожалуйста, как это вообще организовывают обычно?

Советую: RESTful хорошо смотрится только в наглухо синтетических примерах абстрактного каталога объектов без бизнес-логики и взаимосвязей.

но это какой-то странный restful,
POST user/:userUuid/course

А почему не POST /course/? У вас курс - самостоятельный объект, это не свойство юзера же.
Ответ написан
angrySCV
@angrySCV
machine learning, programming, startuping
данные о пользователе передаются или в виде параметра в ссылке, либо в виде заголовка запроса
поэтому можно GET, POST /course/:courseUuid

П. С.
ни что не мешает лесону иметь и ГЕТ /courses/:courseUuid/:lessonUuid
и GET /lesson/:lessonUuid
типовой подход иметь унифицированный апи, с одинаковыми ПОСТ и ГЕТ (и добавочные запросы с низкой иерархией типа /lesson/:lessonUuid)
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы