Dockerize Project

This commit is contained in:
Jan-Marlon Leibl 2023-11-03 21:17:49 +01:00
commit e679d02b41
205 changed files with 17941 additions and 0 deletions

0
src/Controller/.gitignore vendored Normal file
View file

View file

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace App\Controller;
use App\Repository\UserRepository;
use App\Service\ConfigGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ConfigController extends AbstractController
{
public function __construct(
private readonly ConfigGenerator $configGenerator,
private readonly UserRepository $userRepository
) {
}
#[Route('/download-config', name: 'app_config')]
public function index(): Response
{
$user = $this->userRepository->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
return $this->configGenerator->generateConfig($user);
}
}

View file

@ -0,0 +1,86 @@
<?php declare(strict_types=1);
namespace App\Controller\File;
use App\Repository\FileRepository;
use App\Service\SizeFormatter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class FileController extends AbstractController
{
public function __construct(private readonly FileRepository $fileRepository)
{
}
#[Route('/file/{filename}', name: 'app_serve_file')]
public function serveFile(string $filename, SizeFormatter $sizeFormatter): Response
{
$file = $this->fileRepository->findOneBy(['name' => $filename]);
if (!$file) {
return new JsonResponse(['error' => 'File not found'], Response::HTTP_NOT_FOUND);
}
$filePath = $this->getParameter('kernel.project_dir')
. '/public/uploads/' . $file->getUser()->getId() . '/' . $file->getName();
if (!file_exists($filePath) || getimagesize($filePath) === false) {
return new JsonResponse(['error' => 'Invalid file type'], Response::HTTP_BAD_REQUEST);
}
$fileSize = $sizeFormatter->formatSize($file->getSize());
return $this->render('file_upload/index.html.twig', ['file' => $file]);
}
#[Route('/file/{filename}/delete', name: 'app_delete_file')]
public function deleteFile(string $filename): JsonResponse|RedirectResponse
{
$user = $this->getUser();
if (!$user) {
return new JsonResponse(['error' => 'Invalid credentials, log in first'], Response::HTTP_FORBIDDEN);
}
$file = $this->fileRepository->findOneBy(['name' => $filename]);
if (!$file) {
return new JsonResponse(['error' => 'File not found'], Response::HTTP_NOT_FOUND);
}
if ($file->getUser()->getUsername() !== $user->getUserIdentifier()) {
return new JsonResponse(['error' => 'Invalid credentials, log in first'], Response::HTTP_FORBIDDEN);
}
$filePath = $this->getParameter('kernel.project_dir')
. '/public/uploads/' . $file->getUser()->getId() . '/' . $file->getName();
// Delete file from disk and database
unlink($filePath);
$this->fileRepository->deleteFile($file);
if (isset($_GET['redirectToHome'])) {
return $this->redirectToRoute('app_home');
}
return new JsonResponse(['success' => 'File deleted'], Response::HTTP_OK);
}
#[Route('/file/{filename}/thumbnail', name: 'app_thumbnail_file')]
public function showFileThumbnail(string $filename): RedirectResponse|JsonResponse
{
$file = $this->fileRepository->findOneBy(['name' => $filename]);
if (!$file) {
return new JsonResponse(['error' => 'File not found'], Response::HTTP_NOT_FOUND);
}
$filePath = '/uploads/' . $file->getUser()->getId() . '/' . $file->getName();
return new RedirectResponse($filePath);
}
}

View file

@ -0,0 +1,138 @@
<?php declare(strict_types=1);
namespace App\Controller\File;
use App\Entity\File;
use App\Entity\User;
use App\Repository\UserRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class UploadController extends AbstractController
{
public function __construct(
private readonly UserRepository $userRepository,
private readonly EntityManagerInterface $entityManager
) {
}
#[Route('/upload', name: 'app_upload_file')]
public function handleFileUpload(Request $request): Response
{
if (!$request->isMethod('POST') || !$request->request->has('token') || !$request->files->has('file')) {
return new JsonResponse(['error' => 'Invalid Request'], Response::HTTP_BAD_REQUEST);
}
$uploadedFile = $request->files->get('file');
$uploadToken = $request->request->get('token');
$user = $this->getUserByToken($uploadToken);
if (!$user) {
return new JsonResponse(['error' => 'Invalid Credentials'], Response::HTTP_BAD_REQUEST);
}
if (!$this->isValidUploadedFile($uploadedFile)) {
return new JsonResponse(['error' => 'Invalid file type'], Response::HTTP_BAD_REQUEST);
}
try {
$destination = $this->createUserUploadDirectory($user->getId());
$newFilename = $this->generateUniqueFilename($uploadedFile->guessExtension());
$fileSize = $uploadedFile->getSize();
$this->moveUploadedFile($uploadedFile, $destination, $newFilename);
$file = $this->createFileEntity($user, $newFilename, $fileSize, $uploadedFile->getClientOriginalName());
$this->entityManager->persist($file);
$this->entityManager->flush();
$this->setPermissions($destination, $newFilename);
$fileUrl = $this->generateFileUrl($newFilename);
return new Response($fileUrl, Response::HTTP_CREATED);
} catch (\Exception $exception) {
return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
}
}
private function getUserByToken(string $token): ?User
{
return $this->userRepository->findOneBy(['token' => $token]);
}
private function createUserUploadDirectory(int $userId): string
{
$destination = $this->getParameter('kernel.project_dir') . '/public/uploads/' . $userId;
$this->createDirectory($destination);
return $destination;
}
private function isValidUploadedFile(?UploadedFile $uploadedFile): bool
{
if (!$uploadedFile) {
return false;
}
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'ico', 'svg'];
$fileExtension = $uploadedFile->guessExtension();
return in_array($fileExtension, $allowedExtensions);
}
private function createDirectory(string $directory): void
{
if (!file_exists($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) {
throw new RuntimeException(sprintf('Directory "%s" was not created', $directory));
}
}
private function generateUniqueFilename(string $extension): string
{
return uniqid() . '.' . $extension;
}
private function moveUploadedFile(UploadedFile $uploadedFile, string $destination, string $newFilename): void
{
$uploadedFile->move($destination, $newFilename);
}
private function createFileEntity($user, string $newFilename, int $fileSize, string $originalName): File
{
return (new File())
->setUser($user)
->setName($newFilename)
->setSize($fileSize)
->setOriginalFilename(
pathinfo($originalName, PATHINFO_FILENAME)
. '.'
. pathinfo($originalName, PATHINFO_EXTENSION)
)
->setCreatedAt(new DateTimeImmutable());
}
private function setPermissions(string $destination, string $newFilename): void
{
chmod($destination, 0755);
chmod($destination . '/' . $newFilename, 0644);
chown($destination . '/' . $newFilename, 'www-data');
}
private function generateFileUrl(string $newFilename): string
{
return $this->generateUrl(
route: 'app_serve_file',
parameters: ['filename' => $newFilename],
referenceType: UrlGeneratorInterface::ABSOLUTE_URL
);
}
}

View file

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace App\Controller\Page;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
#[Route('/', name: 'app_index', methods: ['GET'])]
public function register(): Response
{
return $this->render('landing/index.html.twig');
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Controller\Page;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class FaqController extends AbstractController
{
#[Route('/faq', name: 'app_faq')]
public function index(): Response
{
return $this->render('faq/index.html.twig');
}
}

View file

@ -0,0 +1,66 @@
<?php declare(strict_types=1);
namespace App\Controller\Page;
use App\Repository\UserRepository;
use App\Service\PaginationMaker;
use App\Service\SizeFormatter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
public function __construct(private readonly UserRepository $userRepository)
{
}
#[Route(
path: '/home/{page?}',
name: 'app_home',
requirements: ['page' => '\d+'],
defaults: ['page' => 1],
methods: ['GET']
)]
public function index(Request $request, int $page): Response
{
$user = $this->userRepository->findOneBy(['username' => $this->getUser()->getUserIdentifier()]);
$allFiles = $user->getFiles();
// Define the number of files to display per page
$perPage = 10;
// Create a pagination object
$pagination = new PaginationMaker();
$files = $pagination->paginate($allFiles->toArray(), $page, $perPage);
$sizeFormatter = new SizeFormatter();
// Calculate total size and total uploads
$totalSize = $sizeFormatter->calculateTotalSize($allFiles);
$todaysUploads = $this->countTodaysUploads($allFiles);
// Calculate total pages
$totalPages = ceil(count($allFiles) / $perPage);
return $this->render('home/index.html.twig', [
'files' => $files,
'totalUploads' => count($allFiles),
'totalSize' => $totalSize,
'todaysUploads' => $todaysUploads,
'currentPage' => $page,
'totalPages' => $totalPages,
]);
}
private function countTodaysUploads($files): int
{
$today = date('Y-m-d');
$filesArray = $files->toArray();
return count(array_filter($filesArray, function ($file) use ($today) {
return $file->getCreatedAt()->format('Y-m-d') === $today;
}));
}
}

View file

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace App\Controller\Page;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends AbstractController
{
#[Route('/login', name: 'app_login', methods: ['GET', 'POST'])]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// Get the last authentication error if one exists
$error = $authenticationUtils->getLastAuthenticationError();
// Get the last username
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
}

View file

