├── .eslintignore ├── .eslintrc.js ├── .gitea └── workflows │ ├── build-and-push-auto-email-sender.yml │ ├── build-and-push-router-app.yml │ ├── build-and-push-web-app.yml │ ├── bump-version-web.yml │ ├── daily-update-s3.yml │ ├── postgres-migration.yml │ ├── push-skills-to-postgres.yml │ ├── verify-build-auto-email-sender.yml │ ├── verify-build-router-app.yml │ └── verify-build-web-app.yml ├── .github └── FUNDING.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .prettierrc.json ├── Dockerfile ├── Dockerfile.auto ├── LICENSE ├── README-en.md ├── README.md ├── apps ├── auto-email-sender │ ├── .env.example │ ├── README.md │ ├── docker-compose.yml │ ├── package.json │ ├── src │ │ ├── email-composer │ │ │ ├── createEmailHtml.tsx │ │ │ ├── getHtmlRoles.ts │ │ │ ├── index.ts │ │ │ ├── parsePreRenderMessage.ts │ │ │ └── renderRolesSection.tsx │ │ ├── email-pre-render │ │ │ ├── getTestimonialLink.ts │ │ │ ├── getUnsubscribeLink.ts │ │ │ ├── index.ts │ │ │ ├── renderFooter.tsx │ │ │ ├── renderHeader.tsx │ │ │ └── renderHeaderAndFooter.ts │ │ ├── email-sender │ │ │ ├── baseEmail.ts │ │ │ ├── config.ts │ │ │ ├── emailSender.ts │ │ │ ├── index.ts │ │ │ └── sendEmails.ts │ │ ├── index.ts │ │ ├── match_roles │ │ │ ├── .gitignore │ │ │ ├── Dockerfile │ │ │ ├── ReadMe.md │ │ │ ├── data │ │ │ │ └── .gitkeep │ │ │ ├── docker-compose.yml │ │ │ ├── models │ │ │ │ └── .gitkeep │ │ │ ├── requirements.txt │ │ │ └── src │ │ │ │ ├── etl │ │ │ │ └── extract_skills.py │ │ │ │ ├── main.py │ │ │ │ ├── predict │ │ │ │ └── rank.py │ │ │ │ └── train │ │ │ │ └── onehot.py │ │ ├── roles-assigner │ │ │ ├── getEmailProps.ts │ │ │ └── index.ts │ │ ├── roles-renderer │ │ │ ├── index.ts │ │ │ └── parseHTML.ts │ │ ├── subs-to-queue │ │ │ └── index.ts │ │ └── utils │ │ │ ├── assignRolesWorker.ts │ │ │ ├── checkMatchRolesUp.ts │ │ │ ├── delay.ts │ │ │ ├── emailPreRenderWorker.ts │ │ │ ├── generateDataMatchRoles.ts │ │ │ └── spawnPromise.ts │ └── tsconfig.json ├── data-lake-seeder │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── bun.lockb │ ├── index.ts │ └── package.json ├── db-migrator │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── dump.sh │ ├── restore.sh │ └── simple-postgres.yml ├── manual-email-sender │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── cleanRoles.ts │ │ ├── confirmation-email │ │ │ └── sendToUnconfirmed.ts │ │ ├── emailSender.ts │ │ ├── openings-email │ │ │ ├── 2023-06-28 │ │ │ │ └── openings.ts │ │ │ ├── 2023-07-05 │ │ │ │ └── openings.ts │ │ │ ├── 2023-07-12 │ │ │ │ └── openings.ts │ │ │ ├── 2023-07-19 │ │ │ │ └── openings.ts │ │ │ ├── 2023-07-26 │ │ │ │ └── openings.ts │ │ │ ├── 2023-08-02 │ │ │ │ └── openings.ts │ │ │ ├── 2023-08-09 │ │ │ │ └── openings.ts │ │ │ ├── 2023-08-16 │ │ │ │ └── openings.ts │ │ │ ├── 2023-08-23 │ │ │ │ └── openings.ts │ │ │ ├── 2023-08-30 │ │ │ │ └── openings.ts │ │ │ ├── 2023-09-06 │ │ │ │ └── openings.ts │ │ │ ├── 2023-09-27 │ │ │ │ └── openings.ts │ │ │ ├── 2023-10-18 │ │ │ │ └── openings.ts │ │ │ ├── 2023-10-25 │ │ │ │ └── openings.ts │ │ │ ├── 2023-11-15 │ │ │ │ └── openings.ts │ │ │ ├── 2023-11-8 │ │ │ │ └── openings.ts │ │ │ ├── Emails.ts │ │ │ ├── Openings.ts │ │ │ └── sendEmails.ts │ │ ├── utils.ts │ │ └── utils │ │ │ ├── chunkArray.ts │ │ │ ├── confirmedSubscribers.ts │ │ │ ├── createUnsubscribeLink.ts │ │ │ ├── emailHtml.ts │ │ │ ├── generateSubjectEmail.ts │ │ │ ├── getEmailsSent.ts │ │ │ ├── getOpeningsByDate.ts │ │ │ └── loadSubscribersHtml.ts │ └── tsconfig.json ├── router │ ├── .env.example │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── bun.lockb │ ├── docker-compose.yml │ ├── index.ts │ ├── load-test │ │ ├── stress_test.sh │ │ └── targets.txt │ ├── package.json │ ├── targets.txt │ └── tsconfig.json └── web │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── app │ ├── (development) │ │ ├── api │ │ │ └── email-preview │ │ │ │ └── send │ │ │ │ └── route.tsx │ │ └── email-preview │ │ │ ├── FakeEmail.tsx │ │ │ ├── Form.tsx │ │ │ ├── PreviewNextWeekEmail.tsx │ │ │ ├── getFakeRoles.ts │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── (resend_webhooks) │ │ └── api │ │ │ └── webhooks │ │ │ └── bounce │ │ │ └── route.ts │ ├── (roles) │ │ ├── api │ │ │ └── vagas │ │ │ │ ├── cron │ │ │ │ └── route.ts │ │ │ │ └── publique │ │ │ │ └── route.ts │ │ ├── formSchema.ts │ │ ├── vaga │ │ │ └── [id] │ │ │ │ ├── RolePage.tsx │ │ │ │ ├── opengraph-image.tsx │ │ │ │ └── page.tsx │ │ └── vagas │ │ │ ├── RolesPage.tsx │ │ │ ├── action.ts │ │ │ ├── page.tsx │ │ │ └── publique │ │ │ ├── RolePreview.tsx │ │ │ ├── RolePreviewSection.tsx │ │ │ ├── RoleTopic.tsx │ │ │ └── page.tsx │ ├── api │ │ ├── getDecryptedId.ts │ │ ├── getId.ts │ │ ├── health │ │ │ └── route.ts │ │ ├── logError.ts │ │ ├── resetJobsCache │ │ │ ├── README.md │ │ │ └── route.ts │ │ ├── roles │ │ │ └── [id] │ │ │ │ └── route.ts │ │ ├── subscribers │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ ├── confirm │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── db.ts │ │ │ ├── optout │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── resend │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ └── version │ │ │ └── route.ts │ ├── apoiadores │ │ └── page.tsx │ ├── blog │ │ ├── entrevistas │ │ │ ├── ArticleHeader.tsx │ │ │ ├── ArticleReader.tsx │ │ │ ├── article-reader.module.css │ │ │ ├── article.module.css │ │ │ ├── layout.tsx │ │ │ ├── samuel-dos-santos-alves │ │ │ │ └── page.mdx │ │ │ └── utils.ts │ │ └── page.tsx │ ├── components │ │ ├── AutoComplete.tsx │ │ ├── ComboOption.tsx │ │ ├── CustomFormField.tsx │ │ ├── DateInput.tsx │ │ ├── EmailForm.tsx │ │ ├── FilteredSelections.tsx │ │ ├── FirstJobPicker.tsx │ │ ├── FormRadioGroup.tsx │ │ ├── Header.tsx │ │ ├── InputWithUseTyped.tsx │ │ ├── ListOption.ts │ │ ├── PTBRRolePage.tsx │ │ ├── SanitizedHTML.tsx │ │ ├── SelectInput.tsx │ │ ├── SkillSuggestionDialog.tsx │ │ ├── SkillSuggestionField.tsx │ │ ├── USRolePage.tsx │ │ ├── hooks │ │ │ └── useTyped.js │ │ └── ui │ │ │ ├── ButtonAnchor.tsx │ │ │ ├── CompanyCard.tsx │ │ │ ├── JobCard.tsx │ │ │ ├── ScrollToTopButton.tsx │ │ │ ├── avatar.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── form.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── loadingOverlay.tsx │ │ │ ├── popover.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ └── toaster.tsx │ ├── confirma-email │ │ └── page.tsx │ ├── constants.ts │ ├── contexts │ │ └── LoadingContext.tsx │ ├── enums │ │ └── uiRoutes.ts │ ├── favicon.ico │ ├── global-error.tsx │ ├── global.css │ ├── hooks │ │ └── use-toast.ts │ ├── landing-page │ │ ├── ContributeDialog.tsx │ │ ├── FAQ.test.tsx │ │ ├── FAQ.tsx │ │ ├── FAQCard.test.tsx │ │ ├── FAQCard.tsx │ │ ├── FocusBanner.tsx │ │ ├── Header.tsx │ │ ├── Hero.tsx │ │ ├── HowItWorks.tsx │ │ ├── LoginPreferences.tsx │ │ ├── PartnerCompanies.tsx │ │ ├── SubscriberForm.tsx │ │ ├── SubscribersCount.tsx │ │ ├── Values.tsx │ │ └── landingPageRoutes.ts │ ├── layout.tsx │ ├── not-found.tsx │ ├── opengraph-image.tsx │ ├── page.tsx │ ├── providers.tsx │ ├── subscribers │ │ ├── optout │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── profile │ │ │ ├── [id] │ │ │ │ ├── error.tsx │ │ │ │ └── page.tsx │ │ │ ├── components │ │ │ │ ├── FormCheckBox.tsx │ │ │ │ ├── FormPage.tsx │ │ │ │ ├── PersonalInfoForm.tsx │ │ │ │ ├── SkillsField.tsx │ │ │ │ ├── SubscriberForm.tsx │ │ │ │ └── sidebar-nav.tsx │ │ │ ├── getSubscriber.ts │ │ │ ├── getSubscriberTopics.ts │ │ │ ├── getTopics.ts │ │ │ ├── profileSchema.ts │ │ │ └── subscription │ │ │ │ ├── FormCheckBox.tsx │ │ │ │ ├── ReceiveEmailConfig.tsx │ │ │ │ ├── SubscriptionForm.tsx │ │ │ │ └── skills.ts │ │ ├── subscriptionTopics.ts │ │ └── testemunho │ │ │ └── [id] │ │ │ ├── Field.tsx │ │ │ ├── Fields.ts │ │ │ ├── SkillsField.tsx │ │ │ ├── TestimonialForm.tsx │ │ │ ├── ToastContainer.tsx │ │ │ ├── error.tsx │ │ │ ├── page.tsx │ │ │ └── saveTestimonial.ts │ └── utils │ │ ├── LoginPreferencesActions.ts │ │ ├── confetti.ts │ │ ├── getRedisClient.ts │ │ ├── getRoles.ts │ │ ├── roleUtils.tsx │ │ ├── shouldRedirect.ts │ │ ├── tracker.ts │ │ ├── updateSearchParams.tsx │ │ ├── utils.ts │ │ └── vercel.ts │ ├── components.json │ ├── global │ ├── EnglishLevel.ts │ └── utils.ts │ ├── mdx-components.tsx │ ├── middleware.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── blog │ │ └── entrevistas │ │ │ ├── samuel-dos-santos-alves.jpeg │ │ │ └── samuel-dos-santos-alves.mp3 │ ├── favicons │ │ ├── android-chrome-128x128.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.svg │ │ ├── favicon-32x32.svg │ │ ├── favicon-60x60.png │ │ └── site.webmanifest │ ├── flaro-assets │ │ ├── .gitkeep │ │ ├── images │ │ │ ├── applications │ │ │ │ ├── app-store-round.png │ │ │ │ ├── app-store.png │ │ │ │ ├── bg.png │ │ │ │ ├── blur.svg │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ ├── gradient4.svg │ │ │ │ ├── iphone.png │ │ │ │ ├── iphone2.png │ │ │ │ ├── iphones.png │ │ │ │ ├── manage.png │ │ │ │ ├── play-store-round.png │ │ │ │ ├── play-store.png │ │ │ │ └── scene.png │ │ │ ├── blog │ │ │ │ ├── bg.jpeg │ │ │ │ ├── blog-avatar.png │ │ │ │ ├── blog-avatar2.png │ │ │ │ ├── blog-avatar3.png │ │ │ │ ├── blog-avatar4.png │ │ │ │ ├── blog-wide.png │ │ │ │ ├── blog-wide2.png │ │ │ │ ├── blog-wide3.png │ │ │ │ ├── blog.png │ │ │ │ ├── blog2.png │ │ │ │ ├── blog3.png │ │ │ │ └── blog4.png │ │ │ ├── career │ │ │ │ ├── gradient.svg │ │ │ │ └── gradient2.svg │ │ │ ├── contact │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── man.png │ │ │ │ └── people.jpeg │ │ │ ├── cta │ │ │ │ ├── avatar-circle.png │ │ │ │ ├── avatar-circle2.png │ │ │ │ ├── avatar-circle3.png │ │ │ │ ├── bg.jpeg │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ ├── gradient4.svg │ │ │ │ ├── gradient5.svg │ │ │ │ ├── man-play.png │ │ │ │ ├── man.png │ │ │ │ ├── people.png │ │ │ │ └── woman-play2.png │ │ │ ├── faqs │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ └── woman.png │ │ │ ├── features │ │ │ │ ├── card.png │ │ │ │ ├── chat.svg │ │ │ │ ├── data.png │ │ │ │ ├── elipse.svg │ │ │ │ ├── feature.png │ │ │ │ ├── feature2.png │ │ │ │ ├── layers.svg │ │ │ │ ├── money-coins.png │ │ │ │ ├── peoples.png │ │ │ │ ├── product.png │ │ │ │ ├── product2.png │ │ │ │ ├── product3.png │ │ │ │ ├── replace.svg │ │ │ │ ├── reports.png │ │ │ │ ├── testimonail.png │ │ │ │ ├── upgrade.png │ │ │ │ ├── users.png │ │ │ │ ├── video.png │ │ │ │ └── woman.jpg │ │ │ ├── footers │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ └── gradient4.svg │ │ │ ├── headers │ │ │ │ ├── card.png │ │ │ │ ├── dashboard.png │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ ├── gradient4.svg │ │ │ │ ├── header.png │ │ │ │ ├── people.png │ │ │ │ ├── people2.png │ │ │ │ ├── people3.png │ │ │ │ ├── profiles.png │ │ │ │ ├── video.png │ │ │ │ └── woman.jpeg │ │ │ ├── how-it-works │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── iphone.png │ │ │ │ ├── line.svg │ │ │ │ ├── line2.svg │ │ │ │ ├── line3.svg │ │ │ │ ├── line4.svg │ │ │ │ └── steps.png │ │ │ ├── http-codes │ │ │ │ ├── 404.png │ │ │ │ ├── error-code.png │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ ├── illustration.png │ │ │ │ └── illustration2.png │ │ │ ├── logos │ │ │ │ ├── background-photo-blur.png │ │ │ │ ├── bg.png │ │ │ │ └── gradient3.svg │ │ │ ├── newsletter │ │ │ │ ├── bg.jpeg │ │ │ │ └── gradient.svg │ │ │ ├── numbers │ │ │ │ ├── bg.png │ │ │ │ ├── gradient.svg │ │ │ │ └── gradient2.svg │ │ │ ├── pricing │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ ├── gradient4.svg │ │ │ │ ├── gradient5.svg │ │ │ │ ├── gradient6.svg │ │ │ │ └── popular.png │ │ │ ├── sign-in │ │ │ │ ├── cards.png │ │ │ │ └── gradient.svg │ │ │ ├── sign-up │ │ │ │ ├── gradient.svg │ │ │ │ └── gradient2.svg │ │ │ ├── team │ │ │ │ ├── bg-square.png │ │ │ │ ├── bg-square2.png │ │ │ │ ├── bg-square3.png │ │ │ │ ├── bg.png │ │ │ │ ├── bg2.png │ │ │ │ ├── bg3.png │ │ │ │ ├── gradient.svg │ │ │ │ ├── people.png │ │ │ │ ├── people2.png │ │ │ │ ├── people3.png │ │ │ │ ├── team-circle.png │ │ │ │ ├── team-circle2.png │ │ │ │ ├── team-circle3.png │ │ │ │ ├── team-circle4.png │ │ │ │ ├── team-circle5.png │ │ │ │ ├── team-circle6.png │ │ │ │ ├── team.png │ │ │ │ ├── team2.png │ │ │ │ ├── team3.png │ │ │ │ └── team4.png │ │ │ └── testimonials │ │ │ │ ├── avatar-social.png │ │ │ │ ├── avatar-social2.png │ │ │ │ ├── avatar.png │ │ │ │ ├── avatar10.png │ │ │ │ ├── avatar11.png │ │ │ │ ├── avatar12.png │ │ │ │ ├── avatar13.png │ │ │ │ ├── avatar14.png │ │ │ │ ├── avatar15.png │ │ │ │ ├── avatar2.png │ │ │ │ ├── avatar3.png │ │ │ │ ├── avatar4.png │ │ │ │ ├── avatar5.png │ │ │ │ ├── avatar6.png │ │ │ │ ├── avatar7.png │ │ │ │ ├── avatar8.png │ │ │ │ ├── avatar9.png │ │ │ │ ├── contact.png │ │ │ │ ├── customer.png │ │ │ │ ├── customer2.png │ │ │ │ ├── gradient.svg │ │ │ │ ├── gradient2.svg │ │ │ │ ├── gradient3.svg │ │ │ │ └── gradient4.svg │ │ └── logos │ │ │ ├── brands │ │ │ ├── BG.png │ │ │ ├── amex.svg │ │ │ ├── apple-pay.svg │ │ │ ├── brand-capterra.png │ │ │ ├── brand-large.png │ │ │ ├── brand-large2.png │ │ │ ├── brand-large3.png │ │ │ ├── brand-large4.png │ │ │ ├── brand-large5.png │ │ │ ├── brand-large6.png │ │ │ ├── brand-light.png │ │ │ ├── brand-light2.png │ │ │ ├── brand-light3.png │ │ │ ├── brand-medium.png │ │ │ ├── brand-medium2.png │ │ │ ├── brand-medium3.png │ │ │ ├── brand-medium4.png │ │ │ ├── brand-medium5.png │ │ │ ├── brand-medium6.png │ │ │ ├── brand-trustpilot.png │ │ │ ├── brand-xl-2.png │ │ │ ├── brand-xl-3.png │ │ │ ├── brand-xl-4.png │ │ │ ├── brand-xl-5.png │ │ │ ├── brand-xl.png │ │ │ ├── brand.png │ │ │ ├── brand2.png │ │ │ ├── brand3.png │ │ │ ├── fb.svg │ │ │ ├── google.svg │ │ │ ├── logo-square-white.png │ │ │ ├── logo-square-white2.png │ │ │ ├── logo-square-white3.png │ │ │ ├── logo-square-white4.png │ │ │ ├── logo-square-white5.png │ │ │ ├── logo-square-white6.png │ │ │ ├── logo-xxl-light.png │ │ │ ├── logo-xxl-light2.png │ │ │ ├── logo-xxl-light3.png │ │ │ ├── logo-xxl-light4.png │ │ │ ├── logo-xxl-light5.png │ │ │ ├── logo-xxl-light6.png │ │ │ ├── logo-xxl-light7.png │ │ │ ├── logo-xxl-light8.png │ │ │ ├── logo-xxl.png │ │ │ ├── logo-xxl2.png │ │ │ ├── logo-xxl3.png │ │ │ ├── logo-xxl4.png │ │ │ ├── logo-xxl5.png │ │ │ ├── logo-xxl6.png │ │ │ ├── logo-xxl7.png │ │ │ ├── logo-xxl8.png │ │ │ ├── mastercard.svg │ │ │ ├── paypal.svg │ │ │ ├── stripe.svg │ │ │ └── visa.svg │ │ │ ├── flaro-logo-black-xl.svg │ │ │ ├── flaro-logo-black.svg │ │ │ └── flaro-logo-light.svg │ ├── fonts │ │ ├── LICENSE.txt │ │ └── Roboto.ttf │ ├── images │ │ ├── HO-brasil.jpg │ │ ├── HO-brasil.png │ │ ├── HO-brasil.webp │ │ ├── UKflag.png │ │ ├── USAFlag.png │ │ ├── brazilianFlag.png │ │ ├── casa.png │ │ ├── close.svg │ │ ├── companies │ │ │ ├── crafta.webp │ │ │ ├── frontin.svg │ │ │ ├── frontin.webp │ │ │ ├── fros.webp │ │ │ ├── husky-with-bg.webp │ │ │ ├── husky.webp │ │ │ ├── impulso.png │ │ │ ├── meteor.webp │ │ │ ├── rock_n_code.webp │ │ │ ├── slikdesk.webp │ │ │ └── strider.svg │ │ ├── errorImage.png │ │ ├── footer-image.webp │ │ ├── form.gif │ │ ├── hearth.png │ │ ├── logo.png │ │ ├── logo.svg │ │ └── partnership.png │ ├── robots.txt │ └── sitemap.xml │ ├── supabase │ ├── .gitignore │ ├── config.toml │ └── seed.sql │ ├── tailwind.config.js │ ├── tests │ ├── profileSchema.spec.ts │ └── setup.js │ ├── tsconfig.json │ ├── tsconfig.tsbuildinfo │ ├── vercel.json │ └── vitest.config.ts ├── contributing.md ├── docker-compose.yml ├── manifesto.md ├── package.json ├── packages ├── analytics │ ├── package.json │ └── src │ │ ├── events.ts │ │ ├── index.ts │ │ └── tracker.ts ├── db │ ├── README.md │ ├── README.pt.md │ ├── db.code-workspace │ ├── index.ts │ ├── injectSkills.ts │ ├── package.json │ ├── pnpm-lock.yaml │ ├── removeDuplicatedSkills.ts │ ├── seed.ts │ ├── src │ │ ├── mongodb │ │ │ └── domains │ │ │ │ └── roles │ │ │ │ └── saveSubscriberRoles.ts │ │ ├── postgres │ │ │ ├── client.ts │ │ │ └── getPostgresClient.ts │ │ ├── supabase │ │ │ ├── domains │ │ │ │ ├── roles │ │ │ │ │ ├── getRoles.spec.ts │ │ │ │ │ ├── getRoles.ts │ │ │ │ │ ├── getSubscriberRoles.ts │ │ │ │ │ └── saveRoles.ts │ │ │ │ └── subscribers │ │ │ │ │ ├── getAllConfirmedSubscribersPaginated.ts │ │ │ │ │ └── getConfirmedSubscribersRowsBlock.ts │ │ │ ├── getSupabaseClient.ts │ │ │ ├── type.ts │ │ │ └── utilityTypes.ts │ │ └── types.ts │ └── supabase │ │ ├── .gitignore │ │ ├── config.toml │ │ ├── migrations │ │ ├── 20230622201209_database_init.sql │ │ ├── 20230622232812_change_name_to_english.sql │ │ ├── 20230622233914_change_table_name_to_subscribers.sql │ │ ├── 20230623010108_email_unique_field.sql │ │ ├── 20230626123921_prod_migration.sql │ │ ├── 20230626132801_change_case_for_fields.sql │ │ ├── 20230627193949_add_skills_to_roles_model.sql │ │ ├── 20230627211920_add_opt_out_field_on_subscribers_model.sql │ │ ├── 20230710010737_update_sent_roles.sql │ │ ├── 20230710015614_remove_not_null_constraint_for_name.sql │ │ ├── 20230710015723_remove_not_null_constraint_for_updated_at.sql │ │ ├── 20230710015752_remove_not_null_constraint_for_started_working_at.sql │ │ ├── 20230710171556_create_started_working_at_column.sql │ │ ├── 20230710190336_create_description_topics_table.sql │ │ ├── 20230711133646_grant_access_on_supabase.sql │ │ ├── 20230711140941_explicit_relationship.sql │ │ ├── 20230714131130_create_ready_column.sql │ │ ├── 20230714134532_add_url_column_on_roles.sql │ │ ├── 20230724171559_add_minimun_years.sql │ │ ├── 20230724180934_add_minimun_years_and_role_language_enum.sql │ │ ├── 20230724181446_fix_typo.sql │ │ ├── 20230724201409_intermediary_table.sql │ │ ├── 20230724203839_remove_unnecessary_column.sql │ │ ├── 20230809182857_roles_skills_view.sql │ │ ├── 20230809183616_add_roles_skilss_view.sql │ │ ├── 20230823143835_create_skills_id_column.sql │ │ ├── 20230904133450_remove_company_info_of_roles_skills_view.sql │ │ ├── 20230904133944_remove_company_relationship.sql │ │ ├── 20230904134151_drop_company_table.sql │ │ ├── 20230904134723_add_company_name_column.sql │ │ ├── 20230904144205_drop_role_skills_table.sql │ │ ├── 20230904144454_use_gen_random_uuid_for_generate_id.sql │ │ ├── 20230904181419_add companyName on rolesSkillsView.sql │ │ ├── 20230904194709_filter_roles-skills-view_by_ready_column.sql │ │ ├── 20230905192657_add_subscriberTopic_relationship_with_Roles.sql │ │ ├── 20230905200804_add_topicId_column_on_rolesSkillsView.sql │ │ ├── 20230911143916_create_roles_subscribers_view.sql │ │ ├── 20230911144528_remove_skillsId_column_of_subscribers_view.sql │ │ ├── 20231024160400_add-normalized-column-on-skills.sql │ │ ├── 20231108162536_create_skills_suggestions_table.sql │ │ ├── 20231109185122_create_send_best_openings_column_on_subscribers.sql │ │ ├── 20231207174007_create_rtestimonial_table.sql │ │ ├── 20231207184642_alter_testimonial_table.sql │ │ ├── 20240112125133_delete_unused_tables.sql │ │ ├── 20240112134713_delete_sent_roles_table.sql │ │ ├── 20240125135043_create_roles_recommendations_table.sql │ │ ├── 20240125143826_rename_table.sql │ │ ├── 20240125144308_create_relashionship.sql │ │ ├── 20240125170725_fix_roles_rcommendation_id_and_createe_at.sql │ │ ├── 20240125174453_recreate_roles_recommendation.sql │ │ ├── 20240126131604_add_approved_column.sql │ │ ├── 20240126134654_create_trigger_for_rolesRecommendation.sql │ │ ├── 20240126134933_use_enum_on_language_column.sql │ │ ├── 20241008172006_add_roles_related_views.sql │ │ └── 20241008190105_remote_schema.sql │ │ ├── seed.sql │ │ └── views │ │ ├── create_view_countrie_in_roles.sql │ │ └── create_view_skills_in_roles.sql ├── eslint-config-custom │ ├── .eslintrc.js │ ├── index.js │ └── package.json └── shared │ ├── .eslintrc.js │ ├── .gitignore │ ├── emails │ └── ProfileEmail.tsx │ ├── encrypt.ts │ ├── package.json │ ├── sendProfileEmaillForAllSubscribers.ts │ ├── src │ ├── email │ │ ├── confirm-email │ │ │ ├── ConfirmationEmail.tsx │ │ │ ├── htmlTemplate.tsx │ │ │ ├── index.ts │ │ │ └── sendConfirmationEmail.ts │ │ ├── index.ts │ │ ├── openings-email │ │ │ ├── striderOpening.ts │ │ │ └── style.ts │ │ └── sendProfileEmail.tsx │ ├── enums │ │ ├── apiRoutes.ts │ │ ├── emailQueues.ts │ │ ├── entities.ts │ │ ├── index.ts │ │ ├── mongo.ts │ │ ├── redis.ts │ │ ├── supabaseCodes.ts │ │ ├── topics.ts │ │ ├── uiRoutes.ts │ │ └── views.ts │ ├── index.ts │ ├── infos │ │ ├── skills.ts │ │ ├── skillsMatcher.spec.ts │ │ └── updateSkillsOnDatabase.ts │ ├── mongo │ │ ├── getMongoConnection.ts │ │ └── getMongoCoonnection.ts │ ├── observability │ │ ├── console.ts │ │ ├── timeSpan.ts │ │ └── withExecutionTimeLogging.ts │ ├── queue │ │ ├── connectToQueue.ts │ │ ├── createRabbitMqChannel.ts │ │ ├── createRabbitMqConnection.ts │ │ ├── index.ts │ │ └── sendToQueue.ts │ ├── security │ │ ├── decrypts.ts │ │ ├── encrypt.ts │ │ └── index.ts │ ├── services │ │ ├── createProfileFormLink.ts │ │ ├── getRowsBlock.ts │ │ └── trackedRoleURL.ts │ └── test │ │ ├── factories │ │ └── roleFactory.ts │ │ ├── helpers │ │ ├── mockAsyncGeneratorFunction.ts │ │ ├── mocks.ts │ │ ├── rabbitMQ.ts │ │ └── stubs.ts │ │ └── mocks │ │ └── mongodb.ts │ ├── tsconfig.json │ ├── ui │ └── email │ │ ├── EmailWrapper.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ ├── RoleCard.tsx │ │ └── RoleHTML.ts │ └── vitest.config.ts ├── prettier.config.js ├── scripts └── build-and-push-web.sh ├── server ├── .gitignore ├── analytics.yml ├── crawlers.yml ├── docker-compose.yml ├── grafana.yml ├── nginx.conf ├── nginx.yml ├── prometheus │ └── prometheus.yml ├── redis.yml └── web.yml ├── setup.mjs ├── turbo.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.d.ts 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "next", 4 | "prettier", 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:@typescript-eslint/recommended", 8 | "next/core-web-vitals", 9 | "prettier" 10 | ], 11 | rules: { 12 | "@next/next/no-html-link-for-pages": "off", 13 | "@next/next/no-img-element": "off", 14 | "react/react-in-jsx-scope": 0, 15 | "no-console": "warn", 16 | "react-hooks/exhaustive-deps": "off" 17 | }, 18 | parserOptions: { 19 | babelOptions: { 20 | presets: [require.resolve("next/babel")], 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitea/workflows/daily-update-s3.yml: -------------------------------------------------------------------------------- 1 | # name: Data-lake filler 2 | # 3 | # on: 4 | # schedule: 5 | # - cron: '0 20 * * *' # Runs at 20:00 UTC every day 6 | # workflow_dispatch: # Allows manual triggering 7 | # 8 | # jobs: 9 | # export-roles: 10 | # runs-on: ubuntu-latest 11 | # steps: 12 | # - uses: actions/checkout@v3 13 | # 14 | # - name: Setup Bun 15 | # uses: oven-sh/setup-bun@v1 16 | # with: 17 | # bun-version: latest 18 | # 19 | # - name: Install dependencies and run script 20 | # env: 21 | # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 22 | # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 23 | # SUPABASE_URL: ${{ secrets.SUPABASE_URL }} 24 | # SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} 25 | # run: | 26 | # cd apps/data-lake-seeder 27 | # bun install 28 | # bun run index.ts -------------------------------------------------------------------------------- /.gitea/workflows/postgres-migration.yml: -------------------------------------------------------------------------------- 1 | name: Run PostgreSQL migrations 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'packages/db/migrations/**/*' 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | POSTGRES_URL: ${{ secrets.POSTGRES_URL }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: 20.5.1 24 | 25 | - name: Install dependencies 26 | working-directory: ./packages/db 27 | run: yarn install 28 | 29 | - name: Run migrations 30 | working-directory: ./packages/db 31 | run: yarn migrate -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ocodista 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | # As we are using YARN, we need to ensure that no package-lock will ever be pushed 6 | # Pushing it to the main repo will make nextjs build to fail 7 | package-lock.json 8 | *package-lock.json 9 | **/*package-lock.json 10 | **/.clinic 11 | .pnp 12 | .pnp.js 13 | 14 | # testing 15 | coverage 16 | 17 | # next.js 18 | .next/ 19 | out/ 20 | build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # turbo 39 | .turbo 40 | 41 | # vercel 42 | .vercel 43 | .secrets 44 | **/*.secrets 45 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true, 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /apps/auto-email-sender/.env.example: -------------------------------------------------------------------------------- 1 | URL_PREFIX= 2 | SECRET_KEY= 3 | CRYPT_SECRET= 4 | RESEND_KEY= 5 | OWNER_EMAIL= 6 | 7 | SUPABASE_URL= 8 | SUPABASE_SERVICE_ROLE= 9 | 10 | DEBUG= 11 | 12 | MONGO_ADDRESS= 13 | MONGO_PASSWORD= 14 | MONGO_USERNAME= 15 | 16 | RABBITMQ_ADDRESS= 17 | RABBITMQ_DEFAULT_USER= 18 | RABBITMQ_DEFAULT_PASS= 19 | REGISTRY_URL= 20 | -------------------------------------------------------------------------------- /apps/auto-email-sender/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | rabbitmq: 3 | image: rabbitmq:3-management 4 | env_file: .env 5 | ports: 6 | - 5672:5672 7 | - 15672:15672 8 | expose: 9 | - 5672 10 | - 15672 11 | 12 | mongo: 13 | image: mongo:latest 14 | env_file: .env 15 | ports: 16 | - '27017:27017' 17 | volumes: 18 | - mongo-data:/data/db 19 | 20 | volumes: 21 | mongo-data: 22 | driver: local 23 | -------------------------------------------------------------------------------- /apps/auto-email-sender/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-email-sender", 3 | "version": "1.0.0", 4 | "main": "src/index.ts", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "ts-node ./src/index.ts" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "@types/async": "^3.2.24", 14 | "dotenv": "^16.4.5", 15 | "ts-node": "^10.9.2", 16 | "typescript": "^5.5.2", 17 | "zod": "^3.0.0" 18 | }, 19 | "dependencies": { 20 | "@json2csv/plainjs": "^7.0.6", 21 | "async": "^3.2.5", 22 | "json2csv": "^6.0.0-alpha.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/auto-email-sender/src/email-composer/createEmailHtml.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/render' 2 | import React from 'react' 3 | import { EmailWrapper } from 'shared/ui/email/EmailWrapper' 4 | 5 | enum HtmlKeys { 6 | contentHtml = '##CONTENT_HTML', 7 | } 8 | 9 | export const renderEmailWrapperHtml = () => { 10 | const renderedEmailHtml = render( 11 | {HtmlKeys.contentHtml} 12 | ) 13 | return renderedEmailHtml 14 | } 15 | export async function createEmailHtml( 16 | contentHTML: string, 17 | renderedEmailWrapperHtml: string 18 | ) { 19 | return renderedEmailWrapperHtml.replace(HtmlKeys.contentHtml, contentHTML) 20 | } 21 | -------------------------------------------------------------------------------- /apps/auto-email-sender/src/email-composer/getHtmlRoles.ts: -------------------------------------------------------------------------------- 1 | import { Collection, Document } from 'mongodb' 2 | import { Topics } from 'shared' 3 | import { RenderRolesSection } from './renderRolesSection' 4 | 5 | export type RolesRendererCollection = { 6 | id: string 7 | content: string 8 | topic: Topics 9 | } 10 | 11 | export const getHtmlRoles = async ( 12 | rolesId: string[], 13 | mongoCollection: Collection, 14 | memoizedRoles: Map, 15 | renderedRolesHtml: string 16 | ): Promise => { 17 | const roles: RolesRendererCollection[] = [] 18 | let role 19 | for (const roleId of rolesId) { 20 | role = memoizedRoles.get(roleId) 21 | if (!role) { 22 | role = await mongoCollection.findOne({ 23 | id: roleId, 24 | }) 25 | memoizedRoles.set(roleId, role) 26 | } 27 | if (role) { 28 | roles.push(role) 29 | } 30 | } 31 | 32 | return RenderRolesSection( 33 | { 34 | roles, 35 | }, 36 | renderedRolesHtml 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /apps/auto-email-sender/src/email-pre-render/getTestimonialLink.ts: -------------------------------------------------------------------------------- 1 | import { UiRoutes, encrypt } from 'shared' 2 | export const SECRET_KEY_ERROR_MESSAGE = 'SECRET_KEY env is not settled' 3 | 4 | export function getTestimonialLink(url: string, id: string) { 5 | const secretKey = process.env['SECRET_KEY'] || '' 6 | if (!secretKey) throw new Error(SECRET_KEY_ERROR_MESSAGE) 7 | const encryptedId = encrypt(secretKey, id) 8 | 9 | return `${url}${UiRoutes.Testimonial}/${encryptedId}` 10 | } 11 | -------------------------------------------------------------------------------- /apps/auto-email-sender/src/email-pre-render/getUnsubscribeLink.ts: -------------------------------------------------------------------------------- 1 | import { UiRoutes, encrypt } from 'shared' 2 | export const SECRET_KEY_ERROR_MESSAGE = 'SECRET_KEY env is not settled' 3 | 4 | export function getUnsubscribeLink(url: string, id: string) { 5 | const secretKey = process.env['SECRET_KEY'] || '' 6 | if (!secretKey) throw new Error(SECRET_KEY_ERROR_MESSAGE) 7 | const encryptedId = encrypt(secretKey, id) 8 | 9 | return `${url}${UiRoutes.OptOut}/${encryptedId}` 10 | } 11 | -------------------------------------------------------------------------------- /apps/auto-email-sender/src/email-pre-render/renderFooter.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/render' 2 | import React from 'react' 3 | import { Footer, HtmlFooterKeys } from 'shared/ui/email/Footer' 4 | import { getUnsubscribeLink } from './getUnsubscribeLink' 5 | 6 | export const renderFooterHTML = () => { 7 | const renderedHtml = render(