Mise en place intégration Discord

This commit is contained in:
Angels-dev
2024-08-22 02:37:00 +02:00
parent 2be55bc65d
commit bc4489098d
34 changed files with 4054 additions and 4 deletions

3
.gitignore vendored
View File

@@ -24,4 +24,5 @@
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
.env
/var/db.sqlite
.env

11
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"sqltools.useNodeRuntime": true,
"sqltools.connections": [
{
"previewLimit": 50,
"driver": "SQLite",
"name": "Zac-Mainframe-Back",
"database": "./var/db.sqlite"
}
]
}

View File

@@ -7,14 +7,25 @@
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/dbal": "^3",
"doctrine/doctrine-bundle": "^2.12",
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.2",
"nelmio/cors-bundle": "^2.5",
"symfony/console": "6.4.*",
"symfony/dotenv": "6.4.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "6.4.*",
"symfony/http-client": "6.4.*",
"symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*",
"symfony/serializer": "6.4.*",
"symfony/yaml": "6.4.*"
},
"require-dev": {
"symfony/maker-bundle": "^1.60",
"symfony/stopwatch": "6.4.*",
"symfony/web-profiler-bundle": "6.4.*"
},
"config": {
"allow-plugins": {

3080
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,4 +2,11 @@
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,45 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
profiling_collect_backtrace: '%kernel.debug%'
use_savepoints: true
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
report_fields_where_declared: true
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View File

@@ -9,9 +9,11 @@ framework:
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
cookie_lifetime: 604800
handler_id: 'session.handler.native_file'
save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
#esi: true
#fragments: true

View File

@@ -0,0 +1,10 @@
nelmio_cors:
defaults:
allow_credentials: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_headers: ['Content-Type', 'Authorization', 'X-Requested-With']
expose_headers: ['Authorization']
allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
max_age: 3600
paths:
'^/': ~

View File

@@ -0,0 +1,39 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@@ -0,0 +1,6 @@
twig:
file_name_pattern: '*.twig'
when@test:
twig:
strict_variables: true

View File

@@ -0,0 +1,19 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler:
only_exceptions: false
collect_serializer_data: true
trusted_proxies: '10.69.1.1,2a01:e0a:354:721::101'
trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix']
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@@ -3,3 +3,25 @@ controllers:
path: ../src/Controller/
namespace: App\Controller
type: attribute
slash:
path: /
controller: App\Controller\RootController::redirectToFrontend
discord_auth_redirect:
path: /discord/auth/redirect
controller: App\Controller\AuthController::redirectToDiscord
discord_auth_callback:
path: /discord/auth/callback
controller: App\Controller\AuthController::handleDiscordCallback
discord_user:
path: /discord/user
controller: App\Controller\UserController::getUserDetails
discord_user_guilds:
path: /discord/user/guilds
controller: App\Controller\UserController::getUserGuilds
discord_bots:
path: /discord/bots
controller: App\Controller\BotController::getBotsDetails

View File

@@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service

View File

@@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

0
migrations/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240821140402 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240821140708 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, test VARCHAR(255) NOT NULL)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE test');
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240821141246 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, discord_id VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) DEFAULT NULL)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D64943349DE ON user (discord_id)');
$this->addSql('DROP TABLE test');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, test VARCHAR(255) NOT NULL COLLATE "BINARY")');
$this->addSql('DROP TABLE user');
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240821171427 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE user ADD COLUMN access_token VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE user ADD COLUMN refresh_token VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE user ADD COLUMN token_expires_at VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, discord_id, username, email FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, discord_id VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) DEFAULT NULL)');
$this->addSql('INSERT INTO user (id, discord_id, username, email) SELECT id, discord_id, username, email FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D64943349DE ON user (discord_id)');
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/mainframe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Controller;
use DateTime;
use DateInterval;
use App\Entity\User;
use App\Service\DiscordApiService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class AuthController extends AbstractController
{
private $discordApiService;
public function __construct(DiscordApiService $discordApiService)
{
$this->discordApiService = $discordApiService;
}
/**
* @Route("/discord/auth/redirect", name="discord_auth_redirect")
*/
public function redirectToDiscord(): RedirectResponse
{
$clientId = $_ENV['DISCORD_CLIENT_ID'];
$redirectUri = $_ENV['DISCORD_BACKEND_REDIRECT_URI'];
$scope = 'identify email guilds'; // Ajouter les scopes dont tu as besoin
$discordAuthUrl = "https://discord.com/oauth2/authorize?client_id={$clientId}&redirect_uri={$redirectUri}&response_type=code&scope={$scope}";
return $this->redirect($discordAuthUrl);
}
/**
* @Route("/discord/auth/callback", name="discord_auth_callback")
*/
public function handleDiscordCallback(Request $request, EntityManagerInterface $entityManager, SessionInterface $session): Response
{
$code = $request->query->get('code');
if (!$code) return new Response('Authorization code not found', Response::HTTP_BAD_REQUEST);
// Obtenir le token d'accès et de refresh depuis Discord
$tokenData = $this->discordApiService->exchangeCodeForToken($code);
$accessToken = $tokenData['access_token'];
$refreshToken = $tokenData['refresh_token'];
$expiresIn = $tokenData['expires_in'];
// Calcul du délai d'expiration
$tokenExpiresAt = (new DateTime())->add(new DateInterval('PT' . $expiresIn . 'S'));
// Obtenir les informations utilisateur depuis Discord
$discordUser = $this->discordApiService->getUserData($accessToken);
// Chercher l'utilisateur dans la base de données par son discordId
$user = $entityManager->getRepository(User::class)->findOneBy(['discordId' => $discordUser['id']]);
if (!$user) {
// Si l'utilisateur n'existe pas, on le crée
$user = new User();
$user->setDiscordId($discordUser['id']);
}
// Créer ou mettre à jour les informations de l'utilisateur
$user->setUsername($discordUser['username']);
$user->setEmail($discordUser['email'] ?? null);
$user->setAccessToken($accessToken);
$user->setRefreshToken($refreshToken);
$user->setTokenExpiresAt($tokenExpiresAt);
$entityManager->persist($user);
$entityManager->flush();
// Stocker l'utilisateur dans la session
$session->set('user_id', $user->getId());
// Redirection vers le frontend
return $this->redirect($_ENV['DISCORD_FRONTEND_REDIRECT_URI']);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Controller;
use DateTime;
use DateInterval;
use App\Entity\User;
use App\Service\DiscordApiService;
use App\Exception\DiscordApiException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class BotController extends AbstractController
{
private $discordApiService;
public function __construct(DiscordApiService $discordApiService)
{
$this->discordApiService = $discordApiService;
}
private function getUserToken(SessionInterface $session, EntityManagerInterface $entityManager): ?User
{
// Vérifier si une session est en cours
if (!$session->has('user_id')) throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, 'Non authentifié');
$user = $entityManager->getRepository(User::class)->find($session->get('user_id'));
if (!$user) throw new DiscordApiException(Response::HTTP_NOT_FOUND, 'Utilisateur non trouvé');
// Vérifier si l'utilisateur a un token d'accès et qu'il n'est pas expiré
if (!$user->getAccessToken() || $user->getTokenExpiresAt() < new DateTime()) {
if ($user->getRefreshToken()) {
// Rafraîchir le token d'accès
$tokenData = $this->discordApiService->refreshToken($user->getRefreshToken());
$user->setAccessToken($tokenData['access_token']);
$user->setRefreshToken($tokenData['refresh_token']);
$user->setTokenExpiresAt((new DateTime())->add(new DateInterval('PT' . $tokenData['expires_in'] . 'S')));
} throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, "Token d'accès expiré et/ou token de refresh invalide");
}
return $user;
}
/**
* @Route("/discord/bots", name="discord_bots")
*/
public function getBotsDetails(SessionInterface $session, EntityManagerInterface $entityManager): Response
{
try { $user = $this->getUserToken($session, $entityManager); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Obtenir les informations utilisateur supplémentaires depuis Discord
try { $discordBots = $this->discordApiService->getBotsData($user->getAccessToken()); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Renvoyer les détails de l'utilisateur en JSON
return new JsonResponse($discordUser);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
class RootController extends AbstractController
{
/**
* @Route("/", name="slash")
*/
public function redirectToFrontend(): RedirectResponse
{
return $this->redirect($_ENV['FRONTEND_REDIRECT_URI']);
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Controller;
use DateTime;
use DateInterval;
use App\Entity\User;
use App\Service\DiscordApiService;
use App\Exception\DiscordApiException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class UserController extends AbstractController
{
private $discordApiService;
public function __construct(DiscordApiService $discordApiService)
{
$this->discordApiService = $discordApiService;
}
private function getUserToken(SessionInterface $session, EntityManagerInterface $entityManager): ?User
{
// Vérifier si une session est en cours
if (!$session->has('user_id')) throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, 'Non authentifié');
$user = $entityManager->getRepository(User::class)->find($session->get('user_id'));
if (!$user) throw new DiscordApiException(Response::HTTP_NOT_FOUND, 'Utilisateur non trouvé');
// Vérifier si l'utilisateur a un token d'accès et qu'il n'est pas expiré
if (!$user->getAccessToken() || $user->getTokenExpiresAt() < new DateTime()) {
if ($user->getRefreshToken()) {
// Rafraîchir le token d'accès
$tokenData = $this->discordApiService->refreshToken($user->getRefreshToken());
$user->setAccessToken($tokenData['access_token']);
$user->setRefreshToken($tokenData['refresh_token']);
$user->setTokenExpiresAt((new DateTime())->add(new DateInterval('PT' . $tokenData['expires_in'] . 'S')));
} throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, "Token d'accès expiré et/ou token de refresh invalide");
}
return $user;
}
/**
* @Route("/discord/user", name="discord_user")
*/
public function getUserDetails(SessionInterface $session, EntityManagerInterface $entityManager): Response
{
try { $user = $this->getUserToken($session, $entityManager); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Obtenir les informations utilisateur supplémentaires depuis Discord
try { $discordUser = $this->discordApiService->getUserData($user->getAccessToken()); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Mettre à jour les informations utilisateur dans la base de données
$user->setUsername($discordUser['username']);
$user->setEmail($discordUser['email'] ?? null);
$entityManager->persist($user);
$entityManager->flush();
// Renvoyer les détails de l'utilisateur en JSON
return new JsonResponse($discordUser);
}
/**
* @Route("/discord/user/guilds", name="discord_user_guilds")
*/
public function getUserGuilds(SessionInterface $session, EntityManagerInterface $entityManager): Response
{
try { $user = $this->getUserToken($session, $entityManager); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Obtenir les informations utilisateur supplémentaires depuis Discord
try { $discordUserGuilds = $this->discordApiService->getUserGuilds($user->getAccessToken()); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Renvoyer les détails de l'utilisateur en JSON
return new JsonResponse($discordUserGuilds);
}
}

0
src/Entity/.gitignore vendored Normal file
View File

134
src/Entity/User.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
namespace App\Entity;
use DateTime;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
private int $id;
#[ORM\Column(length: 255, unique: true)]
private string $discordId;
#[ORM\Column(length: 255)]
private string $username;
#[ORM\Column(length: 255, nullable: true)]
private string $email;
#[ORM\Column(length: 255, nullable: true)]
private string $accessToken;
#[ORM\Column(length: 255, nullable: true)]
private string $refreshToken;
#[ORM\Column(length: 255, nullable: true)]
private DateTime $tokenExpiresAt;
public function getId(): ?int
{
return $this->id;
}
public function getDiscordId(): ?string
{
return $this->discordId;
}
public function setDiscordId(string $discordId): self
{
$this->discordId = $discordId;
return $this;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
public function getAccessToken(): ?string
{
return $this->accessToken;
}
public function setAccessToken(string $accessToken): self
{
$this->accessToken = $accessToken;
return $this;
}
public function getRefreshToken(): ?string
{
return $this->refreshToken;
}
public function setRefreshToken(string $refreshToken): self
{
$this->refreshToken = $refreshToken;
return $this;
}
public function getTokenExpiresAt(): ?DateTime
{
return $this->tokenExpiresAt;
}
public function setTokenExpiresAt(DateTime $tokenExpiresAt): self
{
$this->tokenExpiresAt = $tokenExpiresAt;
return $this;
}
// Implémentation de la méthode getUserIdentifier()
public function getUserIdentifier(): string
{
return $this->discordId;
}
// Implémentation des méthodes de l'interface UserInterface
public function getRoles(): array
{
return ['ROLE_USER'];
}
public function getPassword(): ?string
{
// Pas de mot de passe car on utilise Discord pour l'authentification
return null;
}
public function getSalt(): ?string
{
return null;
}
public function eraseCredentials()
{
// Pas nécessaire ici car nous n'avons pas de données sensibles
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Exception;
use Symfony\Component\HttpKernel\Exception\HttpException;
class DiscordApiException extends HttpException
{
public function __construct(int $statusCode, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0)
{
parent::__construct($statusCode, $message, $previous, $headers, $code);
}
}

0
src/Repository/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<User>
*
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Service;
use App\Exception\DiscordApiException;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
class DiscordApiService
{
private $client;
private $clientId;
private $clientSecret;
private $redirectUri;
private $baseUrl;
public function __construct(HttpClientInterface $client)
{
$this->client = $client;
$this->clientId = $_ENV['DISCORD_CLIENT_ID'];
$this->clientSecret = $_ENV['DISCORD_CLIENT_SECRET'];
$this->redirectUri = $_ENV['DISCORD_BACKEND_REDIRECT_URI'];
$this->baseUrl = $_ENV['DISCORD_API_BASE_URL'];
}
public function exchangeCodeForToken(string $code): array
{
try {
$response = $this->client->request('POST', $this->baseUrl . '/oauth2/token', [
'body' => [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->redirectUri,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function refreshToken(string $refreshToken): array
{
try {
$response = $this->client->request('POST', $this->baseUrl . '/oauth2/token', [
'body' => [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'redirect_uri' => $this->redirectUri,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function getBotsData(string $accessToken): array
{
/**
$output = array();
$botsId = [$_ENV['DISCORD_BOT_TAMISEUR_ID'], $_ENV['DISCORD_BOT_GROOVE_ID'], $_ENV['DISCORD_BOT_FUNKY_ID'], $_ENV['DISCORD_BOT_JUJUL_ID'], $_ENV['DISCORD_BOT_CHANTIER_ID']];
foreach($botsId as $botId)
{
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/' . $botId, [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
array_push($output, $response->toArray());
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
return new JsonResponse($output);
*/
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/' . '262299921082875904', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function getUserData(string $accessToken): array
{
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/@me', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function getUserGuilds(string $accessToken): array
{
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/@me/guilds', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
}

View File

@@ -1,4 +1,43 @@
{
"doctrine/doctrine-bundle": {
"version": "2.12",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.10",
"ref": "c170ded8fc587d6bd670550c43dafcf093762245"
},
"files": [
"config/packages/doctrine.yaml",
"src/Entity/.gitignore",
"src/Repository/.gitignore"
]
},
"doctrine/doctrine-migrations-bundle": {
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.1",
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
},
"files": [
"config/packages/doctrine_migrations.yaml",
"migrations/.gitignore"
]
},
"nelmio/cors-bundle": {
"version": "2.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"config/packages/nelmio_cors.yaml"
]
},
"symfony/console": {
"version": "6.4",
"recipe": {
@@ -42,6 +81,15 @@
"src/Kernel.php"
]
},
"symfony/maker-bundle": {
"version": "1.60",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/routing": {
"version": "6.4",
"recipe": {
@@ -54,5 +102,44 @@
"config/packages/routing.yaml",
"config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "6.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "2ae08430db28c8eb4476605894296c82a642028f"
},
"files": [
"config/packages/security.yaml",
"config/routes/security.yaml"
]
},
"symfony/twig-bundle": {
"version": "6.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
},
"files": [
"config/packages/twig.yaml",
"templates/base.html.twig"
]
},
"symfony/web-profiler-bundle": {
"version": "6.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.1",
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
},
"files": [
"config/packages/web_profiler.yaml",
"config/routes/web_profiler.yaml"
]
}
}

16
templates/base.html.twig Normal file
View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>