Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e915d41
Events Pagination fix
bernardhanna Aug 13, 2024
56e07ab
Merge branch 'apple_events' into matrix-uat
jakubnewcity Oct 15, 2024
4d53b1c
Added multi-step form feature to event form
bernardhanna Dec 6, 2024
b7ac821
created edit event
bernardhanna Dec 20, 2024
115fd65
various imporvements based on feedback
bernardhanna Jan 9, 2025
a65df74
Merge branch 'new-events-form' into matrix-uat
bernardhanna Jan 9, 2025
1479380
Update login and register pages
stevanpo Feb 12, 2025
42f2a0e
Fixed Greek certificate template with proper language environment han…
bernardhanna Feb 13, 2025
e82676e
community page and latex
bernardhanna Feb 13, 2025
eeb5065
Merge branch 'matrix-uat' into temp
bernardhanna Feb 13, 2025
6e34c34
feat: built new digital girls page
stevanpo Feb 14, 2025
f288eb3
handle hero video
stevanpo Feb 17, 2025
6e9f257
update menu bar
stevanpo Feb 18, 2025
0f840d3
Merge pull request #2474 from codeeu/fix/update-menu-bar
stevanpo Feb 18, 2025
463485f
fix: feedback
stevanpo Feb 19, 2025
7220478
Merge branch 'dev' of stevan.github.com:codeeu/codeweek into feat/new…
stevanpo Feb 19, 2025
64c3f9a
Merge pull request #2476 from codeeu/feat/update-login-register-page
Siroumi Feb 19, 2025
ada005b
forget password page update
Siroumi Feb 19, 2025
9c1626a
Merge pull request #2477 from codeeu/feat/update-login-register-page
Siroumi Feb 19, 2025
9ff3877
password reset page update
Siroumi Feb 19, 2025
106f2f6
Merge pull request #2478 from codeeu/feat/update-login-register-page
Siroumi Feb 19, 2025
f11feb4
update the button colour
Siroumi Feb 19, 2025
ced8a73
Merge pull request #2479 from codeeu/feat/update-login-register-page
Siroumi Feb 19, 2025
844e9e2
Making design update to password page
Siroumi Feb 20, 2025
e52b03f
Merge pull request #2482 from codeeu/feat/update-login-register-page
Siroumi Feb 20, 2025
a7aeea4
Mobile fixes for mobile devices
Siroumi Feb 20, 2025
bd42df8
Merge pull request #2483 from codeeu/feat/update-login-register-page
Siroumi Feb 20, 2025
f33907b
syntax error fix
Siroumi Feb 20, 2025
8da490e
Merge pull request #2484 from codeeu/feat/update-login-register-page
Siroumi Feb 20, 2025
6dd061d
Merge pull request #2459 from codeeu/feat/update-login-register-page
Siroumi Feb 20, 2025
fa47386
Add community page images and view
bernardhanna Feb 21, 2025
45dd67a
Add community images and view
bernardhanna Feb 21, 2025
9cd5923
Merge pull request #2485 from codeeu/community
bernardhanna Feb 21, 2025
c23c534
fix: feedback
stevanpo Feb 21, 2025
ac1e21f
Update community view and Tailwind configuration
bernardhanna Feb 21, 2025
a3aeae4
Merge pull request #2486 from codeeu/community
bernardhanna Feb 21, 2025
5533c79
Update community view css and lorem ipsum
bernardhanna Feb 21, 2025
8f95973
Merge pull request #2487 from codeeu/community
bernardhanna Feb 21, 2025
7a1e60f
removed button
bernardhanna Feb 21, 2025
0c40ea0
Merge pull request #2488 from codeeu/community
bernardhanna Feb 21, 2025
71cbd56
fix: conflict
stevanpo Feb 21, 2025
5dc2660
Merge pull request #2471 from codeeu/feat/new-digital-girls-page
stevanpo Feb 21, 2025
96784aa
Build dreams jobs in digital page
stevanpo Feb 21, 2025
b3f6a9d
added sub text
bernardhanna Feb 21, 2025
864dbf8
added community text
bernardhanna Feb 21, 2025
abff329
Merge pull request #2490 from codeeu/community
bernardhanna Feb 21, 2025
0f54167
Merge pull request #2491 from codeeu/certs-update
bernardhanna Feb 21, 2025
2f3bd09
Fix Greek & Georgian encoding issues and add updated LaTeX templates
bernardhanna Feb 21, 2025
4f3f686
Merge pull request #2495 from codeeu/certs-update
bernardhanna Feb 21, 2025
22b6a9e
Merge pull request #2497 from codeeu/certs-encode-fix
bernardhanna Feb 21, 2025
229955b
fix: conflict
stevanpo Feb 24, 2025
23ef882
Merge branch 'feat/dreams-jobs-in-digital-page' of stevan.github.com:…
stevanpo Feb 24, 2025
f9264ac
Updated Excellence & Super Organiser certificates and related scripts
bernardhanna Feb 27, 2025
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
149 changes: 74 additions & 75 deletions app/CertificateExcellence.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,74 +13,70 @@ class CertificateExcellence
private $templateName;

