ZEP-17 : Elaboration de l'API des membres

a marqué ce sujet comme résolu.

Itou, sauf que je virerais les URLs suivantes :

1
2
http://www.zestedesavoir.com/api/membres/1/sanction/non-lecture-seule/
http://www.zestedesavoir.com/api/membres/1/sanction/non-ban/

Autant utiliser une requête POST ou DELETE non ?

Taguan

Tu as tout a fait raison. Ici un DELETE est tout à fait approprié à la levée de la sanction.

Mais plus généralement ça m'embête un peu qu'on implémente des trucs qui n'ont pas été spécifiées/validées dans la ZEP. Mais peut être ai-je manqué quelque chose.

Mais plus généralement ça m'embête un peu qu'on implémente des trucs qui n'ont pas été spécifiées/validées dans la ZEP. Mais peut être ai-je manqué quelque chose.

firm1

Effectivement, on est un peu hors scope par rapport à ce qui avait été défini. Probablement parce qu'on a réorganisé le code des vues des membres, y compris celui qui concernait les sanctions et la modification d'un profil tiers par le staff, parce que c'était nécessaire pour mettre en place ce qui était initialement prévu dans la ZEP. Du coup, je pense qu'on continue sur la lancée.

Est-ce vraiment un problème ? On peut étendre un peu la ZEP non ? Je pense que c'est mieux de faire le tour complet du module membre avant de passer à d'autres modules. Et si on fait pas ça dans la ZEP "api des membres", dans quelle ZEP le fera-t-on ?

+1 -0

Bien, partons sur ça alors.

Mais plus généralement ça m'embête un peu qu'on implémente des trucs qui n'ont pas été spécifiées/validées dans la ZEP. Mais peut être ai-je manqué quelque chose.

firm1

Oui et non. Il y a une note à propos de la gestion des ressources mais c'est tout. C'est plus du bonus qui est lié au module des membres mais pas à la ZEP. Faut vraiment faire une ZEP rien que pour ça ?

Grilled by Taguan

Ça semble assez logique qu'on n'ait pas pu couvrir toutes les primitives de l'API dans la ZEP.

Ça serait beau hein, mais c'est un peu illusoire. Du coup tant qu'on ne diverge pas sur les principes fondamentaux (auth, …) ça me semble plutôt bien d'en ajouter autant que possible.

Si ça permet de découvrir des cas qui permettent de spécifier un comportement (comme l'a très bien fait firm1 d'ailleurs) qu'on peut garder comme acquis et comme bonne pratique, c'est plutôt bien. C'était le but du jeu de cette ZEP d'ailleurs, défricher un peu le terrain pour une API à grande échelle.

+0 -0

Je crois que mon propos a été mal compris.

C'est juste que je m'attends à ce que ce qui est développé corresponde à ce qui est spécifiée (et donc dans le premier topic).

La nomenclature des urls de l'API fait quand même parti des bases importantes. Raison pour laquelle elles doivent faire partie de la spécification (et donc il faut étendre celle-ci). Sinon on se retrouve avec des surprises à la livraison, et les surprises dans un projet comme celui-ci ne sont pas toujours bonnes.

Tu voudrais que je rajoute les URLs dans le premier message de cette ZEP ?

Andr0

ça me parait nécessaire. D'autant plus que ce sont des actions (POST et DELETE). Que se passe t-il si on fait un GET sur ces urls par exemple ? Et je ne sais pas si les actions du staffs seront couvertes entièrement, mais qu'en est-il de la différence entre staff et Superuser ? Qu'est ce que si passe si je fais un POST 100 fois depuis la même IP ? Comment je déconnecte un membre de l'API si je viens de le bannir ? etc. Beaucoup de questions apparaissent dès lors qu'on veut gérer autre chose que du GET.

ça me parait nécessaire. D'autant plus que ce sont des actions (POST et DELETE). Que se passe t-il si on fait un GET sur ces urls par exemple ? Et je ne sais pas si les actions du staffs seront couvertes entièrement, mais qu'en est-il de la différence entre staff et Superuser ? Qu'est ce que si passe si je fais un POST 100 fois depuis la même IP ? Comment je déconnecte un membre de l'API si je viens de le bannir ? etc. Beaucoup de questions apparaissent dès lors qu'on veut gérer autre chose que du GET.

