Mise en place intégration Discord
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
11
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"sqltools.useNodeRuntime": true,
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"driver": "SQLite",
|
||||
"name": "Zac-Mainframe-Back",
|
||||
"database": "./var/db.sqlite"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
3080
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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],
|
||||
];
|
||||
|
||||
45
config/packages/doctrine.yaml
Normal file
45
config/packages/doctrine.yaml
Normal 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
|
||||
6
config/packages/doctrine_migrations.yaml
Normal file
6
config/packages/doctrine_migrations.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
10
config/packages/nelmio_cors.yaml
Normal file
10
config/packages/nelmio_cors.yaml
Normal 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:
|
||||
'^/': ~
|
||||
39
config/packages/security.yaml
Normal file
39
config/packages/security.yaml
Normal 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
|
||||
6
config/packages/twig.yaml
Normal file
6
config/packages/twig.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
twig:
|
||||
file_name_pattern: '*.twig'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
19
config/packages/web_profiler.yaml
Normal file
19
config/packages/web_profiler.yaml
Normal 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 }
|
||||
@@ -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
|
||||
3
config/routes/security.yaml
Normal file
3
config/routes/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
8
config/routes/web_profiler.yaml
Normal file
8
config/routes/web_profiler.yaml
Normal 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
0
migrations/.gitignore
vendored
Normal file
31
migrations/Version20240821140402.php
Normal file
31
migrations/Version20240821140402.php
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
31
migrations/Version20240821140708.php
Normal file
31
migrations/Version20240821140708.php
Normal 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');
|
||||
}
|
||||
}
|
||||
34
migrations/Version20240821141246.php
Normal file
34
migrations/Version20240821141246.php
Normal 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');
|
||||
}
|
||||
}
|
||||
38
migrations/Version20240821171427.php
Normal file
38
migrations/Version20240821171427.php
Normal 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
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/mainframe.png
Normal file
BIN
public/mainframe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 336 KiB |
86
src/Controller/AuthController.php
Normal file
86
src/Controller/AuthController.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
63
src/Controller/BotController.php
Normal file
63
src/Controller/BotController.php
Normal 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);
|
||||
}
|
||||
}
|
||||
17
src/Controller/RootController.php
Normal file
17
src/Controller/RootController.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
86
src/Controller/UserController.php
Normal file
86
src/Controller/UserController.php
Normal 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
0
src/Entity/.gitignore
vendored
Normal file
134
src/Entity/User.php
Normal file
134
src/Entity/User.php
Normal 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
|
||||
}
|
||||
}
|
||||
13
src/Exception/DiscordApiException.php
Normal file
13
src/Exception/DiscordApiException.php
Normal 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
0
src/Repository/.gitignore
vendored
Normal file
23
src/Repository/UserRepository.php
Normal file
23
src/Repository/UserRepository.php
Normal 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);
|
||||
}
|
||||
}
|
||||
125
src/Service/DiscordApiService.php
Normal file
125
src/Service/DiscordApiService.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
symfony.lock
87
symfony.lock
@@ -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
16
templates/base.html.twig
Normal 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>
|
||||
Reference in New Issue
Block a user