private $name_of_certificate_holder;

private $email_of_certificate_holder;

private $resource_path;

private $pdflatex;

private $personalized_template_name;

private $event;

private $id;

private $edition;

private $number_of_activities;
private $type;

public function __construct($edition, $name_for_certificate, $type, $number_of_activities)
public function __construct($edition, $name_for_certificate, $type = 'excellence', $number_of_activities = 0)
{

$this->edition = $edition;
$this->name_of_certificate_holder = $name_for_certificate;
$this->email_of_certificate_holder = auth()->user()->email ?? '';
$this->personalized_template_name = $edition.'-'.auth()->id();
$this->resource_path = resource_path().'/latex';
$this->personalized_template_name = $edition . '-' . auth()->id();
$this->resource_path = resource_path() . '/latex';
$this->pdflatex = config('codeweek.pdflatex_path');
$this->id = auth()->id().'-'.str_random(10);
$this->id = auth()->id() . '-' . str_random(10);
$this->number_of_activities = $number_of_activities;
$this->type = $type ?? 'excellence';
$this->type = $type ?: 'excellence';

// e.g. "excellence-2025.tex" or "super-organiser-2025.tex"
$this->templateName = "{$this->type}-{$this->edition}.tex";

Log::info('User ID '.auth()->id()." generating {$this->type} certificate with name: ".$name_for_certificate);
Log::info('User ID ' . auth()->id() . " generating {$this->type} certificate with name: " . $name_for_certificate);
}

/**
* Generates the certificate PDF, saves to S3, cleans up temp files.
* Returns the S3 path of the generated PDF.
*/
public function generate()
{

$this->customize_and_save_latex();
$this->run_pdf_creation();
$s3path = $this->copy_to_s3();
$this->clean_temp_files();

return $s3path;

}

/**
* Clean up LaTeX artifacts for the generated file.
*/
private function clean_temp_files()
{
Storage::disk('latex')->delete($this->personalized_template_name.'.aux');
Storage::disk('latex')->delete($this->personalized_template_name.'.tex');
Storage::disk('latex')->delete($this->personalized_template_name.'.pdf');
Storage::disk('latex')->delete($this->personalized_template_name.'.log');
Storage::disk('latex')->delete($this->personalized_template_name . '.aux');
Storage::disk('latex')->delete($this->personalized_template_name . '.tex');
Storage::disk('latex')->delete($this->personalized_template_name . '.pdf');
Storage::disk('latex')->delete($this->personalized_template_name . '.log');
}

/**
* Check for Greek characters in the name.
*/
public function is_greek()
{

$split = preg_split('/[\p{Greek}]/u', $this->name_of_certificate_holder);
if (count($split) > 1) {
// Log::info("Detected as Greek: " . $this->name_of_certificate_holder);
return true;
}

return false;

return (count($split) > 1);
}

/**
* Escape LaTeX special characters in user data.
*/
private function tex_escape($string)
{
$map = [
Expand All @@ -96,82 +92,85 @@ private function tex_escape($string)
'}' => '\\}',
];

return preg_replace_callback("/([\^\%~\\\\#\$%&_\{\}])/",
return preg_replace_callback(
"/([\^\%~\\\\#\$%&_\{\}])/",
function ($matches) use ($map) {
foreach ($matches as $match) {
return $map[$match];
}
}, $string);
}

protected function update_event($s3path)
{
$this->event->update([
'certificate_url' => $s3path,
'certificate_generated_at' => Carbon::now(),
]);
},
$string
);
}