firm1

Tu soulèves beaucoup trop de questions qui n'ont pas fait l'objet d'une réflexion. Il faudrait presque en parler dans la ZEP mère (2 de mémoire) sur l'ensemble des APIs mais dès que j'aurais terminé l'API staff, je ferais un compte rendu de tout le travail fait, les APIs intégrés, leurs URLs, leurs résultats, les différences (minimes) avec la spec de la ZEP, etc. Toutes ces questions répondrons à une bonne partie de tes questions.

Voici ce que je vous propose :

v0.7 : Sanction par le staff
Objectif

Ajoute ou retire une sanction sur un membre donné par une lecture seule ou un bannissement.

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
POST /v0.7/membres/{id}/lecture-seule Raison de la sanction Sanctionne le membre donné par une lecture seule 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
POST /v0.7/membres/{id}/lecture-seule-temporaire Raison de la sanction Sanctionne le membre donné par une lecture seule temporaire 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
DELETE /v0.7/membres/{id}/lecture-seule Raison du retrait de la sanction Retire la sanction lecture seule sur le membre donné 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
POST /v0.7/membres/{id}/ban Raison de la sanction Sanctionne le membre donné par un ban 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
POST /v0.7/membres/{id}/ban-temporaire Raison de la sanction Sanctionne le membre donné par un ban temporaire 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
DELETE /v0.7/membres/{id}/ban Raison du retrait de la sanction Retire la sanction ban sur le membre donné 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.

Pourquoi faire la distinction entre une lecture-seule/ban temporaire et définitif ? Ne serai-ce pas plus simple de ne faire qu'une seule route et dans le POST mettre un paramètre facultatif "expiration" ?

La source

J'approuve. Cela donnerait :

v0.7 : Sanction par le staff
Objectif

Ajoute ou retire une sanction sur un membre donné par une lecture seule ou un bannissement.

Routes
Méthode URL Paramètres et signification Description Codes d'erreur
POST /v0.7/membres/{id}/lecture-seule Raison de la sanction, sanction temporaire ou non Sanctionne le membre donné par une lecture seule 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
DELETE /v0.7/membres/{id}/lecture-seule Raison du retrait de la sanction Retire la sanction lecture seule sur le membre donné 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
POST /v0.7/membres/{id}/ban Raison de la sanction, sanction temporaire ou non Sanctionne le membre donné par un ban 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.
DELETE /v0.7/membres/{id}/ban Raison du retrait de la sanction Retire la sanction ban sur le membre donné 400 si les headers Authbearer sont absents. 401 si pas les autorisations. 403 si login failed.

Pour moi, ces URLs sont pas très logiques, et s'éloignent un peu de la logique REST, puisqu'elles ne représentent pas vraiment des "ressources"… Alors, d'abord, question: est-ce qu'il y peut y avoir plusieurs sanctions à la fois ?

Si c'est pas le cas, je verrais plutôt un truc du type /membres/123/sanction, et avec le type/durée de sanction dans le body, pour les GET comme pour les POST/PUT/DELETE… Sinon, on peut toujours avoir ça sous forme d'array…

Pour moi, ca pourrait même se faire sur le membre directement (sur /membres/{id})… Avec un champ "read_only", et "banned", qu'on peut lire au GET, et modifier au PUT… (modifiable bien évidemment que par le staff)

+1 -0

Alors, d'abord, question: est-ce qu'il y peut y avoir plusieurs sanctions à la fois ?

