251
doc/README.md
@@ -1,251 +0,0 @@
|
||||
# Índice
|
||||
|
||||
* [Funcionalidades](#funcionalidades)
|
||||
* [Registro de usuario](#registro-de-usuario)
|
||||
* [Perfil de usuario](#perfil-de-usuario)
|
||||
* [Perfiles de administrador, moderador y gestor](#perfiles-de-administrador,-moderador-y-gestor)
|
||||
* [Perfiles de evaluador y presidente de mesa](#perfiles-de-evaluador-y-presidente-de-mesa)
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
Actualmente CONSUL soporta:
|
||||
|
||||
* Registro y verificación de usuarios tanto en la misma aplicación como con distintos proveedores (Twitter, Facebook, Google).
|
||||
* Distintos perfiles de usuario, tanto ciudadanos individuales como organizaciones y cargos públicos.
|
||||
* Distintos perfiles de administración, gestión, evaluación y moderación.
|
||||
* Espacio permanente de debates y propuestas.
|
||||
* Comentarios anidados en debates y propuestas.
|
||||
* Presupuestos participativos a través de distintas fases.
|
||||
|
||||
## Registro de usuario
|
||||
|
||||
Para registrar un usuario nuevo es posible hacerlo en la propia aplicación, dando un nombre de usuario (nombre público que aparecerá en tus publicaciones), un correo electrónico y una contraseña con la que se accederá a la web. Se deben aceptar las condiciones de uso. El usuario debe confirmar su correo electrónico para poder iniciar sesión.
|
||||
|
||||

|
||||
|
||||
Por otro lado también se puede habilitar el registro a través de servicios externos como Twitter, Facebook y Google. Para esto hace falta tener la configuración habilitada en Settings y las claves y secretos de estos servicios en el fichero *config/secrets.yml*.
|
||||
|
||||
```
|
||||
twitter_key: ""
|
||||
twitter_secret: ""
|
||||
facebook_key: ""
|
||||
facebook_secret: ""
|
||||
google_oauth2_key: ""
|
||||
google_oauth2_secret: ""
|
||||
```
|
||||
|
||||
Una vez el usuario ha iniciado sesión le aparecerá la posibilidad de verificar su cuenta, a través de una conexión con el padrón municipal.
|
||||
|
||||

|
||||
|
||||
Para esta funcionalidad hace falta que el padrón municipal soporte la posibilidad de conexión a través de una API, puedes ver un ejemplo en *lib/census_api.rb*.
|
||||
|
||||

|
||||
|
||||
## Perfil de usuario
|
||||
|
||||
Dentro de su perfil ("Mi cuenta" en el menú superior) cada usuario puede configurar si quiere mostrar públicamente su lista de actividades, así como las notificaciones que le enviará la aplicación a través de correo electrónico. Estas notificiaciones pueden ser:
|
||||
|
||||
* Recibir un email cuando alguien comenta en sus propuestas o debates.
|
||||
* Recibir un email cuando alguien contesta a sus comentarios.
|
||||
* Recibir emails con información interesante sobre la web.
|
||||
* Recibir resumen de notificaciones sobre propuestas.
|
||||
* Recibir emails con mensajes privados.
|
||||
|
||||
## Perfiles de administrador, moderador y gestor
|
||||
|
||||
CONSUL cuenta con tres perfiles de usuario para administrar contenidos de la web: administrador, moderador y gestor. Además tiene otros dos perfiles para gestión de procesos participativos: [evaluador y presidente de mesa](#perfiles_de_evaluador,_gestor_y_presidente_de_mesa), que se detallan más abajo.
|
||||
|
||||
Los usuarios con perfil de administrador pueden asignar cualquier tipo de perfil a cualquier tipo de usuario. Sin embargo, todos los perfiles tienen que ser usuarios verificados (contrastados con el padrón municipal) para poder realizar ciertas acciones (por ejemplo, los gestores necesitan estar verificados para crear proyectos de gasto).
|
||||
|
||||
### Panel Administrar
|
||||
|
||||

|
||||
|
||||
Desde aquí puedes administrar el sistema, a través de los siguientes menús:
|
||||
|
||||
#### Categorías 
|
||||
|
||||
##### Temas de debates/propuestas
|
||||
|
||||
Los temas (también llamados tags, o etiquetas) son palabras que definen los usuarios al crear debates o propuestas para facilitar su catalogación (ej: sanidad, movilidad, arganzuela, ...). Desde aquí el administrador tiene las siguientes opciones:
|
||||
|
||||
* Crear temas nuevos
|
||||
* Eliminar temas inapropiados
|
||||
* Marcar temas para que aparezcan como sugerencia al crear debates/propuestas. Cada usuario puede crear los que quiera, pero el administrador puede sugerir algunos que le parezcan útiles como catalogación por defecto. Marcando "Proponer tema al crear la propuesta" en cada tema, establece cuáles se sugieren.
|
||||
|
||||
#### Contenido moderado 
|
||||
|
||||
##### Propuestas/Debates/Comentarios ocultos
|
||||
|
||||
Cuando un administrador o moderador oculta una Propuesta/Debate/Comentario desde la web, aparecerá en esta lista. De esta forma los administradores pueden revisar los elementos que se han ocultado y subsanar posibles errores.
|
||||
|
||||
* Al pulsar "Confirmar" se acepta el que se haya ocultado; se considera que se ha hecho correctamente.
|
||||
* Si se considera que la ocultación ha sido errónea, al pulsar "Volver a mostrar" se revierte la acción de ocultar y vuelve a ser una Propuesta/Debate/Comentario visible en la web.
|
||||
|
||||
Para facilitar la gestión, arriba encontramos un filtro con las secciones: "Pendientes" (los elementos sobre los que todavía no se ha pulsado "Confirmar" o "Volver a mostrar", que deberían ser revisados todavía), "Confirmadas" y "Todas".
|
||||
|
||||
Es recomendable revisar regularmente la sección "Pendientes".
|
||||
|
||||
#### Usuarios bloqueados
|
||||
|
||||
Cuando un moderador o un administrador bloquea a un usuario desde la web aparecerá en esta lista. Cuando un usuario está bloqueado no puede realizar acciones en la web, y todas sus Propuestas/Debates/Comentarios dejaran de ser visibles.
|
||||
|
||||
* Al pulsar "Confirmar" se acepta el bloqueo; se considera que se ha hecho correctamente.
|
||||
* Si se considera que el bloqueo ha sido erróneo, al pulsar "Volver a mostrar" se revierte el bloqueo y el usuario vuelve a estar activo.
|
||||
|
||||
#### Votaciones 
|
||||
|
||||
Se puede crear una votación pulsando "Crear votación" y definiendo un nombre, fecha de apertura y de cierre. Adicionalmente se puede restringir la votación a unas zonas determinadas marcando "Restringir por zonas". Las zonas disponibles se definen en el menú [Gestionar distritos](#gestionar-distritos).
|
||||
|
||||
Los usuarios tienen que estar verificados para poder participar en la votación.
|
||||
|
||||
Una vez que se ha creado la votación, se definen y se agregan sus componentes. Las votaciones tienen tres componentes: Preguntas ciudadanas, Presidentes de mesa y Ubicación de las urnas.
|
||||
|
||||
##### Preguntas ciudadanas
|
||||
|
||||
Se puede crear una pregunta ciudadana o buscar una existente. Al crear la pregunta se puede asignar a una votación determinada. También se puede modificar la asignación a una pregunta existente pulsando el ella y seleccionando "Editar".
|
||||
|
||||
Desde el apartado de Preguntas ciudadanas también se pueden asignar a una votación aquellas Propuestas ciudadanas que han superado el umbral de apoyos. Se pueden seleccionar desde la pestaña "Propuestas que han superado el umbral".
|
||||
|
||||
##### Presidentes de mesa
|
||||
|
||||
Cualquier usuario registrado en la web puede convertirse en Presidente de mesa. Para asignarle ese rol se introduce su email en el campo de búsqueda y una vez encontrado se asigna con "Añadir como Presidente de mesa". Cuando los presidentes acceden a la web con su usuario ven en la parte superior una nueva sección llamada "Presidentes de mesa".
|
||||
|
||||
##### Ubicación de las urnas
|
||||
|
||||
Para añadir una urna a la lista, seleccionar "Añadir urna" y a continuación completar los datos de nombre de la urna y ubicación.
|
||||
|
||||
#### Presupuestos participativos 
|
||||
|
||||
Desde esta sección se puede crear un presupuesto participativo seleccionando "Crear nuevo presupuesto" o editar uno existente. Al editar se puede cambiar la fase en la que se encuentra el proceso; este cambio se reflejará en la web. También se pueden crear grupos de partidas presupuestarias y agregar proyectos de gasto que hayan sido creadas previamente por un [gestor](#panel-gestión).
|
||||
|
||||
#### Perfiles 
|
||||
|
||||
##### Organizaciones
|
||||
|
||||
En la web cualquier usuario se puede registrar con un perfil individual o como una organización. Los usuarios de organizaciones pueden ser verificados por parte de los administradores, confirmando que quien gestiona el usuario efectivamente representa a esa organización. Una vez se haya realizado el proceso de verificación, por el proceso externo a la web que se haya definido para ello, se pulsa el botón "Verificar" para confimarlo; lo que hará que al lado del nombre de la organización aparezca una etiqueta señalando que es una organización verificada.
|
||||
|
||||
En caso de que el proceso de verificación haya sido negativo, se pulsa el botón "Rechazar". Para editar alguno de los datos de la organización, se pulsa el botón "Editar".
|
||||
|
||||
Las organizaciones que no aparecen en la lista pueden ser encontradas para actuar sobre ellas por medio del buscador en la parte superior. Para facilitar la gestión, arriba encontramos un filtro con las secciones: "pendientes" (las organizaciones que todavía no han sido verificadas o rechazadas), "verificadas", "rechazadas" y "todas".
|
||||
|
||||
Es recomendable revisar regularmente la sección "pendientes".
|
||||
|
||||
##### Cargos Públicos
|
||||
|
||||
La condición de cargo público no se puede elegir en el registro que se hace desde la web: se asigna directamente desde esta sección. El administrador busca un usuario introduciendo su email en el campo de búsqueda y le asigna el rol de Cargo público.
|
||||
|
||||
El cargo público se diferencia del usuario individual únicamente en que al lado de su nombre aparece una etiqueta que le identifica, y cambia ligeramente el estilo de sus comentarios. Esto permite que los usuarios le identifiquen más fácilmente. Al lado de cada cargo vemos la identificación que aparece en su etiqueta, y su nivel (la manera que internamente usa la web para diferenciar entre un tipo de cargos y otros). Pulsando el botón "Editar" al lado del usuario, se puede modificar su información. Los cargos públicos que no aparecen en la lista pueden ser encontrados para actuar sobre ellos por medio del buscador en la parte superior.
|
||||
|
||||
##### Moderadores
|
||||
|
||||
Cualquier usuario registrado en la web puede convertirse en moderador. Para asignarle ese rol se introduce su email en el campo de búsqueda y una vez encontrado se asigna con "Añadir como Moderador". Cuando los moderadores acceden a la web con su usuario ven en la parte superior una nueva sección llamada "Moderar".
|
||||
|
||||
Al seleccionar "Actividad de moderadores" aparece un listado de todas las acciones que realizan los moderadores: ocultar/mostrar Propuestas/Debates/Comentarios y bloquear usuarios. En la columna "Acción" comprobamos si la acción corresponde con ocultar o con volver a mostrar (restaurar) elementos o con bloquear usuarios. En las demás columnas tenemos el tipo de elemento, el contenido del elemento y el moderador o administrador que ha realizado la acción.
|
||||
|
||||
Esta sección permite que los administradores detecten comportamientos irregulares por parte de moderadores específicos y que por lo tanto puedan corregirlos.
|
||||
|
||||
##### Evaluadores
|
||||
|
||||
Cualquier usuario registrado en la web puede convertirse en evaluador. Para asignarle ese rol se introduce su email en el campo de búsqueda y una vez encontrado se asigna con "Añadir como evaluador". Cuando los evaluadores acceden a la web con su usuario ven en la parte superior una nueva sección llamada "Evaluación".
|
||||
|
||||
##### Gestores
|
||||
|
||||
Cualquier usuario registrado en la web puede convertirse en gestor. Para asignarle ese rol se introduce su email en el campo de búsqueda y una vez encontrado se asigna con "Añadir como gestor". Cuando los gestores acceden a la web con su usuario ven en la parte superior una nueva sección llamada "Gestión".
|
||||
|
||||
#### Banners 
|
||||
|
||||
Desde el menú "Gestionar banners" se pueden crear banners para hacer anuncios especiales que aparecerán siempre en la parte superior de la web, tanto en el apartado de debates como en el de propuestas. Para crearlo hay que seleccionar "Crear banner" e introducir sus datos y fechas de inicio y fin de publicación en formato ```dd/mm/aaa```.
|
||||
|
||||
Por defecto, en la web sólo aparecerá un banner. Si existen varios banners cuyas fechas indican que deberían estar activos, sólo se visualizará aquel cuya fecha de inicio de publicación sea más antigua.
|
||||
|
||||
#### Personalizar sitio 
|
||||
|
||||
##### Personalizar páginas
|
||||
|
||||
Las páginas sirven para mostrar cualquier tipo de contenido estático relativo a los procesos de participación. Al crear o editar una página se debe introducir un _slug_ para definir el _permalink_ de esa página en cuestión. Una vez creada, podemos acceder a ella desde el listado, seleccionando "Ver página".
|
||||
|
||||
##### Personalizar imágenes
|
||||
|
||||
Desde este panel se definen las imágenes de los elementos corporativos de tu CONSUL.
|
||||
|
||||
##### Personalizar bloques
|
||||
|
||||
Puedes crear bloques de HTML que se incrustarán en la cabecera o el pie de tu CONSUL.
|
||||
|
||||
Los bloques de la cabecera (top_links) son bloques de enlaces que deben crearse con este formato:
|
||||
|
||||
```
|
||||
<li><a href="http://site1.com">Site 1</a></li>
|
||||
<li><a href="http://site2.com">Site 2</a></li>
|
||||
<li><a href="http://site3.com">Site 3</a></li>
|
||||
```
|
||||
|
||||
Los bloques del pie (footer) pueden tener cualquier formato y se pueden utilizar para guardar huellas Javascript, contenido CSS o contenido HTML personalizado.
|
||||
|
||||
#### Gestionar distritos
|
||||
|
||||
Desde este menú se pueden crear los distintos distritos de un municipio con su nombre, coordenadas, código externo y código del censo.
|
||||
|
||||
#### Hojas de firmas
|
||||
|
||||
Con el fin de registrar apoyos externos a la plataforma, se pueden crear hojas de firmas de Propuestas ciudadanas o Proyectos de gasto introduciendo el ID de la propuesta en cuestión e introduciendo los números de los documentos separados por comas(,).
|
||||
|
||||
#### Estadísticas
|
||||
|
||||
Estadísticas generales del sistema.
|
||||
|
||||
#### Configuración global
|
||||
|
||||
Opciones generales de configuración del sistema.
|
||||
|
||||
### Panel Moderar
|
||||
|
||||

|
||||
|
||||
Desde aquí puedes moderar el sistema, a través de las siguientes acciones:
|
||||
|
||||
#### Propuestas / Debates / Comentarios
|
||||
|
||||
Cuando un usuario marca en una Propuesta/Debate/Comentario la opción de "denunciar como inapropiado", aparecerá en esta lista. Respecto a cada uno aparecerá el título, fecha, número de denuncias (cuántos usuarios diferentes han marcado la opción de denuncia) y el texto de la Propuesta/Debate/Comentario.
|
||||
|
||||
A la derecha de cada elemento aparece una caja que podemos marcar para seleccionar todos los que queramos de la lista. Una vez seleccionados uno o varios, encontramos al final de la página tres botones para realizar acciones sobre ellos:
|
||||
|
||||
* Ocultar: hará que esos elementos dejen de mostrarse en la web.
|
||||
* Bloquear autores: hará que el autor de ese elemento deje de poder acceder a la web, y que además todas las Propuestas/Debates/Comentarios de ese usuario dejen de mostrarse en la web.
|
||||
* Marcar como revisados cuando consideramos que esos elementos no deben ser moderados, que su contenido es correcto, y que por lo tanto deben dejar de ser mostrados en esta lista de elementos inapropiados.
|
||||
|
||||
Para facilitar la gestión, arriba encontramos un filtro con las secciones:
|
||||
|
||||
* Pendientes: las Propuestas/Debates/Comentarios sobre los que todavía no se ha pulsado "ocultar", "bloquear" o "marcar como revisados", y que por lo tanto deberían ser revisados todavía.
|
||||
* Todos: mostrando todos las Propuestas/Debates/Comentarios de la web, y no sólo los marcados como inapropiados.
|
||||
* Marcados como revisados: los que algún moderador ha marcado como revisados y por lo tanto parecen correctos.
|
||||
|
||||
Es recomendable revisar regularmente la sección "pendientes".
|
||||
|
||||
#### Bloquear usuarios
|
||||
|
||||
Un buscador nos permite encontrar cualquier usuario introduciendo su nombre de usuario o correo electrónico, y bloquearlo una vez encontrado. Al bloquearlo, el usuario no podrá volver a acceder a la web, y todas sus Propuestas/Debates/Comentarios serán ocultados y dejarán de ser visibles en la web.
|
||||
|
||||
### Panel Gestión
|
||||
|
||||

|
||||
|
||||
Desde aquí puedes gestionar usuarios a través de las siguientes acciones:
|
||||
|
||||
* Usuarios.
|
||||
* Editar cuenta de usuario.
|
||||
* Crear propuesta.
|
||||
* Apoyar propuestas.
|
||||
* Crear proyecto de gasto.
|
||||
* Apoyar proyectos de gasto.
|
||||
* Imprimir propuestas.
|
||||
* Imprimir proyectos de gasto.
|
||||
* Invitaciones para usuarios.
|
||||
|
||||
## Perfiles de evaluador y presidente de mesa
|
||||
|
||||
### Panel Evaluación
|
||||
|
||||
### Panel Presidentes de mesa
|
||||
@@ -1,431 +0,0 @@
|
||||
# API Documentation
|
||||
|
||||
* [Characteristics](#characteristics)
|
||||
* [GraphQL](#graphql)
|
||||
* [Making API requests](#making-api-requests)
|
||||
* [Supported clients](#supported-clients)
|
||||
* [GraphiQL](#graphiql)
|
||||
* [Postman](#postman)
|
||||
* [HTTP libraries](#http-libraries)
|
||||
* [Available information](#available-information)
|
||||
* [Examples of queries](#examples-of-queries)
|
||||
* [Request a single record from a collection](#request-a-single-record-from-a-collection)
|
||||
* [Request a complete collection](#request-a-complete-collection)
|
||||
* [Pagination](#pagination)
|
||||
* [Accessing several resources in a single request](#accessing-several-resources-in-a-single-request)
|
||||
* [Security limitations](#security-limitations)
|
||||
* [Example of too deep query](#example-of-too-deep-query)
|
||||
* [Example of too complex query](#example-of-too-complex-query)
|
||||
* [Code examples](#code-examples)
|
||||
|
||||
## Characteristics
|
||||
|
||||
* Read-only API
|
||||
* Public access, no authentication needed
|
||||
* Uses GraphQL technology:
|
||||
* Maximum page size (and the default) is 25 records
|
||||
* Maximum query depth is set at 8 levels
|
||||
* A maximum of two collections can be requested within the same query
|
||||
* Support for GET requests (query must be inside the *query string*) and POST requests (query must be within the *body*, encoded as `application/json` or `application/graphql`)
|
||||
|
||||
## GraphQL
|
||||
|
||||
The CONSUL API uses GraphQL [http://graphql.org](http://graphql.org), the [Ruby implementation](http://graphql-ruby.org/), to be specific. If you're not familiar with this kind of APIs, it's recommended to make some research about GraphQL before.
|
||||
|
||||
One of the characteristics that differentiates a REST API from a GraphQL one is that with the last one it's possible for the client to build its own *custom queries*, so the server will only return information in which we're interested.
|
||||
|
||||
GraphQL queries are written following a standard which ressembles to JSON, for example:
|
||||
|
||||
```
|
||||
{
|
||||
proposal(id: 1) {
|
||||
id,
|
||||
title,
|
||||
public_author {
|
||||
id,
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Responses are formatted in JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposal": {
|
||||
"id": 1,
|
||||
"title": "Hacer las calles del centro de Madrid peatonales",
|
||||
"public_author": {
|
||||
"id": 2,
|
||||
"username": "electrocronopio"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Making API requests
|
||||
|
||||
Following [the official recommendations](http://graphql.org/learn/serving-over-http/), the CONSUL API supports the following kind of requests:
|
||||
|
||||
* GET requests, with the query inside the *query string*.
|
||||
* POST requests
|
||||
* With the query inside the *body*, with `Content-Type: application/json`
|
||||
* With the query inside the *body*, with `Content-Type: application/graphql`
|
||||
|
||||
### Supported clients
|
||||
|
||||
Because it's an API that works through HTTP, any tool capable of making this kind of requests is capable of querying the API.
|
||||
|
||||
This section presents a few examples about how to make requests using:
|
||||
|
||||
* GraphiQL
|
||||
* Chrome extensions like Postman
|
||||
* Any HTTP library
|
||||
|
||||
#### GraphiQL
|
||||
|
||||
[GraphiQL](https://github.com/graphql/graphiql) is a browser interface for making queries against a GraphQL API. It's also an additional source of documentation. It's deployed in the route `/graphiql` and it's the best way to get familiar with GraphQL-based APIs.
|
||||
|
||||

|
||||
|
||||
It has three main panels:
|
||||
|
||||
* The left panel is used to write the query.
|
||||
* The central panel shows the result of the request.
|
||||
* The right panel (occultable) shows a documentation autogenerated from the models and fields exposed in the API.
|
||||
|
||||
#### Postman
|
||||
|
||||
Example of `GET` request, with the query as part of the *query string*:
|
||||
|
||||

|
||||
|
||||
Example of `POST` request, with the query as part of the *body* and encoded as `application/json`:
|
||||
|
||||

|
||||
|
||||
The query must be located inside a valid JSON document, as the value of the `"query"` key:
|
||||
|
||||

|
||||
|
||||
#### HTTP libraries
|
||||
|
||||
Sure you can use any HTTP library available for most programming languages.
|
||||
|
||||
**IMPORTANT**: Due to security protocols from the Madrid City Council servers, it's necessary to include a *User Agent* header from a web browser so the request is not rejected. For example:
|
||||
|
||||
`User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36`
|
||||
|
||||
## Available information
|
||||
|
||||
The [config/api.yml](../../config/api.yml) file contains a complete list of all the models (and their attributes) which are currently being exposed in the API.
|
||||
|
||||
The models are the following:
|
||||
|
||||
| Model | Description |
|
||||
| ----------------------- | ----------------------------- |
|
||||
| `User` | Users |
|
||||
| `Debate` | Debates |
|
||||
| `Proposal` | Proposals |
|
||||
| `Comment` | Comments on debates, proposals and other comments |
|
||||
| `Geozone` | Geozones (districts) |
|
||||
| `ProposalNotification` | Notifications related to proposals |
|
||||
| `Tag` | Tags on debates and proposals |
|
||||
| `Vote` | Information related to votes |
|
||||
|
||||
## Examples of queries
|
||||
|
||||
### Request a single record from a collection
|
||||
|
||||
```
|
||||
{
|
||||
proposal(id: 2) {
|
||||
id,
|
||||
title,
|
||||
comments_count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposal": {
|
||||
"id": 2,
|
||||
"title": "Crear una zona cercada para perros en Las Tablas",
|
||||
"comments_count": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Request a complete collection
|
||||
|
||||
```
|
||||
{
|
||||
proposals {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposals": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"title": "ELIMINACION DE ZONA APARCAMIENTO EXCLUSIVO FUNCIONARIOS EN MADRID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"title": "iluminación de zonas deportivas"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Pagination
|
||||
|
||||
The maximum (and default) number of records that each page contains is set to 25. For navigating through the different pages it's necessary to request also information relative to the `endCursor`:
|
||||
|
||||
```
|
||||
{
|
||||
proposals(first: 25) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposals": {
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": "NQ=="
|
||||
},
|
||||
"edges": [
|
||||
# ...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To retrieve the next page, you have to pass as a parameter the cursor received in the previous request, and so on:
|
||||
|
||||
```
|
||||
{
|
||||
proposals(first: 25, after: "NQ==") {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing several resources in a single request
|
||||
|
||||
This query requests information about several models in a single request: `Proposal`, `User`, `Geozone` and `Comment`:
|
||||
|
||||
```
|
||||
{
|
||||
proposal(id: 15262) {
|
||||
id,
|
||||
title,
|
||||
public_author {
|
||||
username
|
||||
},
|
||||
geozone {
|
||||
name
|
||||
},
|
||||
comments(first: 2) {
|
||||
edges {
|
||||
node {
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security limitations
|
||||
|
||||
Allowing a client to customize queries is a major risk factor. If too complex queries were allowed, it would be possible to perform a DoS attack against the server.
|
||||
|
||||
There are three main mechanisms to prevent such abuses:
|
||||
|
||||
* Pagination of results
|
||||
* Limit the maximum depth of the queries
|
||||
* Limit the amount of information that is possible to request in a query
|
||||
|
||||
### Example of too deep query
|
||||
|
||||
The maximum depth of queries is currently set at 8. Deeper queries (such as the following) will be rejected:
|
||||
|
||||
```
|
||||
{
|
||||
user(id: 1) {
|
||||
public_proposals {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
title,
|
||||
comments {
|
||||
edges {
|
||||
node {
|
||||
body,
|
||||
public_author {
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The response will look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Query has depth of 9, which exceeds max depth of 8"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example of too complex query
|
||||
|
||||
The main risk factor is when multiple collections of resources are requested in the same query. The maximum number of collections that can appear in the same query is limited to 2. The following query requests information from the `users`, `debates` and `proposals` collections, so it will be rejected:
|
||||
|
||||
```
|
||||
{
|
||||
users {
|
||||
edges {
|
||||
node {
|
||||
public_debates {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
},
|
||||
public_proposals {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The response will look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Query has complexity of 3008, which exceeds max complexity of 2500"
|
||||
},
|
||||
{
|
||||
"message": "Query has complexity of 3008, which exceeds max complexity of 2500"
|
||||
},
|
||||
{
|
||||
"message": "Query has complexity of 3008, which exceeds max complexity of 2500"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
However, it is possible to request information belonging to more than two models in a single query, as long as you do not try to access the entire collection. For example, the following query that accesses the `User`, `Proposal` and `Geozone` models is valid:
|
||||
|
||||
```
|
||||
{
|
||||
user(id: 468501) {
|
||||
id
|
||||
public_proposals {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
geozone {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 468501,
|
||||
"public_proposals": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"title": "Empadronamiento necesario para la admisión en GoFit Vallehermoso",
|
||||
"geozone": {
|
||||
"name": "Chamberí"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Code examples
|
||||
|
||||
The [doc/api/examples](examples) directory contains examples of code to access the API.
|
||||
@@ -1,431 +0,0 @@
|
||||
# Documentación de la API
|
||||
|
||||
* [Características](#características)
|
||||
* [GraphQL](#graphql)
|
||||
* [Haciendo peticiones a la API](#haciendo-peticiones-a-la-api)
|
||||
* [Clientes soportados](#clientes-soportados)
|
||||
* [GraphiQL](#graphiql)
|
||||
* [Postman](#postman)
|
||||
* [Librerías HTTP](#librerías-http)
|
||||
* [Información disponible](#información-disponible)
|
||||
* [Ejemplos de consultas](#ejemplos-de-consultas)
|
||||
* [Recuperar un único elemento de una colección](#recuperar-un-único-elemento-de-una-colección)
|
||||
* [Recuperar una colección completa](#recuperar-una-colección-completa)
|
||||
* [Paginación](#paginación)
|
||||
* [Acceder a varios recursos en una única petición](#acceder-a-varios-recursos-en-una-única-petición)
|
||||
* [Limitaciones de seguridad](#limitaciones-de-seguridad)
|
||||
* [Ejemplo de consulta demasiado profunda](#ejemplo-de-consulta-demasiado-profunda)
|
||||
* [Ejemplo de consulta demasiado compleja](#ejemplo-de-consulta-demasiado-compleja)
|
||||
* [Ejemplos de código](#ejemplos-de-código)
|
||||
|
||||
## Características
|
||||
|
||||
* API de sólo lectura
|
||||
* Acceso público, sin autenticación
|
||||
* Usa GraphQL por debajo
|
||||
* El tamaño máximo (y por defecto) de página está establecido a 25
|
||||
* La profundiad máxima de las consultas es de 8 niveles
|
||||
* Como máximo se pueden solicitar 2 colecciones en una misma consulta
|
||||
* Soporte para peticiones GET (consulta dentro del *query string*) y POST (consulta dentro del *body*, como `application/json` o `application/graphql`).
|
||||
|
||||
## GraphQL
|
||||
|
||||
La API de CONSUL utiliza GraphQL [http://graphql.org](https://graphql.org), en concreto la [implementación en Ruby](http://graphql-ruby.org/). Si no estás familiarizado con este tipo de APIs, es recomendable investigar un poco sobre GraphQL previamente.
|
||||
|
||||
Una de las caracteríticas que diferencian una API REST de una GraphQL es que con esta última es posible construir *consultas personalizadas*, de forma que el servidor nos devuelva únicamente la información en la que estamos interesados.
|
||||
|
||||
Las consultas en GraphQL están escritas siguiendo un estándar que presenta ciertas similitudes con el formato JSON, por ejemplo:
|
||||
|
||||
```
|
||||
{
|
||||
proposal(id: 1) {
|
||||
id,
|
||||
title,
|
||||
public_author {
|
||||
id,
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Las respuestas son en formato JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposal": {
|
||||
"id": 1,
|
||||
"title": "Hacer las calles del centro de Madrid peatonales",
|
||||
"public_author": {
|
||||
"id": 2,
|
||||
"username": "electrocronopio"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Haciendo peticiones a la API
|
||||
|
||||
Siguiendo las [directrices oficiales](http://graphql.org/learn/serving-over-http/), la API de CONSUL soporta los siguientes tipos de peticiones:
|
||||
|
||||
* Peticiones GET, con la consulta dentro del *query string*.
|
||||
* Peticiones POST
|
||||
* Con la consulta dentro del *body*, con `Content-Type: application/json`
|
||||
* Con la consulta dentro del *body*, con `Content-Type: application/graphql`
|
||||
|
||||
### Clientes soportados
|
||||
|
||||
Al ser una API que funciona a través de HTTP, cualquier herramienta capaz de realizar este tipo de peticiones resulta válida.
|
||||
|
||||
Esta sección contiene unos pequeños ejemplos sobre cómo hacer las peticiones a través de:
|
||||
|
||||
* GraphiQL
|
||||
* Extensiones de Chrome como Postman
|
||||
* Cualquier librería HTTP
|
||||
|
||||
#### GraphiQL
|
||||
|
||||
[GraphiQL](https://github.com/graphql/graphiql) es una interfaz de navegador para realizar consultas a una API GraphQL, así como una fuente adicional de documentación. Está desplegada en la ruta `/graphiql` y es la mejor forma de familiarizarse una API basada en GraphQL.
|
||||
|
||||

|
||||
|
||||
Tiene tres paneles principales:
|
||||
|
||||
* En el panel de la izquierda se escribe la consulta a realizar.
|
||||
* El panel central muestra el resultado de la petición.
|
||||
* El panel derecho (ocultable) contiene una documentación autogenerada a partir de la información expuesta en la API.
|
||||
|
||||
#### Postman
|
||||
|
||||
Ejemplo de petición `GET`, con la consulta como parte del *query string*:
|
||||
|
||||

|
||||
|
||||
Ejemplo de petición `POST`, con la consulta como parte del *body* y codificada como `application/json`:
|
||||
|
||||

|
||||
|
||||
La consulta debe estar ubicada en un documento JSON válido, como valor de la clave `"query"`:
|
||||
|
||||

|
||||
|
||||
#### Librerías HTTP
|
||||
|
||||
Por supuesto es posible utilizar cualquier librería HTTP de lenguajes de programación.
|
||||
|
||||
**IMPORTANTE**: Debido a los protocolos de seguridad de los servidores del Ayuntamiento de Madrid, es necesario incluir un *User Agent* perteneciente a un navegador para que la petición no sea descartada. Por ejemplo:
|
||||
|
||||
`User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36`
|
||||
|
||||
## Información disponible
|
||||
|
||||
El fichero [config/api.yml](../../config/api.yml) contiene una lista completa de los modelos (y sus campos) que están expuestos actualmente en la API.
|
||||
|
||||
La lista de modelos es la siguiente:
|
||||
|
||||
| Modelo | Descripción |
|
||||
| ----------------------- | ---------------------------- |
|
||||
| `User` | Usuarios |
|
||||
| `Debate` | Debates |
|
||||
| `Proposal` | Propuestas |
|
||||
| `Comment` | Comentarios en debates, propuestas y otros comentarios |
|
||||
| `Geozone` | Geozonas (distritos) |
|
||||
| `ProposalNotification` | Notificaciones asociadas a propuestas |
|
||||
| `Tag` | Tags en debates y propuestas |
|
||||
| `Vote` | Información sobre votos |
|
||||
|
||||
## Ejemplos de consultas
|
||||
|
||||
### Recuperar un único elemento de una colección
|
||||
|
||||
```
|
||||
{
|
||||
proposal(id: 2) {
|
||||
id,
|
||||
title,
|
||||
comments_count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Respuesta:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposal": {
|
||||
"id": 2,
|
||||
"title": "Crear una zona cercada para perros en Las Tablas",
|
||||
"comments_count": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Recuperar una colección completa
|
||||
|
||||
```
|
||||
{
|
||||
proposals {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Respuesta:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposals": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"title": "ELIMINACION DE ZONA APARCAMIENTO EXCLUSIVO FUNCIONARIOS EN MADRID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"title": "iluminación de zonas deportivas"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Paginación
|
||||
|
||||
Actualmente el número máximo (y por defecto) de elementos que se devuelven en cada página está establecido a 25. Para poder navegar por las distintas páginas es necesario solicitar además información relativa al `endCursor`:
|
||||
|
||||
```
|
||||
{
|
||||
proposals(first: 25) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
La respuesta:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"proposals": {
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": "NQ=="
|
||||
},
|
||||
"edges": [
|
||||
# ...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Para recuperar la siguiente página, hay que pasar como parámetro el cursor recibido en la petición anterior, y así sucesivamente:
|
||||
|
||||
```
|
||||
{
|
||||
proposals(first: 25, after: "NQ==") {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Acceder a varios recursos en una única petición
|
||||
|
||||
Esta consulta solicita información relativa a varios modelos distintos en una única peticion: `Proposal`, `User`, `Geozone` y `Comment`:
|
||||
|
||||
```
|
||||
{
|
||||
proposal(id: 15262) {
|
||||
id,
|
||||
title,
|
||||
public_author {
|
||||
username
|
||||
},
|
||||
geozone {
|
||||
name
|
||||
},
|
||||
comments(first: 2) {
|
||||
edges {
|
||||
node {
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Limitaciones de seguridad
|
||||
|
||||
Permitir que un cliente personalice las consultas supone un factor de riesgo importante. Si se permitiesen consultas demasiado complejas, sería posible realizar un ataque DoS contra el servidor.
|
||||
|
||||
Existen tres mecanismos principales para evitar este tipo de abusos:
|
||||
|
||||
* Paginación de resultados
|
||||
* Limitar la profundidad máxima de las consultas
|
||||
* Limitar la cantidad de información que es posible solicitar en una consulta
|
||||
|
||||
### Ejemplo de consulta demasiado profunda
|
||||
|
||||
La profundidad máxima de las consultas está actualmente establecida en 8. Consultas más profundas (como la siguiente), serán rechazadas:
|
||||
|
||||
```
|
||||
{
|
||||
user(id: 1) {
|
||||
public_proposals {
|
||||
edges {
|
||||
node {
|
||||
id,
|
||||
title,
|
||||
comments {
|
||||
edges {
|
||||
node {
|
||||
body,
|
||||
public_author {
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La respuesta obtenida tendrá el siguiente aspecto:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Query has depth of 9, which exceeds max depth of 8"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Ejemplo de consulta demasiado compleja
|
||||
|
||||
El principal factor de riesgo se da cuando se solicitan varias colecciones de recursos en una misma consulta. El máximo número de colecciones que pueden aparecer en una misma consulta está limitado a 2. La siguiente consulta solicita información de las colecciónes `users`, `debates` y `proposals`, así que será rechazada:
|
||||
|
||||
```
|
||||
{
|
||||
users {
|
||||
edges {
|
||||
node {
|
||||
public_debates {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
},
|
||||
public_proposals {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La respuesta obtenida tendrá el siguiente aspecto:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Query has complexity of 3008, which exceeds max complexity of 2500"
|
||||
},
|
||||
{
|
||||
"message": "Query has complexity of 3008, which exceeds max complexity of 2500"
|
||||
},
|
||||
{
|
||||
"message": "Query has complexity of 3008, which exceeds max complexity of 2500"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
No obstante sí que es posible solicitar información perteneciente a más de dos modelos en una única consulta, siempre y cuando no se intente acceder a la colección completa. Por ejemplo, la siguiente consulta que accede a los modelos `User`, `Proposal` y `Geozone` es válida:
|
||||
|
||||
```
|
||||
{
|
||||
user(id: 468501) {
|
||||
id
|
||||
public_proposals {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
geozone {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La respuesta:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 468501,
|
||||
"public_proposals": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"title": "Empadronamiento necesario para la admisión en GoFit Vallehermoso",
|
||||
"geozone": {
|
||||
"name": "Chamberí"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ejemplos de código
|
||||
|
||||
El directorio [doc/api/examples](examples) contiene ejemplos de código para acceder a la API.
|
||||
@@ -1,13 +1,16 @@
|
||||
require "http"
|
||||
require "net/http"
|
||||
|
||||
API_ENDPOINT = "https://decide.madrid.es/graphql".freeze
|
||||
API_ENDPOINT = "https://demo.consulproject.org/graphql".freeze
|
||||
|
||||
def make_request(query_string)
|
||||
HTTP.headers("User-Agent" => "Mozilla/5.0", accept: "application/json")
|
||||
.get(
|
||||
API_ENDPOINT,
|
||||
params: { query: query_string.delete("\n").delete(" ") }
|
||||
)
|
||||
uri = URI(API_ENDPOINT)
|
||||
uri.query = URI.encode_www_form(query: query_string.delete("\n").delete(" "))
|
||||
request = Net::HTTP::Get.new(uri)
|
||||
request[:accept] = "application/json"
|
||||
|
||||
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |https|
|
||||
https.request(request)
|
||||
end
|
||||
end
|
||||
|
||||
query = <<-GRAPHQL
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
require "http"
|
||||
require "net/http"
|
||||
require "json"
|
||||
|
||||
API_ENDPOINT = "https://decide.madrid.es/graphql".freeze
|
||||
API_ENDPOINT = "https://demo.consulproject.org/graphql".freeze
|
||||
|
||||
def make_request(query_string)
|
||||
HTTP.headers("User-Agent" => "Mozilla/5.0", accept: "application/json")
|
||||
.get(
|
||||
API_ENDPOINT,
|
||||
params: { query: query_string.delete("\n").delete(" ") }
|
||||
)
|
||||
uri = URI(API_ENDPOINT)
|
||||
uri.query = URI.encode_www_form(query: query_string.delete("\n").delete(" "))
|
||||
request = Net::HTTP::Get.new(uri)
|
||||
request[:accept] = "application/json"
|
||||
|
||||
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |https|
|
||||
https.request(request)
|
||||
end
|
||||
end
|
||||
|
||||
def build_query(options = {})
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 54 KiB |