/**
* @throws \League\Flysystem\FileNotFoundException
*/
protected function copy_to_s3(): string
{
$inputStream = Storage::disk('latex')->getDriver()->readStream($this->personalized_template_name.'.pdf');
$destination = Storage::disk('s3')->path('/certificates/'.$this->id.'.pdf');
Storage::disk('s3')->put($destination, $inputStream);

return Storage::disk('s3')->url('certificates/'.$this->id.'.pdf');
}

/**
* @return mixed
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
* Read the base template from disk, replace placeholders, and save the .tex file.
*/
protected function customize_and_save_latex()
{
// If the name is Greek, switch to Greek template if it exists:
if ($this->is_greek()) {
$this->templateName = "{$this->type}_greek-{$this->edition}.tex";
}
Log::info($this->templateName);
//open the latex template

Log::info("Using template: {$this->templateName}");
$base_template = Storage::disk('latex')->get($this->templateName);

//replace the text in template
$template = str_replace('<CERTIFICATE_HOLDER_NAME>', $this->tex_escape($this->name_of_certificate_holder), $base_template);
// Always replace <CERTIFICATE_HOLDER_NAME> for both excellence & super-organiser
$template = str_replace(
'<CERTIFICATE_HOLDER_NAME>',
$this->tex_escape($this->name_of_certificate_holder),
$base_template
);

if ($this->type == 'super-organiser') {
$template = str_replace('<CERTIFICATE_EMAIL>', $this->tex_escape($this->email_of_certificate_holder), $template);
$template = str_replace('<CERTIFICATE_DATE>', $this->tex_escape(Carbon::now()->format('d/m/Y')), $template);
// If super-organiser, we also replace these:
if ($this->type === 'super-organiser') {
// Possibly also <EDITION> if you want it dynamic
$template = str_replace('<NUMBER_OF_ACTIVITIES>', $this->tex_escape($this->number_of_activities), $template);
$template = str_replace('<CERTIFICATE_DATE>', $this->tex_escape(Carbon::now()->format('d/m/Y')), $template);
// If you added <CERTIFICATE_EMAIL> or <EDITION> placeholders, handle them as well:
$template = str_replace('<CERTIFICATE_EMAIL>', $this->tex_escape($this->email_of_certificate_holder), $template);
$template = str_replace('<EDITION>', $this->edition, $template);
}

Log::info($template);
// For excellence type, you can do other placeholder replacements here if needed.

//save it locally
Storage::disk('latex')->put($this->personalized_template_name.'.tex', $template);
// Save updated .tex
Log::info($template);
Storage::disk('latex')->put($this->personalized_template_name . '.tex', $template);
}

/**
* Compile the .tex file with pdflatex.
*/
protected function run_pdf_creation(): void
{
$command = $this->pdflatex . ' -interaction=nonstopmode -output-directory ' .
$this->resource_path . ' ' .
$this->resource_path . '/' . $this->personalized_template_name . '.tex';

//call the pdflatex command
$command = $this->pdflatex.' -interaction=nonstopmode -output-directory '.$this->resource_path.' '.$this->resource_path.'/'.$this->personalized_template_name.'.tex';

Log::info($command);

Log::info("pdflatex command: $command");
$cwd = $this->resource_path;

Log::info($cwd);

// $process = new Process($command, $cwd);
$process = Process::fromShellCommandline($command, $cwd);
$process->run();

// executes after the command finishes
if (! $process->isSuccessful()) {
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
}

/**
* Copy the resulting PDF to S3, return its S3 URL.
*/
protected function copy_to_s3(): string
{
$pdfFile = $this->personalized_template_name . '.pdf';
$inputStream = Storage::disk('latex')->getDriver()->readStream($pdfFile);
$destination = Storage::disk('s3')->path('/certificates/' . $this->id . '.pdf');

Storage::disk('s3')->put($destination, $inputStream);

return Storage::disk('s3')->url('certificates/' . $this->id . '.pdf');
}
}
1 change: 0 additions & 1 deletion app/Console/Commands/Excellence.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,5 @@ public function handle(): void
Log::info($ex->getMessage());
}
}

}
}
36 changes: 27 additions & 9 deletions app/Console/Commands/NotifySuperOrganisers.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ class NotifySuperOrganisers extends Command

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
Expand All @@ -39,30 +37,50 @@ public function __construct()
*/
public function handle(): void
{

$edition = $this->argument('edition');
$this->info("🔹 NotifySuperOrganisers command started for edition: $edition");

// Fetch winners for the given edition
$winners = Excellence::byYear($edition, 'SuperOrganiser');

foreach ($winners as $winner) {
if ($winners->isEmpty()) {
$this->warn("⚠️ No winners found for edition: $edition");
Log::info("No SuperOrganiser winners found for edition: $edition.");
return;
}

$notifiedCount = 0;

foreach ($winners as $winner) {
$user = $winner->user;

if (! $user) {
$this->warn("⚠️ Skipping winner with missing user data (ID: $winner->id)");
Log::warning("Skipping winner with missing user data. Winner ID: $winner->id");
continue;
}

if ($user->email && $user->receive_emails) {
// Send email notification
Mail::to($user->email)->queue(new \App\Mail\NotifySuperOrganiser($user, $edition));
$excellence = $user->superOrganisers->where('edition', '=', $edition)->first();

$excellence->notified_at = Carbon::now();
$excellence->save();
// Mark as notified
$excellence = $user->superOrganisers->where('edition', '=', $edition)->first();
if ($excellence) {
$excellence->notified_at = Carbon::now();
$excellence->save();
}

$notifiedCount++;
$this->info("✅ Email queued for: {$user->email}");
Log::info("Email queued for SuperOrganiser: {$user->email}, Edition: $edition");
} else {
Log::info($user->id.' has no valid email address');
$this->warn("⚠️ Skipping user {$user->id} (No valid email or opted out)");
Log::info("User {$user->id} skipped - No valid email or opted out.");
}

}

$this->info("✅ NotifySuperOrganisers command executed for edition: $edition, Emails sent: $notifiedCount");
Log::info("NotifySuperOrganisers command completed. Edition: $edition, Emails sent: $notifiedCount.");
}
}
37 changes: 27 additions & 10 deletions app/Console/Commands/NotifyWinners.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ class NotifyWinners extends Command

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
Expand All @@ -39,27 +37,46 @@ public function __construct()
*/
public function handle(): void
{

$edition = $this->argument('edition');

$this->info("🔹 NotifyWinners command started for edition: {$edition}");

// Get all Excellence rows for this edition that have notified_at=null
$winners = Excellence::byYear($edition);

foreach ($winners as $winner) {
if ($winners->isEmpty()) {
$this->warn("⚠️ No winners found for edition: {$edition}");
return;
}

$count = 0;
foreach ($winners as $winner) {
$user = $winner->user;

// If no user is associated, we remove this row
if (is_null($user)) {
$this->warn("User is null for winner ID {$winner->id}. Deleting row.");
$winner->delete();
} elseif ($user->email && $user->receive_emails === 1) {
continue;
}

// If user has a valid email and is set to receive emails
if ($user->email && $user->receive_emails === 1) {
Mail::to($user->email)->queue(new \App\Mail\NotifyWinner($user, $edition));
$excellence = $user->excellences->where('edition', '=', $edition)->first();
$excellence->notified_at = Carbon::now();
$excellence->save();

// Mark notified
$winner->notified_at = Carbon::now();
$winner->save();

$count++;
$this->info("✅ Queued email for: {$user->email}");
} else {
Log::info($user->id.' has no valid email address');
// user->email is null or user->receive_emails=0
Log::info("User {$user->id} has no valid email or doesn't receive emails");
$this->warn("User {$user->id} not notified (invalid email or receive_emails=0)");
}

}

$this->info("✅ NotifyWinners command finished for edition {$edition}. Emails sent: {$count}");
}
}
Loading
Loading