Oui. Tu peux etre Lecture Seule ET Ban (et autre chose si un jour il y a autre chose de possible.

Si par ressource tu entends "Objet qu'on peut retourner", alors c'est le cas puisque une sanction est un objet Ban (et non pas une variable dans le modele de membre)

Sinon Andro tu aurais pas inverse 401 et 403 dans ton tableau ?

+0 -0

Un objet Ban ne correspond pas vraiment à une sanction malheureusement, ou alors j'ai mal compris le code. Pour moi un objet Ban, c'est plutôt un log lié à un événement de sanction d'un membre. On enregistre un objet Ban lorsqu'on banni quelqu'un ou qu'on le met en LS. Mais on enregistre aussi un objet Ban lorsqu'on lève une sanction.

La sanction en elle-même est plutôt représentée par la propriété can_read ou can_write du membre concerné.

Le débat a eu lieu hier soir sur IRC avec Sandhose, Taguan et moi-même. Je comprends les arguments de Sanhose mais il propose une approche vers la ressource Ban plutôt que la ressource Profile. A l'heure actuelle, les sanctions sont dans un module utils (ou nom équivalent) et, en l'état, ne fera sans doute pas l'objet d'une API. Donc, toujours selon moi et à l'heure actuelle, l'approche par Profile est plus adaptée.

Hier, j'ai mis en place les deux premières URLs qui fonctionnent plutôt très bien et les deux autres arriveront vites.

Sinon Andro tu aurais pas inverse 401 et 403 dans ton tableau ?

Eskimon

  • Un code HTTP 401 correspond à Unauthorized.
  • Un code HTTP 403 correspond à * Forbidden*.

Donc non, il n'y a pas d'erreurs pour moi. Bon après, la librairie se charge de renvoyer 95% des codes HTTPs donc. :)

Bien, la ZEP arrive doucement à son terme et c'est l'occasion pour moi de vous faire un compte rendu (presque) final de la situation puisque (presque) toutes les exigences de la spécification ont été intégrées dans le projet et sont fonctionnelles.

Avant toutes choses, voici toutes les routes intégrées au projet :

Authentification

Fonctionnalité : Authentifie un utilisateur donné.

URL : http://localhost:8000/oauth2/access_token

Methode : POST

Headers : Aucun obligatoire

Paramètres :

  • client_id : Identifiant récupérer à la création du client.
  • client_secret : Secret récupérer à la création du client.
  • username : Username de l'utilisateur.
  • password : Mot de passe de l'utilisateur.
  • grant_type : password

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.

Permissions nécessaires : N/A

Exemple de résultat :

1
2
3
4
5
6
7
{
    "access_token": "27a59cd8b1f3322707781c7347ca20f8e141e594",
    "token_type": "Bearer",
    "expires_in": 31535999,
    "refresh_token": "e751cc50ae9a74e20d9d1f4890b86ab01ad5f343",
    "scope": "read"
}

Attention : Demande une manipulation dans l'interface administrateur du site pour créer un "client" (au sens OAuth2).

Liste de tous les membres

Fonctionnalité : Renvoie une liste paginée pour lister les membres par 10.

URL : http://localhost:8000/api/membres/

Methode : GET

Headers : Aucun obligatoire

Paramètres :

  • page : Affiche les membres de la page voulue.
  • search : Effectue une recherche sur le pseudonyme des membres.

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.
  • 404 : La page n'existe pas.

Permissions nécessaires : N/A

Exemple de résultat pour l'URL http://localhost:8000/api/membres/?page=2 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{
    "count": 27,
    "next": "http://localhost:8000/api/membres/?page=3",
    "previous": "http://localhost:8000/api/membres/?page=1",
    "results": [
        {
            "id": 17,
            "username": "user20",
            "is_active": false,
            "date_joined": "2015-01-10T22:31:50.908831"
        },
        {
            "id": 16,
            "username": "user10",
            "is_active": false,
            "date_joined": "2014-12-29T22:24:49.158187"
        },
        {
            "id": 15,
            "username": "user9",
            "is_active": false,
            "date_joined": "2014-12-29T22:24:00.998709"
        },
        {
            "id": 14,
            "username": "user7",
            "is_active": false,
            "date_joined": "2014-12-29T22:13:43.436301"
        },
        {
            "id": 13,
            "username": "user6",
            "is_active": false,
            "date_joined": "2014-12-29T02:24:43.165852"
        },
        {
            "id": 12,
            "username": "user5",
            "is_active": false,
            "date_joined": "2014-12-29T01:48:24.547008"
        },
        {
            "id": 11,
            "username": "user4",
            "is_active": false,
            "date_joined": "2014-12-29T01:40:18.297339"
        },
        {
            "id": 10,
            "username": "user3",
            "is_active": false,
            "date_joined": "2014-12-29T01:39:32.523008"
        },
        {
            "id": 9,
            "username": "user2",
            "is_active": true,
            "date_joined": "2014-12-29T01:38:56.561351"
        },
        {
            "id": 8,
            "username": "user1",
            "is_active": true,
            "date_joined": "2014-12-29T01:37:16.703046"
        }
    ]
}

