Dockerize Project
This commit is contained in:
commit
e679d02b41
205 changed files with 17941 additions and 0 deletions
0
src/Controller/.gitignore
vendored
Normal file
0
src/Controller/.gitignore
vendored
Normal file
26
src/Controller/ConfigController.php
Executable file
26
src/Controller/ConfigController.php
Executable 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);
|
||||
}
|
||||
}
|
||||
86
src/Controller/File/FileController.php
Executable file
86
src/Controller/File/FileController.php
Executable 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);
|
||||
}
|
||||
}
|
||||
138
src/Controller/File/UploadController.php
Executable file
138
src/Controller/File/UploadController.php
Executable 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
|
||||
);
|
||||
}
|
||||
}
|
||||
16
src/Controller/Page/DefaultController.php
Executable file
16
src/Controller/Page/DefaultController.php
Executable 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');
|
||||
}
|
||||
}
|
||||
16
src/Controller/Page/FaqController.php
Executable file
16
src/Controller/Page/FaqController.php
Executable 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');
|
||||
}
|
||||
}
|
||||
66
src/Controller/Page/HomeController.php
Executable file
66
src/Controller/Page/HomeController.php
Executable 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;
|
||||
}));
|
||||
}
|
||||
}
|
||||
26
src/Controller/Page/LoginController.php
Executable file
26
src/Controller/Page/LoginController.php
Executable 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
60
src/Controller/Page/RegistrationController.php
Executable file
60
src/Controller/Page/RegistrationController.php
Executable 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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
19
src/Controller/Profile/UserController.php
Normal file
19
src/Controller/Profile/UserController.php
Normal 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
0
src/Entity/.gitignore
vendored
Executable file
97
src/Entity/File.php
Executable file
97
src/Entity/File.php
Executable 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
174
src/Entity/User.php
Executable 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;
|
||||
}
|
||||
}
|
||||
55
src/Form/RegistrationFormType.php
Executable file
55
src/Form/RegistrationFormType.php
Executable 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
11
src/Kernel.php
Normal 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
0
src/Repository/.gitignore
vendored
Executable file
29
src/Repository/FileRepository.php
Executable file
29
src/Repository/FileRepository.php
Executable 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();
|
||||
}
|
||||
}
|
||||
66
src/Repository/UserRepository.php
Executable file
66
src/Repository/UserRepository.php
Executable 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()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
52
src/Security/AppAuthenticator.php
Executable file
52
src/Security/AppAuthenticator.php
Executable 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
44
src/Service/ConfigGenerator.php
Executable 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
21
src/Service/PaginationMaker.php
Executable 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
44
src/Service/SizeFormatter.php
Executable 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
19
src/Service/TokenGenerator.php
Executable 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
27
src/Twig/FilesizeExtension.php
Executable 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue