diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 73aa948..1a337d6 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -3,49 +3,46 @@ on: push: branches: - master - - workflows + - mobile_client* + workflow_dispatch: jobs: - DeployBackDocumentation: - runs-on: ubuntu-20.04 + DeployDocumentation: + name: "Generate ${{ matrix.run_name }}'s documentation" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - project_dir: mobile + run_name: 'Mobile client' + doc_dir: 'doc/api' + use: 'subosito/flutter-action@v1.5.3' + warm_up: flutter pub global activate dartdoc; rm -rf doc ; flutter gen-l10n + doc_generation: dartdoc; + defaults: + run: + working-directory: ${{ matrix.project_dir }} steps: - uses: actions/checkout@v2.3.4 - - name: Install stack - run: curl -sSL https://get.haskellstack.org/ | sh -s - -f - - name: Generate Haddock documentation - run: stack haddock --haddock-arguments --odir="docs" + - uses: subosito/flutter-action@v2.2.0 + with: + channel: 'dev' + if: matrix.project_dir == 'mobile' + - name: Warmup + run: ${{ matrix.warm_up }} ; ls -l + - name: Generate documentation + run: ${{ matrix.doc_generation }} - name: Deploy run: | - cd docs + cd "${{ matrix.doc_dir }}" git config --global user.email "${GITHUB_ACTOR}@github.com"; git config --global user.name "${GITHUB_ACTOR}"; git init git add -A; - git commit -m "Deploying the back-end's documentation"; + git commit -m "Deploying the ${{ matrix.run_name }}'s documentation"; git remote add origin https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@${GITHUB_REPO}; - git checkout -b documentation-back - git push --force origin documentation-back; - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPO: "github.com/AnonymusRaccoon/Aeris" - DeployMobileAppDocumentation: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2.3.4 - - uses: subosito/flutter-action@v1.5.3 - - name: Update Documentation - run: | - flutter pub global activate dartdoc - rm -rf doc - dartdoc - cd doc/api - git config --global user.email "${GITHUB_ACTOR}@github.com"; - git config --global user.name "${GITHUB_ACTOR}"; - git init - git add -A; - git commit -m "Deploying the mobile client documentation"; - git remote add origin https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@${GITHUB_REPO}; - git checkout -b documentation-mobile - git push --force origin documentation-mobile; + git checkout -b documentation-${{ matrix.project_dir }} + git push --force origin documentation-${{ matrix.project_dir }}; env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPO: "github.com/AnonymusRaccoon/Aeris" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f157f21..2195681 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .env - +*.secret.yaml .DS_Store -.idea \ No newline at end of file +.idea diff --git a/README.md b/README.md index 5e8ac90..2ca2dce 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Aeris supports a variety of service, on which Pipelines are perfomed upon: - GitHub - Spotify - Youtube -- Discord +- Reddit - Anilist - Twitter diff --git a/api/aeris.cabal b/api/aeris.cabal index 0d391c0..bf28948 100644 --- a/api/aeris.cabal +++ b/api/aeris.cabal @@ -18,8 +18,8 @@ build-type: Simple extra-source-files: README.md services/anilist.json - services/discord.json services/github.json + services/reddit.json services/spotify.json services/twitter.json services/utils.json diff --git a/api/services/discord.json b/api/services/discord.json deleted file mode 100644 index 58bca3f..0000000 --- a/api/services/discord.json +++ /dev/null @@ -1,454 +0,0 @@ -{ - "name": "Discord", - "actions": [ - { - "name": "Discord_OnDiscordMessage", - "description": { - "en": "An event triggered by the reception of a direct message", - "fr": "Un évènement déclenché par la réception d'un message privé." - }, - "label": { - "en": "Reception of a direct message", - "fr": "Réception d'un message privé" - }, - "params": [], - "returns": [ - { - "name": "MESSAGE", - "description": { - "en": "The body of the received message", - "fr": "Corps du message reçu" - } - }, - { - "name": "AUTHOR_ID", - "description": { - "en": "The Discord ID of the message's author", - "fr": "Identifiant Discord de l'auteur du message" - } - }, - { - "name": "AUTHOR_NAME", - "description": { - "en": "The Discord username of the message's author", - "fr": "Pseudo Discord de l'auteur du message" - } - } - ] - }, - { - "name": "Discord_OnDiscordMessageFrom", - "description": { - "en": "An event triggered by the reception of a direct message from a specified user", - "fr": "Un évènement déclenché par la réception d'un message privé d'un certain utilisateur" - }, - "label": { - "en": "Reception of a direct message from a specific user", - "fr": "Réception d'un message privé d'un certain utilisateur" - }, - "params": [ - { - "name": "user_id", - "type": "string", - "description": { - "en": "The Discord ID of the sender", - "fr": "Identifiant Discord de l'envoyeur" - } - } - ], - "returns": [ - { - "name": "MESSAGE", - "description": { - "en": "The body of the received message", - "fr": "Corps du message reçu" - } - }, - { - "name": "AUTHOR_ID", - "description": { - "en": "The Discord ID of the message's author", - "fr": "Identifiant Discord de l'auteur du message" - } - }, - { - "name": "AUTHOR_NAME", - "description": { - "en": "The Discord username of the message's author", - "fr": "Pseudo Discord de l'auteur du message" - } - } - ] - }, - { - "name": "Discord_OnDiscordMention", - "description": { - "en": "An event triggered when the authenticated user is mentioned", - "fr": "Un évènement déclenché lorsque l'utilisateur authentifié est mentionné" - }, - "label": { - "en": "Authenticated user mentioned", - "fr": "Utilisateur authentifié mentioné" - }, - "params": [], - "returns": [ - { - "name": "MESSAGE", - "description": { - "en": "The body of the received message", - "fr": "Corps du message reçu" - } - }, - { - "name": "AUTHOR_ID", - "description": { - "en": "The Discord ID of the message's author", - "fr": "Identifiant Discord de l'auteur du message" - } - }, - { - "name": "AUTHOR_NAME", - "description": { - "en": "The Discord username of the message's author", - "fr": "Pseudo Discord de l'auteur du message" - } - } - ] - }, - { - "name": "Discord_OnNewDiscordGuildMember", - "description": { - "en": "An event triggered when a new member joins a guild", - "fr": "Un évènement déclenché lorsqu'un nouveau membre rejoint un serveur" - }, - "label": { - "en": "New member joins a guild", - "fr": "Nouveau membre rejoint un serveur" - }, - "params": [], - "returns": [ - { - "name": "NEW_MEMBER_ID", - "description": { - "en": "The Discord ID of the new member", - "fr": "Identifiant Discord du nouveau membre" - } - }, - { - "name": "NEW_MEMBER_NAME", - "description": { - "en": "The Discord Username of the new member", - "fr": "Pseudo Discord du nouveau membre" - } - }, - { - "name": "SERVER_ID", - "description": { - "en": "The Discord ID of the guild", - "fr": "Identifiant Discord du serveur" - } - }, - { - "name": "SERVER_NAME", - "description": { - "en": "The name of the guild", - "fr": "Nom du serveur" - } - } - ] - }, - { - "name": "Discord_OnDiscordGuildLeave", - "description": { - "en": "An event triggered when a member leaves a guild", - "fr": "Un évènement déclenché lorsqu'un membre quitte un serveur" - }, - "label": { - "en": "A member leaves a guild", - "fr": "Un utilisateur quitte un serveur" - }, - "params": [], - "returns": [ - { - "name": "NEW_MEMBER_ID", - "description": { - "en": "The Discord ID of the new member", - "fr": "Identifiant Discord du nouveau membre" - } - }, - { - "name": "NEW_MEMBER_NAME", - "description": { - "en": "The Discord Username of the new member", - "fr": "Pseudo Discord du nouveau membre" - } - }, - { - "name": "SERVER_ID", - "description": { - "en": "The Discord ID of the guild", - "fr": "Identifiant Discord du serveur" - } - }, - { - "name": "SERVER_NAME", - "description": { - "en": "The name of the guild", - "fr": "Nom du serveur" - } - } - ] - } - ], - "reactions": [ - { - "name": "Discord_PostDiscordMessage", - "description": { - "en": "Posts a message on a given server (in a given channel)", - "fr": "Poste un message sur le serveur donné dans un canal donné" - }, - "label": { - "en": "Post a message on a server", - "fr": "Poste un message sur un serveur" - }, - "params": [ - { - "name": "server_id", - "type": "string", - "description": { - "en": "The Discord ID of the server", - "fr": "Identifiant Discord du serveur" - } - }, - { - "name": "channel_id", - "type": "string", - "description": { - "en": "Discord ID of the channel", - "fr": "Identifiant Discord du canal" - } - }, - { - "name": "content", - "type": "string", - "description": { - "en": "Body of the message to post", - "fr": "Contenu du message à envoyer" - } - } - ], - "returns": [ - { - "name": "USER_ID", - "description": { - "en": "The Discord ID of the sender", - "fr": "Identifiant Discord de l'envoyeur" - } - }, - { - "name": "USERNAME", - "description": { - "en": "The Discord username of the sender", - "fr": "Pseudo Discord de l'envoyeur" - } - }, - { - "name": "MESSAGE_CONTENT", - "description": { - "en": "The body of the posted message", - "fr": "Corps du message posté" - } - }, - { - "name": "CHANNEL_ID", - "description": { - "en": "The Discord ID of the channel", - "fr": "Identifiant Discord du canal" - } - }, - { - "name": "CHANNEL_NAME", - "description": { - "en": "The Discord name of the channel", - "fr": "Nom du canal Discord" - } - }, - { - "name": "SERVER_ID", - "description": { - "en": "The Discord ID of the server", - "fr": "Identifiant Discord du serveur" - } - }, - { - "name": "SERVER_NAME", - "description": { - "en": "The Discord name of the server", - "fr": "Nom du serveur Discord" - } - } - ] - }, - { - "name": "Discord_PostDiscordDM", - "description": { - "en": "Sends a message to a given user", - "fr": "Envoie un message à un utilisateur donné" - }, - "label": { - "en": "Send a direct message", - "fr": "Envoi un message privé" - }, - "params": [ - { - "name": "other_id", - "type": "string", - "description": { - "en": "The Discord ID of the recipient", - "fr": "Identifiant Discord du receveur" - } - }, - { - "name": "content", - "type": "string", - "description": { - "en": "Body of the message to send", - "fr": "Corps du message à envoyer" - } - } - ], - "returns": [ - { - "name": "USER_ID", - "description": { - "en": "The Discord ID of the sender", - "fr": "Identifiant Discord de l'envoyeur" - } - }, - { - "name": "USERNAME", - "description": { - "en": "The Discord username of the sender", - "fr": "Pseudo Discord de l'envoyeur" - } - }, - { - "name": "MESSAGE_CONTENT", - "description": { - "en": "The body of the posted message", - "fr": "Corps du message posté" - } - }, - { - "name": "SENDEE_ID", - "description": { - "en": "The Discord ID of the recipient", - "fr": "Identifiant Discord du receveur" - } - }, - { - "name": "SENDEE_USERNAME", - "description": { - "en": "The Discord name of the recipient", - "fr": "Pseudo Discord du receveur" - } - } - ] - }, - { - "name": "Discord_LeaveDiscordServer", - "description": { - "en": "Leave given Discord server", - "fr": "Quitte le serveur Discord donné" - }, - "label": { - "en": "Leave a server", - "fr": "Quitte un serveur" - }, - "params": [ - { - "name": "server_id", - "type": "string", - "description": { - "en": "The Discord ID of the server", - "fr": "Identifiant Discord du serveur" - } - } - ], - "returns": [ - { - "name": "USER_ID", - "description": { - "en": "The Discord ID of the leaver", - "fr": "Identifiant Discord de l'utilisateur qui quitte le serveur" - } - }, - { - "name": "USERNAME", - "description": { - "en": "The Discord username of the leaver", - "fr": "Pseudo Discord de l'utilisateur qui quitte le serveur" - } - }, - { - "name": "SERVER_ID", - "description": { - "en": "The Discord ID of the server", - "fr": "Identifiant Discord du serveur" - } - }, - { - "name": "SERVER_NAME", - "description": { - "en": "The Discord name of the server", - "fr": "Nom du serveur Discord" - } - } - ] - }, - { - "name": "Discord_SetDiscordStatus", - "description": { - "en": "Set discord status", - "fr": "Change le status Discord" - }, - "label": { - "en": "Set Discord status", - "fr": "Change le status Discord" - }, - "params": [ - { - "name": "status", - "type": "string", - "description": { - "en": "The status to set", - "fr": "Statut à définir" - } - } - ], - "returns": [ - { - "name": "USER_ID", - "description": { - "en": "The Discord ID of the user", - "fr": "Identifiant Discord de l'utilisateur" - } - }, - { - "name": "USERNAME", - "description": { - "en": "The Discord username of the user", - "fr": "Pseudo Discord de l'utilisateur" - } - }, - { - "name": "STATUS", - "description": { - "en": "The Discord status of the user", - "fr": "Status Discord de l'utilisateur" - } - } - ] - } - ] -} \ No newline at end of file diff --git a/api/services/github.json b/api/services/github.json index a49a3e1..e4649c5 100644 --- a/api/services/github.json +++ b/api/services/github.json @@ -1,723 +1,6 @@ { "name": "Github", - "actions": [ - { - "name": "Github_OnOpenPR", - "description": { - "en": "An event triggered when a pull request is opened on the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'une pull request est ouverte sur le répertoire donné" - }, - "label": { - "en": "Pull request opening", - "fr": "Ouverture d'une pull request" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "PR_NAME", - "description": { - "en": "The opened pull request name", - "fr": "Nom de la pull request ouverte" - } - }, - { - "name": "PR_BODY", - "description": { - "en": "The opened pull request body", - "fr": "Corps de la pull request ouverte" - } - }, - { - "name": "PR_OPENER", - "description": { - "en": "The user's username that opened the pull request", - "fr": "Pseudo de l'utilisateur qui a ouvert la pull request" - } - }, - { - "name": "PR_HEAD", - "description": { - "en": "The name of the branch to merge into the base branch", - "fr": "Nom de la branche à merger dans la branche de base" - } - }, - { - "name": "PR_BASE", - "description": { - "en": "The name of the branch to merge head branch into", - "fr": "Nom de la branche dans laquelle merger la branche 'head'" - } - }, - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - } - ] - }, - { - "name": "Github_OnCommentPR", - "description": { - "en": "An event triggered by a new comment inside the pull request on the repository given as parameter", - "fr": "Un évènement déclenché par un nouveau commentaire sous la pull request du répertoire donné en paramètre" - }, - "label": { - "en": "New comment inside a pull request", - "fr": "Nouveau commentaire sous une pull request" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "PR_NAME", - "description": { - "en": "The opened pull request name", - "fr": "Nom de la pull request ouverte" - } - }, - { - "name": "PR_BODY", - "description": { - "en": "The opened pull request body", - "fr": "Corps de la pull request ouverte" - } - }, - { - "name": "PR_OPENER", - "description": { - "en": "The user's username that opened the pull request", - "fr": "Pseudo de l'utilisateur qui a ouvert la pull request" - } - }, - { - "name": "PR_HEAD", - "description": { - "en": "The name of the branch to merge into the base branch", - "fr": "Nom de la branche à merger dans la branche de base" - } - }, - { - "name": "PR_BASE", - "description": { - "en": "The name of the branch to merge head branch into", - "fr": "Nom de la branche dans laquelle merger la branche 'head'" - } - }, - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "COMMENTER", - "description": { - "en": "The user's username that comment the pull request", - "fr": "Pseudo de l'utilisateur qui a commenté la pull request" - } - }, - { - "name": "COMMENT_BODY", - "description": { - "en": "The body of the new comment on the pull request", - "fr": "Corps du nouveau commentaire sous la pull request" - } - } - ] - }, - { - "name": "Github_OnClosePR", - "description": { - "en": "An event triggered when a pull request is closed on the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'une pull request est fermée sur le répertoire donné" - }, - "label": { - "en": "Closed pull request", - "fr": "Fermeture d'une pull request" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "PR_NAME", - "description": { - "en": "The opened pull request name", - "fr": "Nom de la pull request ouverte" - } - }, - { - "name": "PR_BODY", - "description": { - "en": "The opened pull request body", - "fr": "Corps de la pull request ouverte" - } - }, - { - "name": "PR_OPENER", - "description": { - "en": "The user's username that opened the pull request", - "fr": "Pseudo de l'utilisateur qui a ouvert la pull request" - } - }, - { - "name": "PR_HEAD", - "description": { - "en": "The name of the branch to merge into the base branch", - "fr": "Nom de la branche à merger dans la branche de base" - } - }, - { - "name": "PR_BASE", - "description": { - "en": "The name of the branch to merge head branch into", - "fr": "Nom de la branche dans laquelle merger la branche 'head'" - } - }, - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - } - ] - }, - { - "name": "Github_OnMergePR", - "description": { - "en": "An event triggered when a pull request is merged on the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'une pull request est mergée sur le répertoire donné" - }, - "label": { - "en": "Merged pull request", - "fr": "Pull request mergée" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "PR_NAME", - "description": { - "en": "The opened pull request name", - "fr": "Nom de la pull request ouverte" - } - }, - { - "name": "PR_BODY", - "description": { - "en": "The opened pull request body", - "fr": "Corps de la pull request ouverte" - } - }, - { - "name": "PR_OPENER", - "description": { - "en": "The user's username that opened the pull request", - "fr": "Pseudo de l'utilisateur qui a ouvert la pull request" - } - }, - { - "name": "PR_HEAD", - "description": { - "en": "The name of the branch to merge into the base branch", - "fr": "Nom de la branche à merger dans la branche de base" - } - }, - { - "name": "PR_BASE", - "description": { - "en": "The name of the branch to merge head branch into", - "fr": "Nom de la branche dans laquelle merger la branche 'head'" - } - }, - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - } - ] - }, - { - "name": "Github_OnCreateIssue", - "description": { - "en": "An event triggered when an issue is created on the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'une nouvelle issue est créée sur le répertoire donné" - }, - "label": { - "en": "Created issue", - "fr": "Issue créée" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "ISSUE_NAME", - "description": { - "en": "The title of the created issue", - "fr": "Titre de l'issue créée" - } - }, - { - "name": "ISSUE_CONTENT", - "description": { - "en": "The description of the created issue", - "fr": "Description de l'issue créée" - } - }, - { - "name": "ISSUE_AUTHOR", - "description": { - "en": "The issue creator username", - "fr": "Pseudo de l'auteur de l'issue" - } - } - ] - }, - { - "name": "Github_OnCommentIssue", - "description": { - "en": "An event triggered when an user comments an issue on the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'un utilisateur commente une issue sur le répertoire donné" - }, - "label": { - "en": "New comment on an issue", - "fr": "Nouveau commentaire sur une issue" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "ISSUE_NAME", - "description": { - "en": "The title of the created issue", - "fr": "Titre de l'issue créée" - } - }, - { - "name": "ISSUE_CONTENT", - "description": { - "en": "The description of the created issue", - "fr": "Description de l'issue créée" - } - }, - { - "name": "ISSUE_AUTHOR", - "description": { - "en": "The issue creator username", - "fr": "Pseudo de l'auteur de l'issue" - } - }, - { - "name": "COMMENT", - "description": { - "en": "The body of the new comment on an issue of the repository given as parameter", - "fr": "Corps du nouveau commentaire sur une issue dans le répertoire donné" - } - }, - { - "name": "COMMENTER", - "description": { - "en": "The author of the comment on an issue", - "fr": "Pseudo de l'auteur du commentaire de l'issue" - } - } - ] - }, - { - "name": "Github_OnCloseIssue", - "description": { - "en": "An event triggered when an issue is closed in the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'une issue est fermée sur le répertoire donné" - }, - "label": { - "en": "Issue closing", - "fr": "Fermeture d'une issue" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "ISSUE_NAME", - "description": { - "en": "The title of the created issue", - "fr": "Titre de l'issue créée" - } - }, - { - "name": "ISSUE_CONTENT", - "description": { - "en": "The description of the created issue", - "fr": "Description de l'issue créée" - } - }, - { - "name": "ISSUE_AUTHOR", - "description": { - "en": "The issue creator username", - "fr": "Pseudo de l'auteur de l'issue" - } - } - ] - }, - { - "name": "Github_OnForkRepo", - "description": { - "en": "An event triggered when the repository given as parameter is forked", - "fr": "Un évènement déclenché lorsque le répertoire donné est fourché" - }, - "label": { - "en": "Forked repository", - "fr": "Répertoire fourché" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "FORKER", - "description": { - "en": "The username of the user that fork the repository", - "fr": "Pseudo de l'utilisateur qui a fork le répertoire" - } - } - ] - }, - { - "name": "Github_OnStarRepo", - "description": { - "en": "An event triggered when a user star the repository given as parameter", - "fr": "Un évènement déclenché lorsqu'un utilisateur étoile un répertoire donné" - }, - "label": { - "en": "Starred repository", - "fr": "Répertoire étoilé" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "STAR_COUNT", - "description": { - "en": "The number of stars on the repository", - "fr": "Nombre de star sur le répertoire" - } - }, - { - "name": "STARER", - "description": { - "en": "The username of the user that starred the repository", - "fr": "Pseudo de l'utilisateur qui a star le répertoire" - } - } - ] - }, - { - "name": "Github_OnWatchRepo", - "description": { - "en": "An event triggered when an user watch a repository", - "fr": "Un évènement déclenché lorsqu'un utilisateur watch le répertoire donné" - }, - "label": { - "en": "Watch a repository", - "fr": "Répertoire observé" - }, - "params": [ - { - "name": "owner", - "type": "string", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "repo", - "type": "string", - "description": { - "en": "The targeted repository's name", - "fr": "Nom du répertoire ciblé" - } - } - ], - "returns": [ - { - "name": "REPO_NAME", - "description": { - "en": "The name of the repository", - "fr": "Nom du répertoire" - } - }, - { - "name": "REPO_OWNER", - "description": { - "en": "The user's username that owns the repository", - "fr": "Pseudo de l'utilisateur qui possède le répertoire" - } - }, - { - "name": "WATCH_COUNT", - "description": { - "en": "The number of users watching the repository", - "fr": "Nombre d'utilisateur qui watch le répertoire" - } - }, - { - "name": "WATCHER", - "description": { - "en": "The username of the user that watched the repository", - "fr": "Pseudo de l'utilisateur qui vient de watch le répertoire" - } - } - ] - } - ], + "actions": [], "reactions": [ { "name": "Github_OpenPR", diff --git a/api/services/reddit.json b/api/services/reddit.json new file mode 100644 index 0000000..79eb989 --- /dev/null +++ b/api/services/reddit.json @@ -0,0 +1,320 @@ +{ + "name": "Reddit", + "actions": [], + "reactions": [ + { + "name": "Reddit_JoinSubreddit", + "description": { + "en": "Subscribe to a Subbreddit", + "fr": "S'abonner à un Subreddit" + }, + "label": { + "en": "Join a Subbreddit", + "fr": "Rejoindre un Subreddit" + }, + "params": [ + { + "name": "sub_id", + "type": "string", + "description": { + "en": "The name of the subreddit", + "fr": "Le nom du subreddit" + } + } + ], + "returns": [ + { + "name": "SUB_NAME", + "type": "string", + "description": { + "en": "The name of the subreddit", + "fr": "Le nom du subreddit" + } + }, + { + "name": "SUB_ID", + "type": "string", + "description": { + "en": "The ID of the subreddit", + "fr": "L'ID du subreddit" + } + } + ] + }, + { + "name": "Reddit_LeaveSubreddit", + "description": { + "en": "Unsubscribe to a Subbreddit", + "fr": "Se désabonner à un Subreddit" + }, + "label": { + "en": "Leave a Subbreddit", + "fr": "Quitter un Subreddit" + }, + "params": [ + { + "name": "sub_id", + "type": "string", + "description": { + "en": "The ID of the subreddit", + "fr": "Le ID du subreddit" + } + } + ], + "returns": [ + { + "name": "SUB_NAME", + "type": "string", + "description": { + "en": "The name of the subreddit", + "fr": "Le nom du subreddit" + } + }, + { + "name": "SUB_ID", + "type": "string", + "description": { + "en": "The ID of the subreddit", + "fr": "L'ID du subreddit" + } + } + ] + }, + { + "name": "Reddit_PostInSubreddit", + "description": { + "en": "Post in a Subbreddit", + "fr": "Créer un post dans un Subreddit" + }, + "label": { + "en": "Create a post", + "fr": "Créer un post" + }, + "params": [ + { + "name": "sub_id", + "type": "string", + "description": { + "en": "The ID of the subreddit", + "fr": "Le ID du subreddit" + } + }, + { + "name": "title", + "type": "string", + "description": { + "en": "The title of the post", + "fr": "Le titre du post" + } + }, + { + "name": "body", + "type": "string", + "description": { + "en": "The content of the post", + "fr": "Le contenu du post" + } + } + ], + "returns": [ + { + "name": "SUB_NAME", + "type": "string", + "description": { + "en": "The name of the parent subreddit", + "fr": "Le nom du subreddit parent" + } + }, + { + "name": "SUB_ID", + "type": "string", + "description": { + "en": "The ID of the subreddit parent", + "fr": "L'ID du subreddit parent" + } + }, + { + "name": "POST_ID", + "type": "string", + "description": { + "en": "The ID of the post", + "fr": "L'ID du post" + } + } + ] + }, + { + "name": "Reddit_ReplyToPost", + "description": { + "en": "Reply to Post in a Subbreddit", + "fr": "Créer un réponse à un post dans un Subreddit" + }, + "label": { + "en": "Reply to a post", + "fr": "Répondre à un post" + }, + "params": [ + { + "name": "post_id", + "type": "string", + "description": { + "en": "The ID of the post", + "fr": "L'ID du post" + } + }, + { + "name": "reply_body", + "type": "string", + "description": { + "en": "The content of the reply", + "fr": "Le contenu de la réponse" + } + } + ], + "returns": [ + { + "name": "SUB_NAME", + "type": "string", + "description": { + "en": "The name of the parent subreddit", + "fr": "Le nom du subreddit parent" + } + }, + { + "name": "SUB_ID", + "type": "string", + "description": { + "en": "The ID of the parent subreddit", + "fr": "L'ID du subreddit parent" + } + }, + { + "name": "POST_ID", + "type": "string", + "description": { + "en": "The ID of the post", + "fr": "L'ID du post" + } + }, + { + "name": "REPLY_ID", + "type": "string", + "description": { + "en": "The ID of the reply", + "fr": "L'ID de la réponse" + } + } + ] + }, + { + "name": "Reddit_Upvote", + "description": { + "en": "Upvote a Post in a Subbreddit", + "fr": "Liker un un post dans un Subreddit" + }, + "label": { + "en": "Upvote to a post", + "fr": "Liker un post" + }, + "params": [ + { + "name": "post_id", + "type": "string", + "description": { + "en": "The ID of the post", + "fr": "L'ID du post" + } + } + ], + "returns": [ + { + "name": "SUB_NAME", + "type": "string", + "description": { + "en": "The name of the parent subreddit", + "fr": "Le nom du subreddit parent" + } + }, + { + "name": "SUB_ID", + "type": "string", + "description": { + "en": "The ID of the parent subreddit", + "fr": "L'ID du subreddit parent" + } + }, + { + "name": "POST_ID", + "type": "string", + "description": { + "en": "The ID of the upvoted post", + "fr": "L'ID du post liké" + } + }, + { + "name": "UPVOTE_COUNT", + "type": "integer", + "description": { + "en": "The count of upvote of the post", + "fr": "Le nombre d'upvote sur le post" + } + } + ] + }, + { + "name": "Reddit_Downvote", + "description": { + "en": "Downvote a Post in a Subbreddit", + "fr": "Disliker un un post dans un Subreddit" + }, + "label": { + "en": "Downvote to a post", + "fr": "Disliker un post" + }, + "params": [ + { + "name": "post_id", + "type": "string", + "description": { + "en": "The ID of the post", + "fr": "L'ID du post" + } + } + ], + "returns": [ + { + "name": "SUB_NAME", + "type": "string", + "description": { + "en": "The name of the parent subreddit", + "fr": "Le nom du subreddit parent" + } + }, + { + "name": "SUB_ID", + "type": "string", + "description": { + "en": "The ID of the parent subreddit", + "fr": "L'ID du subreddit parent" + } + }, + { + "name": "POST_ID", + "type": "string", + "description": { + "en": "The ID of the downvoted post", + "fr": "L'ID du post disliké" + } + }, + { + "name": "DOWNVOTE_COUNT", + "type": "integer", + "description": { + "en": "The count of downvote of the post", + "fr": "Le nombre de downvote sur le post" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/api/src/Api/About.hs b/api/src/Api/About.hs index e24446c..4885b2c 100644 --- a/api/src/Api/About.hs +++ b/api/src/Api/About.hs @@ -45,7 +45,7 @@ $(deriveJSON defaultOptions ''About) servicesDir :: [(FilePath, S.ByteString)] servicesDir = $(embedDir "./services/") --- servicesDir = undefined +--servicesDir = undefined about :: SockAddr -> AppM About about host = do diff --git a/api/src/Api/Auth.hs b/api/src/Api/Auth.hs index c2d4a5c..45de609 100644 --- a/api/src/Api/Auth.hs +++ b/api/src/Api/Auth.hs @@ -18,14 +18,18 @@ import Servant ( Post, ReqBody, err401, + err400, + err403, throwError, type (:<|>) (..), - type (:>), + type (:>), Capture, QueryParam ) import Control.Monad.IO.Class (liftIO) +import Control.Monad.Trans.Maybe (MaybeT(runMaybeT)) +import Core.OIDC ( getOauthTokens ) import Data.Aeson (FromJSON, ToJSON) -import Db.User (User', UserDB (UserDB), password, toUser) +import Db.User (User', UserDB (UserDB, userDBId), password, toUser) import GHC.Generics (Generic) import Servant.API.Generic (ToServantApi, (:-)) import Servant.Auth.Server (CookieSettings, JWT, JWTSettings, SetCookie, ThrowAll (throwAll), acceptLogin, AuthResult (Authenticated), makeJWT) @@ -34,10 +38,10 @@ import Servant.Server.Generic (AsServerT) import Data.ByteString.Lazy.Char8 ( unpack ) import Api.OIDC (OauthAPI, oauth) import App (AppM) -import Core.User (User, UserId (UserId)) +import Core.User (User, UserId (UserId), Service) import Data.Text (pack) import Password (hashPassword'', toPassword, validatePassword') -import Repository (createUser, getUserByName') +import Repository (createUser, getUserByName', getUserByToken, updateTokens) import Utils (UserAuth, AuthRes) data LoginUser = LoginUser @@ -57,15 +61,16 @@ newtype LoginResponse = LoginResponse } deriving (Eq, Show, Read, Generic) + +instance ToJSON LoginResponse +instance FromJSON LoginResponse + instance ToJSON LoginUser instance FromJSON LoginUser instance ToJSON SignupUser instance FromJSON SignupUser -instance ToJSON LoginResponse -instance FromJSON LoginResponse - type Protected = "me" :> Get '[JSON] User @@ -80,6 +85,14 @@ type Unprotected = :<|> "signup" :> ReqBody '[JSON] SignupUser :> Post '[JSON] NoContent + :<|> Capture "service" Service :> "signin" + :> QueryParam "code" String + :> Post '[JSON] LoginResponse + :<|> Capture "service" Service :> "signup" + :> QueryParam "code" String + :> ReqBody '[JSON] SignupUser + :> Post '[JSON] LoginResponse + loginHandler :: CookieSettings -> @@ -95,11 +108,35 @@ loginHandler cs jwts (LoginUser username p) = do case etoken of Left e -> throwError err401 Right v -> return $ LoginResponse $ unpack v - {--mApplyCookies <- liftIO $ acceptLogin cs jwts (toUser usr) - case mApplyCookies of - Nothing -> throwError err401 - Just applyCookies -> return $ applyCookies NoContent - --}else throwError err401 + else throwError err401 + +loginOauthHandler :: JWTSettings -> Service -> Maybe String -> AppM LoginResponse +loginOauthHandler jwts _ Nothing = throwError err400 +loginOauthHandler jwts service (Just code) = do + tokens <- liftIO $ runMaybeT $ getOauthTokens service code + case tokens of + Nothing -> throwError err403 + Just t -> do + user <- getUserByToken t + etoken <- liftIO $ makeJWT (toUser user) jwts Nothing + case etoken of + Left e -> throwError err401 + Right v -> return $ LoginResponse $ unpack v + +signupOauthHandler :: JWTSettings -> Service -> Maybe String -> SignupUser -> AppM LoginResponse +signupOauthHandler jwts service (Just code) (SignupUser name p) = do + hashed <- hashPassword'' $ toPassword $ pack p + user <- createUser $ UserDB (UserId 1) (pack name) hashed (pack name) [] + tokens <- liftIO $ runMaybeT $ getOauthTokens service code + case tokens of + Nothing -> throwError err403 + Just t -> do + updateTokens (userDBId user) t + etoken <- liftIO $ makeJWT (toUser user) jwts Nothing + case etoken of + Left e -> throwError err401 + Right v -> return $ LoginResponse $ unpack v +signupOauthHandler _ _ _ _ = throwError err400 signupHandler :: SignupUser -> @@ -111,8 +148,10 @@ signupHandler (SignupUser name p) = do unprotected :: CookieSettings -> JWTSettings -> ServerT Unprotected AppM unprotected cs jwts = - loginHandler cs jwts - :<|> signupHandler + loginHandler cs jwts + :<|> signupHandler + :<|> loginOauthHandler jwts + :<|> signupOauthHandler jwts data AuthAPI mode = AuthAPI { protectedApi :: mode :- UserAuth :> Protected diff --git a/api/src/Api/OIDC.hs b/api/src/Api/OIDC.hs index 4eb3cdd..ff33a51 100644 --- a/api/src/Api/OIDC.hs +++ b/api/src/Api/OIDC.hs @@ -7,22 +7,24 @@ module Api.OIDC where import App (AppM) import Control.Monad.IO.Class (liftIO) -import Core.User (ExternalToken (ExternalToken, service), Service (Github, Spotify, Twitter, Google, Anilist, Discord), UserId (UserId), User (User)) +import Core.User (ExternalToken (ExternalToken, service), Service (Github, Spotify, Twitter, Google, Anilist, Reddit), UserId (UserId), User (User)) import Data.Text (pack) import Core.OIDC ( getOauthTokens ) import Repository.User (updateTokens, getTokensByUserId, delTokens) -import Servant (Capture, Get, GetNoContent, JSON, NoContent (NoContent), QueryParam, ServerT, err400, throwError, type (:<|>) ((:<|>)), type (:>), err401, err403, ServerError (errHeaders), err302, Delete) +import Servant (Capture, Get, GetNoContent, JSON, NoContent (NoContent), QueryParam, ServerT, err400, throwError, type (:<|>) ((:<|>)), type (:>), err401, err403, ServerError (errHeaders), err302, Delete, Post) import Servant.API.Generic (type (:-)) import Servant.Server.Generic (AsServerT) import Utils (UserAuth, AuthRes) import qualified Data.ByteString.Char8 as B8 -import Servant.Auth.Server (AuthResult(Authenticated)) +import Servant.Auth.Server (AuthResult(Authenticated), makeJWT) import System.Environment.MrEnv (envAsString) +import Control.Monad.Trans.Maybe (MaybeT(runMaybeT)) +import Db.User (toUser) oauthHandler :: AuthRes -> Service -> Maybe String -> AppM NoContent oauthHandler _ _ Nothing = throwError err400 oauthHandler (Authenticated (User uid _ _)) service (Just code) = do - tokens <- liftIO $ getOauthTokens service code + tokens <- liftIO $ runMaybeT $ getOauthTokens service code case tokens of Nothing -> throwError err403 Just t -> do @@ -43,11 +45,11 @@ urlHandler Anilist (Just r) = do backRedirect <- liftIO $ envAsString "BACK_URL" "" throwError $ err302 { errHeaders = [("Location", B8.pack $ "https://anilist.co/api/v2/oauth/authorize?client_id=" ++ clientId ++ "&response_type=code&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] } -urlHandler Discord (Just r) = do - clientId <- liftIO $ envAsString "DISCORD_CLIENT_ID" "" +urlHandler Reddit (Just r) = do + clientId <- liftIO $ envAsString "REDDIT_CLIENT_ID" "" backRedirect <- liftIO $ envAsString "BACK_URL" "" throwError $ err302 { errHeaders = - [("Location", B8.pack $ "https://discord.com/api/oauth2/authorize?response_type=code&scope=identify%20guilds%20messages.read%20activities.write%20webhook.incoming&client_id=" ++ clientId ++ "&response_type=code&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] } + [("Location", B8.pack $ "https://www.reddit.com/api/v1/authorize?client_id=" ++ clientId ++ "&response_type=code&state=" ++ r ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&duration=permanent&scope=*")] } urlHandler Google (Just r) = do clientId <- liftIO $ envAsString "GOOGLE_CLIENT_ID" "" backRedirect <- liftIO $ envAsString "BACK_URL" "" @@ -63,7 +65,6 @@ urlHandler Spotify (Just r) = do backRedirect <- liftIO $ envAsString "BACK_URL" "" throwError $ err302 { errHeaders = [("Location", B8.pack $ "https://accounts.spotify.com/authorize?response_type=code&scope=user-library-read user-library-modify streaming playlist-modify-private playlist-read-collaborative playlist-read-private playlist-modify-public user-modify-playback-state user-read-private&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] } - urlHandler Github (Just r) = do clientId <- liftIO $ envAsString "GITHUB_CLIENT_ID" "" backRedirect <- liftIO $ envAsString "BACK_URL" "" @@ -89,7 +90,7 @@ type OauthAPI = UserAuth :> Capture "service" Service :> QueryParam "code" Strin :<|> Capture "service" Service :> "url" :> QueryParam "redirect_uri" String :> Get '[JSON] NoContent :<|> UserAuth :> "services" :> Get '[JSON] [String] :<|> "redirect" :> QueryParam "code" String :> QueryParam "state" String :> Get '[JSON] NoContent - + oauth :: ServerT OauthAPI AppM oauth = oauthHandler :<|> oauthDelHandler diff --git a/api/src/Api/Worker.hs b/api/src/Api/Worker.hs index d701889..b3ac08a 100644 --- a/api/src/Api/Worker.hs +++ b/api/src/Api/Worker.hs @@ -114,7 +114,7 @@ refreshHandler :: Service -> UserId -> Maybe String -> RefreshBody -> AppM NoCon refreshHandler service uid (Just key) (RefreshBody at rt ex) = do k <- liftIO $ envAsString "WORKER_API_KEY" "" if k == key then do - updateTokens uid $ ExternalToken at rt ex service + updateTokens uid $ ExternalToken at rt ex service Nothing return NoContent else throwError err403 refreshHandler _ _ _ _ = throwError err403 diff --git a/api/src/Core/OIDC.hs b/api/src/Core/OIDC.hs index 2a74b5d..c5cc976 100644 --- a/api/src/Core/OIDC.hs +++ b/api/src/Core/OIDC.hs @@ -6,13 +6,17 @@ import qualified Data.ByteString.Char8 as B8 import qualified Data.HashMap.Strict as HM import App (AppM) -import Core.User (ExternalToken (ExternalToken, expiresAt), Service (Github, Discord, Spotify, Google, Twitter, Anilist)) +import Core.User (ExternalToken (ExternalToken, accessToken, providerId, expiresAt), Service (Github, Reddit, Spotify, Google, Twitter, Anilist)) import Data.Aeson.Types (Object, Value (String)) import Data.Text (Text, pack, unpack) -import Network.HTTP.Simple (JSONException, addRequestHeader, getResponseBody, httpJSONEither, parseRequest, setRequestMethod, setRequestQueryString, setRequestBodyURLEncoded) +import Network.HTTP.Simple (JSONException, addRequestHeader, getResponseBody, httpJSONEither, parseRequest, setRequestMethod, setRequestQueryString, setRequestBodyURLEncoded, setRequestBodyJSON, setRequestBodyLBS) import System.Environment.MrEnv (envAsBool, envAsInt, envAsInteger, envAsString) -import Utils (lookupObjString, lookupObjInt) -import Data.ByteString.Base64 +import Utils (lookupObjString, lookupObjObject, lookupObjInt) +import Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT)) +import Control.Monad.IO.Class (MonadIO(liftIO)) +import Control.Monad (MonadPlus (mzero)) +import Data.Aeson (decode) +import Data.ByteString.Base64 ( encodeBase64 ) import Data.Time (getCurrentTime, addUTCTime) data OAuth2Conf = OAuth2Conf { oauthClientId :: String @@ -33,6 +37,10 @@ tokenEndpoint code oc = , code ] +liftMaybe :: (MonadPlus m) => Maybe a -> m a +liftMaybe = maybe mzero return + + -- GITHUB getGithubConfig :: IO OAuth2Conf getGithubConfig = @@ -41,8 +49,8 @@ getGithubConfig = <*> envAsString "GITHUB_SECRET" "" <*> pure "https://github.com/login/oauth/access_token" -getGithubTokens :: String -> IO (Maybe ExternalToken) -getGithubTokens code = do +getGithubTokens :: String -> MaybeT IO ExternalToken +getGithubTokens code = MaybeT $ do gh <- getGithubConfig let endpoint = tokenEndpoint code gh request' <- parseRequest endpoint @@ -57,46 +65,79 @@ getGithubTokens code = do request' response <- httpJSONEither request currTime <- getCurrentTime - return $ case (getResponseBody response :: Either JSONException Object) of - Left _ -> Nothing - Right obj -> do - access <- lookupObjString obj "access_token" - Just $ ExternalToken (pack access) "" currTime Github + let (Right obj) = (getResponseBody response :: Either JSONException Object) + access <- liftMaybe $ lookupObjString obj "access_token" + let t = ExternalToken access "" currTime Github Nothing + githubId <- runMaybeT $ getGithubId t + return $ Just $ t { providerId = githubId } --- DISCORD -getDiscordConfig :: IO OAuth2Conf -getDiscordConfig = +rightToMaybe :: Either a b -> Maybe b +rightToMaybe = either (const Nothing) Just + +getGithubId :: ExternalToken -> MaybeT IO Text +getGithubId t = MaybeT $ do + let endpoint = "https://api.github.com/user" + request' <- parseRequest endpoint + let request = + addRequestHeader "Authorization" (B8.pack ("token " ++ unpack (accessToken t))) $ + addRequestHeader "Accept" "application/json" $ + addRequestHeader "User-Agent" "aeris-server" + request' + response <- httpJSONEither request + case (getResponseBody response :: Either JSONException Object) of + Left err -> return Nothing + Right obj -> case lookupObjInt obj "id" of + Just githubId -> return $ Just $ pack $ show githubId + _ -> return Nothing + +-- Reddit +getRedditConfig :: IO OAuth2Conf +getRedditConfig = OAuth2Conf - <$> envAsString "DISCORD_CLIENT_ID" "" - <*> envAsString "DISCORD_SECRET" "" - <*> pure "https://discord.com/api/oauth2/token" + <$> envAsString "REDDIT_CLIENT_ID" "" + <*> envAsString "REDDIT_SECRET" "" + <*> pure "https://www.reddit.com/api/v1/access_token" -getDiscordTokens :: String -> IO (Maybe ExternalToken) -getDiscordTokens code = do - cfg <- getDiscordConfig +getRedditTokens :: String -> MaybeT IO ExternalToken +getRedditTokens code = MaybeT $ do + cfg <- getRedditConfig + backUrl <- envAsString "BACK_URL" "" + let basicAuth = encodeBase64 $ B8.pack $ oauthClientId cfg ++ ":" ++ oauthClientSecret cfg let endpoint = tokenEndpoint code cfg request' <- parseRequest endpoint let request = setRequestMethod "POST" $ addRequestHeader "Accept" "application/json" $ + addRequestHeader "User-Agent" "Aeris" $ + addRequestHeader "Authorization" (B8.pack $ "Basic " ++ unpack basicAuth) $ setRequestBodyURLEncoded - [ ("client_id", B8.pack . oauthClientId $ cfg) - , ("client_secret", B8.pack . oauthClientSecret $ cfg) - , ("code", B8.pack code) - , ("grant_type", "authorization_code") - , ("redirect_uri", "http://localhost:8080/auth/redirect") + [ ("grant_type", "authorization_code") + , ("redirect_uri", B8.pack $ backUrl ++ "auth/redirect") ] request' response <- httpJSONEither request + let (Right obj) = (getResponseBody response :: Either JSONException Object) + access <- liftMaybe $ lookupObjString obj "access_token" + refresh <- liftMaybe $ lookupObjString obj "refresh_token" currTime <- getCurrentTime - return $ case (getResponseBody response :: Either JSONException Object) of - Left _ -> Nothing - Right obj -> do - access <- lookupObjString obj "access_token" - refresh <- lookupObjString obj "refresh_token" - expiresIn <- lookupObjInt obj "expires_in" - let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime - Just $ ExternalToken (pack access) (pack refresh) expiresAt Discord + expiresIn <- liftMaybe $ lookupObjInt obj "expires_in" + let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime + let t = ExternalToken access refresh expiresAt Reddit Nothing + id <- runMaybeT $ getRedditId t + return $ Just $ t { providerId = id } + +getRedditId :: ExternalToken -> MaybeT IO Text +getRedditId t = MaybeT $ do + let endpoint = "https://oauth.reddit.com/api/v1/me" + request' <- parseRequest endpoint + let request = + addRequestHeader "Accept" "application/json" $ + addRequestHeader "User-Agent" "Aeris" $ + addRequestHeader "Authorization" (B8.pack $ "Bearer " ++ unpack (accessToken t)) + request' + response <- httpJSONEither request + let (Right obj) = (getResponseBody response :: Either JSONException Object) + return $ lookupObjString obj "id" -- GOOGLE getGoogleConfig :: IO OAuth2Conf @@ -106,9 +147,10 @@ getGoogleConfig = <*> envAsString "GOOGLE_SECRET" "" <*> pure "https://oauth2.googleapis.com/token" -getGoogleTokens :: String -> IO (Maybe ExternalToken) -getGoogleTokens code = do +getGoogleTokens :: String -> MaybeT IO ExternalToken +getGoogleTokens code = MaybeT $ do cfg <- getGoogleConfig + backUrl <- envAsString "BACK_URL" "" let endpoint = tokenEndpoint code cfg request' <- parseRequest endpoint let request = @@ -119,19 +161,33 @@ getGoogleTokens code = do , ("client_secret", B8.pack . oauthClientSecret $ cfg) , ("code", B8.pack code) , ("grant_type", "authorization_code") - , ("redirect_uri", "http://localhost:8080/auth/redirect") + , ("redirect_uri", B8.pack $ backUrl ++ "auth/redirect") ] request' response <- httpJSONEither request currTime <- getCurrentTime - return $ case (getResponseBody response :: Either JSONException Object) of - Left _ -> Nothing - Right obj -> do - access <- lookupObjString obj "access_token" - refresh <- lookupObjString obj "refresh_token" - expiresIn <- lookupObjInt obj "expires_in" - let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime - Just $ ExternalToken (pack access) (pack refresh) expiresAt Google + let (Right obj) = (getResponseBody response :: Either JSONException Object) + access <- liftMaybe $ lookupObjString obj "access_token" + refresh <- liftMaybe $ lookupObjString obj "refresh_token" + expiresIn <- liftMaybe $ lookupObjInt obj "expires_in" + let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime + let t = ExternalToken access refresh expiresAt Google Nothing + googleId <- runMaybeT $ getGoogleId t + return $ Just $ t { providerId = googleId } + +getGoogleId :: ExternalToken -> MaybeT IO Text +getGoogleId t = MaybeT $ do + let endpoint = "https://oauth2.googleapis.com/tokeninfo" + request' <- parseRequest endpoint + let request = + addRequestHeader "Accept" "application/json" $ + setRequestQueryString + [ ("access_token", Just . B8.pack . unpack $ accessToken t) + ] + request' + response <- httpJSONEither request + let (Right obj) = (getResponseBody response :: Either JSONException Object) + return $ lookupObjString obj "sub" -- SPOTIFY getSpotifyConfig :: IO OAuth2Conf @@ -141,10 +197,10 @@ getSpotifyConfig = <*> envAsString "SPOTIFY_SECRET" "" <*> pure "https://accounts.spotify.com/api/token" -getSpotifyTokens :: String -> IO (Maybe ExternalToken) -getSpotifyTokens code = do +getSpotifyTokens :: String -> MaybeT IO ExternalToken +getSpotifyTokens code = MaybeT $ do cfg <- getSpotifyConfig - + backUrl <- envAsString "BACK_URL" "" let basicAuth = encodeBase64 $ B8.pack $ oauthClientId cfg ++ ":" ++ oauthClientSecret cfg let endpoint = tokenEndpoint code cfg request' <- parseRequest endpoint @@ -155,19 +211,31 @@ getSpotifyTokens code = do setRequestBodyURLEncoded [ ("code", B8.pack code) , ("grant_type", "authorization_code") - , ("redirect_uri", "http://localhost:8080/auth/redirect") + , ("redirect_uri", B8.pack $ backUrl ++ "auth/redirect") ] request' response <- httpJSONEither request currTime <- getCurrentTime - return $ case (getResponseBody response :: Either JSONException Object) of - Left _ -> Nothing - Right obj -> do - access <- lookupObjString obj "access_token" - refresh <- lookupObjString obj "refresh_token" - expiresIn <- lookupObjInt obj "expires_in" - let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime - Just $ ExternalToken (pack access) (pack refresh) expiresAt Spotify + let (Right obj) = (getResponseBody response :: Either JSONException Object) + access <- liftMaybe $ lookupObjString obj "access_token" + refresh <- liftMaybe $ lookupObjString obj "refresh_token" + expiresIn <- liftMaybe $ lookupObjInt obj "expires_in" + let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime + let t = ExternalToken access refresh expiresAt Spotify Nothing + spotifyId <- runMaybeT $ getSpotifyId t + return $ Just $ t { providerId = spotifyId } + +getSpotifyId :: ExternalToken -> MaybeT IO Text +getSpotifyId t = MaybeT $ do + let endpoint = "https://api.spotify.com/v1/me" + request' <- parseRequest endpoint + let request = + addRequestHeader "Content-Type" "application/json" $ + addRequestHeader "Authorization" (B8.pack $ "Bearer " ++ unpack (accessToken t)) + request' + response <- httpJSONEither request + let (Right obj) = (getResponseBody response :: Either JSONException Object) + return $ lookupObjString obj "id" -- TWITTER getTwitterConfig :: IO OAuth2Conf @@ -177,9 +245,10 @@ getTwitterConfig = <*> envAsString "TWITTER_SECRET" "" <*> pure "https://api.twitter.com/2/oauth2/token" -getTwitterTokens :: String -> IO (Maybe ExternalToken) -getTwitterTokens code = do +getTwitterTokens :: String -> MaybeT IO ExternalToken +getTwitterTokens code = MaybeT $ do cfg <- getTwitterConfig + backUrl <- envAsString "BACK_URL" "" let basicAuth = encodeBase64 $ B8.pack $ "Basic " ++ oauthClientId cfg ++ ":" ++ oauthClientSecret cfg let endpoint = tokenEndpoint code cfg request' <- parseRequest endpoint @@ -190,20 +259,34 @@ getTwitterTokens code = do setRequestBodyURLEncoded [ ("code", B8.pack code) , ("grant_type", "authorization_code") - , ("redirect_uri", "http://localhost:8080/auth/redirect") + , ("redirect_uri", B8.pack $ backUrl ++ "auth/redirect") , ("code_verifier", "challenge") ] request' response <- httpJSONEither request currTime <- getCurrentTime - return $ case (getResponseBody response :: Either JSONException Object) of - Left _ -> Nothing - Right obj -> do - access <- lookupObjString obj "access_token" - refresh <- lookupObjString obj "refresh_token" - expiresIn <- lookupObjInt obj "expires_in" - let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime - Just $ ExternalToken (pack access) (pack refresh) expiresAt Twitter + let (Right obj) = (getResponseBody response :: Either JSONException Object) + access <- liftMaybe $ lookupObjString obj "access_token" + refresh <- liftMaybe $ lookupObjString obj "refresh_token" + expiresIn <- liftMaybe $ lookupObjInt obj "expires_in" + let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime + let t = ExternalToken access refresh expiresAt Twitter Nothing + twitterId <- runMaybeT $ getTwitterId t + return $ Just $ t { providerId = twitterId } + +getTwitterId :: ExternalToken -> MaybeT IO Text +getTwitterId t = MaybeT $ do + let endpoint = "https://api.twitter.com/2/users/me" + request' <- parseRequest endpoint + let request = + addRequestHeader "Content-Type" "application/json" $ + addRequestHeader "Authorization" (B8.pack $ "Bearer " ++ unpack (accessToken t)) + request' + response <- httpJSONEither request + let (Right obj) = (getResponseBody response :: Either JSONException Object) + case lookupObjObject obj "data" of + Just dataBody -> return $ lookupObjString dataBody "id" + _ -> return Nothing -- ANILIST getAnilistConfig :: IO OAuth2Conf @@ -213,9 +296,10 @@ getAnilistConfig = <*> envAsString "ANILIST_SECRET" "" <*> pure "https://anilist.co/api/v2/oauth/token" -getAnilistTokens :: String -> IO (Maybe ExternalToken) -getAnilistTokens code = do +getAnilistTokens :: String -> MaybeT IO ExternalToken +getAnilistTokens code = MaybeT $ do cfg <- getAnilistConfig + backUrl <- envAsString "BACK_URL" "" let endpoint = tokenEndpoint code cfg request' <- parseRequest endpoint let request = @@ -226,27 +310,47 @@ getAnilistTokens code = do , ("client_secret", B8.pack . oauthClientSecret $ cfg) , ("code", B8.pack code) , ("grant_type", "authorization_code") - , ("redirect_uri", "http://localhost:8080/auth/redirect") + , ("redirect_uri", B8.pack $ backUrl ++ "auth/redirect") ] request' response <- httpJSONEither request currTime <- getCurrentTime - return $ case (getResponseBody response :: Either JSONException Object) of - Left _ -> Nothing - Right obj -> do - access <- lookupObjString obj "access_token" - refresh <- lookupObjString obj "refresh_token" - expiresIn <- lookupObjInt obj "expires_in" - let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime - Just $ ExternalToken (pack access) (pack refresh) expiresAt Anilist - - + let (Right obj) = (getResponseBody response :: Either JSONException Object) + access <- liftMaybe $ lookupObjString obj "access_token" + refresh <- liftMaybe $ lookupObjString obj "refresh_token" + expiresIn <- liftMaybe $ lookupObjInt obj "expires_in" + let expiresAt = addUTCTime (fromInteger . fromIntegral $ expiresIn) currTime + let t = ExternalToken access refresh expiresAt Anilist Nothing + anilistId <- runMaybeT $ getAnilistId t + return $ Just $ t { providerId = anilistId } +getAnilistId :: ExternalToken -> MaybeT IO Text +getAnilistId t = MaybeT $ do + let endpoint = "https://graphql.anilist.co" + request' <- parseRequest endpoint + let query = (decode "{Viewer {id,}}" :: Maybe Object) + let request = + addRequestHeader "Content-Type" "application/json" $ + addRequestHeader "Authorization" (B8.pack $ "Bearer " ++ unpack (accessToken t)) $ + addRequestHeader "User-Agent" "aeris-server" $ + setRequestMethod "POST" $ + setRequestBodyLBS "{\"query\": \"{Viewer {id}}\"}" + request' + response <- httpJSONEither request + let (Right obj) = (getResponseBody response :: Either JSONException Object) + case lookupObjObject obj "data" of + Just dataBody -> case liftMaybe $ lookupObjObject dataBody "Viewer" of + Just viewer -> case lookupObjInt viewer "id" of + Just anilistId -> return . Just . pack . show $ anilistId + _ -> return Nothing + _ -> return Nothing + _ -> return Nothing + -- General -getOauthTokens :: Service -> String -> IO (Maybe ExternalToken) +getOauthTokens :: Service -> String -> MaybeT IO ExternalToken getOauthTokens Github = getGithubTokens -getOauthTokens Discord = getDiscordTokens +getOauthTokens Reddit = getRedditTokens getOauthTokens Spotify = getSpotifyTokens getOauthTokens Google = getGoogleTokens getOauthTokens Twitter = getTwitterTokens diff --git a/api/src/Core/User.hs b/api/src/Core/User.hs index 3a7f7b2..9136718 100644 --- a/api/src/Core/User.hs +++ b/api/src/Core/User.hs @@ -26,7 +26,7 @@ newtype UserId = UserId {toInt64 :: Int64} deriving newtype (DBEq, DBType, Eq, Show, Num, FromJSON, ToJSON, FromHttpApiData) deriving stock (Generic) -data Service = Github | Google | Spotify | Twitter | Discord | Anilist +data Service = Github | Google | Spotify | Twitter | Reddit | Anilist deriving (Eq, Show, Generic, ToJSON, FromJSON) instance FromHttpApiData Service where @@ -35,7 +35,7 @@ instance FromHttpApiData Service where parseUrlPiece "google" = Right Google parseUrlPiece "spotify" = Right Spotify parseUrlPiece "twitter" = Right Twitter - parseUrlPiece "discord" = Right Discord + parseUrlPiece "reddit" = Right Reddit parseUrlPiece "anilist" = Right Anilist parseUrlPiece _ = Left "not a service" @@ -44,6 +44,7 @@ data ExternalToken = ExternalToken , refreshToken :: Text , expiresAt :: UTCTime , service :: Service + , providerId :: Maybe Text } deriving (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) diff --git a/api/src/Repository/User.hs b/api/src/Repository/User.hs index ce3ef9b..a1659ae 100644 --- a/api/src/Repository/User.hs +++ b/api/src/Repository/User.hs @@ -1,12 +1,14 @@ module Repository.User where import App (AppM) -import Core.User (ExternalToken, UserId, Service) +import Core.User (ExternalToken (service, providerId), UserId, Service) import Data.Text (Text) -import Db.User (User', getUserByName, getUserTokensById, insertUser, selectAllUser, updateUserTokens, getUserById, updateDelTokens) +import Db.User (User', getUserByName, getUserTokensById, insertUser, selectAllUser, updateUserTokens, getUserById, updateDelTokens, UserDB (externalTokens)) import Rel8 (insert, select, update, limit, lit) import Repository.Utils (runQuery) import Data.Int (Int64) +import Data.List (find) +import Servant (err401, throwError) users :: AppM [User'] users = runQuery (select selectAllUser) @@ -19,8 +21,25 @@ getUserById' uid = do res <- runQuery (select $ limit 1 $ getUserById (lit uid)) return $ head res -createUser :: User' -> AppM [UserId] -createUser user = runQuery (insert $ insertUser user) +getUserByToken :: ExternalToken -> AppM User' +getUserByToken t = do + users' <- users + case find findByToken users' of + Nothing -> throwError err401 + Just x -> return x + where + findByToken :: User' -> Bool + findByToken usr = do + let userTokens = externalTokens usr + case find (\tok -> service tok == service t) userTokens of + Nothing -> False + Just tok -> providerId tok == providerId t + + +createUser :: User' -> AppM User' +createUser user = do + ids <- runQuery (insert $ insertUser user) + getUserById' $ head ids getTokensByUserId :: UserId -> AppM [ExternalToken] getTokensByUserId uid = do diff --git a/api/src/Utils.hs b/api/src/Utils.hs index 7a35ef2..bb6ab89 100644 --- a/api/src/Utils.hs +++ b/api/src/Utils.hs @@ -3,6 +3,7 @@ {-# OPTIONS_GHC -Wno-deferred-out-of-scope-variables #-} {-# LANGUAGE FlexibleInstances #-} + module Utils where import Data.Aeson.Types (Value (String), Object) @@ -18,16 +19,22 @@ import Db.Pipeline (Pipeline (Pipeline), PipelineId (PipelineId), pipelineLastTr import Core.Pipeline (PipelineParams (PipelineParams)) import Data.Time (UTCTime (UTCTime), fromGregorian, secondsToDiffTime) import Data.Default (Default, def) -import Data.Aeson (Value(Number, Object), decode) +import Data.Aeson (Value(Number, Object), decode, ToJSON, FromJSON) import Data.Int (Int64) import Data.Scientific ( toBoundedInteger ) +import GHC.Generics (Generic) mapInd :: (a -> Int -> b) -> [a] -> [b] mapInd f l = zipWith f l [0 ..] -lookupObjString :: Object -> Text -> Maybe String +lookupObjString :: Object -> Text -> Maybe Text lookupObjString obj key = case Data.HashMap.Strict.lookup key obj of - Just (String x) -> Just . unpack $ x + Just (String x) -> Just x + _ -> Nothing + +lookupObjObject :: Object -> Text -> Maybe Object +lookupObjObject obj key = case Data.HashMap.Strict.lookup key obj of + Just (Object x) -> Just x _ -> Nothing @@ -55,4 +62,5 @@ instance Default (Pipeline Identity) where } type UserAuth = Servant.Auth.Server.Auth '[JWT] User -type AuthRes = Servant.Auth.Server.AuthResult User \ No newline at end of file +type AuthRes = Servant.Auth.Server.AuthResult User + diff --git a/db/Dockerfile b/db/Dockerfile new file mode 100644 index 0000000..2307466 --- /dev/null +++ b/db/Dockerfile @@ -0,0 +1,3 @@ +FROM postgres:14 + +COPY db.sql /docker-entrypoint-initdb.d/ diff --git a/db/db.sql b/db/db.sql new file mode 100644 index 0000000..3469156 --- /dev/null +++ b/db/db.sql @@ -0,0 +1,35 @@ +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + slug VARCHAR(255) UNIQUE NOT NULL, + external_tokens JSONB[] NOT NULL +); + +CREATE TABLE IF NOT EXISTS pipelines ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type VARCHAR(255) NOT NULL, + params JSONB NOT NULL, + user_id INTEGER NOT NULL, + enabled BOOLEAN NOT NULL, + error TEXT, + trigger_count INTEGER NOT NULL, + last_trigger TIMESTAMP, + CONSTRAINT fk_user + FOREIGN KEY (user_id) + REFERENCES users(id) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS reactions ( + id SERIAL PRIMARY KEY, + type VARCHAR(255) NOT NULL, + params JSONB NOT NULL, + pipeline_id INTEGER NOT NULL, + react_order INTEGER NOT NULL, + CONSTRAINT fk_pipeline + FOREIGN KEY (pipeline_id) + REFERENCES pipelines(id) + ON DELETE CASCADE +); diff --git a/docker-compose.yml b/docker-compose.yml index feedd51..4734e8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,7 @@ services: dockerfile: Dockerfile args: - API_ROUTE=/api + - MOBILE_APK_LOCATION=/client.apk ports: - "8081:80" depends_on: @@ -43,8 +44,8 @@ services: - WORKER_API_KEY=${WORKER_API_KEY} - WORKER_URL=${WORKER_URL} - BACK_URL=${BACK_URL} - - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID} - - DISCORD_SECRET=${DISCORD_SECRET} + - REDDIT_CLIENT_ID=${REDDIT_CLIENT_ID} + - REDDIT_SECRET=${REDDIT_SECRET} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} - GITHUB_SECRET=${GITHUB_SECRET} - TWITTER_CLIENT_ID=${TWITTER_CLIENT_ID} @@ -62,8 +63,8 @@ services: depends_on: - "api" environment: - - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID} - - DISCORD_SECRET=${DISCORD_SECRET} + - REDDIT_CLIENT_ID=${REDDIT_CLIENT_ID} + - REDDIT_SECRET=${REDDIT_SECRET} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} - GITHUB_SECRET=${GITHUB_SECRET} - TWITTER_CLIENT_ID=${TWITTER_CLIENT_ID} diff --git a/k8s/aeris/.helmignore b/k8s/aeris/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/k8s/aeris/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/k8s/aeris/Chart.yaml b/k8s/aeris/Chart.yaml new file mode 100644 index 0000000..3624076 --- /dev/null +++ b/k8s/aeris/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: aeris +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/k8s/aeris/templates/back/back.configmap.yaml b/k8s/aeris/templates/back/back.configmap.yaml new file mode 100644 index 0000000..18adef2 --- /dev/null +++ b/k8s/aeris/templates/back/back.configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-back +data: + WORKER_API_URL: http://api:8080/worker + WORKER_URL: http://worker:5000 + BACK_URL: https://aeris.westeurope.cloudapp.azure.com/api/ diff --git a/k8s/aeris/templates/back/back.deployment.yaml b/k8s/aeris/templates/back/back.deployment.yaml new file mode 100644 index 0000000..fbf980a --- /dev/null +++ b/k8s/aeris/templates/back/back.deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: aeris-back +spec: + replicas: 3 + selector: + matchLabels: + app: back + template: + metadata: + labels: + app: back + spec: + containers: + - name: back + imagePullPolicy: Always + image: {{ .Values.BACK_IMAGE }} + ports: + - containerPort: 8080 + volumeMounts: + - mountPath: "/cache" + name: postgres-volume-claim + envFrom: + - configMapRef: + name: configmap-postgres + - configMapRef: + name: configmap-back + - secretRef: + name: secret-postgres + - secretRef: + name: secret-back + volumes: + - name: postgres-volume-claim + persistentVolumeClaim: + claimName: volume-claim-back \ No newline at end of file diff --git a/k8s/aeris/templates/back/back.service.yaml b/k8s/aeris/templates/back/back.service.yaml new file mode 100644 index 0000000..bed46d6 --- /dev/null +++ b/k8s/aeris/templates/back/back.service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: api-service +spec: + type: LoadBalancer + # loadBalancerIP: "20.105.195.175" + selector: + app: back + ports: + - port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/k8s/aeris/templates/back/back.volume.yaml b/k8s/aeris/templates/back/back.volume.yaml new file mode 100644 index 0000000..9950b0e --- /dev/null +++ b/k8s/aeris/templates/back/back.volume.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: volume-back + labels: + type: local + name: volume-back +spec: + storageClassName: manual + capacity: + storage: 50M + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: volume-claim-back +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50M \ No newline at end of file diff --git a/k8s/aeris/templates/back/back_cluster.service.yaml b/k8s/aeris/templates/back/back_cluster.service.yaml new file mode 100644 index 0000000..8b5e7e0 --- /dev/null +++ b/k8s/aeris/templates/back/back_cluster.service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: api +spec: + type: ClusterIP + # loadBalancerIP: "20.105.195.175" + selector: + app: back + ports: + - port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/k8s/aeris/templates/front/cluster-issuer.yaml b/k8s/aeris/templates/front/cluster-issuer.yaml new file mode 100644 index 0000000..c5019d6 --- /dev/null +++ b/k8s/aeris/templates/front/cluster-issuer.yaml @@ -0,0 +1,18 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: louis.auzuret@gmail.com + privateKeySecretRef: + name: letsencrypt + solvers: + - http01: + ingress: + class: nginx + podTemplate: + spec: + nodeSelector: + "kubernetes.io/os": linux diff --git a/k8s/aeris/templates/front/front.deployment.yaml b/k8s/aeris/templates/front/front.deployment.yaml new file mode 100644 index 0000000..2f7088d --- /dev/null +++ b/k8s/aeris/templates/front/front.deployment.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: aeris-front +spec: + replicas: 3 + selector: + matchLabels: + app: front + template: + metadata: + labels: + app: front + spec: + containers: + - name: front + imagePullPolicy: Always + image: {{ .Values.FRONT_IMAGE }} + ports: + - containerPort: 80 \ No newline at end of file diff --git a/k8s/aeris/templates/front/front.ingress.yaml b/k8s/aeris/templates/front/front.ingress.yaml new file mode 100644 index 0000000..c11504a --- /dev/null +++ b/k8s/aeris/templates/front/front.ingress.yaml @@ -0,0 +1,52 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: aeris-ingress + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/use-regex: "true" + cert-manager.io/cluster-issuer: letsencrypt +spec: + tls: + - hosts: + - aeris.westeurope.cloudapp.azure.com + secretName: tls-secret + rules: + - host: aeris.westeurope.cloudapp.azure.com + http: + paths: + - path: /(.*) + pathType: Prefix + backend: + service: + name: front-cluster + port: + number: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: aeris-ingress-static + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/rewrite-target: /static/$2 + nginx.ingress.kubernetes.io/use-regex: "true" + cert-manager.io/cluster-issuer: letsencrypt +spec: + tls: + - hosts: + - aeris.westeurope.cloudapp.azure.com + secretName: tls-secret + rules: + - host: aeris.westeurope.cloudapp.azure.com + http: + paths: + - path: + pathType: Prefix + backend: + service: + name: front-cluster + port: + number: 80 + path: /static(/|$)(.*) diff --git a/k8s/aeris/templates/front/front.service.yaml b/k8s/aeris/templates/front/front.service.yaml new file mode 100644 index 0000000..24d1f26 --- /dev/null +++ b/k8s/aeris/templates/front/front.service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: front-service +spec: + type: LoadBalancer + #loadBalancerIP: "13.81.34.213" + selector: + app: front + ports: + - port: 80 + targetPort: 80 \ No newline at end of file diff --git a/k8s/aeris/templates/front/front_cluster.service.yaml b/k8s/aeris/templates/front/front_cluster.service.yaml new file mode 100644 index 0000000..2a893eb --- /dev/null +++ b/k8s/aeris/templates/front/front_cluster.service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: front-cluster +spec: + type: ClusterIP + #loadBalancerIP: "13.81.34.213" + selector: + app: front + ports: + - port: 80 + targetPort: 80 \ No newline at end of file diff --git a/k8s/aeris/templates/postgres/postgres.configmap.yaml b/k8s/aeris/templates/postgres/postgres.configmap.yaml new file mode 100644 index 0000000..8efd3a7 --- /dev/null +++ b/k8s/aeris/templates/postgres/postgres.configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-postgres +data: + POSTGRES_HOST: "postgres-service" + POSTGRES_PORT: "5432" + POSTGRES_DB: "aeris" + PGDATA: /var/lib/postgresql/data/d \ No newline at end of file diff --git a/k8s/aeris/templates/postgres/postgres.deployment.yaml b/k8s/aeris/templates/postgres/postgres.deployment.yaml new file mode 100644 index 0000000..21c00ba --- /dev/null +++ b/k8s/aeris/templates/postgres/postgres.deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + name: postgres + labels: + app: postgres + spec: + containers: + - name: postgres + image: {{ .Values.DB_IMAGE }} + ports: + - containerPort: 5432 + hostPort: 5432 + volumeMounts: + - mountPath: "/var/lib/postgresql/data" + name: postgres-volume-claim + envFrom: + - configMapRef: + name: configmap-postgres + - secretRef: + name: secret-postgres + volumes: + - name: postgres-volume-claim + persistentVolumeClaim: + claimName: volume-claim-postgres + restartPolicy: Always \ No newline at end of file diff --git a/k8s/aeris/templates/postgres/postgres.service.yaml b/k8s/aeris/templates/postgres/postgres.service.yaml new file mode 100644 index 0000000..6dbca56 --- /dev/null +++ b/k8s/aeris/templates/postgres/postgres.service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-service +spec: + type: ClusterIP + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 \ No newline at end of file diff --git a/k8s/aeris/templates/postgres/postgres.volume.yaml b/k8s/aeris/templates/postgres/postgres.volume.yaml new file mode 100644 index 0000000..eec6598 --- /dev/null +++ b/k8s/aeris/templates/postgres/postgres.volume.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: volume-postgres + labels: + type: local + name: volume-postgres +spec: + storageClassName: manual + capacity: + storage: 200M + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: volume-claim-postgres +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 200M \ No newline at end of file diff --git a/k8s/aeris/templates/worker/worker.deployment.yaml b/k8s/aeris/templates/worker/worker.deployment.yaml new file mode 100644 index 0000000..43c020f --- /dev/null +++ b/k8s/aeris/templates/worker/worker.deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: aeris-worker +spec: + replicas: 3 + selector: + matchLabels: + app: worker + template: + metadata: + labels: + app: worker + spec: + restartPolicy: Always + containers: + - name: worker + imagePullPolicy: Always + image: {{ .Values.WORKER_IMAGE }} + ports: + - containerPort: 5000 + envFrom: + - secretRef: + name: secret-back + - configMapRef: + name: configmap-back \ No newline at end of file diff --git a/k8s/aeris/templates/worker/worker.service.yaml b/k8s/aeris/templates/worker/worker.service.yaml new file mode 100644 index 0000000..6a31656 --- /dev/null +++ b/k8s/aeris/templates/worker/worker.service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: worker +spec: + type: ClusterIP + selector: + app: worker + ports: + - port: 5000 + targetPort: 5000 \ No newline at end of file diff --git a/k8s/aeris/values.yaml b/k8s/aeris/values.yaml new file mode 100644 index 0000000..f488ced --- /dev/null +++ b/k8s/aeris/values.yaml @@ -0,0 +1,4 @@ +BACK_IMAGE: aeris.azurecr.io/back +FRONT_IMAGE: aeris.azurecr.io/front +WORKER_IMAGE: aeris.azurecr.io/worker +DB_IMAGE: aeris.azurecr.io/postgres \ No newline at end of file diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 704f300..ca84d3e 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -31,9 +31,7 @@ - - - +