@ -0,0 +1,60 @@
<?php declare(strict_types=1);
namespace App\Controller\Page;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\AppAuthenticator;
use App\Service\TokenGenerator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
class RegistrationController extends AbstractController
{
#[Route('/register', name: 'app_register', methods: ['GET', 'POST'])]
public function register(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
UserAuthenticatorInterface $userAuthenticator,
AppAuthenticator $authenticator,
EntityManagerInterface $entityManager,
TokenGenerator $tokenGenerator
): Response {
// Create a new User entity
$user = new User();
// Create and process the registration form
$form = $this->createForm(RegistrationFormType::class, $user)->handleRequest($request);
// If the form is submitted and valid, create and authenticate the user
if ($form->isSubmitted() && $form->isValid()) {
// Hash the user's password
$user->setPassword($userPasswordHasher->hashPassword($user, $form->get('plainPassword')->getData()));
// Generate a unique registration token
$user->setToken('ventry-' . $tokenGenerator->getRandomStringSha1(32));
// Set time of registration
$user->setCreatedAt(new \DateTimeImmutable());
// Persist the user and their details
$entityManager->persist($user);
$entityManager->flush();
// Authenticate the user and redirect to the home page
$userAuthenticator->authenticateUser($user, $authenticator, $request);
return $this->redirectToRoute('app_home');
}
// Render the registration form
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
}

View file

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace App\Controller\Profile;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserController extends AbstractController
{
#[Route('/profile/{user}', name: 'app_user_profile', methods: ['GET'])]
public function profile(User $user): Response
{
return $this->render('profile/index.html.twig', [
'user' => $user,
]);
}
}

0
src/Entity/.gitignore vendored Executable file
View file

97
src/Entity/File.php Executable file
View file

@ -0,0 +1,97 @@
<?php
namespace App\Entity;
use App\Repository\FileRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: FileRepository::class)]
#[ORM\Cache]
class File
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\ManyToOne(fetch: 'EAGER', inversedBy: 'files')]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
#[ORM\Column(length: 255)]
private ?string $original_filename = null;
#[ORM\Column]
private ?int $size = null;
#[ORM\Column]
private ?\DateTimeImmutable $created_at = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): static
{
$this->user = $user;
return $this;
}
public function getOriginalFilename(): ?string
{
return $this->original_filename;
}
public function setOriginalFilename(string $original_filename): static
{
$this->original_filename = $original_filename;
return $this;
}
public function getSize(): ?int
{
return $this->size;
}
public function setSize(int $size): static
{
$this->size = $size;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeImmutable $created_at): static
{
$this->created_at = $created_at;
return $this;
}
}

174
src/Entity/User.php Executable file
View file

@ -0,0 +1,174 @@
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ORM\Cache]
#[UniqueEntity(fields: ['username'], message: 'There is already an account with this username')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $username = null;
#[ORM\Column]
private array $roles = [];
/**
* @var ?string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 255)]
private ?string $token = null;
#[ORM\OneToMany(mappedBy: 'user', targetEntity: File::class, fetch: 'EAGER')]
private Collection $files;
#[ORM\Column]
private ?\DateTimeImmutable $created_at = null;
public function __construct()
{
$this->files = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): static
{
$this->username = $username;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string)$this->username;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getToken(): ?string
{
return $this->token;
}
public function setToken(string $token): static
{
$this->token = $token;
return $this;
}
/**
* @return Collection<int, File>
*/
public function getFiles(): Collection
{
//return in reversed order
return new ArrayCollection(array_reverse($this->files->toArray()));
}
public function addFile(File $file): static
{
if (!$this->files->contains($file)) {
$this->files->add($file);
$file->setUser($this);
}
return $this;
}
public function removeFile(File $file): static
{
if ($this->files->removeElement($file)) {
// set the owning side to null (unless already changed)
if ($file->getUser() === $this) {
$file->setUser(null);
}
}
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeImmutable $created_at): static
{
$this->created_at = $created_at;
return $this;
}
}

View file

@ -0,0 +1,55 @@
<?php declare(strict_types=1);
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username', TextType::class, [
'attr' => [
'class' => 'textField',
'placeholder' => 'Enter username'
],
'label' => false,
'constraints' => [
new NotBlank(['message' => 'Please enter a username']),
new Length([
'min' => 3, 'max' => 64,
'minMessage' => 'Username should be at least {{ limit }} characters',
'maxMessage' => 'Username should be at most {{ limit }} characters',
])
]
])
->add('plainPassword', PasswordType::class, [
'mapped' => false,
'attr' => ['autocomplete' => 'new-password', 'class' => 'textField', 'placeholder' => 'Enter password'],
'constraints' => [
new NotBlank(['message' => 'Please enter a password']),
new Length([
'min' => 6, 'max' => 4096, 'minMessage' => 'Password should be at least {{ limit }} characters'
]),
],
'label' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
// enable/disable CSRF protection for this form
'csrf_protection' => true,
]);
}
}

11
src/Kernel.php Normal file
View file

@ -0,0 +1,11 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

0
src/Repository/.gitignore vendored Executable file
View file

View file

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

View file

@ -0,0 +1,66 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
/**
* @extends ServiceEntityRepository<User>
* @implements PasswordUpgraderInterface<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 implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
}
$user->setPassword($newHashedPassword);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
// /**
// * @return User[] Returns an array of User objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?User
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View file

@ -0,0 +1,52 @@
<?php declare(strict_types=1);
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class AppAuthenticator extends AbstractAuthenticator
{
public function __construct(private readonly UrlGeneratorInterface $urlGenerator)
{
}
public function supports(Request $request): ?bool
{
// Check if the request is a POST request to the '/login' URL
return ($request->getPathInfo() === '/login' && $request->isMethod('POST'));
}
public function authenticate(Request $request): Passport
{
// Get the username and password from the request
$username = $request->request->get('_username');
$password = $request->request->get('_password');
// Create a Passport with UserBadge and PasswordCredentials
return new Passport(
new UserBadge($username),
new PasswordCredentials($password)
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// Redirect to the home page on successful authentication
return new RedirectResponse($this->urlGenerator->generate('app_home'));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
// Handle authentication failure (not implemented in this example)
return null;
}
}

44
src/Service/ConfigGenerator.php Executable file
View file

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace App\Service;
use App\Entity\User;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class ConfigGenerator
{
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
}
public function generateConfig(User $user): Response
{
// Define the configuration parameters
$config = [
'Version' => '15.0.0',
'Name' => 'ventry-config-' . $user->getUsername(),
'DestinationType' => 'ImageUploader, FileUploader',
'RequestMethod' => 'POST',
'RequestURL' => $this->urlGenerator->generate('app_upload_file', referenceType: 0),
'Body' => 'MultipartFormData',
'Arguments' => ['token' => $user->getToken()],
'FileFormName' => 'file',
'ThumbnailURL' => '{response}/thumbnail',
'DeletionURL' => '{response}/delete',
'ErrorMessage' => '{json:error}'
];
// Generate a JSON representation of the configuration
$jsonConfig = json_encode($config, JSON_PRETTY_PRINT);
// Create a response with the JSON configuration
$response = new Response($jsonConfig, Response::HTTP_OK);
$response->headers->set(
'Content-Disposition',
"attachment; filename=\"ventry-config-{$user->getUsername()}.sxcu\""
);
return $response;
}
}

21
src/Service/PaginationMaker.php Executable file
View file

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
namespace App\Service;
class PaginationMaker
{
/**
* Paginate an array of items based on the page and items per page.
*
* @param array $items The array of items to paginate.
* @param int $page The current page number.
* @param int $perPage The number of items to display per page.
* @return array The paginated subset of items for the current page.
*/
public function paginate(array $items, int $page, int $perPage): array
{
$offset = ($page - 1) * $perPage;
return array_slice($items, $offset, $perPage);
}
}

44
src/Service/SizeFormatter.php Executable file
View file

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace App\Service;
use Doctrine\Common\Collections\ArrayCollection;
class SizeFormatter
{
/**
* Calculate the total size of files in a collection and format it.
*
* @param ArrayCollection $files The collection of files.
* @return string The total size formatted in KB or MB.
*/
public function calculateTotalSize(ArrayCollection $files): string
{
$totalSizeInBytes = 0;
foreach ($files as $file) {
$fileSize = $file->getSize();
if (is_numeric($fileSize)) {
$totalSizeInBytes += $fileSize;
}
}
return $this->formatSize($totalSizeInBytes);
}
/**
* Format a size in bytes to KB or MB.
*
* @param int $sizeInBytes The size in bytes to format.
* @return string The formatted size with units (KB or MB).
*/
public function formatSize(int $sizeInBytes): string
{
if ($sizeInBytes >= 1048576) {
return number_format($sizeInBytes / 1048576, 2) . ' MB';
}
return number_format($sizeInBytes / 1024, 2) . ' KB';
}
}

19
src/Service/TokenGenerator.php Executable file
View file

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace App\Service;
class TokenGenerator
{
/**
* Generate a random string using SHA1.
*
* @param int $length The length of the random string.
* @return string The random string.
*/
public function getRandomStringSha1(int $length = 16): string
{
$string = sha1((string)rand());
return substr($string, 0, $length);
}
}

27
src/Twig/FilesizeExtension.php Executable file
View file

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace App\Twig;
use App\Service\SizeFormatter;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class FilesizeExtension extends AbstractExtension
{
public function __construct(
private readonly SizeFormatter $sizeFormatter
) {
}
public function getFilters(): array
{
return [
new TwigFilter('formatFilesize', [$this, 'formatFilesize']),
];
}
public function formatFilesize($size): string
{
return $this->sizeFormatter->formatSize($size);
}
}