Création d'un membre

Fonctionnalité : Création d'un membre inactif (un mail est envoyé pour qu'il valide son inscription).

URL : http://localhost:8000/api/membres/

Methode : POST

Headers : Aucun obligatoire

Paramètres :

  • username : Pseudo du futur membre.
  • email : Adresse e-mail du futur membre.
  • password : Mot de passe du futur membre.

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.
  • 400 : Dès qu'il y a un problème avec les paramètres de la requête.
1
2
3
4
5
6
7
8
{
    "username": [
        "This field must be unique."
    ],
    "password": [
        "This field may not be blank."
    ]
}

Permissions nécessaires : N/A

Exemple de résultat :

1
2
3
4
5
{
    "username": "Andr0",
    "email": "test@zestedesavoir.com",
    "password": "pbkdf2_sha256$12000$89HbWyOVZTZI$T5Tk9cfKhEzubf8TF92rh/uX1nYnIcYLGKOYp4sFim8="
}

Informations d'un membre donné

Fonctionnalité : Donne toutes les informations sur un membre donné.

URL : http://localhost:8000/api/membres/{id}/

Methode : GET

Headers : Aucun obligatoire

Paramètres : N/A

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.
  • 404 : Le membre avec l'identifiant donné n'existe pas.

Permissions nécessaires : N/A

Exemple de résultat pour l'URL http://localhost:8000/api/membres/1/ :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "id": 1,
    "username": "admin",
    "show_email": true,
    "email": "admin@email.com", # Attribut affiché si show_email est à true
    "is_active": true,
    "site": "www.zestedesavoir.com",
    "avatar_url": "https://cdn-images.xda-developers.com/direct/1/6/3/5/9/8/6/yoda-droid.jpg",
    "biography": "testou",
    "sign": "Testi",
    "email_for_answer": false,
    "last_visit": "2015-01-11T17:27:08.264549",
    "date_joined": "2014-12-26T23:11:59.200374"
}

Modification d'un membre donné

Fonctionnalité : Modifie les informations d'un membre donné.

URL : http://localhost:8000/api/membres/{id}/

Methode : PUT

Headers :

  • Authorization : Bearer < access token >

Paramètres :

  • Champ du modèle d'un utilisateur

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.
  • 400 : Dès qu'il y a un problème avec les paramètres de la requête.
  • 401 : Problème avec l'authentification.
  • 403 : Pas les permissions nécessaires.
  • 404 : Le membre avec l'identifiant donné n'existe pas.

Permissions nécessaires :

  • Être identifié (fournir dans Authorization dans l'en-tête de la requête).
  • L'identifiant renseigné correspond à l'identifiant de l'utilisateur authentifié.

Exemple de résultat pour l'URL http://localhost:8000/api/membres/1/ :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "id": 1,
    "username": "admin",
    "show_email": true,
    "email": "admin@email.com",
    "is_active": true,
    "site": "www.zestedesavoir.com",
    "avatar_url": "https://cdn-images.xda-developers.com/direct/1/6/3/5/9/8/6/yoda-droid.jpg",
    "biography": "testou",
    "sign": "Testi",
    "email_for_answer": false,
    "last_visit": "2015-01-11T17:27:08.264549",
    "date_joined": "2014-12-26T23:11:59.200374"
}

Lecture seule sur un membre

Fonctionnalité : Sanctionner un membre en lecture seule.

URL : http://localhost:8000/api/membres/{id}/lecture-seule/

Methode : POST ou DELETE

Headers :

  • Authorization : Bearer < access token >

Paramètres :

  • ls-jrs : Pour passer le membre en lecture seule temporairement.
  • ls-text : Pour donner une explication à la sanction au membre donné.

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.
  • 401 : Si un problème dans l'authentification (utilisateur supprimé ou inactif).
  • 403 : Pas les permissions nécessaires.
  • 404 : Le membre avec l'identifiant donné n'existe pas.

Permissions nécessaires :

  • Être identifié (fournir dans Authorization dans l'en-tête de la requête).
  • Avoir les permissions d'un staff.

Exemple de résultat pour l'URL http://localhost:8000/api/membres/{id}/lecture-seule/ :

1
2
3
4
5
6
7
8
9
{
    "id": 4,
    "username": "ïtrema",
    "email": "",
    "can_write": false,
    "end_ban_write": "2015-01-13T18:50:40.400991",
    "can_read": true,
    "end_ban_read": null
}

Bannissement d'un membre

Fonctionnalité : Sanctionner un membre par un bannissement.

URL : http://localhost:8000/api/membres/{id}/ban/

Methode : POST ou DELETE

Headers :

  • Authorization : Bearer < access token >

Paramètres :

  • ban-jrs : Pour passer le membre en bannissement temporairement.
  • ban-text : Pour donner une explication à la sanction au membre donné.

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.
  • 401 : Si un problème dans l'authentification (utilisateur supprimé ou inactif).
  • 403 : Pas les permissions nécessaires.
  • 404 : Le membre avec l'identifiant donné n'existe pas.

Permissions nécessaires :

  • Être identifié (fournir dans Authorization dans l'en-tête de la requête).
  • Avoir les permissions d'un staff.

Exemple de résultat pour l'URL http://localhost:8000/api/membres/{id}/ban/ :

1
2
3
4
5
6
7
8
9
{
    "id": 4,
    "username": "ïtrema",
    "email": "",
    "can_write": true,
    "end_ban_write": null,
    "can_read": false,
    "end_ban_read": "2015-01-13T18:51:14.315816"
}

Informations générales

Headers possibles

  • Accept : L'API accepte plusieurs rendus, à savoir le JSON (par défaut) et l'XML. Toutes les requêtes peuvent donc renvoyer un résultat dans ces deux formats.
  • Content-Type : L'API accepte plusieurs parsers, à savoir le JSON (par défaut), l'XML, le formulaire et le multi part (x-www-form-urlencoded).
  • Authorization : Renseigne le token d'authentification pour toutes les requêtes qui nécessitent une authentification.
  • ETag : Attention, header en réponse par le serveur. J'en reviens dans la section juste après.

Cache

Des systèmes de cache ont été installés. Au pluriel puisqu'il y en a deux :

  • ETag : Pour toutes les requêtes GET et PUT, un ETag est calculé par le serveur et renvoyé dans l'en-tête de la réponse (disponible sous le même nom, ETag). Il réagit à l'identique que la spécification de cette ZEP.
  • Cache : Toutes les requêtes GET disposent d'un cache de 15 minutes (paramétrable). Les requêtes peuvent donc passer de plus de 200ms à +/- 70ms. Attention, ces chiffres sont à prendre avec des pincettes puisque les tests ont été effectués en local avec ma configuration de ZdS. Mais il y aura d'office un gain dans le temps de réponse de la requête.

Les différences

Mettons le doigt sur les différences avec la spécification de la ZEP :

  • Les URLs ne sont pas sous la forme http://api.zestedesavoir.com/ mais http://www.zestedesavoir.com/api/. Cependant, ceci peut changer lors de la mise en production de la fonctionnalité.
  • User-Agent n'est pas obligatoire. Je ne suis pas parvenu à savoir comment faire et j'ai fini par me demander si c'était vraiment inutile.
  • CORS n'est pas supporté. J'ai pu trouvé une librairie simple à intégrer mais je ne suis pas assez compétent pour sa configuration.
  • Erreurs HTTP : Le code de l'erreur n'est pas renseigné dans le body de la réponse mais dans l'en-tête.
  • Le format X-Data-Format n'est pas supporté au vu de l'absence de nécessité.
  • La recherche et les associations (les éléments hors scopes) n'ont pas été intégrés.
  • Aucun versionning de l'API n'a été mise en oeuvre.

Voilà, je pense n'avoir rien oublié. En écrivant ce compte-rendu, j'ai pu décelé quelques bugs (notamment lié aux permissions comme vous avez pu le lire dans ce compte-rendu) que je vais corriger dans les heures ou jours à venir.

Après ça et si ce compte rendu va à tout le monde, il suffit de rebase notre ZEP par rapport au dépôt officiel et de faire la PR de la ZEP sur ce même dépôt.

+6 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte