Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow install app via api external to support multi tenant #8

Open
wants to merge 1 commit into
base: stable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ APP_SECRET=e8d4e7defffc2055ab5646b0ad724960
CLIENT_SECRET=
MAKAIRA_API_URL=
MAKAIRA_INSTANCE=
# The secret that allow install app for multi tenant via external api
APP_STORE_URL=
APP_STORE_SECRET=
# Secret that can be found in the Admin UI at /admin/<instance>/setup-information.
# Can be used to send authenticaed requests to Makaira-API and don't want to use the JWT Token exchange.
MAKAIRA_SHARED_SECRET=
Expand Down
6 changes: 6 additions & 0 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ security:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false

# external-install:
# pattern: ^/api/app-external-install
# security: true
# custom_authenticators:
# - App\Security\MakairaAppAuthenticator

main:
security: true
stateless: true
Expand Down
8 changes: 8 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ parameters:
MAKAIRA_APP_SECRET_CONTENT_WIDGET: '%env(resolve:MAKAIRA_APP_SECRET_CONTENT_WIDGET)%'
MAKAIRA_APP_SECRET_CONTENT_MODAL: '%env(resolve:MAKAIRA_APP_SECRET_CONTENT_MODAL)%'
MAKAIRA_APP_SECRET: '%env(resolve:MAKAIRA_APP_SECRET)%'
APP_STORE_URL: '%env(resolve:APP_STORE_URL)%'
APP_STORE_SECRET: '%env(resolve:APP_STORE_SECRET)%'

services:
# default configuration for services in *this* file
Expand All @@ -20,6 +22,7 @@ services:
$appUrl: '%env(resolve:APP_URL)%'
$billingToken: '%env(resolve:BILLING_TOKEN)%'
$appName: '%env(resolve:APP_NAME)%'
$appSecret: '%env(resolve:APP_SECRET)%'

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Expand All @@ -41,5 +44,10 @@ services:
$aggregate: '@Makaira\HttpClient\Curl'
$sharedSecret: '%SHARED_SECRET%'

App\Security\MakairaAppAuthenticator:
arguments:
$appStoreUrl: '%APP_STORE_URL%'
$appStoreSecret: '%APP_STORE_SECRET%'

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
35 changes: 35 additions & 0 deletions migrations/Version20240719023644.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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 Version20240719023644 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 billing_data (id INT AUTO_INCREMENT NOT NULL, slug VARCHAR(255) NOT NULL, app_url VARCHAR(255) NOT NULL, billing_token VARCHAR(255) NOT NULL, app_name VARCHAR(255) NOT NULL, credits_to_charge VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE UNIQUE INDEX UNIQ_D281CDA4989D9B62 ON billing_data (slug)');
$this->addSql('ALTER TABLE app_info ADD app_widget_slug VARCHAR(255) DEFAULT NULL, ADD app_widget_secret VARCHAR(255) DEFAULT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE app_info DROP app_widget_slug, DROP app_widget_secret');
$this->addSql('DROP INDEX UNIQ_D281CDA4989D9B62 ON billing_data');
$this->addSql('DROP TABLE billing_data');
}
}
59 changes: 59 additions & 0 deletions src/Command/CreateBillingDataCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Command;

use App\Entity\BillingData;
use App\Service\EncryptionService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'app:create-billing-data',
description: 'create billing data',
)]
class CreateBillingDataCommand extends Command
{
public function __construct(
private readonly EncryptionService $encryptionService,
private readonly EntityManagerInterface $em,
string $name = null,
)
{
parent::__construct($name);
}

protected function configure(): void
{
$this
->addArgument('slug', InputArgument::REQUIRED, 'app slug')
->addArgument('app-url', InputArgument::REQUIRED, 'app url')
->addArgument('billing-token', InputArgument::REQUIRED, 'non-encrypted billing token')
->addArgument('app-name', InputArgument::REQUIRED, 'app-name')
->addArgument('credits-to-charge', InputArgument::OPTIONAL, '150 default', '150')
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$billingData = (new BillingData())
->setSlug($input->getArgument('slug'))
->setAppUrl($input->getArgument('app-url'))
->setBillingToken($this->encryptionService->encryptValue($input->getArgument('billing-token')))
->setAppName($input->getArgument('app-name'))
->setCreditsToCharge($input->getArgument('credits-to-charge'));

$this->em->persist($billingData);
$this->em->flush();

$io->success('Billing Data saved.');

return Command::SUCCESS;
}
}
35 changes: 35 additions & 0 deletions src/Controller/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace App\Controller;

use App\Entity\AppInfo;
use App\Repository\AppInfoRepository;
use App\Service\CommunicationService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
Expand Down Expand Up @@ -116,4 +119,36 @@ public function contentWidget(Request $request): Response
'pageTitle' => $pageTitle,
]);
}

// #[Route('/api/app-external-install', name: 'app_external_install')]
// public function externalInstall(
// Request $request,
// AppInfoRepository $appInfoRepository,
// ): JsonResponse
// {
// $body = json_decode($request->getContent(), false, 512, JSON_THROW_ON_ERROR);
// $domain = $body->domain;
// $clientSecret = $body->clientSecret;
// $instance = $body->instance;
// $slug = $body->slug;
// $widgetSlug = $body->widgetSlug ?? '';
// $widgetSecret = $body->widgetSecret ?? '';

// $appInfo = $appInfoRepository->findOneByAppInfo($domain, $instance, $slug);
// if ($appInfo === null) {
// $appInfo = new AppInfo();
// $appInfo->setMakairaDomain($domain)
// ->setMakairaInstance($instance)
// ->setAppSlug($slug)
// ->setAppSecret($clientSecret)
// ->setAppWidgetSlug($widgetSlug)
// ->setAppWidgetSecret(($widgetSecret));

// $appInfoRepository->save($appInfo, true);
// }

// return new JsonResponse([
// 'message' => 'success',
// ]);
// }
}
42 changes: 42 additions & 0 deletions src/Entity/AppInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class AppInfo
#[ORM\Column(length: 255, nullable: false)]
private string $appSecret = '';

#[ORM\Column(length: 255, nullable: true)]
private ?string $appWidgetSlug = '';

#[ORM\Column(length: 255, nullable: true)]
private ?string $appWidgetSecret = '';

public function getId(): ?int
{
return $this->id;
Expand Down Expand Up @@ -78,6 +84,42 @@ public function setAppSecret(string $appSecret): self
return $this;
}

public function setAppWidgetSecret(string $appWidgetSecret): self
{
$this->appWidgetSecret = $appWidgetSecret;

return $this;
}

public function getAppWidgetSecret(): ?string
{
return $this->appWidgetSecret;
}

/**
* return right secret base on appType
* @return string | null
*/
public function getSecret(string $appType = 'app'): ?string {
if ($appType === 'content-widget') {
return $this->appWidgetSecret;
}

return $this->appSecret;
}

public function setAppWidgetSlug(string $appWidgetSlug = ''): self
{
$this->appWidgetSlug = $appWidgetSlug;

return $this;
}

public function getAppWidgetSlug(): ?string
{
return $this->appWidgetSlug;
}

public function getSubDomain(): ?string
{
$makairaDomain = $this->getMakairaDomain();
Expand Down
96 changes: 96 additions & 0 deletions src/Entity/BillingData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace App\Entity;

use App\Repository\BillingDataRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: BillingDataRepository::class)]
class BillingData
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(length: 255, unique: true)]
private ?string $slug = null;

#[ORM\Column(length: 255)]
private ?string $appUrl = null;

// this value is encrypted in the database by using the app_secret
#[ORM\Column(length: 255)]
private ?string $billingToken = null;

#[ORM\Column(length: 255)]
private ?string $appName = null;

#[ORM\Column(length: 255)]
private ?string $creditsToCharge = '150';

public function getId(): ?int
{
return $this->id;
}

public function getSlug(): ?string
{
return $this->slug;
}

public function setSlug(string $slug): static
{
$this->slug = $slug;

return $this;
}

public function getAppUrl(): ?string
{
return $this->appUrl;
}

public function setAppUrl(string $appUrl): static
{
$this->appUrl = $appUrl;

return $this;
}

public function getBillingToken(): ?string
{
return $this->billingToken;
}

public function setBillingToken(string $billingToken): static
{
$this->billingToken = $billingToken;

return $this;
}

public function getAppName(): ?string
{
return $this->appName;
}

public function setAppName(string $appName): static
{
$this->appName = $appName;

return $this;
}

public function getCreditsToCharge(): ?string
{
return $this->creditsToCharge;
}

public function setCreditsToCharge(string $creditsToCharge): static
{
$this->creditsToCharge = $creditsToCharge;

return $this;
}
}
42 changes: 42 additions & 0 deletions src/Repository/BillingDataRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Repository;

use App\Entity\BillingData;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
* @extends ServiceEntityRepository<BillingData>
*
* @method BillingData|null find($id, $lockMode = null, $lockVersion = null)
* @method BillingData|null findOneBy(array $criteria, array $orderBy = null)
* @method BillingData[] findAll()
* @method BillingData[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BillingDataRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, BillingData::class);
}

public function save(BillingData $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);

if ($flush) {
$this->getEntityManager()->flush();
}
}

public function remove(BillingData $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);

if ($flush) {
$this->getEntityManager()->flush();
}
}

}
Loading