diff --git a/.env b/.env index 804b8c999e..6cc19b399b 100644 --- a/.env +++ b/.env @@ -76,4 +76,6 @@ _APP_LOGGING_PROVIDER= _APP_LOGGING_CONFIG= _APP_REGION=default _APP_DOCKER_HUB_USERNAME= -_APP_DOCKER_HUB_PASSWORD= \ No newline at end of file +_APP_DOCKER_HUB_PASSWORD= +_APP_CONSOLE_GITHUB_SECRET= +_APP_CONSOLE_GITHUB_APP_ID= \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 6785e119cb..511b4eb3b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 2.2.2 + branch = cloud-current diff --git a/app/config/cloud/contributors.json b/app/config/cloud/contributors.json new file mode 100644 index 0000000000..efa36c3cb2 --- /dev/null +++ b/app/config/cloud/contributors.json @@ -0,0 +1 @@ +[1297371,1759475,6360216,20852629,5857008,19310830,9708641,26739219,23742426,22174310,77877486,1477010,29069505,62933155,45863583,42211,176163,58045728,7091609,31401437,1911066,66096031,22432834,43054051,79051850,22895284,100597998,91385411,49699333,7818620,11004008,54898623,27856297,33250853,49375670,30630364,50206,47822499,7481165,52557347,13681567,20492520,51369094,51821861,50256986,28431370,13692220,24373771,15938422,27148250,14805534,36137226,36071208,40193621,56179878,53281158,48085134,19358691,32528768,19733683,37348419,13719696,18309412,42106787,53618500,5306011,4408379,35486736,18586611,15062564,27011453,56090587,44623032,25107942,81188,33922418,16717633,32809211,69401139,25815659,4104127,20889958,3102249,12476526,2635185,28495651,50957556,2791280,31023616,20955511,835733,471907,69008866,44906587,45271396,76054330,45748739,41908747,23402178,47356149,8921,36632821,3668741,71702982,29725587,26303198,785830,51410502,60089135,2847349,43172716,48546075,8216525,41161981,51828039,4334997,80918302,38534289,47860497,80036766,41341387,49818988,58487637,29237374,46913894,5148229,4377199,29686102,26272249,75117692,4090256,27357868,33062368,38664231,46695441,743291,22633385,6368283,11593067,45097959,43381712,3284228,1972717,33012425,61755381,25405707,3144291,44156359,5497267,7423905,20716175,28586681,5975506,23518097,22187384,24191952,7768078,971530,51240166,55633427,34207400,77061285,11719476,35950229,66742927,34406802,802933,50047839,39148877,26602940,9693472,44273767,19362725,31209978,30521594,686298,6237394,35039730,42580581,36671793,8502129,8466918,81866614,54903252,28373606,13381361,72331432,30694270,5355510,8209163,86675510,9453522,42496309,56145786,2149381,393945,22084723,52621436,8872447,5575392,29619660,5547479,8852116,11151445,4717349,17725274,65615065,18537755,29292618,53044263,26597930,10313411,55998629,77529288,17404636,33729848,19422168,17916404,66111735,10329006,33502846,398230,81643826,105039167,47522632,91655303,9774614,10603631,284924,60857954,22885912,116552306,36103454,794606,27729549,1754457,36594527,13899668,78664749,47406531,27698189,5305654,53345517,6756412,29176704,77790497,47504894,37251540,52361778,52200375,1351177,66022861,73975409,25745396,31433638,37118134,43210805,20317665,11923975,47187468,16362381,36751163,14959876,32362757,65529384,52352285,74085816,3628535,43902034,75667593,26132902,466713,617558,96806061,33605526,11290524,43621940,12446314,17146935,55018955,56096559,79797000,40014186,34449936,58387964,23368207,42414965,44056349,33743031,12294525,58251592,33755729,9021747,932084,11428067,97121933,80122730,60894542,58583793,56051809,32243289,9934371,90936802,74638775,65399526,77604,64524822,47782249,43633955,42793632,55969597,72334601,82395440,92818577,60866204,65016769,23725091,45892107,55308895,86314140,82756460,47685349,63562160,73419211,1613216,50882624,91469717,46166258,60927324,41763158,83607556,2171717,50497814,39427312,61322830,40076195,39419448,29397545,55090719,53259730,20885012,64558515,69677883,55741087,72426535,46033036,68477507,30376878,73700530,25518600,29922887,36229969,47573417,40424087,49054503,16880385,22801227,72848513,64347914,814402,49149679,55017867,49481876,67067955,31439735,63878173,80322286,43746210,17332970,22702905,62476876,89888292,75736952,54059881,90782137,63588969,57111920,63330165,70258211,46371923,17837758,59364507,52203828,60147326,18481195,74822422,9803078,67309607,60410049,47360939,19922556,90848252,24698014,58886915,63579762,96648934,68523530,60518745,37345795,3929651,54993657,52061363,43019989,5787917,94674993,71593494,17143469,10288548,1830380,71510505,59124772,2335145,70798495,46474346,49263351,52062536,63151043,65248303,26071571,53626355,43992469,60785452,63467479,71837281,19490891,58628586,38250310,7271718,1110414,57227290,11625672,85063520,88965873,70096901,42029519,85363195,64471630,69353350,66922161,2221746,100430077,12299813,62690310,68282006,99184676,2450,22989561,22212661,59973863,11232940,76688923,22321353,77732479,84286404,32268377,34828782,23068019,57074509,24620969,20735983,26173690,75809937,49760818,86646105,52617262] \ No newline at end of file diff --git a/app/config/cloud/employees.json b/app/config/cloud/employees.json new file mode 100644 index 0000000000..3aaa2852b0 --- /dev/null +++ b/app/config/cloud/employees.json @@ -0,0 +1,32 @@ +{ + "eldad@appwrite.io": { "memberSince": "2020-10-15", "spot": "0", "gitHub": "eldadfux" }, + "christy@appwrite.io": { "memberSince": "2020-12-01", "spot": "1", "gitHub": "christyjacob4" }, + "torsten@appwrite.io": { "memberSince": "2020-12-28", "spot": "2", "gitHub": "torstendittmann" }, + "damodar@appwrite.io": { "memberSince": "2021-01-02", "spot": "3", "gitHub": "lohanidamodar" }, + "bradley@appwrite.io": { "memberSince": "2021-05-21", "spot": "5", "gitHub": "PineappleIOnic" }, + "jake@appwrite.io": { "memberSince": "2021-06-28", "spot": "6", "gitHub": "abnegate" }, + "sara@appwrite.io": { "memberSince": "2021-08-16", "spot": "7", "gitHub": "sarakaandorp" }, + "matej@appwrite.io": { "memberSince": "2021-08-23", "spot": "8", "gitHub": "meldiron" }, + "aditya@appwrite.io": { "memberSince": "2021-09-01", "spot": "9", "gitHub": "adityaoberai" }, + "wess@appwrite.io": { "memberSince": "2021-11-08", "spot": "12", "gitHub": "wess" }, + "may@appwrite.io": { "memberSince": "2021-11-28", "spot": "14", "gitHub": "MayEnder" }, + "elad@appwrite.io": { "memberSince": "2021-12-19", "spot": "15", "gitHub": "elad2412" }, + "vincent@appwrite.io": { "memberSince": "2022-01-01", "spot": "16", "gitHub": "gewenyu99" }, + "haimantika@appwrite.io": { "memberSince": "2022-04-01", "spot": "18", "gitHub": "Haimantika" }, + "chen@appwrite.io": { "memberSince": "2022-01-24", "spot": "19", "gitHub": "chenparnasa" }, + "tessa@appwrite.io": { "memberSince": "2022-04-21", "spot": "20", "gitHub": "tessamero" }, + "shimon@appwrite.io": { "memberSince": "2022-05-01", "spot": "23", "gitHub": "shimonewman" }, + "shmuel@appwrite.io": { "memberSince": "2022-03-20", "spot": "24", "gitHub": "fogelito" }, + "arman@appwrite.io": { "memberSince": "2022-04-04", "spot": "25", "gitHub": "ArmanNik" }, + "carla@appwrite.io": { "memberSince": "2022-04-04", "spot": "26", "gitHub": "heyCarla" }, + "emma@appwrite.io": { "memberSince": "2022-05-08", "spot": "27", "gitHub": "emmacarpagnano1" }, + "dylan@appwrite.io": { "memberSince": "2022-05-09", "spot": "28", "gitHub": "DylanG-64" }, + "steven@appwrite.io": { "memberSince": "2022-07-01", "spot": "30", "gitHub": "stnguyen90" }, + "jyoti@appwrite.io": { "memberSince": "2022-10-24", "spot": "31", "gitHub": "joeyouss" }, + "jade@appwrite.io": { "memberSince": "2022-10-31", "spot": "32", "gitHub": "dajebp" }, + "khushboo@appwrite.io": { "memberSince": "2021-11-08", "spot": "13", "gitHub": "vermakhushboo" }, + "thomas@appwrite.io": { "memberSince": "2022-11-03", "spot": "34", "gitHub": "TGlide" }, + "holly@appwrite.io": { "memberSince": "2022-12-05", "spot": "35", "gitHub": "HollyBarclay" }, + "laura@appwrite.io": { "memberSince": "2023-01-25", "spot": "36", "gitHub": "LauraDuRy" }, + "caio@appwrite.io": { "memberSince": "2023-03-27", "spot": "37", "gitHub": "ariascaio" } +} diff --git a/app/config/cloud/heroes.json b/app/config/cloud/heroes.json new file mode 100644 index 0000000000..d711df31c3 --- /dev/null +++ b/app/config/cloud/heroes.json @@ -0,0 +1,9 @@ +{ + "bishwajeet.techmaster@gmail.com": { "memberSince": "2023-02-07" }, + "lucasaudart@gmail.com": { "memberSince": "2023-02-07" }, + "tkarmakar27112000@gmail.com": { "memberSince": "2023-02-07" }, + "alves.mckl@gmail.com": { "memberSince": "2023-02-07" }, + "emilia@emilia.codes": { "memberSince": "2023-02-07" }, + "a.stephensimon@outlook.com": { "memberSince": "2023-02-07" }, + "hidianapham@gmail.com": { "memberSince": "2023-02-07" } +} diff --git a/app/console b/app/console index cad6f3b1bf..6ce5f46900 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit cad6f3b1bfdae4d423ba6f0735ba2a5cd5a58551 +Subproject commit 6ce5f469005815290c5993ba80fc5751f0c84fe8 diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index fbc77d69f6..64b7051a60 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -8,7 +8,11 @@ use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; use Utopia\App; use Utopia\Config\Config; +use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\UID; use Utopia\Image\Image; use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; @@ -49,11 +53,92 @@ $avatarCallback = function (string $type, string $code, int $width, int $height, $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/png') - ->file($data) - ; + ->file($data); unset($image); }; +$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole) { + try { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + $sessions = $user->getAttribute('sessions', []); + $session = $sessions[0] ?? new Document(); + + $provider = $session->getAttribute('provider'); + $accessToken = $session->getAttribute('providerAccessToken'); + $accessTokenExpiry = $session->getAttribute('providerAccessTokenExpiry'); + $refreshToken = $session->getAttribute('providerRefreshToken'); + + $appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? ''; + $appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}'; + + $className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider); + + if (!\class_exists($className)) { + throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED); + } + + $oauth2 = new $className($appId, $appSecret, '', [], []); + + $isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now'); + if ($isExpired) { + try { + $oauth2->refreshTokens($refreshToken); + + $accessToken = $oauth2->getAccessToken(''); + $refreshToken = $oauth2->getRefreshToken(''); + + $verificationId = $oauth2->getUserID($accessToken); + + if (empty($verificationId)) { + throw new \Exception("Locked tokens."); // Race codition, handeled in catch + } + + $session + ->setAttribute('providerAccessToken', $accessToken) + ->setAttribute('providerRefreshToken', $refreshToken) + ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); + + Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $session->getId(), $session)); + + $dbForProject->deleteCachedDocument('users', $user->getId()); + } catch (Throwable $err) { + $index = 0; + do { + $previousAccessToken = $session->getAttribute('providerAccessToken'); + + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + $sessions = $user->getAttribute('sessions', []); + $session = $sessions[0] ?? new Document(); + $accessToken = $session->getAttribute('providerAccessToken'); + + if ($accessToken !== $previousAccessToken) { + break; + } + + $index++; + \usleep(500000); + } while ($index < 10); + } + } + + $oauth2 = new $className($appId, $appSecret, '', [], []); + $githubUser = $oauth2->getUserSlug($accessToken); + $githubId = $oauth2->getUserID($accessToken); + + return [ + 'name' => $githubUser, + 'id' => $githubId + ]; + } catch (Exception $err) { + \var_dump($err->getMessage()); + \var_dump($err->getTraceAsString()); + \var_dump($err->getLine()); + \var_dump($err->getFile()); + return []; + } +}; + App::get('/v1/avatars/credit-cards/:code') ->desc('Get Credit Card Icon') ->groups(['api', 'avatars']) @@ -160,8 +245,7 @@ App::get('/v1/avatars/image') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/png') - ->file($data) - ; + ->file($data); unset($image); }); @@ -274,8 +358,7 @@ App::get('/v1/avatars/favicon') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/x-icon') - ->file($data) - ; + ->file($data); } $fetch = @\file_get_contents($outputHref, false); @@ -292,8 +375,7 @@ App::get('/v1/avatars/favicon') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/png') - ->file($data) - ; + ->file($data); unset($image); }); @@ -334,8 +416,7 @@ App::get('/v1/avatars/qr') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->send($image->output('png', 9)) - ; + ->send($image->output('png', 9)); }); App::get('/v1/avatars/initials') @@ -419,6 +500,675 @@ App::get('/v1/avatars/initials') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->file($image->getImageBlob()) - ; + ->file($image->getImageBlob()); + }); + +App::get('/v1/cards/cloud') + ->desc('Get Front Of Cloud Card') + ->groups(['api', 'avatars']) + ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resourceType', 'cards/cloud') + ->label('cache.resource', 'card/{request.userId}') + ->label('docs', false) + ->label('origin', '*') + ->param('userId', '', new UID(), 'User ID.', true) + ->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long']), 'Mocking behaviour.', true) + ->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true) + ->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true) + ->inject('user') + ->inject('project') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->inject('heroes') + ->inject('contributors') + ->inject('employees') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + if ($user->isEmpty() && empty($mock)) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if (!$mock) { + $name = $user->getAttribute('name', 'Anonymous'); + $email = $user->getAttribute('email', ''); + $createdAt = new \DateTime($user->getCreatedAt()); + + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole); + $githubName = $gitHub['name'] ?? ''; + $githubId = $gitHub['id'] ?? ''; + + $isHero = \array_key_exists($email, $heroes); + $isContributor = \in_array($githubId, $contributors); + $isEmployee = \array_key_exists($email, $employees); + $employeeNumber = $isEmployee ? $employees[$email]['spot'] : ''; + + if ($isHero) { + $createdAt = new \DateTime($heroes[$email]['memberSince'] ?? ''); + } elseif ($isEmployee) { + $createdAt = new \DateTime($employees[$email]['memberSince'] ?? ''); + } + + if (!$isEmployee && !empty($githubName)) { + $employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees)); + if (!empty($employeeGitHub)) { + $isEmployee = true; + $employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : ''; + $createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? ''); + } + } + + $isPlatinum = $user->getInternalId() % 100 === 0; + } else { + $name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; + $createdAt = new \DateTime('now'); + $githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian'); + $isHero = $mock === 'hero'; + $isContributor = $mock === 'contributor'; + $isEmployee = \str_starts_with($mock, 'employee'); + $employeeNumber = match ($mock) { + 'employee' => '1', + 'employee-2digit' => '18', + default => '' + }; + + $isPlatinum = $mock === 'platinum'; + } + + if ($isEmployee) { + $isContributor = false; + $isHero = false; + } + + if ($isHero) { + $isContributor = false; + $isEmployee = false; + } + + if ($isContributor) { + $isHero = false; + $isEmployee = false; + } + + $isGolden = $isEmployee || $isHero || $isContributor; + $isPlatinum = $isGolden ? false : $isPlatinum; + $memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o')); + + $imagePath = $isGolden ? 'front-golden.png' : ($isPlatinum ? 'front-platinum.png' : 'front.png'); + + $baseImage = new \Imagick("public/images/cards/cloud/" . $imagePath); + + if ($isEmployee) { + $image = new Imagick('public/images/cards/cloud/employee.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 35); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont("public/fonts/Inter-Bold.ttf"); + $text->setFillColor(new \ImagickPixel('#FFFADF')); + $text->setFontSize(\strlen($employeeNumber) <= 2 ? 54 : 48); + $text->setFontWeight(700); + $metricsText = $baseImage->queryFontMetrics($text, $employeeNumber); + + $hashtag = new \ImagickDraw(); + $hashtag->setTextAlignment(Imagick::ALIGN_CENTER); + $hashtag->setFont("public/fonts/Inter-Bold.ttf"); + $hashtag->setFillColor(new \ImagickPixel('#FFFADF')); + $hashtag->setFontSize(28); + $hashtag->setFontWeight(700); + $metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#'); + + $startX = 898; + $totalWidth = $metricsHashtag['textWidth'] + 12 + $metricsText['textWidth']; + + $hashtagX = ($metricsHashtag['textWidth'] / 2); + $textX = $hashtagX + 12 + ($metricsText['textWidth'] / 2); + + $hashtagX -= $totalWidth / 2; + $textX -= $totalWidth / 2; + + $hashtagX += $startX; + $textX += $startX; + + $baseImage->annotateImage($hashtag, $hashtagX, 150, 0, '#'); + $baseImage->annotateImage($text, $textX, 150, 0, $employeeNumber); + } + + if ($isContributor) { + $image = new Imagick('public/images/cards/cloud/contributor.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34); + } + + if ($isHero) { + $image = new Imagick('public/images/cards/cloud/hero.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34); + } + + setlocale(LC_ALL, "en_US.utf8"); + $name = \iconv("utf-8", "ascii//TRANSLIT", $name); + $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince); + $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont("public/fonts/Poppins-Bold.ttf"); + $text->setFillColor(new \ImagickPixel('#FFFFFF')); + + if (\strlen($name) > 33) { + $name = \substr($name, 0, 33); + } + + if (\strlen($name) <= 23) { + $text->setFontSize(80); + $scalingDown = false; + } else { + $text->setFontSize(54); + $scalingDown = true; + } + $text->setFontWeight(700); + $baseImage->annotateImage($text, 512, 477, 0, $name); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont("public/fonts/Inter-SemiBold.ttf"); + $text->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC')); + $text->setFontSize(27); + $text->setFontWeight(600); + $text->setTextKerning(1.08); + $baseImage->annotateImage($text, 512, 541, 0, \strtoupper($memberSince)); + + if (!empty($githubName)) { + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont("public/fonts/Inter-Regular.ttf"); + $text->setFillColor(new \ImagickPixel('#FFFFFF')); + $text->setFontSize($scalingDown ? 28 : 32); + $text->setFontWeight(400); + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $baseImage->annotateImage($text, 512 + 20 + 4, 373 + ($scalingDown ? 2 : 0), 0, $githubName); + + $image = new Imagick('public/images/cards/cloud/github.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $precisionFix = 5; + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2) - 20 - 4, 373 - ($metrics['textHeight'] - $precisionFix)); + } + + if (!empty($width) || !empty($height)) { + $baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1); + } + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->setContentType('image/png') + ->file($baseImage->getImageBlob()); + }); + +App::get('/v1/cards/cloud-back') + ->desc('Get Back Of Cloud Card') + ->groups(['api', 'avatars']) + ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resourceType', 'cards/cloud-back') + ->label('cache.resource', 'card-back/{request.userId}') + ->label('docs', false) + ->label('origin', '*') + ->param('userId', '', new UID(), 'User ID.', true) + ->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true) + ->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true) + ->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true) + ->inject('user') + ->inject('project') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->inject('heroes') + ->inject('contributors') + ->inject('employees') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + if ($user->isEmpty() && empty($mock)) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if (!$mock) { + $userId = $user->getId(); + $email = $user->getAttribute('email', ''); + + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole); + $githubId = $gitHub['id'] ?? ''; + + $isHero = \array_key_exists($email, $heroes); + $isContributor = \in_array($githubId, $contributors); + $isEmployee = \array_key_exists($email, $employees); + + $isGolden = $isEmployee || $isHero || $isContributor; + $isPlatinum = $user->getInternalId() % 100 === 0; + } else { + $userId = '63e0bcf3c3eb803ba530'; + + $isGolden = $mock === 'golden'; + $isPlatinum = $mock === 'platinum'; + } + + $userId = 'UID ' . $userId; + + $isPlatinum = $isGolden ? false : $isPlatinum; + + $imagePath = $isGolden ? 'back-golden.png' : ($isPlatinum ? 'back-platinum.png' : 'back.png'); + + $baseImage = new \Imagick("public/images/cards/cloud/" . $imagePath); + + setlocale(LC_ALL, "en_US.utf8"); + $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont("public/fonts/SourceCodePro-Regular.ttf"); + $text->setFillColor(new \ImagickPixel($isGolden ? '#664A1E' : ($isPlatinum ? '#555555' : '#E8E9F0'))); + $text->setFontSize(28); + $text->setFontWeight(400); + $baseImage->annotateImage($text, 512, 596, 0, $userId); + + if (!empty($width) || !empty($height)) { + $baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1); + } + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->setContentType('image/png') + ->file($baseImage->getImageBlob()); + }); + +App::get('/v1/cards/cloud-og') + ->desc('Get OG Image From Cloud Card') + ->groups(['api', 'avatars']) + ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resourceType', 'cards/cloud-og') + ->label('cache.resource', 'card-og/{request.userId}') + ->label('docs', false) + ->label('origin', '*') + ->param('userId', '', new UID(), 'User ID.', true) + ->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long', 'normal-long-right', 'normal-long-middle', 'normal-bg2', 'normal-bg3', 'normal-right', 'normal-middle', 'platinum-right', 'platinum-middle', 'hero-middle', 'hero-right', 'contributor-right', 'employee-right', 'contributor-middle', 'employee-middle', 'employee-2digit-middle', 'employee-2digit-right']), 'Mocking behaviour.', true) + ->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true) + ->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true) + ->inject('user') + ->inject('project') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->inject('heroes') + ->inject('contributors') + ->inject('employees') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + if ($user->isEmpty() && empty($mock)) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if (!$mock) { + $internalId = $user->getInternalId(); + $bgVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3'); + $cardVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3'); + + $name = $user->getAttribute('name', 'Anonymous'); + $email = $user->getAttribute('email', ''); + $createdAt = new \DateTime($user->getCreatedAt()); + + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole); + $githubName = $gitHub['name'] ?? ''; + $githubId = $gitHub['id'] ?? ''; + + $isHero = \array_key_exists($email, $heroes); + $isContributor = \in_array($githubId, $contributors); + $isEmployee = \array_key_exists($email, $employees); + $employeeNumber = $isEmployee ? $employees[$email]['spot'] : ''; + + if ($isHero) { + $createdAt = new \DateTime($heroes[$email]['memberSince'] ?? ''); + } elseif ($isEmployee) { + $createdAt = new \DateTime($employees[$email]['memberSince'] ?? ''); + } + + if (!$isEmployee && !empty($githubName)) { + $employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees)); + if (!empty($employeeGitHub)) { + $isEmployee = true; + $employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : ''; + $createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? ''); + } + } + + $isPlatinum = $user->getInternalId() % 100 === 0; + } else { + $bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1'); + $cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1'); + $name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; + $createdAt = new \DateTime('now'); + $githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian'); + $isHero = \str_starts_with($mock, 'hero'); + $isContributor = \str_starts_with($mock, 'contributor'); + $isEmployee = \str_starts_with($mock, 'employee'); + $employeeNumber = match ($mock) { + 'employee' => '1', + 'employee-right' => '1', + 'employee-middle' => '1', + 'employee-2digit' => '18', + 'employee-2digit-right' => '18', + 'employee-2digit-middle' => '18', + default => '' + }; + + $isPlatinum = \str_starts_with($mock, 'platinum'); + } + + if ($isEmployee) { + $isContributor = false; + $isHero = false; + } + + if ($isHero) { + $isContributor = false; + $isEmployee = false; + } + + if ($isContributor) { + $isHero = false; + $isEmployee = false; + } + + $isGolden = $isEmployee || $isHero || $isContributor; + $isPlatinum = $isGolden ? false : $isPlatinum; + $memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o')); + + $baseImage = new \Imagick("public/images/cards/cloud/og-background{$bgVariation}.png"); + + $cardType = $isGolden ? '-golden' : ($isPlatinum ? '-platinum' : ''); + + $image = new Imagick("public/images/cards/cloud/og-card{$cardType}{$cardVariation}.png"); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 1008 / 2 - $image->getImageWidth() / 2, 1008 / 2 - $image->getImageHeight() / 2); + + $imageLogo = new Imagick("public/images/cards/cloud/og-background-logo.png"); + $imageShadow = new Imagick("public/images/cards/cloud/og-shadow{$cardType}.png"); + if ($cardVariation === '1') { + $baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 32, 1008 - $imageLogo->getImageHeight() - 32); + $baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -450, 700); + } elseif ($cardVariation === '2') { + $baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32); + $baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -20, 710); + } else { + $baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32); + $baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -135, 710); + } + + if ($isEmployee) { + $file = $cardVariation === '3' ? 'employee-skew.png' : 'employee.png'; + $image = new Imagick('public/images/cards/cloud/' . $file); + $image->setGravity(Imagick::GRAVITY_CENTER); + + $hashtag = new \ImagickDraw(); + $hashtag->setTextAlignment(Imagick::ALIGN_LEFT); + $hashtag->setFont("public/fonts/Inter-Bold.ttf"); + $hashtag->setFillColor(new \ImagickPixel('#FFFADF')); + $hashtag->setFontSize(20); + $hashtag->setFontWeight(700); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_LEFT); + $text->setFont("public/fonts/Inter-Bold.ttf"); + $text->setFillColor(new \ImagickPixel('#FFFADF')); + $text->setFontSize(\strlen($employeeNumber) <= 1 ? 36 : 28); + $text->setFontWeight(700); + + if ($cardVariation === '3') { + $hashtag->skewY(20); + $hashtag->skewX(20); + $text->skewY(20); + $text->skewX(20); + } + + $metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#'); + $metricsText = $baseImage->queryFontMetrics($text, $employeeNumber); + + $group = new Imagick(); + $groupWidth = $metricsHashtag['textWidth'] + 6 + $metricsText['textWidth']; + + if ($cardVariation === '1') { + $group->newImage($groupWidth, $metricsText['textHeight'], '#00000000'); + $group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#'); + $group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber); + + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), -20); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203); + + $group->rotateImage(new ImagickPixel('#00000000'), -22); + + if (\strlen($employeeNumber) <= 1) { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 660, 245); + } else { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 655, 247); + } + } elseif ($cardVariation === '2') { + $group->newImage($groupWidth, $metricsText['textHeight'], '#00000000'); + $group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#'); + $group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber); + + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), 30); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425); + + $group->rotateImage(new ImagickPixel('#00000000'), 32); + + if (\strlen($employeeNumber) <= 1) { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 775, 465); + } else { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 767, 470); + } + } else { + $group->newImage(300, 300, '#00000000'); + + $hashtag->annotation(0, $metricsText['textHeight'], '#'); + $text->annotation($metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], $employeeNumber); + + $group->drawImage($hashtag); + $group->drawImage($text); + + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293); + + if (\strlen($employeeNumber) <= 1) { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 662, 310); + } else { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 659, 320); + } + } + } + + + if ($isContributor) { + $file = $cardVariation === '3' ? 'contributor-skew.png' : 'contributor.png'; + $image = new Imagick('public/images/cards/cloud/' . $file); + $image->setGravity(Imagick::GRAVITY_CENTER); + + if ($cardVariation === '1') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), -20); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203); + } elseif ($cardVariation === '2') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), 30); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425); + } else { + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293); + } + } + + if ($isHero) { + $file = $cardVariation === '3' ? 'hero-skew.png' : 'hero.png'; + $image = new Imagick('public/images/cards/cloud/' . $file); + $image->setGravity(Imagick::GRAVITY_CENTER); + + if ($cardVariation === '1') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), -20); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 615, 190); + } elseif ($cardVariation === '2') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), 30); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425); + } else { + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293); + } + } + + setlocale(LC_ALL, "en_US.utf8"); + $name = \iconv("utf-8", "ascii//TRANSLIT", $name); + $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince); + $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName); + + $textName = new \ImagickDraw(); + $textName->setTextAlignment(Imagick::ALIGN_CENTER); + $textName->setFont("public/fonts/Poppins-Bold.ttf"); + $textName->setFillColor(new \ImagickPixel('#FFFFFF')); + + if (\strlen($name) > 33) { + $name = \substr($name, 0, 33); + } + + if ($cardVariation === '1') { + if (\strlen($name) <= 23) { + $scalingDown = false; + $textName->setFontSize(54); + } else { + $scalingDown = true; + $textName->setFontSize(36); + } + } elseif ($cardVariation === '2') { + if (\strlen($name) <= 23) { + $scalingDown = false; + $textName->setFontSize(50); + } else { + $scalingDown = true; + $textName->setFontSize(34); + } + } else { + if (\strlen($name) <= 23) { + $scalingDown = false; + $textName->setFontSize(44); + } else { + $scalingDown = true; + $textName->setFontSize(32); + } + } + + $textName->setFontWeight(700); + + $textMember = new \ImagickDraw(); + $textMember->setTextAlignment(Imagick::ALIGN_CENTER); + $textMember->setFont("public/fonts/Inter-Medium.ttf"); + $textMember->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC')); + $textMember->setFontWeight(500); + $textMember->setTextKerning(1.12); + + if ($cardVariation === '1') { + $textMember->setFontSize(21); + + $baseImage->annotateImage($textName, 550, 600, -22, $name); + $baseImage->annotateImage($textMember, 585, 635, -22, $memberSince); + } elseif ($cardVariation === '2') { + $textMember->setFontSize(20); + + $baseImage->annotateImage($textName, 435, 590, 31.37, $name); + $baseImage->annotateImage($textMember, 412, 628, 31.37, $memberSince); + } else { + $textMember->setFontSize(16); + + $textName->skewY(20); + $textName->skewX(20); + $textName->annotation(320, 695, $name); + + $textMember->skewY(20); + $textMember->skewX(20); + $textMember->annotation(330, 735, $memberSince); + + $baseImage->drawImage($textName); + $baseImage->drawImage($textMember); + } + + if (!empty($githubName)) { + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_LEFT); + $text->setFont("public/fonts/Inter-Regular.ttf"); + $text->setFillColor(new \ImagickPixel('#FFFFFF')); + $text->setFontSize($scalingDown ? 22 : 26); + $text->setFontWeight(400); + + if ($cardVariation === '1') { + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $group = new Imagick(); + $groupWidth = $metrics['textWidth'] + 32 + 4; + $group->newImage($groupWidth, $metrics['textHeight'] + ($scalingDown ? 10 : 0), '#00000000'); + $image = new Imagick('public/images/cards/cloud/github.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1); + $precisionFix = 5; + + $group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0); + $group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName); + + $group->rotateImage(new ImagickPixel('#00000000'), -22); + $x = 510 - $group->getImageWidth() / 2; + $y = 530 - $group->getImageHeight() / 2; + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y); + } elseif ($cardVariation === '2') { + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $group = new Imagick(); + $groupWidth = $metrics['textWidth'] + 32 + 4; + $group->newImage($groupWidth, $metrics['textHeight'] + ($scalingDown ? 10 : 0), '#00000000'); + $image = new Imagick('public/images/cards/cloud/github.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1); + $precisionFix = 5; + + $group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0); + $group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName); + + $group->rotateImage(new ImagickPixel('#00000000'), 31.11); + $x = 485 - $group->getImageWidth() / 2; + $y = 530 - $group->getImageHeight() / 2; + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y); + } else { + $text->skewY(20); + $text->skewX(20); + $text->setTextAlignment(\Imagick::ALIGN_CENTER); + + $text->annotation(325 + 15 + 2, 630, $githubName); + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $image = new Imagick('public/images/cards/cloud/github-skew.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2), 510 + \strlen($githubName) * 1.3); + + $baseImage->drawImage($text); + } + } + + if (!empty($width) || !empty($height)) { + $baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1); + } + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->setContentType('image/png') + ->file($baseImage->getImageBlob()); }); diff --git a/app/init.php b/app/init.php index 2f71a60ce8..1d56e0baea 100644 --- a/app/init.php +++ b/app/init.php @@ -1040,6 +1040,11 @@ App::setResource('console', function () { ], 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'authProviders' => [ + 'githubEnabled' => true, + 'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), + 'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') + ], ]); }, []); @@ -1186,3 +1191,21 @@ App::setResource('servers', function () { return $languages; }); + +App::setResource('contributors', function () { + $path = 'app/config/cloud/contributors.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}, []); + +App::setResource('employees', function () { + $path = 'app/config/cloud/employees.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}, []); + +App::setResource('heroes', function () { + $path = 'app/config/cloud/heroes.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}, []); diff --git a/composer.lock b/composer.lock index 8e0857592e..604e0699cb 100644 --- a/composer.lock +++ b/composer.lock @@ -3571,16 +3571,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.19.1", + "version": "1.20.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "f545fc30978190a056832aa7ed995e36a66267f3" + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/f545fc30978190a056832aa7ed995e36a66267f3", - "reference": "f545fc30978190a056832aa7ed995e36a66267f3", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2", + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2", "shasum": "" }, "require": { @@ -3610,9 +3610,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.19.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3" }, - "time": "2023-04-18T11:30:56+00:00" + "time": "2023-04-25T09:01:03+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/docker-compose.yml b/docker-compose.yml index a2117adc5a..591b051223 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -165,6 +165,8 @@ services: - _APP_SMS_PROVIDER - _APP_SMS_FROM - _APP_REGION + - _APP_CONSOLE_GITHUB_APP_ID + - _APP_CONSOLE_GITHUB_SECRET appwrite-realtime: entrypoint: realtime diff --git a/public/fonts/Inter-Bold.ttf b/public/fonts/Inter-Bold.ttf new file mode 100644 index 0000000000..8e82c70d10 Binary files /dev/null and b/public/fonts/Inter-Bold.ttf differ diff --git a/public/fonts/Inter-Medium.ttf b/public/fonts/Inter-Medium.ttf new file mode 100644 index 0000000000..9a3396fc43 Binary files /dev/null and b/public/fonts/Inter-Medium.ttf differ diff --git a/public/fonts/Inter-Regular.ttf b/public/fonts/Inter-Regular.ttf new file mode 100644 index 0000000000..8d4eebf206 Binary files /dev/null and b/public/fonts/Inter-Regular.ttf differ diff --git a/public/fonts/Inter-SemiBold.ttf b/public/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000000..c6aeeb16a6 Binary files /dev/null and b/public/fonts/Inter-SemiBold.ttf differ diff --git a/public/fonts/Poppins-Bold.ttf b/public/fonts/Poppins-Bold.ttf new file mode 100644 index 0000000000..00559eeb29 Binary files /dev/null and b/public/fonts/Poppins-Bold.ttf differ diff --git a/public/fonts/SourceCodePro-Regular.ttf b/public/fonts/SourceCodePro-Regular.ttf new file mode 100644 index 0000000000..c37a7b78d0 Binary files /dev/null and b/public/fonts/SourceCodePro-Regular.ttf differ diff --git a/public/images/cards/cloud/back-golden.png b/public/images/cards/cloud/back-golden.png new file mode 100644 index 0000000000..68f1114c45 Binary files /dev/null and b/public/images/cards/cloud/back-golden.png differ diff --git a/public/images/cards/cloud/back-platinum.png b/public/images/cards/cloud/back-platinum.png new file mode 100644 index 0000000000..cd56d227a2 Binary files /dev/null and b/public/images/cards/cloud/back-platinum.png differ diff --git a/public/images/cards/cloud/back.png b/public/images/cards/cloud/back.png new file mode 100644 index 0000000000..bb8e5b0b29 Binary files /dev/null and b/public/images/cards/cloud/back.png differ diff --git a/public/images/cards/cloud/contributor-skew.png b/public/images/cards/cloud/contributor-skew.png new file mode 100644 index 0000000000..c80b40990e Binary files /dev/null and b/public/images/cards/cloud/contributor-skew.png differ diff --git a/public/images/cards/cloud/contributor.png b/public/images/cards/cloud/contributor.png new file mode 100644 index 0000000000..203f0f18e8 Binary files /dev/null and b/public/images/cards/cloud/contributor.png differ diff --git a/public/images/cards/cloud/employee-skew.png b/public/images/cards/cloud/employee-skew.png new file mode 100644 index 0000000000..487b50c1d1 Binary files /dev/null and b/public/images/cards/cloud/employee-skew.png differ diff --git a/public/images/cards/cloud/employee.png b/public/images/cards/cloud/employee.png new file mode 100644 index 0000000000..587c1feb84 Binary files /dev/null and b/public/images/cards/cloud/employee.png differ diff --git a/public/images/cards/cloud/front-golden.png b/public/images/cards/cloud/front-golden.png new file mode 100644 index 0000000000..6f7c251de4 Binary files /dev/null and b/public/images/cards/cloud/front-golden.png differ diff --git a/public/images/cards/cloud/front-platinum.png b/public/images/cards/cloud/front-platinum.png new file mode 100644 index 0000000000..0bea38e9f3 Binary files /dev/null and b/public/images/cards/cloud/front-platinum.png differ diff --git a/public/images/cards/cloud/front.png b/public/images/cards/cloud/front.png new file mode 100644 index 0000000000..19dcad74b7 Binary files /dev/null and b/public/images/cards/cloud/front.png differ diff --git a/public/images/cards/cloud/github-skew.png b/public/images/cards/cloud/github-skew.png new file mode 100644 index 0000000000..8f836f9bd5 Binary files /dev/null and b/public/images/cards/cloud/github-skew.png differ diff --git a/public/images/cards/cloud/github.png b/public/images/cards/cloud/github.png new file mode 100644 index 0000000000..c05c9b8f32 Binary files /dev/null and b/public/images/cards/cloud/github.png differ diff --git a/public/images/cards/cloud/hero-skew.png b/public/images/cards/cloud/hero-skew.png new file mode 100644 index 0000000000..3928f66356 Binary files /dev/null and b/public/images/cards/cloud/hero-skew.png differ diff --git a/public/images/cards/cloud/hero.png b/public/images/cards/cloud/hero.png new file mode 100644 index 0000000000..811df0f8cd Binary files /dev/null and b/public/images/cards/cloud/hero.png differ diff --git a/public/images/cards/cloud/og-background-logo.png b/public/images/cards/cloud/og-background-logo.png new file mode 100644 index 0000000000..c426c89171 Binary files /dev/null and b/public/images/cards/cloud/og-background-logo.png differ diff --git a/public/images/cards/cloud/og-background1.png b/public/images/cards/cloud/og-background1.png new file mode 100644 index 0000000000..cbd7221feb Binary files /dev/null and b/public/images/cards/cloud/og-background1.png differ diff --git a/public/images/cards/cloud/og-background2.png b/public/images/cards/cloud/og-background2.png new file mode 100644 index 0000000000..f62d46f8e3 Binary files /dev/null and b/public/images/cards/cloud/og-background2.png differ diff --git a/public/images/cards/cloud/og-background3.png b/public/images/cards/cloud/og-background3.png new file mode 100644 index 0000000000..3746cb620d Binary files /dev/null and b/public/images/cards/cloud/og-background3.png differ diff --git a/public/images/cards/cloud/og-card-golden1.png b/public/images/cards/cloud/og-card-golden1.png new file mode 100644 index 0000000000..563d64f98c Binary files /dev/null and b/public/images/cards/cloud/og-card-golden1.png differ diff --git a/public/images/cards/cloud/og-card-golden2.png b/public/images/cards/cloud/og-card-golden2.png new file mode 100644 index 0000000000..70331f1fcd Binary files /dev/null and b/public/images/cards/cloud/og-card-golden2.png differ diff --git a/public/images/cards/cloud/og-card-golden3.png b/public/images/cards/cloud/og-card-golden3.png new file mode 100644 index 0000000000..f03ee1d265 Binary files /dev/null and b/public/images/cards/cloud/og-card-golden3.png differ diff --git a/public/images/cards/cloud/og-card-platinum1.png b/public/images/cards/cloud/og-card-platinum1.png new file mode 100644 index 0000000000..736ae50afe Binary files /dev/null and b/public/images/cards/cloud/og-card-platinum1.png differ diff --git a/public/images/cards/cloud/og-card-platinum2.png b/public/images/cards/cloud/og-card-platinum2.png new file mode 100644 index 0000000000..bfc975c40c Binary files /dev/null and b/public/images/cards/cloud/og-card-platinum2.png differ diff --git a/public/images/cards/cloud/og-card-platinum3.png b/public/images/cards/cloud/og-card-platinum3.png new file mode 100644 index 0000000000..4f08ffcd07 Binary files /dev/null and b/public/images/cards/cloud/og-card-platinum3.png differ diff --git a/public/images/cards/cloud/og-card1.png b/public/images/cards/cloud/og-card1.png new file mode 100644 index 0000000000..8cf9b7fd15 Binary files /dev/null and b/public/images/cards/cloud/og-card1.png differ diff --git a/public/images/cards/cloud/og-card2.png b/public/images/cards/cloud/og-card2.png new file mode 100644 index 0000000000..eb5ad1d80b Binary files /dev/null and b/public/images/cards/cloud/og-card2.png differ diff --git a/public/images/cards/cloud/og-card3.png b/public/images/cards/cloud/og-card3.png new file mode 100644 index 0000000000..1766b24d94 Binary files /dev/null and b/public/images/cards/cloud/og-card3.png differ diff --git a/public/images/cards/cloud/og-shadow-golden.png b/public/images/cards/cloud/og-shadow-golden.png new file mode 100644 index 0000000000..1421d03a29 Binary files /dev/null and b/public/images/cards/cloud/og-shadow-golden.png differ diff --git a/public/images/cards/cloud/og-shadow-platinum.png b/public/images/cards/cloud/og-shadow-platinum.png new file mode 100644 index 0000000000..29d8404dc4 Binary files /dev/null and b/public/images/cards/cloud/og-shadow-platinum.png differ diff --git a/public/images/cards/cloud/og-shadow.png b/public/images/cards/cloud/og-shadow.png new file mode 100644 index 0000000000..ccb005c581 Binary files /dev/null and b/public/images/cards/cloud/og-shadow.png differ diff --git a/src/Appwrite/Auth/OAuth2/Github.php b/src/Appwrite/Auth/OAuth2/Github.php index c582617d67..059d163035 100644 --- a/src/Appwrite/Auth/OAuth2/Github.php +++ b/src/Appwrite/Auth/OAuth2/Github.php @@ -158,6 +158,18 @@ class Github extends OAuth2 return $user['name'] ?? ''; } + /** + * @param string $accessToken + * + * @return string + */ + public function getUserSlug(string $accessToken): string + { + $user = $this->getUser($accessToken); + + return $user['login'] ?? ''; + } + /** * @param string $accessToken *