From 11b178629423bdad39a2b95752f47be0891bb9ed Mon Sep 17 00:00:00 2001
From: Guilhem CARRON <gcarron@grandlyon.com>
Date: Fri, 24 Sep 2021 12:57:06 +0000
Subject: [PATCH] featt: Add backoffice first version with monthlyreport and
 authentification

---
 .gitignore                                |   2 +
 .gitlab-ci.yml                            |  34 ++
 .vscode/launch.json                       |  69 +++
 Dockerfile                                |  70 +++
 README.md                                 |  30 +-
 configs/tokenskey.json                    |   3 +
 dev_certificates/RootCA.crt               |  20 +
 dev_certificates/RootCA.key               |  28 +
 dev_certificates/RootCA.pem               |  20 +
 dev_certificates/RootCA.srl               |   1 +
 dev_certificates/domains.ext              |   6 +
 dev_certificates/generate-certificates.sh |   6 +
 dev_certificates/localhost.crt            |  21 +
 dev_certificates/localhost.csr            |  17 +
 dev_certificates/localhost.key            |  28 +
 docker-compose.yml                        |  61 ++
 docs/docs.go                              | 694 ++++++++++++++++++++++
 docs/swagger.json                         | 626 +++++++++++++++++++
 docs/swagger.yaml                         | 416 +++++++++++++
 go.mod                                    |  13 +
 go.sum                                    | 465 +++++++++++++++
 internal/auth/auth.go                     | 215 +++++++
 internal/auth/oauth2.go                   | 143 +++++
 internal/common/common.go                 | 173 ++++++
 internal/common/common_test.go            |  86 +++
 internal/file/file.go                     |  38 ++
 internal/file/file_test.go                |  38 ++
 internal/mocks/mocks.go                   |  71 +++
 internal/models/models.go                 |  45 ++
 internal/models/monthlyInfo.go            | 155 +++++
 internal/models/monthlyNews.go            | 155 +++++
 internal/models/monthlyReport.go          |  96 +++
 internal/models/poll.go                   | 152 +++++
 internal/rootmux/rootmux.go               |  69 +++
 internal/rootmux/rootmux_test.go          | 196 ++++++
 internal/tester/tester.go                 |  92 +++
 internal/tokens/tokens.go                 | 232 ++++++++
 internal/tokens/tokens_test.go            |  64 ++
 main.go                                   |  44 ++
 template.env                              |  20 +
 40 files changed, 4712 insertions(+), 2 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 create mode 100644 .vscode/launch.json
 create mode 100644 Dockerfile
 create mode 100644 configs/tokenskey.json
 create mode 100644 dev_certificates/RootCA.crt
 create mode 100644 dev_certificates/RootCA.key
 create mode 100644 dev_certificates/RootCA.pem
 create mode 100644 dev_certificates/RootCA.srl
 create mode 100644 dev_certificates/domains.ext
 create mode 100755 dev_certificates/generate-certificates.sh
 create mode 100644 dev_certificates/localhost.crt
 create mode 100644 dev_certificates/localhost.csr
 create mode 100644 dev_certificates/localhost.key
 create mode 100644 docker-compose.yml
 create mode 100644 docs/docs.go
 create mode 100644 docs/swagger.json
 create mode 100644 docs/swagger.yaml
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 internal/auth/auth.go
 create mode 100644 internal/auth/oauth2.go
 create mode 100644 internal/common/common.go
 create mode 100644 internal/common/common_test.go
 create mode 100644 internal/file/file.go
 create mode 100644 internal/file/file_test.go
 create mode 100644 internal/mocks/mocks.go
 create mode 100644 internal/models/models.go
 create mode 100644 internal/models/monthlyInfo.go
 create mode 100644 internal/models/monthlyNews.go
 create mode 100644 internal/models/monthlyReport.go
 create mode 100644 internal/models/poll.go
 create mode 100644 internal/rootmux/rootmux.go
 create mode 100644 internal/rootmux/rootmux_test.go
 create mode 100644 internal/tester/tester.go
 create mode 100644 internal/tokens/tokens.go
 create mode 100644 internal/tokens/tokens_test.go
 create mode 100644 main.go
 create mode 100644 template.env

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8c09811
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.env
+backoffice.db
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..abddb22
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,34 @@
+#
+# Ce fichier doit être adapté en fonction du projet en renseignant les variables SONAR_PROJECT_KEY et SONAR_TOKEN dans la configuration graphique du projet (https://forge.grandlyon.com/<CHEMIN_DE_VOTRE_PROJET>/settings/ci_cd)
+# La variable SONAR_PROJET_KEY peut être trouvée sur https://sonarqube.forge.grandlyon.com/dashboard en ouvrant le projet et en copiant collant le champ en bas à droite (Project Key)
+#
+# La variable SONAR_TOKEN doit être générée par le responsable du projet depuis son interface sonar : https://sonarqube.forge.grandlyon.com/account/security/
+#
+
+image: docker:git
+
+services:
+  - docker:dind
+
+variables:
+  DOCKER_DRIVER: overlay2
+  DOCKER_TLS_CERTDIR: ""
+  GIT_STRATEGY: clone
+  GIT_DEPTH: 0
+
+stages:
+  - build
+
+build:
+  image: docker:18.09
+  services:
+    - docker:18.09-dind
+  stage: build
+  script:
+    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
+    - DOCKER_BUILDKIT=1 docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" .
+    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
+  only:
+    - dev
+    - master
+
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..37a6cd1
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,69 @@
+{
+  // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
+  // Pointez pour afficher la description des attributs existants.
+  // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Debug Back-office with Mock OAuth2",
+      "type": "go",
+      "request": "launch",
+      "mode": "debug",
+      "remotePath": "",
+      "port": 2345,
+      "host": "127.0.0.1",
+      "program": "${workspaceFolder}/main.go",
+      "env": {
+        "REDIRECT_URL": "https://localhost:1443/OAuth2Callback",
+        "CLIENT_ID": "foo",
+        "CLIENT_SECRET": "bar",
+        "AUTH_URL": "http://localhost:8090/auth",
+        "TOKEN_URL": "http://localhost:8090/token",
+        "USERINFO_URL": "http://localhost:8090/admininfo",
+        "LOGOUT_URL": "/",
+        "HOSTNAME": "localhost",
+        "ADMIN_ROLE" : "ADMINS",
+        "INMEMORY_TOKEN_LIFE_DAYS": "2",
+        "DEBUG_MODE": "true",
+        "HTTPS_PORT": "1443",
+        "DATABASE_USER": "root",
+        "DATABASE_PASSWORD": "password",
+        "DATABASE_NAME": "backoffice",
+        "DATABASE_HOST": "127.0.0.1",
+      },
+      "showLog": true
+    },
+    {
+      "name": "Debug Back-office with Sign&Go",
+      "type": "go",
+      "request": "launch",
+      "mode": "debug",
+      "remotePath": "",
+      "port": 2345,
+      "host": "127.0.0.1",
+      "program": "${workspaceFolder}/main.go",
+      "env": {
+        "REDIRECT_URL": "https://localhost:1443/OAuth2Callback",
+        "CLIENT_ID": "<GET ONE FROM YOUR IDP>",
+        "CLIENT_SECRET": "<GET ONE FROM YOUR IDP>",
+        "AUTH_URL": "https://connexion-rec.grandlyon.fr/IdPOAuth2/authorize/oidc-rec",
+        "TOKEN_URL": "https://connexion-rec.grandlyon.fr/IdPOAuth2/token/oidc-rec",
+        "USERINFO_URL": "https://connexion-rec.grandlyon.fr/IdPOAuth2/userinfo/oidc-rec",
+        "LOGOUT_URL": "https://connexion-rec.grandlyon.fr/auth/logout.jsp",
+        "ADMIN_ROLE": "GGD_ORG_DG-DEES-DINSI-DAAG_TOUS",
+        "HOSTNAME": "ecolyobackoffice.127.0.0.1.nip.io",
+        "DEBUG_MODE": "true",
+        "HTTPS_PORT": "1443"
+      },
+      "showLog": true
+    },
+    {
+      "name": "Debug back office client",
+      "type": "firefox",
+      "request": "launch",
+      "reAttach": true,
+      "url": "https://ecolyobackoffice.127.0.0.1.nip.io:1443",
+      "webRoot": "${workspaceFolder}/web"
+    }
+  ]
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8aa2263
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,70 @@
+# Dockerfile from https://github.com/chemidy/smallest-secured-golang-docker-image
+
+##################################
+# STEP 1 build executable binary #
+##################################
+
+FROM golang:alpine as builder
+
+# Install git + SSL ca certificates.
+# Git is required for fetching the dependencies.
+# Ca-certificates is required to call HTTPS endpoints.
+RUN apk update && apk add --no-cache git ca-certificates tzdata libcap mailcap && update-ca-certificates
+RUN apk add build-base
+
+# Create appuser
+ENV USER=appuser
+ENV UID=1000
+# See https://stackoverflow.com/a/55757473/12429735
+RUN adduser \
+    --disabled-password \
+    --gecos "" \
+    --home "/nonexistent" \
+    --shell "/sbin/nologin" \
+    --no-create-home \
+    --uid "${UID}" \
+    "${USER}"
+
+WORKDIR /app
+
+ADD . .
+
+RUN chown -Rf "${UID}" ./*
+
+# Get dependencies and run tests
+RUN go version
+RUN go get -d -v
+RUN CGO_ENABLED=1 go test ./...
+
+# Build the binary
+RUN CGO_ENABLED=1 go build \
+    -ldflags='-w -s -extldflags "-static"' -a \
+    -o /app/backoffice-server .
+
+# Allow running on ports < 1000
+RUN setcap cap_net_bind_service=+ep /app/backoffice-server
+
+##############################
+# STEP 2 build a small image #
+##############################
+FROM alpine:3.14.0
+
+WORKDIR /app
+
+# Import global resources from builder
+COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+COPY --from=builder /etc/passwd /etc/passwd
+COPY --from=builder /etc/group /etc/group
+COPY --from=builder /etc/mime.types /etc/mime.types
+
+# Copy static executable and application resources
+COPY --from=builder /app/backoffice-server /app/backoffice-server
+COPY --from=builder /app/dev_certificates /app/dev_certificates
+COPY --from=builder /app/configs /app/configs
+
+# Use an unprivileged user.
+USER appuser:appuser
+
+# Run the binary
+ENTRYPOINT ["./backoffice-server"]
\ No newline at end of file
diff --git a/README.md b/README.md
index a6da557..0f6ecf8 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,29 @@
-# Backoffice - Server
+# Ecolyo Agent - Server
 
-Backoffice server of ecolyo app
+Ecolyo Agent is the backoffice for the Ecolyo app.
+
+This repository contains the backend part of this backoffice.
+
+# Features
+
+- Authentification using OAuth2
+
+- Connected admins can use an API to edit and save different sections of the newsletters that will be sent to Ecolyo users
+
+- Exposes a public route to get the sections of a specific or the last newsletter
+
+# How to setup :
+
+This backend should be deployed with the frontend from [this repo](https://forge.grandlyon.com/web-et-numerique/llle_project/backoffice-client) 
+
+However this backend can be run in standalone :
+
+- Clone the repository
+
+- Set a .env file at the root and add all variables declared in the [template.env](https://forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/-/blob/dev/template.env) file
+
+- (**Optionnal**) you can create new certificates by running `cd dev_certificates && ./generate-certificates.sh`
+
+- Run `docker-compose up -d`
+
+Once deployed, you can access to a Swagger documentation of the API on https://${HOSTNAME}/swagger/index.html
diff --git a/configs/tokenskey.json b/configs/tokenskey.json
new file mode 100644
index 0000000..3920d9a
--- /dev/null
+++ b/configs/tokenskey.json
@@ -0,0 +1,3 @@
+{
+	"Key": "2ioa6+gmlILIQcsG/HmBqDSsszPCe4GWKjCfyrtgLek="
+}
\ No newline at end of file
diff --git a/dev_certificates/RootCA.crt b/dev_certificates/RootCA.crt
new file mode 100644
index 0000000..efcaf26
--- /dev/null
+++ b/dev_certificates/RootCA.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDMzCCAhugAwIBAgIUW08A6nSohnq7VLjzATCAK8uJRH4wDQYJKoZIhvcNAQEL
+BQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMB4X
+DTIxMDcwOTEwNDUzMVoXDTI0MDQyODEwNDUzMVowKTELMAkGA1UEBhMCVVMxGjAY
+BgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsGnISpIxiVdObomVjon7fWU2IkeWFZnjs47CdF9gitinIzn8j1bD
+a6U3TrY490c8bXv1KGLNw3/4QN7svsQ0I+NTU4JTgO/5LYvFItUqC7NchG/XKM4y
+VDWnmrR8Hm0RErMVInyM9Ww3YgX5nn6dUI6u8isvydL66mHkL340Ej0T/F/eyiqU
+LL5mkRAg+6nvdh2kfLqbqGKAN6cQxISLfnMNUf8IqKGE6FaPw8C3xKR1ModQBvcJ
+pjeCCefJN+tp6UBJSNakhwxUOcSc8ogb5d1FHm8cbAmf1yubNpcKWzcenPRDJPzK
+vRThD775FGQKO3ZMJg7neekPYQei4DR/HwIDAQABo1MwUTAdBgNVHQ4EFgQUtxCV
+RcrVAGPYKayZkktjJTtiAQMwHwYDVR0jBBgwFoAUtxCVRcrVAGPYKayZkktjJTti
+AQMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAe3n9IVVWm//M
+oYpCLjqwVFdXh22v0jf5RB5wQvDYnBlQPmh0g37TVwsSHLNkVmCS6q6NdAewbd52
+mrthFuZ8BSHNWcevZYeyKo7Ji3UkF9sECAxiPmd9tMDxG5zqCCEfU0qcSHDY8AC1
+R68Z20jMpx8PjJN3hQtG77J66YB2eXy0DGO8NzB5aBrD9s/nrm9CiVkysFUJaCEk
+rN2A2w2mAcXsCbi33GvPe06EnAjTH4DUZrd4buPYSWmHnalHi9GLH4/3hkNBroux
+iVrfXW3W/aeYiYb8snbFSwKniB+1V4wtDJenhPzXZOjWb1TiPqOJYLrMsVsXt/6b
++nnB7t6tMA==
+-----END CERTIFICATE-----
diff --git a/dev_certificates/RootCA.key b/dev_certificates/RootCA.key
new file mode 100644
index 0000000..b6dca61
--- /dev/null
+++ b/dev_certificates/RootCA.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwachKkjGJV05u
+iZWOift9ZTYiR5YVmeOzjsJ0X2CK2KcjOfyPVsNrpTdOtjj3Rzxte/UoYs3Df/hA
+3uy+xDQj41NTglOA7/kti8Ui1SoLs1yEb9cozjJUNaeatHwebRESsxUifIz1bDdi
+Bfmefp1Qjq7yKy/J0vrqYeQvfjQSPRP8X97KKpQsvmaRECD7qe92HaR8upuoYoA3
+pxDEhIt+cw1R/wiooYToVo/DwLfEpHUyh1AG9wmmN4IJ58k362npQElI1qSHDFQ5
+xJzyiBvl3UUebxxsCZ/XK5s2lwpbNx6c9EMk/Mq9FOEPvvkUZAo7dkwmDud56Q9h
+B6LgNH8fAgMBAAECggEAYaHJlWufOrE02PwP5xj6NAXFleckasQGPqNtftYiKfWJ
+WneHDRUphfOjzk4O7Nth1/3YSgeUdPPnwo8dWt5fNNVkkjz4Vc15i/lkmsh6Qot7
+UlhLLWwgcnZXUck9P+GAp2aw9asUn+bKJ9fCtDLCgYjVzXSVOA8pinmuvZIM42I6
+g164YSMMgdz0DleHArmK9RW4S5v7mlOeF0FSxBxwuja1qOeFylMMqNn7IRP+UWOo
+AKu0YszT9uqcAqih8jNDvPv7vNVzVmJSSjwKRLQ7QBmfIPnlf3o42+oc2Winiqac
+8i5ODjpKox38H47u+Viy4vA5nWmXOYqXyZDE1eJ7EQKBgQDhRm9kMVKr+9ccmZO2
+u+JZSUDD2dYGTfBdewU6vLbmNUhYv7Mlcme9a0+FSn/PSW2HAqSPTp5BQ6bQiHME
+G+Ve0wCQhwXML4zRFV58Ooq2GKvSoHbkptCmmxcT+I4Ef9j6Q1wtWn4QSpF7fZ49
+PONkfT8d5GTkTPRJoyU5id56GwKBgQDIeVCnxTcRXKr/opCiXB8RYNk5ySkNovPy
+WpzEpKQ8ibJWDRYSNMtE/1+UK9FEJKOhbySnk0hnAyJ3ZNLZ5Z/wsAUwsqy4WvWB
+jqZ+rABkAaSy1IYZEl7RL4Yd/oPih1FiN2GfYRmUHIpRsr5IVwDzBLulBRixt15E
+xg3JxomfTQKBgQCmcco/twmkNND9OqOfMjbNTYhirIKr6c4c45Y7jc99TAUGPa15
+j4wCslTw4NiKKXCZfmRj1eyrv/ZywT5p3MqeQzx5jKnF8aQTn7xOAVsXrCbX2uO9
+kVs8nf5xLQaRYHzKfBaRE/lsxAu1uFzAVkqUps2JooTBAfLErZwFZU5R3QKBgE6L
+YXNqDGpMAV9JBRvntfBsHo/KZcBHAQcKQ3O0AfkKBgo69FPLxXxSBdOa21G0fTvJ
+vPW++dYKX12h7g6bLe/yNwZeateMI7ZP+qGUqE6Gak36gFOgY+/Xi9eCmY+Obu9p
+PWFhfNEP4Y2i13SmSePtDcvY1FUEv/V4F3zfwZndAoGASst6PPyVWY84uuib3Rwu
+p4btHMuRRoXc4o5mBNfnVRy9vE7HMuFyk3l7bQ375C68iv7FWIJV90y0DBWgtOUU
+yb8kUGe1VNAKijhA0DU7BRJkV2hQiXAR6nIrNuSn8J/41UyBpYxf30T0xGgh/qqY
+zxf//ghXSRHPuAzaf1LGJrQ=
+-----END PRIVATE KEY-----
diff --git a/dev_certificates/RootCA.pem b/dev_certificates/RootCA.pem
new file mode 100644
index 0000000..efcaf26
--- /dev/null
+++ b/dev_certificates/RootCA.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDMzCCAhugAwIBAgIUW08A6nSohnq7VLjzATCAK8uJRH4wDQYJKoZIhvcNAQEL
+BQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMB4X
+DTIxMDcwOTEwNDUzMVoXDTI0MDQyODEwNDUzMVowKTELMAkGA1UEBhMCVVMxGjAY
+BgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsGnISpIxiVdObomVjon7fWU2IkeWFZnjs47CdF9gitinIzn8j1bD
+a6U3TrY490c8bXv1KGLNw3/4QN7svsQ0I+NTU4JTgO/5LYvFItUqC7NchG/XKM4y
+VDWnmrR8Hm0RErMVInyM9Ww3YgX5nn6dUI6u8isvydL66mHkL340Ej0T/F/eyiqU
+LL5mkRAg+6nvdh2kfLqbqGKAN6cQxISLfnMNUf8IqKGE6FaPw8C3xKR1ModQBvcJ
+pjeCCefJN+tp6UBJSNakhwxUOcSc8ogb5d1FHm8cbAmf1yubNpcKWzcenPRDJPzK
+vRThD775FGQKO3ZMJg7neekPYQei4DR/HwIDAQABo1MwUTAdBgNVHQ4EFgQUtxCV
+RcrVAGPYKayZkktjJTtiAQMwHwYDVR0jBBgwFoAUtxCVRcrVAGPYKayZkktjJTti
+AQMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAe3n9IVVWm//M
+oYpCLjqwVFdXh22v0jf5RB5wQvDYnBlQPmh0g37TVwsSHLNkVmCS6q6NdAewbd52
+mrthFuZ8BSHNWcevZYeyKo7Ji3UkF9sECAxiPmd9tMDxG5zqCCEfU0qcSHDY8AC1
+R68Z20jMpx8PjJN3hQtG77J66YB2eXy0DGO8NzB5aBrD9s/nrm9CiVkysFUJaCEk
+rN2A2w2mAcXsCbi33GvPe06EnAjTH4DUZrd4buPYSWmHnalHi9GLH4/3hkNBroux
+iVrfXW3W/aeYiYb8snbFSwKniB+1V4wtDJenhPzXZOjWb1TiPqOJYLrMsVsXt/6b
++nnB7t6tMA==
+-----END CERTIFICATE-----
diff --git a/dev_certificates/RootCA.srl b/dev_certificates/RootCA.srl
new file mode 100644
index 0000000..cdd51ea
--- /dev/null
+++ b/dev_certificates/RootCA.srl
@@ -0,0 +1 @@
+3C13A2E5C7F49006B6843A514AD3AD7E1FC88B9B
diff --git a/dev_certificates/domains.ext b/dev_certificates/domains.ext
new file mode 100644
index 0000000..0bba95d
--- /dev/null
+++ b/dev_certificates/domains.ext
@@ -0,0 +1,6 @@
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = localhost
diff --git a/dev_certificates/generate-certificates.sh b/dev_certificates/generate-certificates.sh
new file mode 100755
index 0000000..98a5266
--- /dev/null
+++ b/dev_certificates/generate-certificates.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+rm -f *.crt *.csr *.key *.pem *.srl
+openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=EcolyoBackOffice-Root-CA"
+openssl x509 -outform pem -in RootCA.pem -out RootCA.crt
+openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=EcolyoBackOffice-Certificates/CN=localhost.local"
+openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile domains.ext -out localhost.crt
diff --git a/dev_certificates/localhost.crt b/dev_certificates/localhost.crt
new file mode 100644
index 0000000..49ccddb
--- /dev/null
+++ b/dev_certificates/localhost.crt
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIUPBOi5cf0kAa2hDpRStOtfh/Ii5swDQYJKoZIhvcNAQEL
+BQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMB4X
+DTIxMDcwOTEwNDUzMVoXDTI0MDQyODEwNDUzMVowbzELMAkGA1UEBhMCVVMxEjAQ
+BgNVBAgMCVlvdXJTdGF0ZTERMA8GA1UEBwwIWW91ckNpdHkxHzAdBgNVBAoMFlZl
+c3RpYnVsZS1DZXJ0aWZpY2F0ZXMxGDAWBgNVBAMMD2xvY2FsaG9zdC5sb2NhbDCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALLU4bOy9xtFmTVCUxuCCCGm
+WFMgPlfFzwzZGqGN0cGvuynEUTcwgoyjYHidPC+k4q8/H/0IiOwBQSbOqm6D96YH
+t74veeeudCmkM5ilemwba2zK11PDRTjw+91r3vZyQv/d5lyDhoBDeC6YD2Nf6MF7
+BP8wIAPwQksDK43AdGxEv6tPIHQ54exEpB9zfqcY1afSR6OhLDq0CEg04CKi6B/y
+YLMaVa9RH+UeAUX7VSuJrCNgCxj950YVVIJ1jizkgodhdL4HMbRTXLK6hPpEqn5a
+XuV4OOGlmnDn3o+UJAmmZj4LiSVCY2PPpOhOX7CJgwUEhM1XgelLFdUrKVhVJlsC
+AwEAAaNRME8wHwYDVR0jBBgwFoAUtxCVRcrVAGPYKayZkktjJTtiAQMwCQYDVR0T
+BAIwADALBgNVHQ8EBAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3
+DQEBCwUAA4IBAQAl7lHURZytJ1eO5AUiYa/4HqmlIwdV3JzEUpDbLcLdUQOEz95s
+56CDh1iji1RWA+8RMOJlgnWptG3HHBXkzsQPYMlI9ofpbq4ZGCLh6ZWk1OsRnj6l
+d4gyWlobf0WgVLuFo1vQZrzm5zDbAaVG2cj0avdhaLiMMWpneqdx9Zfn/QIvh+AB
+IBN8bUIEylbNUmZDFbVImyoDC0pbnnsp5Qn3wLL1RyIJhXF9O84M/htOPFs0t0s1
+ZsQKrkG9/TqOPfFePjNbgWcvVGvsTKfX9/B1Uyvpr2ko9quj5Q0UvjOm2Dypi/fe
+Vk1+m4gEWo4P3SyOXTcU8nX1EmiLeyLaLpcQ
+-----END CERTIFICATE-----
diff --git a/dev_certificates/localhost.csr b/dev_certificates/localhost.csr
new file mode 100644
index 0000000..29522a4
--- /dev/null
+++ b/dev_certificates/localhost.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICtDCCAZwCAQAwbzELMAkGA1UEBhMCVVMxEjAQBgNVBAgMCVlvdXJTdGF0ZTER
+MA8GA1UEBwwIWW91ckNpdHkxHzAdBgNVBAoMFlZlc3RpYnVsZS1DZXJ0aWZpY2F0
+ZXMxGDAWBgNVBAMMD2xvY2FsaG9zdC5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALLU4bOy9xtFmTVCUxuCCCGmWFMgPlfFzwzZGqGN0cGvuynE
+UTcwgoyjYHidPC+k4q8/H/0IiOwBQSbOqm6D96YHt74veeeudCmkM5ilemwba2zK
+11PDRTjw+91r3vZyQv/d5lyDhoBDeC6YD2Nf6MF7BP8wIAPwQksDK43AdGxEv6tP
+IHQ54exEpB9zfqcY1afSR6OhLDq0CEg04CKi6B/yYLMaVa9RH+UeAUX7VSuJrCNg
+Cxj950YVVIJ1jizkgodhdL4HMbRTXLK6hPpEqn5aXuV4OOGlmnDn3o+UJAmmZj4L
+iSVCY2PPpOhOX7CJgwUEhM1XgelLFdUrKVhVJlsCAwEAAaAAMA0GCSqGSIb3DQEB
+CwUAA4IBAQCSS3TfTAsUsGYTXfnJ/w1+2KCatkQBbU9cUrxalQrCdTPu+sDeG+OL
+1YRl27hzarN8UXwtEPV1VZNs7kAWdm3ndwy6Aqd5oTenC47bzPFwu3USioSwIyVI
+h7c7TrryFUSJDXSNRXDuLoIaPCx9ZSkY0xkbCW+thiuWP0tANFmkAfo92EGBhQmp
+g59Tn0A4ZveQBi0zHTHVMI2Y73FxA7pyyU1+eGAlBMqV/t6ipA0s5qKsVqXNJyRE
+QgR9FRYZ0nJwiIKlM3YMFwpIu23xMHV3LxN8DjnsZzGzLCkvHja9E2gLtKu3wrRj
+D1Ce/P/2hc4BWInuDIbA++b6O8oeWFq+
+-----END CERTIFICATE REQUEST-----
diff --git a/dev_certificates/localhost.key b/dev_certificates/localhost.key
new file mode 100644
index 0000000..709c5c5
--- /dev/null
+++ b/dev_certificates/localhost.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCy1OGzsvcbRZk1
+QlMbggghplhTID5Xxc8M2RqhjdHBr7spxFE3MIKMo2B4nTwvpOKvPx/9CIjsAUEm
+zqpug/emB7e+L3nnrnQppDOYpXpsG2tsytdTw0U48Pvda972ckL/3eZcg4aAQ3gu
+mA9jX+jBewT/MCAD8EJLAyuNwHRsRL+rTyB0OeHsRKQfc36nGNWn0kejoSw6tAhI
+NOAiougf8mCzGlWvUR/lHgFF+1UriawjYAsY/edGFVSCdY4s5IKHYXS+BzG0U1yy
+uoT6RKp+Wl7leDjhpZpw596PlCQJpmY+C4klQmNjz6ToTl+wiYMFBITNV4HpSxXV
+KylYVSZbAgMBAAECggEBAJeMFCDmvsXbuzQJGsiq7x0lDKCVKH/VX9sxeIID3wfW
+VjPU7EZq05c/NJfCF6kAgCGmywLxYqctgPrUFFZHe2y2CZ4gOZx+mG5ZemgXg6Ft
+syGk28leJ6FThv1jVrVeqyyN7ZPk2eyEQKqrrg62zlZ4XAmtzNPJnURYUWZ0+7Rr
+Ozl5zQ458ebxKzvbHSkUyp6hlT8MWf2Nnh2Bu5EIdV2xibU1rjP+FSok6Jl3EwGq
+O9nD70E4NMr2ReRPgwHVpTQ/U/eAyK4OO3E0CXMSX/K+/3vGVb/Urbdf9NneSXbT
+w271vXHAtGFOaUkd19xykjP5iYnAEazHau4mswFDV0ECgYEA4IZnz/bh9s/8tMOw
+QGULWa9lzWpas2YV1TSLD64GNYFjIN11rQCJ/hNvYfLaRHs0qwZGm+mOxZqU9Vvi
+XRry1alGAn288P27+3RukeBRx2b7Ho2l9pNgWtYpkWhsOrMiTuPmyBr3eLb4E9RU
+TRt1foz9GuqPW0QIaq8nRctpDxECgYEAy+apdlekhufwtsuydchaoScnSkE7qaLK
+ot6QJzFXcYPD5r1ROv9jAAo4rBKgPOiUhANVXvkeZsT+/VEffP2o0DtapBWx+VPM
+GI4IkMknsDT6lsapQWbKdGHrY9nJGRiT4cvOrzaSBYVFVWWfoWs46vLKCU13BJig
+HzwrgaVStqsCgYEAqA1R1IHofdENR8uUt44p4bX7z7WEL/T/8HYEg/bwZMn0hVvd
+QWE+5/JqEvkvz8QcFsp6vSYim9rpFYDxvFh4W934LdMpQYPZWQu72un4q/Rzj1nc
+V+PVYggcUt7C62i7DCteyHYOtsbUhhsOAizEU7V5mNTp+hjA6AEztvTaLNECgYEA
+yg5aABv5vnY54+sXfgB9TxUtqjfal8/qXluPHkeXD7Yze4Q/6ucJhBCc+Ge8wp74
+DZoAD41uwwiUZxLs0T/M+gzXVaLqKtkPd4XIlzG/Uq4tZRyYvWbPWWVvjhNTZLsm
+UKtWteqt6SqX+ngqKBvI24qdC3roZnWYt1s5AdCCluECgYAxHYJ8C7CJRmLps1At
+yml7L0KCKzmeDmbHdejhbO2/rKGL42Uua9NfOAKT268az6eCOLjcMeRdC7tKmS7K
+8Z5QYYXY7WOnMOijBiBeowkx3oqpwh1N/PmxExMufStusGZMymrQSQXYWGSQ/ey3
+NXnceAUytLBRTy9zam0jqHHprQ==
+-----END PRIVATE KEY-----
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..d141301
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,61 @@
+version: '3.1'
+
+services: 
+    database:
+        image: mysql:5
+        ports:
+            - 3306:3306
+        environment:
+            MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
+            MYSQL_DATABASE: ${DATABASE_NAME}
+        healthcheck:
+            test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
+            interval: 5s
+            timeout: 10s
+            retries: 60
+
+    phpmyadmin:
+        image: phpmyadmin/phpmyadmin:latest
+        depends_on:
+            - database
+        ports:
+            - 8008:80
+        environment:
+            PMA_HOST: database
+
+    backoffice-container:
+        image: backoffice        
+        depends_on:
+            database:
+                condition: service_healthy
+        build: .
+        restart: unless-stopped
+        volumes:
+            - /etc/localtime:/etc/localtime:ro
+            - ./configs:/app/configs
+            - ./letsencrypt_cache:/app/letsencrypt_cache
+            - ./data:/app/data
+            - ./../${IMAGE_FOLDER}:/app/${IMAGE_FOLDER}
+        ports:
+            - ${HTTPS_PORT}:${HTTPS_PORT}
+            - 8090:8090
+        environment:
+            - HOSTNAME=${HOSTNAME}
+            - HTTPS_PORT=${HTTPS_PORT}
+            - ADMIN_ROLE=${ADMIN_ROLE}
+            - REDIRECT_URL=${REDIRECT_URL}
+            - IMAGE_FOLDER=${IMAGE_FOLDER}
+            - CLIENT_ID=${CLIENT_ID}
+            - CLIENT_SECRET=${CLIENT_SECRET}
+            - AUTH_URL=${AUTH_URL}
+            - TOKEN_URL=${TOKEN_URL}
+            - USERINFO_URL=${USERINFO_URL}
+            - DEBUG_MODE=${DEBUG_MODE}
+            - MOCK_OAUTH2=${MOCK_OAUTH2}
+            - DATABASE_USER=${DATABASE_USER}
+            - DATABASE_NAME=${DATABASE_NAME}
+            - DATABASE_PASSWORD=${DATABASE_PASSWORD}
+            - DATABASE_HOST=database
+
+volumes:
+    db_data:
\ No newline at end of file
diff --git a/docs/docs.go b/docs/docs.go
new file mode 100644
index 0000000..5bd2306
--- /dev/null
+++ b/docs/docs.go
@@ -0,0 +1,694 @@
+// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
+// This file was generated by swaggo/swag
+package docs
+
+import (
+	"bytes"
+	"encoding/json"
+	"strings"
+	"text/template"
+
+	"github.com/swaggo/swag"
+)
+
+var doc = `{
+    "schemes": {{ marshal .Schemes }},
+    "swagger": "2.0",
+    "info": {
+        "description": "{{escape .Description}}",
+        "title": "{{.Title}}",
+        "termsOfService": "http://swagger.io/terms/",
+        "contact": {
+            "name": "API Support",
+            "email": "rpailharey@grandlyon.com"
+        },
+        "version": "{{.Version}}"
+    },
+    "host": "{{.Host}}",
+    "basePath": "{{.BasePath}}",
+    "paths": {
+        "/api/admin/monthlyInfo": {
+            "get": {
+                "description": "Get details of all monthlyInfo",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "List all monthlyInfo",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/models.MonthlyInfo"
+                            }
+                        }
+                    }
+                }
+            },
+            "put": {
+                "description": "Create/update a specific monthlyInfo' content",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "Create/update a specific monthlyInfo' content",
+                "parameters": [
+                    {
+                        "description": "MonthlyInfo to create/update with new content",
+                        "name": "monthlyInfo",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    },
+                    "201": {
+                        "description": "Created successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal server error",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/monthlyInfo/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific monthlyInfo",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "Get details of a specific monthlyInfo",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyInfo",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyInfo",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            },
+            "delete": {
+                "description": "Delete a specific monthlyInfo",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "Delete a specific monthlyInfo",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyInfo",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyInfo",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "successful delete",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/monthlyNews": {
+            "get": {
+                "description": "Get details of all monthlyNews",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "List all monthlyNews",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/models.MonthlyNews"
+                            }
+                        }
+                    }
+                }
+            },
+            "put": {
+                "description": "Create/update a specific monthlyNews' content",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "Create/update a specific monthlyNews' content",
+                "parameters": [
+                    {
+                        "description": "MonthlyNews to create/update with new content",
+                        "name": "monthlyNews",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    },
+                    "201": {
+                        "description": "Created successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal server error",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/monthlyNews/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific monthlyNews",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "Get details of a specific monthlyNews",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyNews",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyNews",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            },
+            "delete": {
+                "description": "Delete a specific monthlyNews",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "Delete a specific monthlyNews",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyNews",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyNews",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "successful delete",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/poll": {
+            "get": {
+                "description": "Get details of all polls",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "List all polls",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/models.Poll"
+                            }
+                        }
+                    }
+                }
+            },
+            "put": {
+                "description": "Update a specific poll' content",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "Update a specific poll' content",
+                "parameters": [
+                    {
+                        "description": "Poll to update with new content",
+                        "name": "poll",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    },
+                    "201": {
+                        "description": "Created successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal server error",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/poll/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific poll",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "Get details of a specific poll",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the poll",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the poll",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            },
+            "delete": {
+                "description": "Delete a specific poll",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "Delete a specific poll",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the poll",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the poll",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "successful delete",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/common/monthlyReport": {
+            "get": {
+                "description": "Find the MonthlyInfo of the current month and try to find the corresponding monthlyNews and poll",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyReport"
+                ],
+                "summary": "Get details of the current monthlyReport",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyReport"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/common/monthlyReport/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific monthlyReport",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyReport"
+                ],
+                "summary": "Get details of a specific monthlyReport",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyReport",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyReport",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyReport"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "models.MonthlyInfo": {
+            "type": "object",
+            "properties": {
+                "info": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        },
+        "models.MonthlyNews": {
+            "type": "object",
+            "properties": {
+                "content": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        },
+        "models.MonthlyReport": {
+            "type": "object",
+            "properties": {
+                "info": {
+                    "type": "string"
+                },
+                "link": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "newsContent": {
+                    "type": "string"
+                },
+                "newsTitle": {
+                    "type": "string"
+                },
+                "question": {
+                    "type": "string"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        },
+        "models.Poll": {
+            "type": "object",
+            "properties": {
+                "link": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "question": {
+                    "type": "string"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        }
+    }
+}`
+
+type swaggerInfo struct {
+	Version     string
+	Host        string
+	BasePath    string
+	Schemes     []string
+	Title       string
+	Description string
+}
+
+// SwaggerInfo holds exported Swagger Info so clients can modify it
+var SwaggerInfo = swaggerInfo{
+	Version:     "1.0",
+	Host:        "localhost:1443",
+	BasePath:    "/",
+	Schemes:     []string{},
+	Title:       "Backoffice API",
+	Description: "This is a sample service for managing newsletters for Ecolyo",
+}
+
+type s struct{}
+
+func (s *s) ReadDoc() string {
+	sInfo := SwaggerInfo
+	sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
+
+	t, err := template.New("swagger_info").Funcs(template.FuncMap{
+		"marshal": func(v interface{}) string {
+			a, _ := json.Marshal(v)
+			return string(a)
+		},
+		"escape": func(v interface{}) string {
+			// escape tabs
+			str := strings.Replace(v.(string), "\t", "\\t", -1)
+			// replace " with \", and if that results in \\", replace that with \\\"
+			str = strings.Replace(str, "\"", "\\\"", -1)
+			return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
+		},
+	}).Parse(doc)
+	if err != nil {
+		return doc
+	}
+
+	var tpl bytes.Buffer
+	if err := t.Execute(&tpl, sInfo); err != nil {
+		return doc
+	}
+
+	return tpl.String()
+}
+
+func init() {
+	swag.Register(swag.Name, &s{})
+}
diff --git a/docs/swagger.json b/docs/swagger.json
new file mode 100644
index 0000000..e7aa192
--- /dev/null
+++ b/docs/swagger.json
@@ -0,0 +1,626 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "description": "This is a sample service for managing newsletters for Ecolyo",
+        "title": "Backoffice API",
+        "termsOfService": "http://swagger.io/terms/",
+        "contact": {
+            "name": "API Support",
+            "email": "rpailharey@grandlyon.com"
+        },
+        "version": "1.0"
+    },
+    "host": "localhost:1443",
+    "basePath": "/",
+    "paths": {
+        "/api/admin/monthlyInfo": {
+            "get": {
+                "description": "Get details of all monthlyInfo",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "List all monthlyInfo",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/models.MonthlyInfo"
+                            }
+                        }
+                    }
+                }
+            },
+            "put": {
+                "description": "Create/update a specific monthlyInfo' content",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "Create/update a specific monthlyInfo' content",
+                "parameters": [
+                    {
+                        "description": "MonthlyInfo to create/update with new content",
+                        "name": "monthlyInfo",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    },
+                    "201": {
+                        "description": "Created successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal server error",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/monthlyInfo/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific monthlyInfo",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "Get details of a specific monthlyInfo",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyInfo",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyInfo",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyInfo"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            },
+            "delete": {
+                "description": "Delete a specific monthlyInfo",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyInfo"
+                ],
+                "summary": "Delete a specific monthlyInfo",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyInfo",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyInfo",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "successful delete",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/monthlyNews": {
+            "get": {
+                "description": "Get details of all monthlyNews",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "List all monthlyNews",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/models.MonthlyNews"
+                            }
+                        }
+                    }
+                }
+            },
+            "put": {
+                "description": "Create/update a specific monthlyNews' content",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "Create/update a specific monthlyNews' content",
+                "parameters": [
+                    {
+                        "description": "MonthlyNews to create/update with new content",
+                        "name": "monthlyNews",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    },
+                    "201": {
+                        "description": "Created successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal server error",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/monthlyNews/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific monthlyNews",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "Get details of a specific monthlyNews",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyNews",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyNews",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyNews"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            },
+            "delete": {
+                "description": "Delete a specific monthlyNews",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyNews"
+                ],
+                "summary": "Delete a specific monthlyNews",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyNews",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyNews",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "successful delete",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/poll": {
+            "get": {
+                "description": "Get details of all polls",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "List all polls",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/models.Poll"
+                            }
+                        }
+                    }
+                }
+            },
+            "put": {
+                "description": "Update a specific poll' content",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "Update a specific poll' content",
+                "parameters": [
+                    {
+                        "description": "Poll to update with new content",
+                        "name": "poll",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    },
+                    "201": {
+                        "description": "Created successfully",
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal server error",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/admin/poll/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific poll",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "Get details of a specific poll",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the poll",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the poll",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.Poll"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            },
+            "delete": {
+                "description": "Delete a specific poll",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "poll"
+                ],
+                "summary": "Delete a specific poll",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the poll",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the poll",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "successful delete",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/common/monthlyReport": {
+            "get": {
+                "description": "Find the MonthlyInfo of the current month and try to find the corresponding monthlyNews and poll",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyReport"
+                ],
+                "summary": "Get details of the current monthlyReport",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyReport"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/common/monthlyReport/{year}/{month}": {
+            "get": {
+                "description": "Get details of a specific monthlyReport",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "monthlyReport"
+                ],
+                "summary": "Get details of a specific monthlyReport",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Year of the monthlyReport",
+                        "name": "year",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Month of the monthlyReport",
+                        "name": "month",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/models.MonthlyReport"
+                        }
+                    },
+                    "404": {
+                        "description": "Not found",
+                        "schema": {
+                            "type": "string"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "models.MonthlyInfo": {
+            "type": "object",
+            "properties": {
+                "info": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        },
+        "models.MonthlyNews": {
+            "type": "object",
+            "properties": {
+                "content": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        },
+        "models.MonthlyReport": {
+            "type": "object",
+            "properties": {
+                "info": {
+                    "type": "string"
+                },
+                "link": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "newsContent": {
+                    "type": "string"
+                },
+                "newsTitle": {
+                    "type": "string"
+                },
+                "question": {
+                    "type": "string"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        },
+        "models.Poll": {
+            "type": "object",
+            "properties": {
+                "link": {
+                    "type": "string"
+                },
+                "month": {
+                    "type": "integer"
+                },
+                "question": {
+                    "type": "string"
+                },
+                "year": {
+                    "type": "integer"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
new file mode 100644
index 0000000..5edbca6
--- /dev/null
+++ b/docs/swagger.yaml
@@ -0,0 +1,416 @@
+basePath: /
+definitions:
+  models.MonthlyInfo:
+    properties:
+      info:
+        type: string
+      month:
+        type: integer
+      year:
+        type: integer
+    type: object
+  models.MonthlyNews:
+    properties:
+      content:
+        type: string
+      month:
+        type: integer
+      title:
+        type: string
+      year:
+        type: integer
+    type: object
+  models.MonthlyReport:
+    properties:
+      info:
+        type: string
+      link:
+        type: string
+      month:
+        type: integer
+      newsContent:
+        type: string
+      newsTitle:
+        type: string
+      question:
+        type: string
+      year:
+        type: integer
+    type: object
+  models.Poll:
+    properties:
+      link:
+        type: string
+      month:
+        type: integer
+      question:
+        type: string
+      year:
+        type: integer
+    type: object
+host: localhost:1443
+info:
+  contact:
+    email: rpailharey@grandlyon.com
+    name: API Support
+  description: This is a sample service for managing newsletters for Ecolyo
+  termsOfService: http://swagger.io/terms/
+  title: Backoffice API
+  version: "1.0"
+paths:
+  /api/admin/monthlyInfo:
+    get:
+      description: Get details of all monthlyInfo
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.MonthlyInfo'
+            type: array
+      summary: List all monthlyInfo
+      tags:
+      - monthlyInfo
+    put:
+      consumes:
+      - application/json
+      description: Create/update a specific monthlyInfo' content
+      parameters:
+      - description: MonthlyInfo to create/update with new content
+        in: body
+        name: monthlyInfo
+        required: true
+        schema:
+          $ref: '#/definitions/models.MonthlyInfo'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: Updated successfully
+          schema:
+            $ref: '#/definitions/models.MonthlyInfo'
+        "201":
+          description: Created successfully
+          schema:
+            $ref: '#/definitions/models.MonthlyInfo'
+        "400":
+          description: Bad Request
+          schema:
+            type: string
+        "500":
+          description: Internal server error
+          schema:
+            type: string
+      summary: Create/update a specific monthlyInfo' content
+      tags:
+      - monthlyInfo
+  /api/admin/monthlyInfo/{year}/{month}:
+    delete:
+      description: Delete a specific monthlyInfo
+      parameters:
+      - description: Year of the monthlyInfo
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the monthlyInfo
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: successful delete
+          schema:
+            type: string
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Delete a specific monthlyInfo
+      tags:
+      - monthlyInfo
+    get:
+      description: Get details of a specific monthlyInfo
+      parameters:
+      - description: Year of the monthlyInfo
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the monthlyInfo
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.MonthlyInfo'
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Get details of a specific monthlyInfo
+      tags:
+      - monthlyInfo
+  /api/admin/monthlyNews:
+    get:
+      description: Get details of all monthlyNews
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.MonthlyNews'
+            type: array
+      summary: List all monthlyNews
+      tags:
+      - monthlyNews
+    put:
+      consumes:
+      - application/json
+      description: Create/update a specific monthlyNews' content
+      parameters:
+      - description: MonthlyNews to create/update with new content
+        in: body
+        name: monthlyNews
+        required: true
+        schema:
+          $ref: '#/definitions/models.MonthlyNews'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: Updated successfully
+          schema:
+            $ref: '#/definitions/models.MonthlyNews'
+        "201":
+          description: Created successfully
+          schema:
+            $ref: '#/definitions/models.MonthlyNews'
+        "400":
+          description: Bad Request
+          schema:
+            type: string
+        "500":
+          description: Internal server error
+          schema:
+            type: string
+      summary: Create/update a specific monthlyNews' content
+      tags:
+      - monthlyNews
+  /api/admin/monthlyNews/{year}/{month}:
+    delete:
+      description: Delete a specific monthlyNews
+      parameters:
+      - description: Year of the monthlyNews
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the monthlyNews
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: successful delete
+          schema:
+            type: string
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Delete a specific monthlyNews
+      tags:
+      - monthlyNews
+    get:
+      description: Get details of a specific monthlyNews
+      parameters:
+      - description: Year of the monthlyNews
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the monthlyNews
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.MonthlyNews'
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Get details of a specific monthlyNews
+      tags:
+      - monthlyNews
+  /api/admin/poll:
+    get:
+      description: Get details of all polls
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.Poll'
+            type: array
+      summary: List all polls
+      tags:
+      - poll
+    put:
+      consumes:
+      - application/json
+      description: Update a specific poll' content
+      parameters:
+      - description: Poll to update with new content
+        in: body
+        name: poll
+        required: true
+        schema:
+          $ref: '#/definitions/models.Poll'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: Updated successfully
+          schema:
+            $ref: '#/definitions/models.Poll'
+        "201":
+          description: Created successfully
+          schema:
+            $ref: '#/definitions/models.Poll'
+        "400":
+          description: Bad Request
+          schema:
+            type: string
+        "500":
+          description: Internal server error
+          schema:
+            type: string
+      summary: Update a specific poll' content
+      tags:
+      - poll
+  /api/admin/poll/{year}/{month}:
+    delete:
+      description: Delete a specific poll
+      parameters:
+      - description: Year of the poll
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the poll
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: successful delete
+          schema:
+            type: string
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Delete a specific poll
+      tags:
+      - poll
+    get:
+      description: Get details of a specific poll
+      parameters:
+      - description: Year of the poll
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the poll
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.Poll'
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Get details of a specific poll
+      tags:
+      - poll
+  /api/common/monthlyReport:
+    get:
+      description: Find the MonthlyInfo of the current month and try to find the corresponding
+        monthlyNews and poll
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.MonthlyReport'
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Get details of the current monthlyReport
+      tags:
+      - monthlyReport
+  /api/common/monthlyReport/{year}/{month}:
+    get:
+      description: Get details of a specific monthlyReport
+      parameters:
+      - description: Year of the monthlyReport
+        in: path
+        name: year
+        required: true
+        type: integer
+      - description: Month of the monthlyReport
+        in: path
+        name: month
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.MonthlyReport'
+        "404":
+          description: Not found
+          schema:
+            type: string
+      summary: Get details of a specific monthlyReport
+      tags:
+      - monthlyReport
+swagger: "2.0"
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..985e21b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,13 @@
+module forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server
+
+go 1.15
+
+require (
+	github.com/gorilla/mux v1.8.0
+	github.com/swaggo/http-swagger v1.1.1
+	github.com/swaggo/swag v1.7.1
+	golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
+	gorm.io/driver/mysql v1.1.2
+	gorm.io/driver/sqlite v1.1.4
+	gorm.io/gorm v1.21.14
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..91f942d
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,465 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
+github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
+github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
+github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
+github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
+github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
+github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
+github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
+github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
+github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
+github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
+github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
+github.com/swaggo/http-swagger v1.1.1 h1:7cBYOcF/TS0Nx5uA6oOP9DfFV5RYogpazzK1IUmQUII=
+github.com/swaggo/http-swagger v1.1.1/go.mod h1:cKIcshBU9yEAnfWv6ZzVKSsEf8h5ozxB8/zHQWyOQ/8=
+github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
+github.com/swaggo/swag v1.7.1 h1:gY9ZakXlNWg/i/v5bQBic7VMZ4teq4m89lpiao74p/s=
+github.com/swaggo/swag v1.7.1/go.mod h1:gAiHxNTb9cIpNmA/VEGUP+CyZMCP/EW7mdtc8Bny+p8=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201207224615-747e23833adb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
+gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
+gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
+gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
+gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
+gorm.io/gorm v1.21.14 h1:NAR9A/3SoyiPVHouW/rlpMUZvuQZ6Z6UYGz+2tosSQo=
+gorm.io/gorm v1.21.14/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
new file mode 100644
index 0000000..fd469bd
--- /dev/null
+++ b/internal/auth/auth.go
@@ -0,0 +1,215 @@
+package auth
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+	"os"
+	"strings"
+	"time"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tokens"
+)
+
+type key int
+
+const (
+	authTokenKey string = "auth_token"
+	// ContextData is the user
+	ContextData key = 0
+)
+
+var (
+	// AdminRole represents the role reserved for admins
+	AdminRole = common.StringValueFromEnv("ADMIN_ROLE", "ADMINS")
+	hostname  = common.StringValueFromEnv("HOSTNAME", "ecolyobackoffice.127.0.0.1.nip.io")
+)
+
+// User represents a logged in user
+type User struct {
+	ID           string   `json:"id,omitempty"`
+	Login        string   `json:"login"`
+	DisplayName  string   `json:"displayName,omitempty"`
+	Email        string   `json:"email,omitempty"`
+	Roles        []string `json:"memberOf"`
+	IsAdmin      bool     `json:"isAdmin,omitempty"`
+	Name         string   `json:"name,omitempty"`
+	Surname      string   `json:"surname,omitempty"`
+	PasswordHash string   `json:"passwordHash,omitempty"`
+	Password     string   `json:"password,omitempty"`
+}
+
+// TokenData represents the data held into a token
+type TokenData struct {
+	User
+	URL              string `json:"url,omitempty"`
+	ReadOnly         bool   `json:"readonly,omitempty"`
+	SharingUserLogin string `json:"sharinguserlogin,omitempty"`
+	XSRFToken        string `json:"xsrftoken,omitempty"`
+}
+
+func AdminAuthMiddleware(next http.Handler) http.Handler {
+	return ValidateAuthMiddleware(next, []string{os.Getenv("ADMIN_ROLE")}, true)
+}
+
+func CommonAuthMiddleware(next http.Handler) http.Handler {
+	return ValidateAuthMiddleware(next, []string{"*"}, false)
+}
+
+// ValidateAuthMiddleware validates that the token is valid and that the user has the correct roles
+func ValidateAuthMiddleware(next http.Handler, allowedRoles []string, checkXSRF bool) http.Handler {
+	roleChecker := func(w http.ResponseWriter, r *http.Request) {
+		user := TokenData{}
+		checkXSRF, err := tokens.ExtractAndValidateToken(r, authTokenKey, &user, checkXSRF)
+		if err != nil {
+			// Handle CORS preflight requests
+			if r.Method == "OPTIONS" {
+				return
+			}
+			// Default to redirect to authentication
+			redirectTo := hostname
+			_, port, perr := net.SplitHostPort(r.Host)
+			if perr == nil {
+				redirectTo += ":" + port
+			}
+			w.Header().Set("Content-Type", "text/html")
+			w.WriteHeader(http.StatusUnauthorized)
+			responseContent := fmt.Sprintf("error extracting token: %v<meta http-equiv=\"Refresh\" content=\"0; url=https://%v#login\"/>", err.Error(), redirectTo)
+			fmt.Fprint(w, responseContent)
+			return
+
+		}
+		// Check XSRF Token
+		if checkXSRF && r.Header.Get("XSRF-TOKEN") != user.XSRFToken {
+			http.Error(w, "XSRF protection triggered", http.StatusUnauthorized)
+			return
+		}
+		err = checkUserHasRole(user, allowedRoles)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusForbidden)
+			return
+		}
+		err = checkUserHasRole(user, []string{AdminRole})
+		if err == nil {
+			user.IsAdmin = true
+		}
+		// Check for url
+		if user.URL != "" {
+			requestURL := strings.Split(r.Host, ":")[0] + r.URL.EscapedPath()
+			if user.URL != requestURL {
+				http.Error(w, "token restricted to url: "+user.URL, http.StatusUnauthorized)
+				return
+			}
+		}
+		// Check for method
+		if user.ReadOnly && r.Method != http.MethodGet {
+			http.Error(w, "token is read only", http.StatusForbidden)
+			return
+		}
+		ctx := context.WithValue(r.Context(), ContextData, user)
+		next.ServeHTTP(w, r.WithContext(ctx))
+	}
+	return http.HandlerFunc(roleChecker)
+}
+
+// HandleLogout remove the user from the cookie store
+func (m Manager) HandleLogout(w http.ResponseWriter, r *http.Request) {
+	// Delete the auth cookie
+	c := http.Cookie{
+		Name:   authTokenKey,
+		Domain: m.Hostname,
+		MaxAge: -1,
+	}
+	http.SetCookie(w, &c)
+	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+}
+
+// WhoAmI returns the user data
+func WhoAmI() http.Handler {
+	whoAmI := func(w http.ResponseWriter, r *http.Request) {
+		user, err := GetTokenData(r)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+			return
+		}
+		json.NewEncoder(w).Encode(user)
+	}
+	return http.HandlerFunc(whoAmI)
+}
+
+// checkUserHasRole checks if the user has the required role
+func checkUserHasRole(user TokenData, allowedRoles []string) error {
+	for _, allowedRole := range allowedRoles {
+		if allowedRole == "*" {
+			return nil
+		}
+		for _, userRole := range user.Roles {
+			if userRole != "" && (userRole == allowedRole) {
+				return nil
+			}
+		}
+	}
+	return fmt.Errorf("no user role among %v is in allowed roles (%v)", user.Roles, allowedRoles)
+}
+
+//GetShareToken gets a share token for a given ressource
+func GetShareToken(w http.ResponseWriter, r *http.Request) {
+	user, err := GetTokenData(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+	var wantedToken struct {
+		Sharedfor string `json:"sharedfor"`
+		URL       string `json:"url"`
+		Lifespan  int    `json:"lifespan"`
+		ReadOnly  bool   `json:"readonly,omitempty"`
+	}
+	err = json.NewDecoder(r.Body).Decode(&wantedToken)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	if wantedToken.URL == "" {
+		http.Error(w, "url cannot be empty", http.StatusBadRequest)
+		return
+	}
+	user.Login = user.Login + "_share_for_" + wantedToken.Sharedfor
+	user.URL = wantedToken.URL
+	user.ReadOnly = wantedToken.ReadOnly
+	user.SharingUserLogin = wantedToken.Sharedfor
+	token, err := tokens.CreateToken(user, time.Now().Add(time.Hour*time.Duration(24*wantedToken.Lifespan)))
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	fmt.Fprint(w, token)
+}
+
+// GetTokenData gets an user from a request
+func GetTokenData(r *http.Request) (TokenData, error) {
+	user, ok := r.Context().Value(ContextData).(TokenData)
+	if !ok {
+		return user, errors.New("user could not be got from context")
+	}
+	return user, nil
+}
+
+// isWebdav works out if an user agent is a webdav user agent
+func isWebdav(ua string) bool {
+	for _, a := range []string{"vfs", "Microsoft-WebDAV", "Konqueror", "LibreOffice", "Rei.Fs.WebDAV"} {
+		if strings.Contains(ua, a) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/internal/auth/oauth2.go b/internal/auth/oauth2.go
new file mode 100644
index 0000000..870b2de
--- /dev/null
+++ b/internal/auth/oauth2.go
@@ -0,0 +1,143 @@
+package auth
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+	"time"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tokens"
+	"golang.org/x/oauth2"
+)
+
+const (
+	oAuth2StateKey string = "oauth2_state"
+)
+
+// Manager exposes the handlers for OAuth2 endpoints
+type Manager struct {
+	Config      *oauth2.Config
+	Hostname    string
+	UserInfoURL string
+}
+
+// NewManager returns a new Manager according to environment variables
+func NewManager() Manager {
+	return Manager{Config: &oauth2.Config{
+		RedirectURL:  os.Getenv("REDIRECT_URL"),
+		ClientID:     os.Getenv("CLIENT_ID"),
+		ClientSecret: os.Getenv("CLIENT_SECRET"),
+		Scopes:       []string{"login", "memberOf", "displayName", "email"},
+		Endpoint: oauth2.Endpoint{
+			AuthURL:  os.Getenv("AUTH_URL"),
+			TokenURL: os.Getenv("TOKEN_URL"),
+		},
+	},
+		Hostname:    common.StringValueFromEnv("HOSTNAME", "ecolyobackoffice.127.0.0.1.nip.io"),
+		UserInfoURL: os.Getenv("USERINFO_URL"),
+	}
+}
+
+// HandleOAuth2Login handles the OAuth2 login
+func (m Manager) HandleOAuth2Login(w http.ResponseWriter, r *http.Request) {
+	// Generate state and store it in cookie
+	oauthStateString, err := common.GenerateRandomString(16)
+	if err != nil {
+		log.Fatalf("Error generating OAuth2 strate string :%v\n", err)
+	}
+	tokens.CreateCookie(oauthStateString, m.Hostname, oAuth2StateKey, 60*time.Second, w)
+	url := m.Config.AuthCodeURL(oauthStateString)
+	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
+}
+
+// HandleOAuth2Callback handles the OAuth2 Callback and get user info
+func (m Manager) HandleOAuth2Callback() http.Handler {
+	oauth2Handler := func(w http.ResponseWriter, r *http.Request) {
+		// Recover state from tokens
+		var oauthState string
+		_, err := tokens.ExtractAndValidateToken(r, oAuth2StateKey, &oauthState, false)
+		if err != nil {
+			fmt.Println("Code exchange failed with ", err)
+			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+			return
+		}
+		// Check states match
+		state := r.FormValue("state")
+		if state != oauthState {
+			fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthState, state)
+			http.Error(w, "invalid oauth state", http.StatusInternalServerError)
+			return
+		}
+		// Delete the state cookie
+		c := http.Cookie{
+			Name:   oAuth2StateKey,
+			Domain: m.Hostname,
+			MaxAge: -1,
+		}
+		http.SetCookie(w, &c)
+		// Perform code exchange
+		code := r.FormValue("code")
+		token, err := m.Config.Exchange(context.Background(), code)
+		if err != nil {
+			fmt.Printf("Code exchange failed with '%s'\n", err)
+			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+			return
+		}
+		// Get user infos
+		client := &http.Client{}
+		req, _ := http.NewRequest("GET", m.UserInfoURL+"?access_token="+token.AccessToken, nil)
+		req.Header.Set("Authorization", "Bearer "+token.AccessToken)
+		response, err := client.Do(req)
+		if err != nil || response.StatusCode == http.StatusBadRequest {
+			fmt.Printf("User info failed with '%s'\n", err)
+			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+			return
+		}
+		// Get user
+		var user User
+		if response.Body == nil {
+			http.Error(w, "no response body", http.StatusBadRequest)
+			return
+		}
+		////////////////////////////////////////////////
+		// UNCOMMENT THIS TO DEBUG USERINFO RESPONSE //
+		// readBody, err := ioutil.ReadAll(response.Body)
+		// if err != nil {
+		// 	panic(err)
+		// }
+		// newBody := ioutil.NopCloser(bytes.NewBuffer(readBody))
+		// response.Body = newBody
+		// if string(readBody) != "" {
+		// 	fmt.Printf("BODY : %q \n", readBody)
+		// }
+		////////////////////////////////////////////////
+		err = json.NewDecoder(response.Body).Decode(&user)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+			return
+		}
+		// Trim the user roles in case they come from LDAP
+		for key, role := range user.Roles {
+			user.Roles[key] = strings.TrimPrefix(strings.Split(role, ",")[0], "CN=")
+		}
+		// Store the user in cookie
+		// Generate
+		xsrfToken, err := common.GenerateRandomString(16)
+		if err != nil {
+			http.Error(w, "error generating XSRF Token", http.StatusInternalServerError)
+			return
+		}
+		tokenData := TokenData{User: user, XSRFToken: xsrfToken}
+		tokens.CreateCookie(tokenData, m.Hostname, authTokenKey, 24*time.Hour, w)
+		// Log the connexion
+		log.Printf("| %v (%v %v) | Login success | %v", user.Login, user.Name, user.Surname, req.RemoteAddr)
+		// Redirect
+		http.Redirect(w, r, "/", http.StatusFound)
+	}
+	return http.HandlerFunc(oauth2Handler)
+}
diff --git a/internal/common/common.go b/internal/common/common.go
new file mode 100644
index 0000000..708ba1c
--- /dev/null
+++ b/internal/common/common.go
@@ -0,0 +1,173 @@
+package common
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"io"
+	"net/http"
+	"os"
+	"path"
+	"strconv"
+	"sync"
+
+	"log"
+
+	"github.com/gorilla/mux"
+)
+
+var (
+	disableLogFatal = false
+	lock            sync.Mutex // Mutex used to lock file writing
+)
+
+// Save saves a representation of v to the file at path.
+func Save(path string, v interface{}) error {
+	lock.Lock()
+	defer lock.Unlock()
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	r, err := Marshal(v)
+	if err != nil {
+		return err
+	}
+	_, err = io.Copy(f, r)
+	return err
+}
+
+// Load loads the file at path into v. Use os.IsNotExist() to see if the returned error is due to the file being missing.
+func Load(path string, v interface{}) error {
+	lock.Lock()
+	defer lock.Unlock()
+	f, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return Unmarshal(f, v)
+}
+
+// Marshal is a function that marshals the object into an io.Reader. By default, it uses the JSON marshaller.
+var Marshal = func(v interface{}) (io.Reader, error) {
+	b, err := json.MarshalIndent(v, "", "\t")
+	if err != nil {
+		return nil, err
+	}
+	return bytes.NewReader(b), nil
+}
+
+// Unmarshal is a function that unmarshals the data from the reader into the specified value. By default, it uses the JSON unmarshaller.
+var Unmarshal = func(r io.Reader, v interface{}) error {
+	return json.NewDecoder(r).Decode(v)
+}
+
+// GenerateRandomBytes returns securely generated random bytes.
+// It will return an error if the system's secure random
+// number generator fails to function correctly, in which
+// case the caller should not continue.
+func GenerateRandomBytes(n int) ([]byte, error) {
+	b := make([]byte, n)
+	_, err := rand.Read(b)
+	// Note that err == nil only if we read len(b) bytes.
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+// GenerateRandomString returns a URL-safe, base64 encoded
+// securely generated random string.
+// It will return an error if the system's secure random
+// number generator fails to function correctly, in which
+// case the caller should not continue.
+func GenerateRandomString(s int) (string, error) {
+	b, err := GenerateRandomBytes(s)
+	return base64.URLEncoding.EncodeToString(b), err
+}
+
+// FallBackWrapper serves a file if found and else default to index.html
+type FallBackWrapper struct {
+	Assets http.FileSystem
+}
+
+// Open serves a file if found and else default to index.html
+func (i *FallBackWrapper) Open(name string) (http.File, error) {
+	file, err := i.Assets.Open(name)
+	// If the file is found but there is another error or the asked for file has an extension : return the file or error
+	if !os.IsNotExist(err) || path.Ext(name) != "" {
+		return file, err
+	}
+	// Else fall back to index.html
+	return i.Assets.Open("index.html")
+}
+
+// Contains works out if a string slice contains a given string element
+func Contains(a []string, x string) bool {
+	for _, n := range a {
+		if x == n {
+			return true
+		}
+	}
+	return false
+}
+
+// StringValueFromEnv set a value into an *interface from an environment variable or default
+func StringValueFromEnv(ev string, def string) string {
+	val := os.Getenv(ev)
+	if val == "" {
+		return def
+	}
+	return val
+}
+
+// IntValueFromEnv set a value into an *interface from an environment variable or default
+func IntValueFromEnv(ev string, def int) int {
+	val := os.Getenv(ev)
+	if val == "" {
+		return def
+	}
+	v, err := strconv.Atoi(val)
+	if err != nil && !disableLogFatal {
+		log.Fatalf("Error : could not get integer value from environment variable %v=%v\n", ev, val)
+	}
+	return v
+}
+
+// BoolValueFromEnv set a value into an *interface from an environment variable or default
+func BoolValueFromEnv(ev string, def bool) bool {
+	val := os.Getenv(ev)
+	if val == "" {
+		return def
+	}
+	v, err := strconv.ParseBool(val)
+	if err != nil && !disableLogFatal {
+		log.Fatalf("Error : could not get boolean value from environment variable %v=%v\n", ev, val)
+	}
+	return v
+}
+
+func YearMonthFromRequest(r *http.Request) (year int, month int, err error) {
+	vars := mux.Vars(r)
+	yearStr := vars["year"]
+	monthStr := vars["month"]
+
+	if yearStr == "" || monthStr == "" {
+		return 0, 0, errors.New("missing query element")
+	}
+
+	year, err = strconv.Atoi(yearStr)
+	if err != nil {
+		return 0, 0, errors.New("year is not an integer")
+	}
+	month, err = strconv.Atoi(monthStr)
+	if err != nil {
+		return 0, 0, errors.New("month is not an integer")
+	}
+
+	return year, month, nil
+}
diff --git a/internal/common/common_test.go b/internal/common/common_test.go
new file mode 100644
index 0000000..e8e5fbd
--- /dev/null
+++ b/internal/common/common_test.go
@@ -0,0 +1,86 @@
+package common
+
+import (
+	"os"
+	"testing"
+)
+
+func init() {
+	disableLogFatal = true
+}
+
+func TestStringValueFromEnv(t *testing.T) {
+	os.Setenv("MY_EV", "from_env")
+	var rv string
+	type args struct {
+		ev  string
+		def string
+	}
+	tests := []struct {
+		name     string
+		args     args
+		expected string
+	}{
+		{"string_value_from_env", args{"MY_EV", "test"}, "from_env"},
+		{"string_value_from_def", args{"MY_DEF", "test"}, "test"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			rv = StringValueFromEnv(tt.args.ev, tt.args.def)
+			if rv != tt.expected {
+				t.Errorf("StringValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
+			}
+		})
+	}
+}
+
+func TestIntValueFromEnv(t *testing.T) {
+	os.Setenv("MY_EV", "from_env")
+	var rv int
+	type args struct {
+		ev  string
+		def int
+	}
+	tests := []struct {
+		name     string
+		args     args
+		expected int
+	}{
+		{"int_value_from_def", args{"MY_DEF", 1}, 1},
+		{"string_on_int_from_env", args{"MY_EV", 1}, 0},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			rv = IntValueFromEnv(tt.args.ev, tt.args.def)
+			if rv != tt.expected {
+				t.Errorf("IntValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
+			}
+		})
+	}
+}
+
+func TestBoolValueFromEnv(t *testing.T) {
+	os.Setenv("MY_EV", "from_env")
+	var rv bool
+	type args struct {
+		ev  string
+		def bool
+	}
+	tests := []struct {
+		name     string
+		args     args
+		expected bool
+	}{
+
+		{"bool_value_from_def", args{"MY_DEF", true}, true},
+		{"string_on_bool_from_def", args{"MY_EV", true}, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			rv = BoolValueFromEnv(tt.args.ev, tt.args.def)
+			if rv != tt.expected {
+				t.Errorf("BoolValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
+			}
+		})
+	}
+}
diff --git a/internal/file/file.go b/internal/file/file.go
new file mode 100644
index 0000000..dba702c
--- /dev/null
+++ b/internal/file/file.go
@@ -0,0 +1,38 @@
+package file
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+var imageFolder = common.StringValueFromEnv("IMAGE_FOLDER", "")
+
+func GetEcogestureImages(w http.ResponseWriter, r *http.Request) {
+	filenames, err := fileNamesFromFolder(imageFolder)
+	jsondata, err := json.Marshal(filenames)
+	if err != nil {
+		fmt.Printf("Error: %s", err.Error())
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(jsondata)
+	log.Printf("| get image names | %v", r.RemoteAddr)
+}
+
+func fileNamesFromFolder(folder string) (filenames []string, err error) {
+	files, err := ioutil.ReadDir(folder)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, file := range files {
+		filenames = append(filenames, file.Name())
+	}
+	return filenames, nil
+}
diff --git a/internal/file/file_test.go b/internal/file/file_test.go
new file mode 100644
index 0000000..d8dcc99
--- /dev/null
+++ b/internal/file/file_test.go
@@ -0,0 +1,38 @@
+package file
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+	"testing"
+)
+
+func TestFilenamesFromFolder(t *testing.T) {
+
+	// Create temporary file
+	err := os.MkdirAll("test", 0755)
+	if err != nil {
+		log.Fatal(err)
+	}
+	// Create some files
+	os.Create("test/file1")
+	os.Create("test/file2")
+	os.Create("test/file3")
+	expected := `["file1","file2","file3"]`
+
+	filenames, err := fileNamesFromFolder("test")
+	if err != nil {
+		t.Errorf(`error: %s`, err)
+		return
+	}
+	jsondata, err := json.Marshal(filenames)
+	if err != nil {
+		t.Errorf(`error: %s`, err)
+		return
+	}
+	if string(jsondata) != expected {
+		t.Errorf(`unexpected answer: got %s want %s`, string(jsondata), expected)
+		return
+	}
+	os.RemoveAll("test")
+}
diff --git a/internal/mocks/mocks.go b/internal/mocks/mocks.go
new file mode 100644
index 0000000..f25b70c
--- /dev/null
+++ b/internal/mocks/mocks.go
@@ -0,0 +1,71 @@
+// Package mocks provide mocks for development purposes (debug mode)
+package mocks
+
+import (
+	"fmt"
+	"net/http"
+)
+
+var (
+	port int
+)
+
+// Init initialize the configuration
+func Init(portFromMain int) {
+	port = portFromMain
+}
+
+// CreateMockOAuth2 creates a mock OAuth2 serve mux for development purposes
+func CreateMockOAuth2() *http.ServeMux {
+	mux := http.NewServeMux()
+	// Returns authorization code back to the user
+	mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
+		query := r.URL.Query()
+		redir := query.Get("redirect_uri") + "?state=" + query.Get("state") + "&code=mock_code"
+		http.Redirect(w, r, redir, http.StatusFound)
+	})
+	// Returns authorization code back to the user, but without the provided state
+	mux.HandleFunc("/auth-wrong-state", func(w http.ResponseWriter, r *http.Request) {
+		query := r.URL.Query()
+		redir := query.Get("redirect_uri") + "?state=" + "a-random-state" + "&code=mock_code"
+		http.Redirect(w, r, redir, http.StatusFound)
+	})
+
+	// Returns access token back to the user
+	mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
+		w.Write([]byte("access_token=mocktoken&scope=user&token_type=bearer"))
+	})
+	// Returns userinfo back to the user
+	mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte(`{
+			"displayName": "Us ER",
+			"memberOf": [
+				"CN=USERS",
+				"CN=OTHER_GROUP"
+			],
+			"id": "1000",
+			"login": "USER"
+		}`))
+	})
+	// Returns userinfo back to the user (with an admin user)
+	mux.HandleFunc("/admininfo", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte(`{
+			"displayName": "Ad MIN",
+			"memberOf": [
+				"CN=ADMINS",
+				"CN=OTHER_GROUP"
+			],
+			"id": "1",
+			"login": "ADMIN"
+		}`))
+	})
+	// Logout
+	mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, "Logout OK")
+	})
+
+	return mux
+}
diff --git a/internal/models/models.go b/internal/models/models.go
new file mode 100644
index 0000000..fc72cf8
--- /dev/null
+++ b/internal/models/models.go
@@ -0,0 +1,45 @@
+package models
+
+import (
+	"fmt"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+	"gorm.io/driver/mysql"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+)
+
+type DataHandler struct {
+	db *gorm.DB
+}
+
+var (
+	dbUser     = common.StringValueFromEnv("DATABASE_USER", "")
+	dbPassword = common.StringValueFromEnv("DATABASE_PASSWORD", "")
+	dbName     = common.StringValueFromEnv("DATABASE_NAME", "")
+	dbHost     = common.StringValueFromEnv("DATABASE_HOST", "")
+)
+
+// NewDataHandler init a DataHandler and returns a pointer to it
+func NewDataHandler() *DataHandler {
+	var db *gorm.DB
+	var err error
+	if dbUser == "" || dbPassword == "" || dbName == "" {
+		db, err = gorm.Open(sqlite.Open("backoffice.db"), &gorm.Config{})
+		if err != nil {
+			panic("failed to connect database")
+		}
+	} else {
+		dsn := fmt.Sprintf("%v:%v@tcp(%v:3306)/%v?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbHost, dbName)
+		db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
+		if err != nil {
+			panic("failed to connect database")
+		}
+	}
+
+	// Migrate the schema
+	db.AutoMigrate(&MonthlyInfo{})
+	db.AutoMigrate(&MonthlyNews{})
+	db.AutoMigrate(&Poll{})
+	return &DataHandler{db: db}
+}
diff --git a/internal/models/monthlyInfo.go b/internal/models/monthlyInfo.go
new file mode 100644
index 0000000..5425f8b
--- /dev/null
+++ b/internal/models/monthlyInfo.go
@@ -0,0 +1,155 @@
+package models
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+type MonthlyInfo struct {
+	Year  int    `json:"year"`
+	Month int    `json:"month"`
+	Info  string `json:"info"`
+	Image string `json:"image"`
+}
+
+// GetAllMonthlyInfo godoc
+// @Summary List all monthlyInfo
+// @Description Get details of all monthlyInfo
+// @Tags monthlyInfo
+// @Produce  json
+// @Success 200 {array} MonthlyInfo
+// @Router /api/admin/monthlyInfo [get]
+func (dh *DataHandler) GetAllMonthlyInfo(w http.ResponseWriter, r *http.Request) {
+	var monthlyInfo []MonthlyInfo
+	dh.db.Find(&monthlyInfo)
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(monthlyInfo)
+	log.Printf("| get all monthlyInfo | %v", r.RemoteAddr)
+}
+
+// GetSingleMonthlyInfo godoc
+// @Summary Get details of a specific monthlyInfo
+// @Description Get details of a specific monthlyInfo
+// @Tags monthlyInfo
+// @Produce json
+// @Success 200 {object} MonthlyInfo
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the monthlyInfo"
+// @Param month path int true "Month of the monthlyInfo"
+// @Router /api/admin/monthlyInfo/{year}/{month} [get]
+func (dh *DataHandler) GetSingleMonthlyInfo(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+
+	var monthlyInfo MonthlyInfo
+	err = dh.db.Where("year = ? AND month = ?", year, month).First(&monthlyInfo).Error
+	if err != nil {
+		// If not found, answer "not found"
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+	json.NewEncoder(w).Encode(monthlyInfo)
+}
+
+// SaveMonthlyInfo godoc
+// @Summary Create/update a specific monthlyInfo' content
+// @Description Create/update a specific monthlyInfo' content
+// @Tags monthlyInfo
+// @Accept json
+// @Produce  json
+// @Success 200 {object} MonthlyInfo "Updated successfully"
+// @Success 201 {object} MonthlyInfo "Created successfully"
+// @Failure 400 {string} string "Bad Request"
+// @Failure 500 {string} string "Internal server error"
+// @Param monthlyInfo body MonthlyInfo true "MonthlyInfo to create/update with new content"
+// @Router /api/admin/monthlyInfo [put]
+func (dh *DataHandler) SaveMonthlyInfo(w http.ResponseWriter, r *http.Request) {
+	if r.Body == http.NoBody {
+		http.Error(w, "request body is empty", http.StatusBadRequest)
+		return
+	}
+
+	decoder := json.NewDecoder(r.Body)
+	var monthlyInfo MonthlyInfo
+	err := decoder.Decode(&monthlyInfo)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	// Check if this monthlyInfo exists
+	err = dh.db.Where("year = ? AND month = ?", monthlyInfo.Year, monthlyInfo.Month).First(&MonthlyInfo{}).Error
+
+	if err != nil {
+		// Create a monthlyInfo
+		err = dh.db.Create(&MonthlyInfo{
+			Year:  monthlyInfo.Year,
+			Month: monthlyInfo.Month,
+			Info:  monthlyInfo.Info,
+			Image: monthlyInfo.Image,
+		}).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+		w.WriteHeader(http.StatusCreated)
+		json.NewEncoder(w).Encode(monthlyInfo)
+
+		log.Printf("| new monthlyInfo | year : %d month : %d | %v", monthlyInfo.Year, monthlyInfo.Month, r.RemoteAddr)
+		return
+
+	} else {
+		// Update info
+		err = dh.db.Model(&MonthlyInfo{}).Where("year = ? AND month = ?", monthlyInfo.Year, monthlyInfo.Month).Update("info", monthlyInfo.Info).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+
+		// Update info
+		err = dh.db.Model(&MonthlyInfo{}).Where("year = ? AND month = ?", monthlyInfo.Year, monthlyInfo.Month).Update("image", monthlyInfo.Image).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+
+		json.NewEncoder(w).Encode(monthlyInfo)
+		log.Printf("| updated monthlyInfo | year : %d month : %d | %v", monthlyInfo.Year, monthlyInfo.Month, r.RemoteAddr)
+	}
+}
+
+// DeleteMonthlyInfo godoc
+// @Summary Delete a specific monthlyInfo
+// @Description Delete a specific monthlyInfo
+// @Tags monthlyInfo
+// @Produce  json
+// @Success 200 {string} string "successful delete"
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the monthlyInfo"
+// @Param month path int true "Month of the monthlyInfo"
+// @Router /api/admin/monthlyInfo/{year}/{month} [delete]
+func (dh *DataHandler) DeleteMonthlyInfo(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	err = dh.db.Where("year = ? AND month = ?", year, month).Delete(&MonthlyInfo{}).Error
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusNotFound)
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte("successful delete"))
+}
diff --git a/internal/models/monthlyNews.go b/internal/models/monthlyNews.go
new file mode 100644
index 0000000..5a7ce8e
--- /dev/null
+++ b/internal/models/monthlyNews.go
@@ -0,0 +1,155 @@
+package models
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+type MonthlyNews struct {
+	Year    int    `json:"year"`
+	Month   int    `json:"month"`
+	Title   string `json:"title"`
+	Content string `json:"content"`
+}
+
+// GetAllMonthlyNews godoc
+// @Summary List all monthlyNews
+// @Description Get details of all monthlyNews
+// @Tags monthlyNews
+// @Produce  json
+// @Success 200 {array} MonthlyNews
+// @Router /api/admin/monthlyNews [get]
+func (dh *DataHandler) GetAllMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	var monthlyNews []MonthlyNews
+	dh.db.Find(&monthlyNews)
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(monthlyNews)
+	log.Printf("| get all monthlyNews | %v", r.RemoteAddr)
+}
+
+// GetSingleMonthlyNews godoc
+// @Summary Get details of a specific monthlyNews
+// @Description Get details of a specific monthlyNews
+// @Tags monthlyNews
+// @Produce json
+// @Success 200 {object} MonthlyNews
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the monthlyNews"
+// @Param month path int true "Month of the monthlyNews"
+// @Router /api/admin/monthlyNews/{year}/{month} [get]
+func (dh *DataHandler) GetSingleMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+
+	var monthlyNews MonthlyNews
+	err = dh.db.Where("year = ? AND month = ?", year, month).First(&monthlyNews).Error
+	if err != nil {
+		// If not found, answer "not found"
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+	json.NewEncoder(w).Encode(monthlyNews)
+}
+
+// SaveMonthlyNews godoc
+// @Summary Create/update a specific monthlyNews' content
+// @Description Create/update a specific monthlyNews' content
+// @Tags monthlyNews
+// @Accept json
+// @Produce  json
+// @Success 200 {object} MonthlyNews "Updated successfully"
+// @Success 201 {object} MonthlyNews "Created successfully"
+// @Failure 400 {string} string "Bad Request"
+// @Failure 500 {string} string "Internal server error"
+// @Param monthlyNews body MonthlyNews true "MonthlyNews to create/update with new content"
+// @Router /api/admin/monthlyNews [put]
+func (dh *DataHandler) SaveMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	if r.Body == http.NoBody {
+		http.Error(w, "request body is empty", http.StatusBadRequest)
+		return
+	}
+
+	decoder := json.NewDecoder(r.Body)
+	var monthlyNews MonthlyNews
+	err := decoder.Decode(&monthlyNews)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	// Check if this monthlyNews exists
+	err = dh.db.Where("year = ? AND month = ?", monthlyNews.Year, monthlyNews.Month).First(&MonthlyNews{}).Error
+
+	// Default title
+	if monthlyNews.Title == "" {
+		monthlyNews.Title = "Les nouveautés du service"
+	}
+
+	if err != nil {
+
+		// Create a monthlyNews
+		err = dh.db.Create(&MonthlyNews{Year: monthlyNews.Year, Month: monthlyNews.Month, Title: monthlyNews.Title, Content: monthlyNews.Content}).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+		w.WriteHeader(http.StatusCreated)
+		json.NewEncoder(w).Encode(monthlyNews)
+
+		log.Printf("| new monthlyNews | year : %d month : %d title : %v| %v", monthlyNews.Year, monthlyNews.Month, monthlyNews.Title, r.RemoteAddr)
+		return
+
+	} else {
+		// Update title
+		err = dh.db.Model(&MonthlyNews{}).Where("year = ? AND month = ?", monthlyNews.Year, monthlyNews.Month).Update("title", monthlyNews.Title).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+		// Update content
+		err = dh.db.Model(&MonthlyNews{}).Where("year = ? AND month = ?", monthlyNews.Year, monthlyNews.Month).Update("content", monthlyNews.Content).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+
+		json.NewEncoder(w).Encode(monthlyNews)
+		log.Printf("| updated monthlyNews | year : %d month : %d title : %v| %v", monthlyNews.Year, monthlyNews.Month, monthlyNews.Title, r.RemoteAddr)
+	}
+}
+
+// DeleteMonthlyNews godoc
+// @Summary Delete a specific monthlyNews
+// @Description Delete a specific monthlyNews
+// @Tags monthlyNews
+// @Produce  json
+// @Success 200 {string} string "successful delete"
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the monthlyNews"
+// @Param month path int true "Month of the monthlyNews"
+// @Router /api/admin/monthlyNews/{year}/{month} [delete]
+func (dh *DataHandler) DeleteMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	err = dh.db.Where("year = ? AND month = ?", year, month).Delete(&MonthlyNews{}).Error
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusNotFound)
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte("successful delete"))
+}
diff --git a/internal/models/monthlyReport.go b/internal/models/monthlyReport.go
new file mode 100644
index 0000000..eb8816b
--- /dev/null
+++ b/internal/models/monthlyReport.go
@@ -0,0 +1,96 @@
+package models
+
+import (
+	"encoding/json"
+	"net/http"
+	"time"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+type MonthlyReport struct {
+	Year        int    `json:"year"`
+	Month       int    `json:"month"`
+	Info        string `json:"info"`
+	Image       string `json:"image"`
+	NewsTitle   string `json:"newsTitle"`
+	NewsContent string `json:"newsContent"`
+	Question    string `json:"question"`
+	Link        string `json:"link"`
+}
+
+// GetMonthlyReport godoc
+// @Summary Get details of a specific monthlyReport
+// @Description Get details of a specific monthlyReport
+// @Tags monthlyReport
+// @Produce json
+// @Success 200 {object} MonthlyReport
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the monthlyReport"
+// @Param month path int true "Month of the monthlyReport"
+// @Router /api/common/monthlyReport/{year}/{month} [get]
+func (dh *DataHandler) GetMonthlyReport(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	monthlyReport, err := dh.getMonthlyReport(year, month)
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(monthlyReport)
+}
+
+// GetCurrentMonthlyReport godoc
+// @Summary Get details of the current monthlyReport
+// @Description Find the MonthlyInfo of the current month and try to find the corresponding monthlyNews and poll
+// @Tags monthlyReport
+// @Produce json
+// @Success 200 {object} MonthlyReport
+// @Failure 404 {string} string "Not found"
+// @Router /api/common/monthlyReport [get]
+func (dh *DataHandler) GetCurrentMonthlyReport(w http.ResponseWriter, r *http.Request) {
+
+	year, month, _ := time.Now().Date()
+
+	monthlyReport, err := dh.getMonthlyReport(year, int(month)-1)
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(monthlyReport)
+}
+
+func (dh *DataHandler) getMonthlyReport(year int, month int) (monthlyReport MonthlyReport, err error) {
+	var monthlyInfo MonthlyInfo
+	err = dh.db.Where("year = ? AND month = ?", year, month).First(&monthlyInfo).Error
+	if err != nil {
+		return MonthlyReport{}, err
+	}
+
+	var monthlyNews MonthlyNews
+	dh.db.Where("year = ? AND month = ?", year, month).First(&monthlyNews)
+
+	var poll Poll
+	dh.db.Where("year = ? AND month = ?", year, month).First(&poll)
+
+	monthlyReport = MonthlyReport{
+		Year:        year,
+		Month:       month,
+		Info:        monthlyInfo.Info,
+		Image:       monthlyInfo.Image,
+		NewsTitle:   monthlyNews.Title,
+		NewsContent: monthlyNews.Content,
+		Question:    poll.Question,
+		Link:        poll.Link,
+	}
+
+	return monthlyReport, nil
+}
diff --git a/internal/models/poll.go b/internal/models/poll.go
new file mode 100644
index 0000000..76acbc8
--- /dev/null
+++ b/internal/models/poll.go
@@ -0,0 +1,152 @@
+package models
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+type Poll struct {
+	Year     int    `json:"year"`
+	Month    int    `json:"month"`
+	Question string `json:"question"`
+	Link     string `json:"link"`
+}
+
+// GetAllPolls godoc
+// @Summary List all polls
+// @Description Get details of all polls
+// @Tags poll
+// @Produce  json
+// @Success 200 {array} Poll
+// @Router /api/admin/poll [get]
+func (dh *DataHandler) GetAllPolls(w http.ResponseWriter, r *http.Request) {
+	var polls []Poll
+	err := dh.db.Find(&polls).Error
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(polls)
+	log.Printf("| get all polls | %v", r.RemoteAddr)
+}
+
+// GetSinglePoll godoc
+// @Summary Get details of a specific poll
+// @Description Get details of a specific poll
+// @Tags poll
+// @Produce  json
+// @Success 200 {object} Poll
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the poll"
+// @Param month path int true "Month of the poll"
+// @Router /api/admin/poll/{year}/{month} [get]
+func (dh *DataHandler) GetSinglePoll(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+
+	var poll Poll
+	err = dh.db.Where("year = ? AND month = ?", year, month).First(&poll).Error
+	if err != nil {
+		// If not found, answer "not found"
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+	json.NewEncoder(w).Encode(poll)
+}
+
+// SavePoll godoc
+// @Summary Update a specific poll' content
+// @Description Update a specific poll' content
+// @Tags poll
+// @Accept json
+// @Produce  json
+// @Success 200 {object} Poll "Updated successfully"
+// @Success 201 {object} Poll "Created successfully"
+// @Failure 400 {string} string "Bad Request"
+// @Failure 500 {string} string "Internal server error"
+// @Param poll body Poll true "Poll to update with new content"
+// @Router /api/admin/poll [put]
+func (dh *DataHandler) SavePoll(w http.ResponseWriter, r *http.Request) {
+	if r.Body == http.NoBody {
+		http.Error(w, "request body is empty", http.StatusBadRequest)
+		return
+	}
+
+	decoder := json.NewDecoder(r.Body)
+	var poll Poll
+	err := decoder.Decode(&poll)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	// Check if this poll exists
+	err = dh.db.Where("year = ? AND month = ?", poll.Year, poll.Month).First(&Poll{}).Error
+
+	if err != nil {
+		// Create a poll
+		err = dh.db.Create(&Poll{Year: poll.Year, Month: poll.Month, Question: poll.Question, Link: poll.Link}).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+		w.WriteHeader(http.StatusCreated)
+		json.NewEncoder(w).Encode(poll)
+		log.Printf("| new poll | year : %d month : %d | %v", poll.Year, poll.Month, r.RemoteAddr)
+		return
+
+	} else {
+		// Update question
+		err = dh.db.Model(&Poll{}).Where("year = ? AND month = ?", poll.Year, poll.Month).Update("question", poll.Question).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+		// Update link
+		err = dh.db.Model(&Poll{}).Where("year = ? AND month = ?", poll.Year, poll.Month).Update("link", poll.Link).Error
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+
+		json.NewEncoder(w).Encode(poll)
+		log.Printf("| updated poll year : %d month : %d | %v", poll.Year, poll.Month, r.RemoteAddr)
+	}
+}
+
+// DeletePoll godoc
+// @Summary Delete a specific poll
+// @Description Delete a specific poll
+// @Tags poll
+// @Produce  json
+// @Success 200 {string} string "successful delete"
+// @Failure 404 {string} string "Not found"
+// @Param year path int true "Year of the poll"
+// @Param month path int true "Month of the poll"
+// @Router /api/admin/poll/{year}/{month} [delete]
+func (dh *DataHandler) DeletePoll(w http.ResponseWriter, r *http.Request) {
+	year, month, err := common.YearMonthFromRequest(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	err = dh.db.Where("year = ? AND month = ?", year, month).Delete(&Poll{}).Error
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusNotFound)
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte("successful delete"))
+}
diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go
new file mode 100644
index 0000000..8430785
--- /dev/null
+++ b/internal/rootmux/rootmux.go
@@ -0,0 +1,69 @@
+package rootmux
+
+import (
+	"net/http"
+
+	_ "forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/docs"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/auth"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/file"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/models"
+	"github.com/gorilla/mux"
+	httpSwagger "github.com/swaggo/http-swagger"
+)
+
+type RootMux struct {
+	Router  *mux.Router
+	Manager *auth.Manager
+}
+
+// @title Backoffice API
+// @version 1.0
+// @description This is a sample service for managing newsletters for Ecolyo
+// @termsOfService http://swagger.io/terms/
+// @contact.name API Support
+// @contact.email rpailharey@grandlyon.com
+// @host localhost:1443
+// @BasePath /
+
+func CreateRootMux() RootMux {
+
+	r := mux.NewRouter()
+	m := auth.NewManager()
+	dh := models.NewDataHandler()
+
+	r.HandleFunc("/OAuth2Login", m.HandleOAuth2Login)
+	r.Handle("/OAuth2Callback", m.HandleOAuth2Callback())
+	r.HandleFunc("/Logout", m.HandleLogout)
+	r.Handle("/api/common/WhoAmI", auth.ValidateAuthMiddleware(auth.WhoAmI(), []string{"*"}, false))
+
+	r.HandleFunc("/api/common/monthlyReport", dh.GetCurrentMonthlyReport).Methods(http.MethodGet)
+	r.HandleFunc("/api/common/monthlyReport/{year}/{month}", dh.GetMonthlyReport).Methods(http.MethodGet)
+
+	apiAdmin := r.PathPrefix("/api/admin").Subrouter()
+	apiAdmin.Use(auth.AdminAuthMiddleware)
+
+	apiAdmin.HandleFunc("/monthlyNews", dh.GetAllMonthlyNews).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/monthlyNews/{year}/{month}", dh.GetSingleMonthlyNews).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/monthlyNews", dh.SaveMonthlyNews).Methods(http.MethodPut)
+	apiAdmin.HandleFunc("/monthlyNews/{year}/{month}", dh.DeleteMonthlyNews).Methods(http.MethodDelete)
+
+	apiAdmin.HandleFunc("/monthlyInfo", dh.GetAllMonthlyInfo).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/monthlyInfo/{year}/{month}", dh.GetSingleMonthlyInfo).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/monthlyInfo", dh.SaveMonthlyInfo).Methods(http.MethodPut)
+	apiAdmin.HandleFunc("/monthlyInfo/{year}/{month}", dh.DeleteMonthlyInfo).Methods(http.MethodDelete)
+
+	apiAdmin.HandleFunc("/poll", dh.GetAllPolls).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/poll/{year}/{month}", dh.GetSinglePoll).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/poll", dh.SavePoll).Methods(http.MethodPut)
+	apiAdmin.HandleFunc("/poll/{year}/{month}", dh.DeletePoll).Methods(http.MethodDelete)
+
+	apiAdmin.HandleFunc("/imageNames", file.GetEcogestureImages).Methods(http.MethodGet)
+
+	// Swagger
+	r.PathPrefix("/swagger").Handler(httpSwagger.WrapHandler)
+
+	// Redirect route
+	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
+
+	return RootMux{r, &m}
+}
diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go
new file mode 100644
index 0000000..9fa492d
--- /dev/null
+++ b/internal/rootmux/rootmux_test.go
@@ -0,0 +1,196 @@
+package rootmux
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/cookiejar"
+	"net/http/httptest"
+	"net/url"
+	"os"
+	"testing"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/auth"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/mocks"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/models"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tester"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tokens"
+)
+
+var (
+	oAuth2Server   *httptest.Server
+	monthlyInfo    = models.MonthlyInfo{Year: 2021, Month: 0, Info: "Informations du mois", Image: "imagebase64"}
+	monthlyInfoStr string
+	monthlyNews    = models.MonthlyNews{Year: 2021, Month: 0, Title: "", Content: "Nouvelles fonctionnalités"}
+	monthlyNewsStr string
+	newPoll        = models.Poll{Year: 2021, Month: 0, Question: "pollQuestion", Link: "pollLink"}
+	newPollStr     string
+	noH            map[string]string
+)
+
+func TestMain(m *testing.M) {
+
+	// Create the OAuth2 mock server
+	oAuth2Server = httptest.NewServer(mocks.CreateMockOAuth2())
+	defer oAuth2Server.Close()
+
+	// Set the constants with environment variables
+	os.Setenv("HOSTNAME", "localhost")
+	os.Setenv("ADMIN_ROLE", "ADMINS")
+	os.Setenv("CLIENT_ID", "foo")
+	os.Setenv("CLIENT_SECRET", "bar")
+	os.Setenv("TOKEN_URL", oAuth2Server.URL+"/token")
+	os.Setenv("USERINFO_URL", oAuth2Server.URL+"/userinfo")
+	os.Setenv("LOGOUT_URL", oAuth2Server.URL+"/logout")
+
+	// Setup the token manager to use debug mode
+	os.Setenv("DEBUG_MODE", "true")
+	tokens.Init("../configs/tokenskey.json", true)
+
+	// Convert example objects to string
+	monthlyNewsBytes, _ := json.Marshal(monthlyNews)
+	monthlyNewsStr = string(monthlyNewsBytes)
+	monthlyInfoBytes, _ := json.Marshal(monthlyInfo)
+	monthlyInfoStr = string(monthlyInfoBytes)
+	newPollBytes, _ := json.Marshal(newPoll)
+	newPollStr = string(newPollBytes)
+
+	code := m.Run()
+	// Remove the database
+	os.Remove("backoffice.db")
+	os.Exit(code)
+}
+
+func TestAll(t *testing.T) {
+
+	// Set up testers
+	os.Setenv("AUTH_URL", oAuth2Server.URL+"/auth-wrong-state") // Set the server to access failing OAuth2 endpoints
+	oauth2Tests(t)
+	os.Setenv("AUTH_URL", oAuth2Server.URL+"/auth") // Set the server to access the correct OAuth2Endpoint
+	unloggedTests(t)
+
+	os.Setenv("USERINFO_URL", oAuth2Server.URL+"/admininfo")
+	adminTests(t)
+
+}
+
+/**
+SECURITY TESTS (this tests are to check that the security protections works)
+**/
+func oauth2Tests(t *testing.T) {
+	// Create the tester
+	ts, do, _ := createTester(t)
+	defer ts.Close() // Close the tester
+	// Try to login (must fail)
+	do("GET", "/OAuth2Login", noH, "", http.StatusInternalServerError, "invalid oauth state")
+}
+
+/**
+UNLOGGED USER TESTS (this tests are to check that the security protections works)
+**/
+func unloggedTests(t *testing.T) {
+	// Create the tester
+	ts, do, _ := createTester(t)
+	defer ts.Close() // Close the tester
+
+	// Try to create a monthlyNews (must fail)
+	do("PUT", "/api/admin/monthlyNews", noH, monthlyNewsStr, http.StatusUnauthorized, "error extracting token")
+	// Try to get the most recent monthlyReport (must fail because not found)
+	do("GET", "/api/common/monthlyReport", noH, "", http.StatusNotFound, "")
+}
+
+/**
+ADMIN TESTS (this tests are to check that an administrator can edit a newsletter's content)
+**/
+func adminTests(t *testing.T) {
+	// Create the tester
+	ts, do, _ := createTester(t)
+	defer ts.Close() // Close the tester
+	tests := func() {
+		// Get the XSRF Token
+		response := do("GET", "/api/common/WhoAmI", noH, "", http.StatusOK, "")
+		token := auth.TokenData{}
+		json.Unmarshal([]byte(response), &token)
+		xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken}
+		// Try to create a monthlyNews without the XSRF-TOKEN (must fail)
+		do("PUT", "/api/admin/monthlyNews", noH, monthlyNewsStr, http.StatusUnauthorized, "XSRF protection triggered")
+		// Try to create a monthlyNews without body (must fail)
+		do("PUT", "/api/admin/monthlyNews", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
+		// Try to get a monthlyNews before it is created (must fail because not found)
+		do("GET", "/api/admin/monthlyNews/2021/0", xsrfHeader, "", http.StatusNotFound, "")
+		// Try to create a monthlyNews (must pass)
+		do("PUT", "/api/admin/monthlyNews", xsrfHeader, monthlyNewsStr, http.StatusCreated, `{"year":2021,"month":0,"title":"Les nouveautés du service","content":"Nouvelles fonctionnalités"`)
+		// Try to update a monthlyNews (must pass)
+		do("PUT", "/api/admin/monthlyNews", xsrfHeader, monthlyNewsStr, http.StatusOK, `{"year":2021,"month":0,"title":"Les nouveautés du service","content":"Nouvelles fonctionnalités"`)
+		// Try to get the monthlyNews created (must pass)
+		do("GET", "/api/admin/monthlyNews/2021/0", xsrfHeader, "", http.StatusOK, `{"year":2021,"month":0,"title":"Les nouveautés du service","content":"Nouvelles fonctionnalités"`)
+		// Try to get the monthlyReport (must fail because monthlyInfo not found)
+		do("GET", "/api/common/monthlyReport/2021/0", noH, "", http.StatusNotFound, "")
+
+		// Try to create a poll without the XSRF-TOKEN (must fail)
+		do("PUT", "/api/admin/poll", noH, newPollStr, http.StatusUnauthorized, "XSRF protection triggered")
+		// Try to create a poll without body (must fail)
+		do("PUT", "/api/admin/poll", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
+		// Try to get a poll before it is created (must fail because not found')
+		do("GET", "/api/admin/poll/2021/0", xsrfHeader, "", http.StatusNotFound, "")
+		// Try to create a poll (must pass)
+		do("PUT", "/api/admin/poll", xsrfHeader, newPollStr, http.StatusCreated, newPollStr)
+		// Try to update a poll (must pass)
+		do("PUT", "/api/admin/poll", xsrfHeader, newPollStr, http.StatusOK, newPollStr)
+		// Try to get the poll created (must pass)
+		do("GET", "/api/admin/poll/2021/0", xsrfHeader, "", http.StatusOK, newPollStr)
+		// Try to get the monthlyReport (must fail because monthlyInfo not found)
+		do("GET", "/api/common/monthlyReport/2021/0", noH, "", http.StatusNotFound, "")
+
+		// Try to create a monthlyInfo without the XSRF-TOKEN (must fail)
+		do("PUT", "/api/admin/monthlyInfo", noH, monthlyInfoStr, http.StatusUnauthorized, "XSRF protection triggered")
+		// Try to create a monthlyInfo without body (must fail)
+		do("PUT", "/api/admin/monthlyInfo", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
+		// Try to get a monthlyInfo before it is created (must fail because not found)
+		do("GET", "/api/admin/monthlyInfo/2021/0", xsrfHeader, "", http.StatusNotFound, "")
+		// Try to create a monthlyInfo (must pass)
+		do("PUT", "/api/admin/monthlyInfo", xsrfHeader, monthlyInfoStr, http.StatusCreated, monthlyInfoStr)
+		// Try to update a monthlyInfo (must pass)
+		do("PUT", "/api/admin/monthlyInfo", xsrfHeader, monthlyInfoStr, http.StatusOK, monthlyInfoStr)
+		// Try to get the monthlyInfo created (must pass)
+		do("GET", "/api/admin/monthlyInfo/2021/0", xsrfHeader, "", http.StatusOK, monthlyInfoStr)
+		// Try to get the monthlyReport (must pass)
+		do("GET", "/api/common/monthlyReport/2021/0", noH, "", http.StatusOK, `{"year":2021,"month":0,"info":"Informations du mois","image":"imagebase64","newsTitle":"Les nouveautés du service","newsContent":"Nouvelles fonctionnalités","question":"pollQuestion","link":"pollLink"`)
+
+		// Try to delete the monthlyNews created (must pass)
+		do("DELETE", "/api/admin/monthlyNews/2021/0", xsrfHeader, "", http.StatusOK, "successful delete")
+		// Try to get a monthlyNews after it is deleted (must fail because not found)
+		do("GET", "/api/admin/monthlyNews/2021/0", xsrfHeader, "", http.StatusNotFound, "")
+		// Try to get the monthlyReport (must pass)
+		do("GET", "/api/common/monthlyReport/2021/0", noH, "", http.StatusOK, `{"year":2021,"month":0,"info":"Informations du mois","image":"imagebase64","newsTitle":"","newsContent":"","question":"pollQuestion","link":"pollLink"`)
+		// Try to delete the poll created (must pass)
+		do("DELETE", "/api/admin/poll/2021/0", xsrfHeader, "", http.StatusOK, "successful delete")
+		// Try to get a poll after it is deleted (must fail because not found)
+		do("GET", "/api/admin/poll/2021/0", xsrfHeader, "", http.StatusNotFound, "")
+		// Try to get the monthlyReport (must pass)
+		do("GET", "/api/common/monthlyReport/2021/0", noH, "", http.StatusOK, `{"year":2021,"month":0,"info":"Informations du mois","image":"imagebase64","newsTitle":"","newsContent":"","question":"","link":""`)
+	}
+	// Try to login (must pass)
+	do("GET", "/OAuth2Login", noH, "", http.StatusOK, "")
+	// Run the tests
+	tests()
+	// Try to logout (must pass)
+	do("GET", "/Logout", noH, "", http.StatusOK, "")
+	// Try to get a monthlyNews again (must fail)
+	do("GET", "/api/admin/monthlyNews", noH, "", http.StatusUnauthorized, "error extracting token")
+	// Try to get a poll again (must fail)
+	do("GET", "/api/admin/poll", noH, "", http.StatusUnauthorized, "error extracting token")
+}
+
+func createTester(t *testing.T) (*httptest.Server, tester.DoFn, tester.DoFn) {
+	// Create the server
+	mux := CreateRootMux()
+	ts := httptest.NewServer(mux.Router)
+	url, _ := url.Parse(ts.URL)
+	port := url.Port()
+	mux.Manager.Config.RedirectURL = "http://" + os.Getenv("HOSTNAME") + ":" + port + "/OAuth2Callback"
+	mux.Manager.Hostname = "http://" + os.Getenv("HOSTNAME") + ":" + port
+	// Create the cookie jar
+	jar, _ := cookiejar.New(nil)
+	// wrap the testing function
+	return ts, tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), jar), tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), nil)
+}
diff --git a/internal/tester/tester.go b/internal/tester/tester.go
new file mode 100644
index 0000000..d7c6a67
--- /dev/null
+++ b/internal/tester/tester.go
@@ -0,0 +1,92 @@
+package tester
+
+import (
+	"context"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/http/cookiejar"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"testing"
+	"time"
+)
+
+type DoFn func(method string, url string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string
+
+// DoRequestOnHandler does a request on a router (or handler) and check the response
+func DoRequestOnHandler(t *testing.T, router http.Handler, method string, route string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string {
+	req, err := http.NewRequest(method, route, strings.NewReader(payload))
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i, v := range headers {
+		req.Header.Set(i, v)
+	}
+	rr := httptest.NewRecorder()
+	router.ServeHTTP(rr, req)
+	if status := rr.Code; status != expectedStatus {
+		t.Errorf("Tested %v %v %v ; handler returned wrong status code: got %v want %v", method, route, payload, status, expectedStatus)
+	}
+	if !strings.HasPrefix(rr.Body.String(), expectedBody) {
+		t.Errorf("Tested %v %v %v ; handler returned unexpected body: got %v want %v", method, route, payload, rr.Body.String(), expectedBody)
+	}
+	return string(rr.Body.String())
+}
+
+// DoRequestOnServer does a request on listening server
+func DoRequestOnServer(t *testing.T, hostname string, port string, jar *cookiejar.Jar, method string, testURL string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string {
+	dialer := &net.Dialer{
+		Timeout:   30 * time.Second,
+		KeepAlive: 30 * time.Second,
+		DualStack: true,
+	}
+	// or create your own transport, there's an example on godoc.
+	http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
+		addrAndPort := strings.Split(addr, ":")
+		if strings.HasSuffix(addrAndPort[0], "vestibule.io") {
+			addr = "127.0.0.1:" + addrAndPort[1]
+		}
+		return dialer.DialContext(ctx, network, addr)
+	}
+	if strings.HasPrefix(testURL, "/") {
+		testURL = "http://" + hostname + ":" + port + testURL
+	} else {
+		u, _ := url.Parse("http://" + testURL)
+		testURL = "http://" + u.Host + ":" + port + u.Path + "?" + u.RawQuery
+	}
+	req, err := http.NewRequest(method, testURL, strings.NewReader(payload))
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i, v := range headers {
+		req.Header.Set(i, v)
+	}
+	var client *http.Client
+	if jar != nil {
+		client = &http.Client{Jar: jar}
+	} else {
+		client = &http.Client{}
+	}
+	res, err := client.Do(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+	body, _ := ioutil.ReadAll(res.Body)
+	bodyString := string(body)
+	if status := res.StatusCode; status != expectedStatus {
+		t.Errorf("Tested %v %v %v ; handler returned wrong status code: got %v want %v", method, testURL, payload, status, expectedStatus)
+	}
+	if !strings.HasPrefix(bodyString, expectedBody) {
+		t.Errorf("Tested %v %v %v ; handler returned unexpected body: got %v want %v", method, testURL, payload, bodyString, expectedBody)
+	}
+	return bodyString
+}
+
+// CreateServerTester wraps DoRequestOnServer to factorize t, port and jar
+func CreateServerTester(t *testing.T, hostname string, port string, jar *cookiejar.Jar) DoFn {
+	return func(method string, url string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string {
+		return DoRequestOnServer(t, port, hostname, jar, method, url, headers, payload, expectedStatus, expectedBody)
+	}
+}
diff --git a/internal/tokens/tokens.go b/internal/tokens/tokens.go
new file mode 100644
index 0000000..c22225f
--- /dev/null
+++ b/internal/tokens/tokens.go
@@ -0,0 +1,232 @@
+package tokens
+
+import (
+	"bytes"
+	"compress/flate"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"strings"
+	"time"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+var (
+	now = time.Now
+	// m is the current token manager
+	m manager
+)
+
+// manager manages tokens
+type manager struct {
+	key       []byte
+	debugMode bool
+}
+
+// Init inits the main token manager
+func Init(keyfile string, debug bool) {
+	m = newManager(keyfile, debug)
+}
+
+// newManager creates a manager
+func newManager(keyfile string, debug bool) manager {
+	var keyConfig struct {
+		Key []byte
+	}
+	err := common.Load(keyfile, &keyConfig)
+	if err != nil {
+		keyConfig.Key, err = common.GenerateRandomBytes(32)
+		if err != nil {
+			log.Fatal(err)
+		}
+		err := common.Save(keyfile, keyConfig)
+		if err != nil {
+			log.Println("Token signing key could not be saved")
+		}
+	}
+	log.Println("Token signing key set")
+	return manager{
+		debugMode: debug,
+		key:       keyConfig.Key,
+	}
+}
+
+// Token represents a token containting data
+type Token struct {
+	ExpiresAt int64
+	IssuedAt  int64 `json:"iat,omitempty"`
+	Data      []byte
+}
+
+// CreateCookie creates a token with the given data and returns it in a cookie
+func CreateCookie(data interface{}, hostName string, cookieName string, duration time.Duration, w http.ResponseWriter) {
+	expiration := now().Add(duration)
+	value, err := CreateToken(data, expiration)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	cookie := http.Cookie{Name: cookieName, Domain: hostName, Value: value, Expires: expiration, Secure: !m.debugMode, HttpOnly: true, SameSite: http.SameSiteLaxMode}
+	http.SetCookie(w, &cookie)
+}
+
+// CreateToken creates a token with the given data
+func CreateToken(data interface{}, expiration time.Time) (string, error) {
+	// Marshall the data
+	d, err := json.Marshal(data)
+	if err != nil {
+		return "", err
+	}
+	// Create the payload
+	token := Token{
+		ExpiresAt: expiration.Unix(),
+		Data:      d,
+	}
+	// Serialize the payload
+	sToken, err := json.Marshal(token)
+	if err != nil {
+		return "", err
+	}
+	// Compress with deflate
+	var csToken bytes.Buffer
+	var c *flate.Writer
+	if c, err = flate.NewWriter(&csToken, flate.BestCompression); err != nil {
+		return "", err
+	}
+	if _, err := c.Write(sToken); err != nil {
+		return "", err
+	}
+	if err := c.Close(); err != nil {
+		return "", err
+	}
+	ecsToken, err := Encrypt(csToken.Bytes(), m.key)
+	if err != nil {
+		return "", err
+	}
+	return base64.StdEncoding.EncodeToString(ecsToken), nil
+}
+
+// ExtractAndValidateToken extracts the token from the request, validates it, and return the data n the value pointed to by v
+func ExtractAndValidateToken(r *http.Request, cookieName string, v interface{}, checkXSRF bool) (bool, error) {
+	becsToken, checkXSRF, err := func(r *http.Request, checkXSRF bool) (string, bool, error) {
+		// Try to extract from the query
+		query := r.URL.Query().Get("token")
+		if query != "" {
+			return query, false, nil
+		}
+		// Try to extract from the cookie
+		cookie, err := r.Cookie(cookieName)
+		if err == nil {
+			return cookie.Value, checkXSRF, err
+		}
+		// Try to get an auth token from the header
+		authHeader := strings.Split(r.Header.Get("Authorization"), " ")
+		if authHeader[0] == "Bearer" && len(authHeader) == 2 {
+			return authHeader[1], false, nil
+		}
+		// Try to use the basic auth header instead
+		if authHeader[0] == "Basic" && len(authHeader) == 2 {
+			decoded, err := base64.StdEncoding.DecodeString(authHeader[1])
+			if err == nil {
+				auth := strings.Split(string(decoded), ":")
+				return auth[1], false, nil
+			}
+		}
+		return "", false, errors.New("could not extract token")
+	}(r, checkXSRF)
+
+	if err == nil {
+		return checkXSRF, unstoreData(becsToken, v)
+	}
+	return false, err
+}
+
+// unstoreData decrypt, uncompress, unserialize the token, and returns the data n the value pointed to by v
+func unstoreData(becsToken string, v interface{}) error {
+	// Decrypt the token
+	ecsToken, err := base64.StdEncoding.DecodeString(becsToken)
+	if err != nil {
+		return fmt.Errorf("failed to unbase64 token")
+
+	}
+	csToken, err := Decrypt(ecsToken, m.key)
+	if err != nil {
+		return fmt.Errorf("failed to decrypt token")
+
+	}
+	// Uncompress the token
+	rdata := bytes.NewReader(csToken)
+	r := flate.NewReader(rdata)
+	sToken, err := ioutil.ReadAll(r)
+	if err != nil {
+		return fmt.Errorf("failed to uncompress token")
+	}
+	// Unserialize the token
+	token := Token{}
+	err = json.Unmarshal(sToken, &token)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshall token")
+
+	}
+	// Validate the token
+	if token.ExpiresAt < now().Unix() {
+		return fmt.Errorf("token expired")
+	}
+	// Update the data
+	err = json.Unmarshal(token.Data, v)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshall data")
+
+	}
+	// Return no error if everything is fine
+	return nil
+}
+
+// Encrypt a byte array with AES
+func Encrypt(data []byte, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return []byte{}, err
+	}
+	gcm, err := cipher.NewGCM(block)
+	if err != nil {
+		return []byte{}, err
+	}
+	nonce := make([]byte, gcm.NonceSize())
+	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
+		return []byte{}, err
+	}
+	cipherData := gcm.Seal(nonce, nonce, data, nil)
+	return cipherData, nil
+}
+
+// Decrypt a byte array with AES
+func Decrypt(data []byte, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return []byte{}, err
+	}
+	gcm, err := cipher.NewGCM(block)
+	if err != nil {
+		return []byte{}, err
+	}
+	nonceSize := gcm.NonceSize()
+	if len(data) <= nonceSize {
+		return []byte{}, err
+	}
+	nonce, cipherData := data[:nonceSize], data[nonceSize:]
+	plainData, err := gcm.Open(nil, nonce, cipherData, nil)
+	if err != nil {
+		return []byte{}, err
+	}
+	return plainData, nil
+}
diff --git a/internal/tokens/tokens_test.go b/internal/tokens/tokens_test.go
new file mode 100644
index 0000000..841819a
--- /dev/null
+++ b/internal/tokens/tokens_test.go
@@ -0,0 +1,64 @@
+package tokens
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+)
+
+type user struct {
+	Login    string
+	Password string
+}
+
+func (u user) String() string {
+	return fmt.Sprintf("Login: %v, Password: %v", u.Login, u.Password)
+}
+
+func TestManagerCreateTokenUnStoreData(t *testing.T) {
+	key, _ := common.GenerateRandomBytes(32)
+	key2, _ := common.GenerateRandomBytes(32)
+	type fields struct {
+		encryptKey []byte
+		decryptKey []byte
+		debugMode  bool
+	}
+	type args struct {
+		data       interface{}
+		expiration time.Time
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    bool
+		wantErr bool
+	}{
+		{"future_expiration", fields{key, key, false}, args{user{"admin", "password"}, time.Now().Add(24 * time.Hour)}, true, false},
+		{"past_expiration", fields{key, key, false}, args{user{"admin", "password"}, time.Now().Add(-24 * time.Hour)}, false, true},
+		{"incorrect_aes_key", fields{[]byte("wrong_key_size"), []byte("wrong_key_size"), false}, args{user{"admin", "password"}, time.Now().Add(+24 * time.Hour)}, false, true},
+		{"wrong_decrypt_key", fields{key, key2, false}, args{user{"admin", "password"}, time.Now().Add(+24 * time.Hour)}, false, true},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			m = manager{
+				key:       tt.fields.encryptKey,
+				debugMode: tt.fields.debugMode,
+			}
+			token, _ := CreateToken(tt.args.data, tt.args.expiration)
+			m.key = tt.fields.decryptKey
+			v := user{}
+			err := unstoreData(token, &v)
+			got := tt.args.data == v
+			if (err != nil) != tt.wantErr {
+				t.Errorf("manager.(un)storeData() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("manager.(un)storeData() inData:%v, outData:%v => equality: %v, want %v", tt.args.data, v, got, tt.want)
+			}
+		})
+	}
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..2876f92
--- /dev/null
+++ b/main.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"log"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/mocks"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/rootmux"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tokens"
+)
+
+var (
+	httpsPort  = common.IntValueFromEnv("HTTPS_PORT", 443)     // HTTPS port to serve on
+	debugMode  = common.BoolValueFromEnv("DEBUG_MODE", false)  // Debug mode, disable Secure attribute for cookies
+	mockOAuth2 = common.BoolValueFromEnv("MOCK_OAUTH2", false) // Enable mock OAuth2 login
+)
+
+func main() {
+
+	log.Println("--- Server is starting ---")
+
+	// Initializations
+	tokens.Init("./configs/tokenskey.json", debugMode)
+
+	// Create the server
+	rootMux := rootmux.CreateRootMux()
+
+	// Init the hostname
+	mocks.Init(httpsPort)
+
+	// Start a mock oauth2 server if debug mode is on
+	if mockOAuth2 {
+		mockOAuth2Port := ":8090"
+		go http.ListenAndServe(mockOAuth2Port, mocks.CreateMockOAuth2())
+		fmt.Println("Mock OAuth2 server Listening on: http://localhost" + mockOAuth2Port)
+	}
+
+	// Serve locally with https
+	log.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", rootMux.Router))
+}
diff --git a/template.env b/template.env
new file mode 100644
index 0000000..46c5957
--- /dev/null
+++ b/template.env
@@ -0,0 +1,20 @@
+# Common settings
+HOSTNAME=
+ADMIN_ROLE=
+DEBUG_MODE=
+MOCK_OAUTH2=
+HTTPS_PORT=
+IMAGE_FOLDER=
+
+# Needed to user OAuth2 authentication :
+REDIRECT_URL=
+CLIENT_ID=
+CLIENT_SECRET=
+AUTH_URL=
+TOKEN_URL=
+USERINFO_URL=
+
+# Access to the database
+DATABASE_USER=
+DATABASE_PASSWORD=
+DATABASE_NAME=
\ No newline at end of file
-- 
GitLab