├── achievements ├── .gitignore ├── src │ ├── dev-tools │ │ ├── utils.ts │ │ └── mocks.ts │ ├── achievement.abstract.ts │ ├── __snapshots__ │ │ ├── use-github-bot.achievement.spec.ts.snap │ │ ├── member.achievement.spec.ts.snap │ │ ├── cutting-edges.achievement.spec.ts.snap │ │ ├── optimus-prime.achievement.spec.ts.snap │ │ ├── label-baby-junior.achievement.spec.ts.snap │ │ ├── the-godfather-consigliere.achievement.spec.ts.snap │ │ ├── mr-miyagi.achievement.spec.ts.snap │ │ ├── never-go-full-retard.achievement.spec.ts.snap │ │ ├── double-review.achievement.spec.ts.snap │ │ └── dont-yell-at-me.achievement.spec.ts.snap │ ├── label-baby-junior.achievement.ts │ ├── cutting-edges.achievement.ts │ ├── member.achievement.spec.ts │ ├── use-github-bot.achievement.ts │ ├── member.achievement.ts │ ├── the-godfather-consigliere.achievement.ts │ ├── index.ts │ ├── double-review.achievement.ts │ ├── use-github-bot.achievement.spec.ts │ ├── bi-winning.achievement.ts │ └── reaction-on-every-comment.achievement.ts └── tsconfig.json ├── client ├── src │ ├── assets │ │ ├── .gitkeep │ │ ├── favicon.ico │ │ ├── all-new-rpg.png │ │ ├── layers │ │ │ ├── sky.png │ │ │ ├── trees.png │ │ │ ├── mountains.png │ │ │ ├── far-clouds.png │ │ │ ├── near-clouds.png │ │ │ └── far-mountains.png │ │ ├── logo-more-details.png │ │ ├── private.svg │ │ └── public.svg │ ├── app │ │ ├── pages │ │ │ ├── organizations │ │ │ │ ├── organizations.component.scss │ │ │ │ ├── organizations.component.html │ │ │ │ ├── organizations-all │ │ │ │ │ ├── organizations-all.component.scss │ │ │ │ │ └── organizations-all.component.spec.ts │ │ │ │ ├── organizations.component.ts │ │ │ │ ├── org-by-name-resolver.ts │ │ │ │ ├── organizations.component.spec.ts │ │ │ │ ├── organization-profile │ │ │ │ │ ├── organization-profile.component.spec.ts │ │ │ │ │ └── organization-profile.component.html │ │ │ │ └── organizations.routes.ts │ │ │ ├── pull-requests │ │ │ │ ├── pull-requests.component.scss │ │ │ │ ├── pull-request-profile │ │ │ │ │ ├── pull-request-profile.component.scss │ │ │ │ │ ├── pull-request-profile.component.html │ │ │ │ │ ├── pull-request-profile.component.ts │ │ │ │ │ └── pull-request-profile.component.spec.ts │ │ │ │ ├── pull-request-all │ │ │ │ │ ├── pull-request-all.component.scss │ │ │ │ │ └── pull-request-all.component.spec.ts │ │ │ │ ├── pull-requests.component.html │ │ │ │ ├── pull-requests.component.ts │ │ │ │ ├── pr-by-id-resolver.ts │ │ │ │ ├── pull-requests.component.spec.ts │ │ │ │ └── pull-requests.routes.ts │ │ │ ├── repositories │ │ │ │ ├── repositories.component.scss │ │ │ │ ├── repositories.component.html │ │ │ │ ├── repositories-all │ │ │ │ │ ├── repositories-all.component.scss │ │ │ │ │ └── repositories-all.component.spec.ts │ │ │ │ ├── repositories.component.ts │ │ │ │ ├── repo-by-fullname-resolver.ts │ │ │ │ ├── repositories.component.spec.ts │ │ │ │ ├── repository-profile │ │ │ │ │ ├── repository-profile.component.spec.ts │ │ │ │ │ └── repository-profile.component.html │ │ │ │ └── repositories.routes.ts │ │ │ ├── user-profile │ │ │ │ ├── profile-settings │ │ │ │ │ ├── profile-settings.component.scss │ │ │ │ │ ├── profile-settings.component.spec.ts │ │ │ │ │ └── profile-settings.component.html │ │ │ │ ├── profile-integrations │ │ │ │ │ ├── profile-integrations.component.scss │ │ │ │ │ └── profile-integrations.component.spec.ts │ │ │ │ ├── profile-overview │ │ │ │ │ ├── profile-overview.component.scss │ │ │ │ │ ├── profile-overview.component.html │ │ │ │ │ ├── profile-overview.component.ts │ │ │ │ │ └── profile-overview.component.spec.ts │ │ │ │ ├── user-profile.service.spec.ts │ │ │ │ ├── user-profile.component.spec.ts │ │ │ │ ├── user-profile.component.ts │ │ │ │ ├── user-profile.component.html │ │ │ │ └── user-profile.routes.ts │ │ │ ├── users │ │ │ │ ├── users.component.scss │ │ │ │ ├── users-all │ │ │ │ │ ├── users-all.component.scss │ │ │ │ │ └── users-all.component.spec.ts │ │ │ │ ├── users.component.html │ │ │ │ ├── users.component.ts │ │ │ │ ├── user-by-username-resolver.ts │ │ │ │ ├── users.component.spec.ts │ │ │ │ ├── user-profile │ │ │ │ │ ├── user-profile.component.spec.ts │ │ │ │ │ └── user-profile.component.html │ │ │ │ └── users.routes.ts │ │ │ ├── app-status │ │ │ │ ├── app-status.routes.ts │ │ │ │ ├── app-status.component.scss │ │ │ │ ├── app-status.component.spec.ts │ │ │ │ └── app-status.component.ts │ │ │ └── home │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.spec.ts │ │ ├── shared │ │ │ ├── user-lives │ │ │ │ ├── user-lives.component.scss │ │ │ │ ├── user-lives.component.html │ │ │ │ ├── user-lives.component.spec.ts │ │ │ │ └── user-lives.component.ts │ │ │ ├── minidenticon-avatar │ │ │ │ ├── minidenticon-avatar.component.scss │ │ │ │ ├── minidenticon-avatar.component.html │ │ │ │ ├── minidenticon-avatar.component.ts │ │ │ │ └── minidenticon-avatar.component.spec.ts │ │ │ ├── achievement │ │ │ │ ├── achievement.component.scss │ │ │ │ ├── achievement.component.html │ │ │ │ ├── fade-in-up.animation.ts │ │ │ │ └── achievement.component.spec.ts │ │ │ ├── pipes │ │ │ │ ├── first-error.pipe.spec.ts │ │ │ │ └── first-error.pipe.ts │ │ │ ├── directives │ │ │ │ ├── has-role.directive.spec.ts │ │ │ │ ├── back-button.directive.spec.ts │ │ │ │ ├── back-button.directive.ts │ │ │ │ └── has-role.directive.ts │ │ │ ├── header │ │ │ │ ├── header.component.scss │ │ │ │ └── header.component.spec.ts │ │ │ ├── achievement-one-love │ │ │ │ ├── achievement-one-love.component.html │ │ │ │ ├── achievement-one-love.component.ts │ │ │ │ ├── achievement-one-love.component.scss │ │ │ │ └── achievement-one-love.component.spec.ts │ │ │ ├── an-achievement │ │ │ │ ├── an-achievement.component.ts │ │ │ │ └── an-achievement.component.spec.ts │ │ │ ├── guards │ │ │ │ ├── role.guard.spec.ts │ │ │ │ ├── active-user.guard.spec.ts │ │ │ │ ├── role.guard.ts │ │ │ │ └── active-user.guard.ts │ │ │ ├── achievibit-logo │ │ │ │ ├── achievibit-logo.component.html │ │ │ │ ├── achievibit-logo.component.spec.ts │ │ │ │ └── achievibit-logo.component.scss │ │ │ └── mini-game │ │ │ │ ├── mini-game.component.html │ │ │ │ ├── mini-game.component.spec.ts │ │ │ │ └── mini-game.component.scss │ │ ├── app.component.scss │ │ ├── app.service.ts │ │ ├── app.component.html │ │ ├── services │ │ │ ├── active-user-resolver.ts │ │ │ ├── loader.service.ts │ │ │ ├── api │ │ │ │ ├── api.service.spec.ts │ │ │ │ └── general.service.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── loader.service.spec.ts │ │ │ └── auth.service.ts │ │ ├── app.service.spec.ts │ │ ├── auth.interceptor.spec.ts │ │ ├── app.config.ts │ │ ├── app.component.spec.ts │ │ └── app.routes.ts │ ├── environments │ │ ├── environment.ts │ │ └── environment.development.ts │ ├── favicon.ico │ ├── main.ts │ └── styles.scss ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── site.webmanifest ├── tsconfig.app.json ├── tsconfig.spec.json ├── .editorconfig ├── private.svg ├── proxy.conf.json ├── public.svg ├── git-branch-svgrepo-com.svg ├── .gitignore ├── logout-svgrepo-com.svg ├── tsconfig.json └── README.md ├── server ├── .husky │ └── pre-commit ├── src │ ├── migrations │ │ ├── .gitkeep │ │ └── index.ts │ ├── setup-globals.ts │ ├── models │ │ ├── authorization │ │ │ ├── permissions.enum.ts │ │ │ ├── roles.enum.ts │ │ │ └── role-permissions.map.ts │ │ ├── pr-status.enum.ts │ │ ├── system.enum.ts │ │ ├── pagination │ │ │ └── page.model.ts │ │ ├── api.model.ts │ │ └── index.ts │ ├── middleware │ │ ├── index.ts │ │ └── logger │ │ │ └── logger.middleware.spec.ts │ ├── filters │ │ ├── index.ts │ │ └── http-exception │ │ │ ├── db-exception.filter.spec.ts │ │ │ └── db-exception.filter.ts │ ├── openai │ │ ├── index.ts │ │ ├── openai.module.ts │ │ └── openai.service.spec.ts │ ├── tasks │ │ ├── index.ts │ │ ├── tasks.module.ts │ │ └── tasks.service.spec.ts │ ├── shields │ │ ├── index.ts │ │ ├── shields.module.ts │ │ ├── shields.service.spec.ts │ │ └── shields.service.ts │ ├── users │ │ ├── index.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.controller.spec.ts │ ├── guards │ │ ├── jwt-auth.guard.ts │ │ ├── github-auth.guard.ts │ │ ├── gitlab-auth.guard.ts │ │ ├── bitbucket-auth.guard.ts │ │ ├── github-webhook │ │ │ ├── github-webhook.guard.spec.ts │ │ │ └── github-webhook.guard.ts │ │ ├── gitlab-webhook │ │ │ ├── gitlab-webhook.guard.spec.ts │ │ │ └── gitlab-webhook.guard.ts │ │ ├── bitbucket-webhook │ │ │ ├── bitbucket-webhook.guard.spec.ts │ │ │ └── bitbucket-webhook.guard.ts │ │ ├── disable-in-production │ │ │ ├── disable-in-production.guard.spec.ts │ │ │ └── disable-in-production.guard.ts │ │ ├── index.ts │ │ └── jwt-auth-optional.guard.ts │ ├── health │ │ ├── index.ts │ │ ├── health.controller.spec.ts │ │ └── health.module.ts │ ├── repositories │ │ ├── index.ts │ │ ├── repositories.service.spec.ts │ │ ├── repositories.controller.spec.ts │ │ └── repositories.module.ts │ ├── session-user │ │ ├── index.ts │ │ ├── session-user.service.spec.ts │ │ ├── session-user.controller.spec.ts │ │ └── session-user.module.ts │ ├── organizations │ │ ├── index.ts │ │ ├── organizations.module.ts │ │ ├── organizations.service.spec.ts │ │ └── organizations.controller.spec.ts │ ├── pull-requests │ │ ├── index.ts │ │ ├── pull-requests.module.ts │ │ ├── pull-requests.service.spec.ts │ │ └── pull-requests.controller.spec.ts │ ├── config │ │ ├── smee │ │ │ ├── webhooks-capture │ │ │ │ └── github-webhook-mock.type.ts │ │ │ └── smee.service.spec.ts │ │ ├── index.ts │ │ └── __mocks__ │ │ │ └── config.service.ts │ ├── events │ │ ├── index.ts │ │ ├── events.module.ts │ │ ├── events.service.spec.ts │ │ ├── events.gateway.spec.ts │ │ ├── mini-games │ │ │ └── mini-games.gateway.spec.ts │ │ └── events.service.ts │ ├── webhooks │ │ ├── index.ts │ │ ├── webhooks.module.ts │ │ ├── webhooks.service.spec.ts │ │ ├── webhooks.controller.spec.ts │ │ └── engines │ │ │ └── github.event.ts │ ├── systems │ │ ├── index.ts │ │ ├── github │ │ │ └── github.service.spec.ts │ │ ├── gitlab │ │ │ └── gitlab.service.spec.ts │ │ ├── bitbucket │ │ │ └── bitbucket.service.spec.ts │ │ └── systems.module.ts │ ├── decorators │ │ ├── index.ts │ │ ├── disable-in-production.decorator.ts │ │ ├── is-safe-query.decorator.ts │ │ ├── api-ok-response-paginated.decorator.ts │ │ └── req-user.decorator.ts │ ├── __snapshots__ │ │ └── app.controller.spec.ts.snap │ ├── data-source.ts │ ├── auth │ │ ├── jwt │ │ │ ├── jwt.service.spec.ts │ │ │ ├── jwt.module.ts │ │ │ ├── jwt.service.ts │ │ │ └── jwt.strategy.ts │ │ ├── auth.controller.spec.ts │ │ ├── github │ │ │ ├── github.module.ts │ │ │ └── github.controller.spec.ts │ │ ├── gitlab │ │ │ ├── gitlab.module.ts │ │ │ └── gitlab.controller.spec.ts │ │ ├── bitbucket │ │ │ ├── bitbucket.module.ts │ │ │ └── bitbucket.controller.spec.ts │ │ ├── index.ts │ │ ├── auth.controller.ts │ │ └── auth.module.ts │ ├── custom-socket-io.adapter.ts │ ├── app.controller.spec.ts │ └── logo.ts ├── .dockerignore ├── achieveebeet.mp3 ├── achieveebit.mp3 ├── achieve-a-bit.mp3 ├── client │ ├── favicon.ico │ └── index.html ├── login-app │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── site.webmanifest │ ├── private.svg │ ├── public.svg │ ├── git-branch-svgrepo-com.svg │ └── logout-svgrepo-com.svg ├── tsconfig.build.json ├── nest-cli.json ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── .editorconfig ├── commitlint.config.js ├── scripts │ ├── to-env.ts │ └── migration-create.ts ├── .devcontainer │ ├── Dockerfile │ └── docker-compose.yml └── .gitignore ├── .dockerignore ├── sdk ├── src │ ├── rxjsify │ │ └── index.ts │ ├── decorators │ │ └── index.ts │ ├── sockets │ │ ├── index.ts │ │ ├── base-event-map.type.ts │ │ └── server-to-client.events.ts │ └── generated │ │ ├── index.ts │ │ └── SocketIo.ts ├── index.ts ├── tsconfig.json └── package.json ├── .eslintignore ├── screenshots ├── tour-terminal-client-prefix.png ├── tour-terminal-secrets-error.png └── tour-terminal-server-prefix.png ├── angular-sdk-options.json ├── plugins └── eslint-plugin-attribute-formatting │ ├── index.js │ └── package.json ├── .vscode └── extensions.json ├── pnpm-workspace.yaml ├── docker-compose.yml ├── .github └── workflows │ └── playwright.yml ├── .devcontainer └── docker-compose.dev.yml └── .gitignore /achievements/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /client/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/src/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | # ignore node_modules 2 | node_modules -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-requests.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/src/setup-globals.ts: -------------------------------------------------------------------------------- 1 | jest.mock('./config/config.service'); -------------------------------------------------------------------------------- /client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = {}; 2 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-settings/profile-settings.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/environments/environment.development.ts: -------------------------------------------------------------------------------- 1 | export const environment = {}; 2 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/favicon.ico -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-request-profile/pull-request-profile.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-integrations/profile-integrations.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/favicon.ico -------------------------------------------------------------------------------- /server/achieveebeet.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/achieveebeet.mp3 -------------------------------------------------------------------------------- /server/achieveebit.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/achieveebit.mp3 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .git 4 | Dockerfile 5 | .github 6 | .env* 7 | **/*private-key.pem -------------------------------------------------------------------------------- /client/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/favicon-16x16.png -------------------------------------------------------------------------------- /client/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/favicon-32x32.png -------------------------------------------------------------------------------- /client/src/app/pages/users/users.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | min-height: 100%; 4 | } -------------------------------------------------------------------------------- /server/achieve-a-bit.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/achieve-a-bit.mp3 -------------------------------------------------------------------------------- /server/client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/client/favicon.ico -------------------------------------------------------------------------------- /client/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/apple-touch-icon.png -------------------------------------------------------------------------------- /server/login-app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/login-app/favicon.ico -------------------------------------------------------------------------------- /client/src/app/pages/users/users-all/users-all.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display:block; 3 | padding: 1em; 4 | } -------------------------------------------------------------------------------- /client/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/favicon.ico -------------------------------------------------------------------------------- /client/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/src/app/pages/users/users.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /client/src/assets/all-new-rpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/all-new-rpg.png -------------------------------------------------------------------------------- /client/src/assets/layers/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/layers/sky.png -------------------------------------------------------------------------------- /client/src/app/shared/user-lives/user-lives.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex-direction: row-reverse; 4 | } -------------------------------------------------------------------------------- /client/src/assets/layers/trees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/layers/trees.png -------------------------------------------------------------------------------- /sdk/src/rxjsify/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './rxjsify'; 6 | -------------------------------------------------------------------------------- /server/login-app/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/login-app/favicon-16x16.png -------------------------------------------------------------------------------- /server/login-app/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/login-app/favicon-32x32.png -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-request-profile/pull-request-profile.component.html: -------------------------------------------------------------------------------- 1 |

pull-request-profile works!

2 | -------------------------------------------------------------------------------- /client/src/assets/layers/mountains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/layers/mountains.png -------------------------------------------------------------------------------- /server/login-app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/login-app/apple-touch-icon.png -------------------------------------------------------------------------------- /server/src/models/authorization/permissions.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Permission { 2 | VIEW_PULL_REQUESTS = 'view:pull-requests' 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/.storybook/**/* 2 | **/tester-app/**/* 3 | **/dist/**/* 4 | **/.eslintrc.js 5 | # server/** 6 | plugins/** 7 | **/lib/**/* -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-request-all/pull-request-all.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display:block; 3 | padding: 1em; 4 | } -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-requests.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /client/src/assets/layers/far-clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/layers/far-clouds.png -------------------------------------------------------------------------------- /client/src/assets/layers/near-clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/layers/near-clouds.png -------------------------------------------------------------------------------- /client/src/assets/logo-more-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/logo-more-details.png -------------------------------------------------------------------------------- /server/src/models/authorization/roles.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Role { 2 | ADMIN = 'admin', 3 | USER = 'user', 4 | GUEST = 'guest' 5 | } -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations-all/organizations-all.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display:block; 3 | padding: 1em; 4 | } -------------------------------------------------------------------------------- /client/src/assets/layers/far-mountains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/client/src/assets/layers/far-mountains.png -------------------------------------------------------------------------------- /screenshots/tour-terminal-client-prefix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/screenshots/tour-terminal-client-prefix.png -------------------------------------------------------------------------------- /screenshots/tour-terminal-secrets-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/screenshots/tour-terminal-secrets-error.png -------------------------------------------------------------------------------- /screenshots/tour-terminal-server-prefix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/screenshots/tour-terminal-server-prefix.png -------------------------------------------------------------------------------- /sdk/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/generated/index'; 2 | export * from './src/decorators/index'; 3 | export * from './src/rxjsify/index'; 4 | -------------------------------------------------------------------------------- /server/login-app/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/login-app/android-chrome-192x192.png -------------------------------------------------------------------------------- /server/login-app/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/achievibit-v3/main/server/login-app/android-chrome-512x512.png -------------------------------------------------------------------------------- /server/src/models/pr-status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum PrStatusEnum { 2 | OPEN = 'OPEN', 3 | CLOSED = 'CLOSED', 4 | MERGED = 'MERGED' 5 | } 6 | -------------------------------------------------------------------------------- /server/src/models/system.enum.ts: -------------------------------------------------------------------------------- 1 | export enum SystemEnum { 2 | GITHUB = 'github', 3 | GITLAB = 'gitlab', 4 | BITBUCKET = 'bitbucket' 5 | } 6 | -------------------------------------------------------------------------------- /server/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './logger/logger.middleware'; 6 | -------------------------------------------------------------------------------- /client/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | main { 2 | position: relative; 3 | padding: 0; 4 | } 5 | 6 | // nav a.active { 7 | // color: var(--primary); 8 | // } -------------------------------------------------------------------------------- /server/src/filters/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './http-exception/db-exception.filter'; 6 | -------------------------------------------------------------------------------- /client/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class AppService {} 7 | -------------------------------------------------------------------------------- /angular-sdk-options.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmName": "@kibibit/achievibit-angular-sdk", 3 | "npmVersion": "0.0.1", 4 | "snapshot": true, 5 | "ngVersion": "17.3.0" 6 | } -------------------------------------------------------------------------------- /server/src/migrations/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './1727707536844-initial-table-definitions'; 6 | -------------------------------------------------------------------------------- /server/src/openai/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './openai.module'; 6 | export * from './openai.service'; 7 | -------------------------------------------------------------------------------- /server/src/tasks/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './tasks.module'; 6 | export * from './tasks.service'; 7 | -------------------------------------------------------------------------------- /client/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /plugins/eslint-plugin-attribute-formatting/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'attribute-formatting': require('./rules/attribute-formatting') 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/shields/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './shields.module'; 6 | export * from './shields.service'; 7 | -------------------------------------------------------------------------------- /sdk/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './cachable.decorator'; 6 | export * from './data-on-error.decorator'; 7 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories-all/repositories-all.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display:block; 3 | padding: 1em; 4 | } 5 | 6 | td.kb-center { 7 | text-align: center; 8 | } -------------------------------------------------------------------------------- /client/src/app/shared/minidenticon-avatar/minidenticon-avatar.component.scss: -------------------------------------------------------------------------------- 1 | .minidenticon { 2 | display: inline-block; 3 | } 4 | .minidenticon svg { 5 | width: 100%; 6 | height: 100%; 7 | } -------------------------------------------------------------------------------- /client/src/app/shared/minidenticon-avatar/minidenticon-avatar.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /server/src/users/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './users.controller'; 6 | export * from './users.module'; 7 | export * from './users.service'; 8 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "test", 6 | "dist", 7 | "**/*spec.ts", 8 | "scripts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /server/src/guards/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} 6 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /achievements/src/dev-tools/utils.ts: -------------------------------------------------------------------------------- 1 | export function createComment(username: string, message: string) { 2 | return { 3 | 'author': { 4 | 'username': username 5 | }, 6 | 'message': message 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /server/src/guards/github-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class GitHubAuthGuard extends AuthGuard('github') {} 6 | -------------------------------------------------------------------------------- /server/src/guards/gitlab-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class GitLabAuthGuard extends AuthGuard('gitlab') {} 6 | -------------------------------------------------------------------------------- /server/src/health/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './certificate-health-indicator'; 6 | export * from './health.controller'; 7 | export * from './health.module'; 8 | -------------------------------------------------------------------------------- /server/src/guards/bitbucket-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class BitBucketAuthGuard extends AuthGuard('bitbucket') {} 6 | -------------------------------------------------------------------------------- /server/src/repositories/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './repositories.controller'; 6 | export * from './repositories.module'; 7 | export * from './repositories.service'; 8 | -------------------------------------------------------------------------------- /server/src/session-user/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './session-user.controller'; 6 | export * from './session-user.module'; 7 | export * from './session-user.service'; 8 | -------------------------------------------------------------------------------- /server/src/organizations/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './organizations.controller'; 6 | export * from './organizations.module'; 7 | export * from './organizations.service'; 8 | -------------------------------------------------------------------------------- /server/src/pull-requests/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './pull-requests.controller'; 6 | export * from './pull-requests.module'; 7 | export * from './pull-requests.service'; 8 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-overview/profile-overview.component.scss: -------------------------------------------------------------------------------- 1 | .kb-user-info-container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | padding: 0.2em; 7 | } -------------------------------------------------------------------------------- /client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | -------------------------------------------------------------------------------- /server/src/config/smee/webhooks-capture/github-webhook-mock.type.ts: -------------------------------------------------------------------------------- 1 | import { WebhookEvent } from '@octokit/webhooks-types'; 2 | 3 | 4 | export class GitHubWebhookEvent { 5 | event: string; 6 | payload: Partial; 7 | } 8 | -------------------------------------------------------------------------------- /server/src/openai/openai.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { OpenaiService } from './openai.service'; 3 | 4 | @Module({ 5 | providers: [OpenaiService], 6 | exports: [OpenaiService] 7 | }) 8 | export class OpenaiModule {} 9 | -------------------------------------------------------------------------------- /server/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server/src/events/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './events.gateway'; 6 | export * from './events.module'; 7 | export * from './events.service'; 8 | export * from './mini-games/mini-games.gateway'; 9 | -------------------------------------------------------------------------------- /server/src/webhooks/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './webhooks.controller'; 6 | export * from './webhooks.module'; 7 | export * from './webhooks.service'; 8 | export * from './engines/github.event'; 9 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement/achievement.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | position: fixed; 4 | bottom: 1em; 5 | left: 0; 6 | right: 0; 7 | z-index: 50000; 8 | } 9 | 10 | kb-an-achievement { 11 | position: relative; 12 | } 13 | -------------------------------------------------------------------------------- /sdk/src/sockets/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './base-event-map.type'; 6 | export * from './client-to-server.events'; 7 | export * from './server-to-client.events'; 8 | export * from './socket.service'; 9 | -------------------------------------------------------------------------------- /server/src/middleware/logger/logger.middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { LoggerMiddleware } from './logger.middleware'; 2 | 3 | describe('LoggerMiddleware', () => { 4 | it('should be defined', () => { 5 | expect(new LoggerMiddleware()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /server/src/systems/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './systems.module'; 6 | export * from './bitbucket/bitbucket.service'; 7 | export * from './github/github.service'; 8 | export * from './gitlab/gitlab.service'; 9 | -------------------------------------------------------------------------------- /server/src/shields/shields.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { ShieldsService } from './shields.service'; 4 | 5 | @Module({ 6 | providers: [ ShieldsService ], 7 | exports: [ ShieldsService ] 8 | }) 9 | export class ShieldsModule {} 10 | -------------------------------------------------------------------------------- /client/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /client/src/app/shared/pipes/first-error.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { FirstErrorPipe } from './first-error.pipe'; 2 | 3 | describe('FirstErrorPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new FirstErrorPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /server/src/filters/http-exception/db-exception.filter.spec.ts: -------------------------------------------------------------------------------- 1 | import { DbExceptionFilter } from './db-exception.filter'; 2 | 3 | describe('HttpExceptionFilter', () => { 4 | it('should be defined', () => { 5 | expect(new DbExceptionFilter()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /server/src/guards/github-webhook/github-webhook.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { GithubWebhookGuard } from './github-webhook.guard'; 2 | 3 | describe('GithubWebhookGuard', () => { 4 | it('should be defined', () => { 5 | expect(new GithubWebhookGuard()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /server/src/guards/gitlab-webhook/gitlab-webhook.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { GitlabWebhookGuard } from './gitlab-webhook.guard'; 2 | 3 | describe('GitlabWebhookGuard', () => { 4 | it('should be defined', () => { 5 | expect(new GitlabWebhookGuard()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement/achievement.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 7 | 8 |
-------------------------------------------------------------------------------- /client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | 3 | import { AppComponent } from './app/app.component'; 4 | import { appConfig } from './app/app.config'; 5 | 6 | bootstrapApplication(AppComponent, appConfig) 7 | .catch((err) => console.error(err)); 8 | -------------------------------------------------------------------------------- /server/login-app/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /server/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './api-ok-response-paginated.decorator'; 6 | export * from './disable-in-production.decorator'; 7 | export * from './is-safe-query.decorator'; 8 | export * from './req-user.decorator'; 9 | -------------------------------------------------------------------------------- /server/src/guards/bitbucket-webhook/bitbucket-webhook.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { BitbucketWebhookGuard } from './bitbucket-webhook.guard'; 2 | 3 | describe('BitbucketWebhookGuard', () => { 4 | it('should be defined', () => { 5 | expect(new BitbucketWebhookGuard()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "cweijan.vscode-postgresql-client2", 4 | "dbaeumer.vscode-eslint", 5 | "stylelint.vscode-stylelint", 6 | "johnpapa.vscode-peacock", 7 | "streetsidesoftware.code-spell-checker", 8 | "postman.postman-for-vscode" 9 | ] 10 | } -------------------------------------------------------------------------------- /client/src/app/pages/app-status/app-status.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { AppStatusComponent } from './app-status.component'; 4 | 5 | export const appStatusRoutes: Routes = [ 6 | { 7 | path: 'app-status', 8 | component: AppStatusComponent 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "declaration": true, 5 | "declarationMap": true, 6 | "emitDeclarationOnly": false, 7 | "module": "CommonJS", 8 | "target": "ESNext", 9 | "strict": true 10 | }, 11 | "include": ["index.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /client/src/app/shared/directives/has-role.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { HasRoleDirective } from './has-role.directive'; 2 | 3 | describe('HasRoleDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new HasRoleDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /server/src/decorators/disable-in-production.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const DISABLE_IN_PRODUCTION = 'disableInProduction'; 4 | 5 | // Custom decorator to disable endpoints in production 6 | export const DisableInProduction = () => SetMetadata(DISABLE_IN_PRODUCTION, true); 7 | -------------------------------------------------------------------------------- /server/src/guards/disable-in-production/disable-in-production.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { DisableInProductionGuard } from './disable-in-production.guard'; 2 | 3 | describe('DisableInProductionGuard', () => { 4 | it('should be defined', () => { 5 | expect(new DisableInProductionGuard()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-overview/profile-overview.component.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client/src/app/shared/directives/back-button.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { BackButtonDirective } from './back-button.directive'; 2 | 3 | describe('BackButtonDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new BackButtonDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /server/src/__snapshots__/app.controller.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AppController root should return "Hello World!" 1`] = ` 4 | ApiInfo { 5 | "description": "test", 6 | "license": "test", 7 | "name": "test", 8 | "repository": "test", 9 | "version": "test", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - achievements 3 | - client 4 | - server 5 | - sdk 6 | link-workspace-packages: true 7 | shared-workspace-lockfile: true 8 | config: 9 | packageImportMethod: copy 10 | onlyBuiltDependencies: 11 | - '@nestjs/core' 12 | - esbuild 13 | - nice-napi 14 | - protobufjs 15 | - puppeteer 16 | -------------------------------------------------------------------------------- /server/src/data-source.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | 3 | import { configService } from '@kb-config'; 4 | 5 | const typeOrmModuleOptions = configService.getTypeOrmPostgresConfig() as any; 6 | 7 | export default new DataSource({ 8 | ...typeOrmModuleOptions, 9 | synchronize: false, 10 | logging: false 11 | }); 12 | -------------------------------------------------------------------------------- /server/src/models/authorization/role-permissions.map.ts: -------------------------------------------------------------------------------- 1 | import { Role } from './roles.enum'; 2 | import { Permission } from './permissions.enum'; 3 | 4 | export const RolePermissions: Record = { 5 | [Role.ADMIN]: [ 6 | Permission.VIEW_PULL_REQUESTS 7 | ], 8 | [Role.USER]: [], 9 | [Role.GUEST]: [], 10 | }; 11 | -------------------------------------------------------------------------------- /client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /server/src/webhooks/webhooks.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { WebhooksController } from './webhooks.controller'; 4 | import { WebhooksService } from './webhooks.service'; 5 | 6 | @Module({ 7 | controllers: [ WebhooksController ], 8 | providers: [ WebhooksService ] 9 | }) 10 | export class WebhooksModule {} 11 | -------------------------------------------------------------------------------- /client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /server/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /client/src/app/pages/users/users.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | 5 | @Component({ 6 | selector: 'kb-users', 7 | standalone: true, 8 | imports: [ RouterModule ], 9 | templateUrl: './users.component.html', 10 | styleUrl: './users.component.scss' 11 | }) 12 | export class UsersComponent {} 13 | -------------------------------------------------------------------------------- /server/src/tasks/tasks.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ScheduleModule } from '@nestjs/schedule'; 3 | 4 | import { SystemsModule } from '@kb-systems'; 5 | 6 | import { TasksService } from './tasks.service'; 7 | 8 | @Module({ 9 | imports: [ 10 | ScheduleModule.forRoot(), 11 | SystemsModule 12 | ], 13 | providers: [ TasksService ] 14 | }) 15 | export class TasksModule {} 16 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-request-profile/pull-request-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'kb-pull-request-profile', 5 | standalone: true, 6 | imports: [], 7 | templateUrl: './pull-request-profile.component.html', 8 | styleUrl: './pull-request-profile.component.scss' 9 | }) 10 | export class PullRequestProfileComponent { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /client/src/app/services/active-user-resolver.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ResolveFn } from '@angular/router'; 3 | 4 | import { User } from '@kibibit/achievibit-sdk'; 5 | 6 | import { MeApiService } from './api/me.service'; 7 | 8 | export const activeUserResolver: ResolveFn = () => { 9 | const meApiService = inject(MeApiService); 10 | return meApiService.getLoggedInUser(); 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'kb-repositories', 6 | standalone: true, 7 | imports: [ RouterModule ], 8 | templateUrl: './repositories.component.html', 9 | styleUrl: './repositories.component.scss' 10 | }) 11 | export class RepositoriesComponent { 12 | } 13 | -------------------------------------------------------------------------------- /server/src/config/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './achievibit.config'; 6 | export * from './config.service'; 7 | export * from './logger/logger'; 8 | export * from './logger/winston.logger'; 9 | export * from './smee/smee.service'; 10 | export * from './smee/webhooks-capture/github-webhook-mock.type'; 11 | export * from './smee/webhooks-capture/installation.webhook'; 12 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | 5 | @Component({ 6 | selector: 'kb-organizations', 7 | standalone: true, 8 | imports: [ RouterModule ], 9 | templateUrl: './organizations.component.html', 10 | styleUrl: './organizations.component.scss' 11 | }) 12 | export class OrganizationsComponent { 13 | } 14 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-requests.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'kb-pull-requests', 6 | standalone: true, 7 | imports: [ RouterModule ], 8 | templateUrl: './pull-requests.component.html', 9 | styleUrl: './pull-requests.component.scss' 10 | }) 11 | export class PullRequestsComponent { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /achievements/src/achievement.abstract.ts: -------------------------------------------------------------------------------- 1 | export interface IShall { 2 | grant(username: string, achievement: IUserAchievement): void; 3 | } 4 | 5 | export interface IAchievement { 6 | name: string; 7 | check(pullRequest: any, shall: IShall): void; 8 | } 9 | 10 | export interface IUserAchievement { 11 | name: string; 12 | avatar: string; 13 | short: string; 14 | description: string; 15 | relatedPullRequest: string; 16 | } 17 | -------------------------------------------------------------------------------- /client/src/app/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppService', () => { 6 | let service: AppService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(AppService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /client/src/app/shared/directives/back-button.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostListener } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Directive({ 5 | selector: '[kbBackButton]', 6 | standalone: true 7 | }) 8 | export class BackButtonDirective { 9 | constructor(private router: Router) { } 10 | 11 | @HostListener('click') 12 | onClick() { 13 | this.router.navigate([ '..' ]); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ '@commitlint/config-angular' ], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', [ 7 | 'build', 8 | 'chore', 9 | 'ci', 10 | 'docs', 11 | 'feat', 12 | 'fix', 13 | 'perf', 14 | 'refactor', 15 | 'revert', 16 | 'style', 17 | 'test' 18 | ] 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/app/shared/header/header.component.scss: -------------------------------------------------------------------------------- 1 | header { 2 | z-index: 40000000000000; 3 | } 4 | 5 | .menu { 6 | margin: 1em; 7 | z-index: 50000000; 8 | } 9 | 10 | .mobile-show { 11 | display: none; 12 | } 13 | 14 | menu hr { 15 | background-color: rgba(255, 255, 255, 0.4); 16 | } 17 | 18 | @media (max-width: 768px) { 19 | .mobile-show { 20 | display: block; 21 | } 22 | .mobile-hide { 23 | display: none; 24 | } 25 | } -------------------------------------------------------------------------------- /client/src/app/services/loader.service.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class LoaderService { 6 | private _loading = new BehaviorSubject(false); 7 | loading$ = this._loading.asObservable(); 8 | 9 | show() { 10 | this._loading.next(true); 11 | } 12 | 13 | hide() { 14 | this._loading.next(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/app/services/api/api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ApiService } from './api.service'; 4 | 5 | describe('ApiService', () => { 6 | let service: ApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /client/src/app/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthService', () => { 6 | let service: AuthService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(AuthService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /server/src/events/events.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { EventsGateway } from './events.gateway'; 4 | import { MiniGamesGateway } from './mini-games/mini-games.gateway'; 5 | import { EventsService } from './events.service'; 6 | 7 | @Module({ 8 | providers: [ 9 | EventsGateway, 10 | MiniGamesGateway, 11 | EventsService 12 | ], 13 | exports: [EventsService], 14 | }) 15 | export class EventsModule {} 16 | -------------------------------------------------------------------------------- /sdk/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './Api'; 6 | export * from './Authentication'; 7 | export * from './Health'; 8 | export * from './Organizations'; 9 | export * from './Repositories'; 10 | export * from './SessionUser'; 11 | export * from './SocketIo'; 12 | export * from './Users'; 13 | export * from './Webhooks'; 14 | export * from './data-contracts'; 15 | export * from './http-client'; 16 | -------------------------------------------------------------------------------- /client/src/app/services/loader.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { LoaderService } from './loader.service'; 4 | 5 | describe('LoaderService', () => { 6 | let service: LoaderService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(LoaderService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /plugins/eslint-plugin-attribute-formatting/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-attribute-formatting", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "@angular-eslint/template-parser": "^18.3.1", 14 | "eslint": "^8.57.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement-one-love/achievement-one-love.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | Achievement icon 5 |
6 |
7 |
{{ achievement.name }}
8 |
{{ achievement.description }}
9 |
10 |
11 |
-------------------------------------------------------------------------------- /client/src/app/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 | rpg 11 |
12 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pr-by-id-resolver.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ResolveFn } from '@angular/router'; 3 | 4 | import { PullRequestsApiService } from '../../services/api/pull-requests.service'; 5 | 6 | export const prByIdResolver: ResolveFn> = (route) => { 7 | const prsApiService = inject(PullRequestsApiService); 8 | const id = route.paramMap.get('id') as string; 9 | 10 | return prsApiService.getPullRequestById(id); 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/app/shared/an-achievement/an-achievement.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { Achievement } from '@kibibit/achievibit-sdk'; 4 | 5 | @Component({ 6 | selector: 'kb-an-achievement', 7 | standalone: true, 8 | imports: [], 9 | templateUrl: './an-achievement.component.html', 10 | styleUrl: './an-achievement.component.scss' 11 | }) 12 | export class AnAchievementComponent { 13 | @Input() achievement!: Partial; 14 | } 15 | -------------------------------------------------------------------------------- /client/private.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/use-github-bot.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`githubBot achievement should be granted if committer username is web-flow 1`] = ` 4 | { 5 | "avatar": "images/achievements/useGithubBot.achievement.jpeg", 6 | "description": "used github to create a pull request, using the web-flow bot", 7 | "name": "Why not bots?", 8 | "relatedPullRequest": "test", 9 | "short": "Hey sexy mama, wanna kill all humans?", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/user-profile.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserProfileService } from './user-profile.service'; 4 | 5 | describe('UserProfileService', () => { 6 | let service: UserProfileService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserProfileService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /client/src/assets/private.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/login-app/private.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:10102/", 4 | "changeOrigin": true, 5 | "secure": false, 6 | "logLevel": "debug" 7 | }, 8 | "/login": { 9 | "target": "http://localhost:10102/", 10 | "changeOrigin": true, 11 | "secure": false, 12 | "logLevel": "debug" 13 | }, 14 | "/socket.io": { 15 | "target": "http://localhost:10102/", 16 | "ws": true, 17 | "changeOrigin": true, 18 | "secure": false, 19 | "logLevel": "debug" 20 | } 21 | } -------------------------------------------------------------------------------- /achievements/src/__snapshots__/member.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`member achievement should be granted if PR opened more than 2 weeks ago 1`] = ` 4 | { 5 | "avatar": "images/achievements/member.achievement.jpg", 6 | "description": "A pull request you've created 2 weeks ago is finally merged", 7 | "name": "Member pull request #undefined?", 8 | "relatedPullRequest": "test", 9 | "short": "Member Commits? member Push? member PR? ohh I member", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /server/src/config/__mocks__/config.service.ts: -------------------------------------------------------------------------------- 1 | import { AchievibitConfig } from "../achievibit.config"; 2 | 3 | export class ConfigServiceMock { 4 | private _config: Partial = {}; 5 | appRoot = 'root/'; 6 | 7 | get config() { 8 | return this._config || {}; 9 | } 10 | 11 | set config(config: Partial) { 12 | this._config = config; 13 | } 14 | 15 | clear() { 16 | this._config = {}; 17 | } 18 | } 19 | 20 | export const configService = new ConfigServiceMock(); 21 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/cutting-edges.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Cutting Edges achievement should be granted if pull request was merged without approvals 1`] = ` 4 | { 5 | "avatar": "images/achievements/cuttingEdges.achievement.jpg", 6 | "description": "You've merged a pull request without a reviewer confirming", 7 | "name": "Cutting Edges", 8 | "relatedPullRequest": "test", 9 | "short": "Cutting corners? I also like to live dangerously", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/optimus-prime.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`optimusPrime achievement should be granted to PR creator if PR number is prime 1`] = ` 4 | { 5 | "avatar": "images/achievements/optimusPrime.achievement.jpeg", 6 | "description": "Pull requests with prime numbers are very rare! yours was 3", 7 | "name": "optimus prime", 8 | "relatedPullRequest": "test", 9 | "short": "Fate rarely calls upon us at a moment of our choosing", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/label-baby-junior.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`labelBabyJunior achievement should be granted to PR creator if more than 5 labels 1`] = ` 4 | { 5 | "avatar": "images/achievements/labelBabyJunior.achievement.jpg", 6 | "description": "You've put many labels, thank you for organizing. You're a gift that keeps on re-giving", 7 | "name": "The Label Maker", 8 | "relatedPullRequest": "test", 9 | "short": "Is this a label maker?", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /client/src/app/pages/users/user-by-username-resolver.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ResolveFn } from '@angular/router'; 3 | 4 | import { User } from '@kibibit/achievibit-sdk'; 5 | 6 | import { UsersApiService } from '../../services/api/users.service'; 7 | 8 | export const userByUsernameResolver: ResolveFn = (route) => { 9 | const usersService = inject(UsersApiService); 10 | const username = route.paramMap.get('username') as string; 11 | 12 | return usersService.getUserByUsername(username); 13 | }; 14 | -------------------------------------------------------------------------------- /server/src/models/pagination/page.model.ts: -------------------------------------------------------------------------------- 1 | import { IsArray } from 'class-validator'; 2 | 3 | import { ApiProperty } from '@nestjs/swagger'; 4 | 5 | import { PageMetaModel } from './page-meta.model'; 6 | 7 | export class PageModel { 8 | @IsArray() 9 | @ApiProperty({ isArray: true }) 10 | readonly data: T[]; 11 | 12 | @ApiProperty({ type: () => PageMetaModel }) 13 | readonly meta: PageMetaModel; 14 | 15 | constructor(data: T[], meta: PageMetaModel) { 16 | this.data = data; 17 | this.meta = meta; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { Integration, User } from '@kb-models'; 5 | 6 | import { UsersController } from './users.controller'; 7 | import { UsersService } from './users.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | TypeOrmModule.forFeature([ User, Integration ]) 12 | ], 13 | providers: [ UsersService ], 14 | controllers: [ UsersController ], 15 | exports: [ UsersService ] 16 | }) 17 | export class UsersModule {} 18 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement/fade-in-up.animation.ts: -------------------------------------------------------------------------------- 1 | import { animate, style, transition, trigger } from '@angular/animations'; 2 | 3 | // add a delay to the fade in animation 4 | export const fadeInUp = trigger('fadeInUp', [ 5 | transition(':enter', [ 6 | style({ opacity: 0, bottom: '-20px' }), 7 | animate('300ms 300ms ease-in', style({ opacity: 1, bottom: '0px' })) 8 | ]), 9 | transition(':leave', [ 10 | style({ opacity: 1, bottom: '0px' }), 11 | animate('300ms ease-out', style({ opacity: 0, bottom: '20px' })) 12 | ]) 13 | ]); 14 | -------------------------------------------------------------------------------- /client/src/app/shared/user-lives/user-lives.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | favorite 5 | 6 | 7 | 8 | 9 | 10 | favorite 11 | 12 | -------------------------------------------------------------------------------- /server/src/auth/jwt/jwt.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { JwtService } from './jwt.service'; 4 | 5 | describe('JwtService', () => { 6 | let service: JwtService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ JwtService ] 11 | }).compile(); 12 | 13 | service = module.get(JwtService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | build: . 6 | ports: 7 | - '80:10102' 8 | depends_on: 9 | - db 10 | environment: 11 | - NODE_ENV=docker 12 | env_file: 13 | - .env 14 | 15 | db: 16 | image: postgres:15-alpine 17 | environment: 18 | - POSTGRES_USER=achievibit 19 | - POSTGRES_PASSWORD=a123456789 20 | - POSTGRES_DB=achievibit 21 | # - POSTGRES_PORT=5436 22 | volumes: 23 | - ./pgdata:/var/lib/postgresql/data 24 | 25 | volumes: 26 | pgdata: 27 | -------------------------------------------------------------------------------- /server/src/guards/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './bitbucket-auth.guard'; 6 | export * from './github-auth.guard'; 7 | export * from './gitlab-auth.guard'; 8 | export * from './jwt-auth-optional.guard'; 9 | export * from './jwt-auth.guard'; 10 | export * from './bitbucket-webhook/bitbucket-webhook.guard'; 11 | export * from './disable-in-production/disable-in-production.guard'; 12 | export * from './github-webhook/github-webhook.guard'; 13 | export * from './gitlab-webhook/gitlab-webhook.guard'; 14 | -------------------------------------------------------------------------------- /server/src/config/smee/smee.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { SmeeService } from './smee.service'; 4 | 5 | describe('SmeeService', () => { 6 | let service: SmeeService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ SmeeService ] 11 | }).compile(); 12 | 13 | service = module.get(SmeeService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/pull-requests/pull-requests.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { PullRequest } from '@kb-models'; 5 | 6 | import { PullRequestsController } from './pull-requests.controller'; 7 | import { PullRequestsService } from './pull-requests.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | TypeOrmModule.forFeature([ PullRequest ]) 12 | ], 13 | providers: [ PullRequestsService ], 14 | controllers: [ PullRequestsController ] 15 | }) 16 | export class PullRequestsModule {} 17 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/org-by-name-resolver.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ResolveFn } from '@angular/router'; 3 | 4 | import { Organization } from '@kibibit/achievibit-sdk'; 5 | 6 | import { OrganizationsApiService } from '../../services/api/organizations.service'; 7 | 8 | export const orgByNameResolver: ResolveFn = (route) => { 9 | const orgsApiService = inject(OrganizationsApiService); 10 | const name = route.paramMap.get('name') as string; 11 | 12 | return orgsApiService.getOrganizationByName(name); 13 | }; 14 | -------------------------------------------------------------------------------- /client/src/app/shared/guards/role.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { CanActivateFn } from '@angular/router'; 3 | 4 | import { roleGuard } from './role.guard'; 5 | 6 | describe('roleGuard', () => { 7 | const executeGuard: CanActivateFn = (...guardParameters) => 8 | TestBed.runInInjectionContext(() => roleGuard(...guardParameters)); 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({}); 12 | }); 13 | 14 | it('should be created', () => { 15 | expect(executeGuard).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /server/src/events/events.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EventsService } from './events.service'; 3 | 4 | describe('EventsService', () => { 5 | let service: EventsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [EventsService], 10 | }).compile(); 11 | 12 | service = module.get(EventsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/openai/openai.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OpenaiService } from './openai.service'; 3 | 4 | describe('OpenaiService', () => { 5 | let service: OpenaiService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OpenaiService], 10 | }).compile(); 11 | 12 | service = module.get(OpenaiService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/tasks/tasks.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { TasksService } from './tasks.service'; 4 | 5 | describe('TasksService', () => { 6 | let service: TasksService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ TasksService ] 11 | }).compile(); 12 | 13 | service = module.get(TasksService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { UsersService } from './users.service'; 4 | 5 | describe('UsersService', () => { 6 | let service: UsersService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ UsersService ] 11 | }).compile(); 12 | 13 | service = module.get(UsersService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /server/src/events/events.gateway.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { EventsGateway } from './events.gateway'; 4 | 5 | describe('EventsGateway', () => { 6 | let gateway: EventsGateway; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ EventsGateway ] 11 | }).compile(); 12 | 13 | gateway = module.get(EventsGateway); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(gateway).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /achievements/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "esModuleInterop": true, 12 | "outDir": "./lib", 13 | "baseUrl": "./src", 14 | "paths": {}, 15 | "incremental": true, 16 | "skipLibCheck": true 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | "lib" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /client/src/app/auth.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpInterceptorFn } from '@angular/common/http'; 2 | import { TestBed } from '@angular/core/testing'; 3 | 4 | import { authInterceptor } from './auth.interceptor'; 5 | 6 | describe('authInterceptor', () => { 7 | const interceptor: HttpInterceptorFn = (req, next) => 8 | TestBed.runInInjectionContext(() => authInterceptor(req, next)); 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({}); 12 | }); 13 | 14 | it('should be created', () => { 15 | expect(interceptor).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /server/src/models/api.model.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class ApiInfo { 4 | @ApiProperty() 5 | name: string; 6 | 7 | @ApiProperty() 8 | description: string; 9 | 10 | @ApiProperty() 11 | version: string; 12 | 13 | @ApiProperty() 14 | license: string; 15 | 16 | @ApiProperty() 17 | repository: string; 18 | 19 | @ApiProperty() 20 | author: string; 21 | 22 | @ApiProperty() 23 | bugs: string; 24 | 25 | constructor(partial: Partial = {}) { 26 | Object.assign(this, partial); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/app/shared/achievibit-logo/achievibit-logo.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | achievibit 5 |
6 |
7 | 10 |
{{ apiVersion }}
-------------------------------------------------------------------------------- /server/src/shields/shields.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { ShieldsService } from './shields.service'; 4 | 5 | describe('ShieldsService', () => { 6 | let service: ShieldsService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ ShieldsService ] 11 | }).compile(); 12 | 13 | service = module.get(ShieldsService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/shields/shields.service.ts: -------------------------------------------------------------------------------- 1 | import { makeBadge } from 'badge-maker'; 2 | 3 | import { Injectable } from '@nestjs/common'; 4 | 5 | @Injectable() 6 | export class ShieldsService { 7 | generate( 8 | label: string, 9 | message: string, 10 | color: string = 'brightgreen' 11 | ) { 12 | const format = { label, message, color }; 13 | 14 | try { 15 | const svg = makeBadge(format); 16 | 17 | return svg; 18 | } catch (error) { 19 | // ValidationError: Field `message` is required 20 | console.log(error); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/src/systems/github/github.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { GithubService } from './github.service'; 4 | 5 | describe('GithubService', () => { 6 | let service: GithubService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ GithubService ] 11 | }).compile(); 12 | 13 | service = module.get(GithubService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/systems/gitlab/gitlab.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { GitlabService } from './gitlab.service'; 4 | 5 | describe('GitlabService', () => { 6 | let service: GitlabService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ GitlabService ] 11 | }).compile(); 12 | 13 | service = module.get(GitlabService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/app/shared/minidenticon-avatar/minidenticon-avatar.component.ts: -------------------------------------------------------------------------------- 1 | import { minidenticon } from 'minidenticons'; 2 | import { Component, computed, Input } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'kb-minidenticon-avatar', 6 | standalone: true, 7 | imports: [], 8 | templateUrl: './minidenticon-avatar.component.html', 9 | styleUrl: './minidenticon-avatar.component.scss' 10 | }) 11 | export class MinidenticonAvatarComponent { 12 | @Input({ required: true }) name!: string; 13 | @Input() size = 64; 14 | 15 | svg = computed(() => minidenticon(this.name)); 16 | } 17 | -------------------------------------------------------------------------------- /server/src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { AuthController } from './auth.controller'; 4 | 5 | describe('AuthController', () => { 6 | let controller: AuthController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ AuthController ] 11 | }).compile(); 12 | 13 | controller = module.get(AuthController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/webhooks/webhooks.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { WebhooksService } from './webhooks.service'; 4 | 5 | describe('WebhooksService', () => { 6 | let service: WebhooksService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ WebhooksService ] 11 | }).compile(); 12 | 13 | service = module.get(WebhooksService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/public.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/src/auth/github/github.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | 4 | import { UsersModule } from '@kb-users'; 5 | 6 | import { JwtModule } from '../jwt/jwt.module'; 7 | import { GithubController } from './github.controller'; 8 | import { GitHubStrategy } from './github.strategy'; 9 | 10 | @Module({ 11 | imports: [ 12 | PassportModule, 13 | JwtModule, 14 | UsersModule 15 | ], 16 | providers: [ 17 | GitHubStrategy 18 | ], 19 | controllers: [ GithubController ] 20 | }) 21 | export class GithubModule {} 22 | -------------------------------------------------------------------------------- /server/src/auth/gitlab/gitlab.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | 4 | import { UsersModule } from '@kb-users'; 5 | 6 | import { JwtModule } from '../jwt/jwt.module'; 7 | import { GitlabController } from './gitlab.controller'; 8 | import { GitLabStrategy } from './gitlab.strategy'; 9 | 10 | @Module({ 11 | imports: [ 12 | PassportModule, 13 | JwtModule, 14 | UsersModule 15 | ], 16 | providers: [ 17 | GitLabStrategy 18 | ], 19 | controllers: [ GitlabController ] 20 | }) 21 | export class GitlabModule {} 22 | -------------------------------------------------------------------------------- /client/src/app/shared/guards/active-user.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { CanActivateFn } from '@angular/router'; 3 | 4 | import { activeUserGuard } from './active-user.guard'; 5 | 6 | describe('activeUserGuard', () => { 7 | const executeGuard: CanActivateFn = (...guardParameters) => 8 | TestBed.runInInjectionContext(() => activeUserGuard(...guardParameters)); 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({}); 12 | }); 13 | 14 | it('should be created', () => { 15 | expect(executeGuard).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /server/src/events/mini-games/mini-games.gateway.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MiniGamesGateway } from './mini-games.gateway'; 3 | 4 | describe('MiniGamesGateway', () => { 5 | let gateway: MiniGamesGateway; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [MiniGamesGateway], 10 | }).compile(); 11 | 12 | gateway = module.get(MiniGamesGateway); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(gateway).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /server/src/users/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { UsersController } from './users.controller'; 4 | 5 | describe('UsersController', () => { 6 | let controller: UsersController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ UsersController ] 11 | }).compile(); 12 | 13 | controller = module.get(UsersController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/assets/public.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/login-app/public.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/src/health/health.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { HealthController } from './health.controller'; 4 | 5 | describe('HealthController', () => { 6 | let controller: HealthController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ HealthController ] 11 | }).compile(); 12 | 13 | controller = module.get(HealthController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/systems/bitbucket/bitbucket.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { BitbucketService } from './bitbucket.service'; 4 | 5 | describe('BitbucketService', () => { 6 | let service: BitbucketService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ BitbucketService ] 11 | }).compile(); 12 | 13 | service = module.get(BitbucketService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /sdk/src/sockets/base-event-map.type.ts: -------------------------------------------------------------------------------- 1 | export interface IServerToClientEventShape { 2 | payload: any; 3 | } 4 | 5 | export interface IClientToServerEventShape { 6 | payload: any; 7 | response: any; 8 | } 9 | 10 | export interface IEventMapBase { 11 | [eventName: string]: T; 12 | } 13 | 14 | export type EventNames> = { 15 | [K in keyof T]: K; 16 | }[keyof T]; 17 | 18 | export type EventPayload, K extends EventNames> = T[K]['payload']; 19 | export type EventResponse, K extends EventNames> = T[K]['response']; 20 | -------------------------------------------------------------------------------- /server/src/auth/github/github.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { GithubController } from './github.controller'; 4 | 5 | describe('GithubController', () => { 6 | let controller: GithubController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ GithubController ] 11 | }).compile(); 12 | 13 | controller = module.get(GithubController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/auth/gitlab/gitlab.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { GitlabController } from './gitlab.controller'; 4 | 5 | describe('GitlabController', () => { 6 | let controller: GitlabController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ GitlabController ] 11 | }).compile(); 12 | 13 | controller = module.get(GitlabController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/organizations/organizations.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { Organization } from '@kb-models'; 5 | 6 | import { OrganizationsController } from './organizations.controller'; 7 | import { OrganizationsService } from './organizations.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | TypeOrmModule.forFeature([ Organization ]) 12 | ], 13 | controllers: [ OrganizationsController ], 14 | providers: [ OrganizationsService ], 15 | exports: [ OrganizationsService ] 16 | }) 17 | export class OrganizationsModule {} 18 | -------------------------------------------------------------------------------- /server/src/session-user/session-user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { SessionUserService } from './session-user.service'; 4 | 5 | describe('SessionUserService', () => { 6 | let service: SessionUserService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ SessionUserService ] 11 | }).compile(); 12 | 13 | service = module.get(SessionUserService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/auth/bitbucket/bitbucket.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | 4 | import { UsersModule } from '@kb-users'; 5 | 6 | import { JwtModule } from '../jwt/jwt.module'; 7 | import { BitbucketController } from './bitbucket.controller'; 8 | import { BitBucketStrategy } from './bitbucket.strategy'; 9 | 10 | @Module({ 11 | imports: [ 12 | PassportModule, 13 | JwtModule, 14 | UsersModule 15 | ], 16 | providers: [ 17 | BitBucketStrategy 18 | ], 19 | controllers: [ BitbucketController ] 20 | }) 21 | export class BitbucketModule {} 22 | -------------------------------------------------------------------------------- /server/src/repositories/repositories.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { RepositoriesService } from './repositories.service'; 4 | 5 | describe('RepositoriesService', () => { 6 | let service: RepositoriesService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ RepositoriesService ] 11 | }).compile(); 12 | 13 | service = module.get(RepositoriesService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/webhooks/webhooks.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { WebhooksController } from './webhooks.controller'; 4 | 5 | describe('WebhooksController', () => { 6 | let controller: WebhooksController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ WebhooksController ] 11 | }).compile(); 12 | 13 | controller = module.get(WebhooksController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/pull-requests/pull-requests.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { PullRequestsService } from './pull-requests.service'; 4 | 5 | describe('PullRequestsService', () => { 6 | let service: PullRequestsService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ PullRequestsService ] 11 | }).compile(); 12 | 13 | service = module.get(PullRequestsService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/organizations/organizations.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { OrganizationsService } from './organizations.service'; 4 | 5 | describe('OrganizationsService', () => { 6 | let service: OrganizationsService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ OrganizationsService ] 11 | }).compile(); 12 | 13 | service = module.get(OrganizationsService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/app/pages/app-status/app-status.component.scss: -------------------------------------------------------------------------------- 1 | article { 2 | align-items: center; 3 | display: flex; 4 | flex-direction: column; 5 | 6 | .status-name { 7 | flex-grow: 1; 8 | text-transform:capitalize; 9 | text-align: center; 10 | } 11 | } 12 | 13 | .container { 14 | padding-bottom: 1em; 15 | } 16 | 17 | .status { 18 | display: flex; 19 | align-items: center; 20 | font-size: 0.9em; 21 | gap: 0.5em; 22 | 23 | &.kb-up { 24 | color: var(--success, green); 25 | } 26 | 27 | &.kb-down { 28 | color: var(--danger, red); 29 | } 30 | 31 | i { 32 | transform: translateY(-1px); 33 | } 34 | } -------------------------------------------------------------------------------- /server/src/auth/bitbucket/bitbucket.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { BitbucketController } from './bitbucket.controller'; 4 | 5 | describe('BitbucketController', () => { 6 | let controller: BitbucketController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ BitbucketController ] 11 | }).compile(); 12 | 13 | controller = module.get(BitbucketController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/the-godfather-consigliere.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`theGodfatherConsigliere achievement should be granted to PR creator if organization is Kibibit 1`] = ` 4 | { 5 | "avatar": "images/achievements/theGodfatherConsigliere.achievement.jpg", 6 | "description": "

You have contributed to Kibibit! We really appreciate it!

Accept this achievement as gift on my daughter's wedding day

", 7 | "name": "The Godfather Consigliere", 8 | "relatedPullRequest": "test", 9 | "short": "Great men are not born great, they contribute to Kibibit . . .", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /server/src/session-user/session-user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { SessionUserController } from './session-user.controller'; 4 | 5 | describe('SessionUserController', () => { 6 | let controller: SessionUserController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ SessionUserController ] 11 | }).compile(); 12 | 13 | controller = module.get(SessionUserController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/mr-miyagi.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`mrMiyagi achievement should be granted to PR creator if coverage increased to 100% 1`] = ` 4 | { 5 | "avatar": "images/achievements/mrMiyagi.achievement.jpg", 6 | "description": "You're the ultimate zen master. You increased a project coverage to 100%. It was a long journey... but you know...
First learn stand, then learn fly. Nature rule, creator-san, not mine", 7 | "name": "Mr Miyagi", 8 | "relatedPullRequest": "test", 9 | "short": "Never put passion in front of principle, even if you win, you’ll lose", 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /server/src/repositories/repositories.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { RepositoriesController } from './repositories.controller'; 4 | 5 | describe('RepositoriesController', () => { 6 | let controller: RepositoriesController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ RepositoriesController ] 11 | }).compile(); 12 | 13 | controller = module.get(RepositoriesController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClient, withInterceptors } from '@angular/common/http'; 2 | import { ApplicationConfig } from '@angular/core'; 3 | import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; 4 | import { provideRouter } from '@angular/router'; 5 | 6 | import { routes } from './app.routes'; 7 | import { authInterceptor } from './auth.interceptor'; 8 | 9 | export const appConfig: ApplicationConfig = { 10 | providers: [ 11 | provideAnimationsAsync(), 12 | provideHttpClient( 13 | withInterceptors([ 14 | authInterceptor 15 | ]) 16 | ), 17 | provideRouter(routes) 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /server/src/pull-requests/pull-requests.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { PullRequestsController } from './pull-requests.controller'; 4 | 5 | describe('PullRequestsController', () => { 6 | let controller: PullRequestsController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ PullRequestsController ] 11 | }).compile(); 12 | 13 | controller = module.get(PullRequestsController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/organizations/organizations.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { OrganizationsController } from './organizations.controller'; 4 | 5 | describe('OrganizationsController', () => { 6 | let controller: OrganizationsController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ OrganizationsController ] 11 | }).compile(); 12 | 13 | controller = module.get(OrganizationsController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/app/shared/mini-game/mini-game.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Mini Game

4 |
5 |
6 |

Test your skills in this mini game!

7 |
8 |
9 |
10 | user avatar 11 | 12 |
13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /server/src/health/health.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios'; 2 | import { Module } from '@nestjs/common'; 3 | import { TerminusModule } from '@nestjs/terminus'; 4 | 5 | import { HealthController } from './health.controller'; 6 | import { CertificateHealthIndicator } from './certificate-health-indicator'; 7 | import { CacheModule } from '@nestjs/cache-manager'; 8 | 9 | @Module({ 10 | imports: [ 11 | CacheModule.register(), 12 | TerminusModule.forRoot({ 13 | errorLogStyle: 'pretty' 14 | }), 15 | HttpModule 16 | ], 17 | controllers: [ HealthController ], 18 | providers: [CertificateHealthIndicator] 19 | }) 20 | export class HealthModule {} 21 | -------------------------------------------------------------------------------- /server/src/repositories/repositories.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { Repository } from '@kb-models'; 5 | 6 | import { RepositoriesController } from './repositories.controller'; 7 | import { RepositoriesService } from './repositories.service'; 8 | import { OpenaiModule } from 'src/openai/openai.module'; 9 | 10 | @Module({ 11 | imports: [ 12 | OpenaiModule, 13 | TypeOrmModule.forFeature([ Repository ]) 14 | ], 15 | providers: [ RepositoriesService ], 16 | controllers: [ RepositoriesController ], 17 | exports: [ RepositoriesService ] 18 | }) 19 | export class RepositoriesModule {} 20 | -------------------------------------------------------------------------------- /client/git-branch-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | ionicons-v5-d -------------------------------------------------------------------------------- /client/src/app/shared/guards/role.guard.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { CanActivateFn } from '@angular/router'; 3 | 4 | import { User } from '@kibibit/achievibit-sdk'; 5 | 6 | import { AuthService } from '../../services/auth.service'; 7 | 8 | export const roleGuard: CanActivateFn = (route, state) => { 9 | const authService = inject(AuthService); 10 | const requiredRoles = (route.data as User).roles; 11 | 12 | const hasAccess = requiredRoles.some((requiredRole) => authService.hasRole(requiredRole)); 13 | 14 | if (!hasAccess) { 15 | // show 404 page eventually 16 | window.location.href = '/login'; 17 | } 18 | 19 | return hasAccess; 20 | }; 21 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement-one-love/achievement-one-love.component.ts: -------------------------------------------------------------------------------- 1 | import { NgIf } from '@angular/common'; 2 | import { Component, Input } from '@angular/core'; 3 | 4 | import { Achievement } from '@kibibit/achievibit-sdk'; 5 | 6 | import { AnAchievementComponent } from '../an-achievement/an-achievement.component'; 7 | 8 | @Component({ 9 | selector: 'kb-achievement-one-love', 10 | standalone: true, 11 | imports: [ NgIf, AnAchievementComponent ], 12 | templateUrl: './achievement-one-love.component.html', 13 | styleUrl: './achievement-one-love.component.scss' 14 | }) 15 | export class AchievementOneLoveComponent { 16 | @Input() achievement!: Partial; 17 | } 18 | -------------------------------------------------------------------------------- /server/login-app/git-branch-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | ionicons-v5-d -------------------------------------------------------------------------------- /client/src/app/shared/guards/active-user.guard.ts: -------------------------------------------------------------------------------- 1 | import { lastValueFrom } from 'rxjs'; 2 | import { inject } from '@angular/core'; 3 | import { CanActivateFn, Router } from '@angular/router'; 4 | 5 | import { MeApiService } from '../../services/api/me.service'; 6 | 7 | export const activeUserGuard: CanActivateFn = (route, state) => { 8 | const meApiService = inject(MeApiService); 9 | const router = inject(Router); 10 | 11 | return lastValueFrom(meApiService 12 | .getLoggedInUser()) 13 | .then((user) => { 14 | if (!user) { 15 | return router.navigateByUrl('/login'); 16 | } 17 | return true; 18 | }) 19 | .catch(() => router.navigateByUrl('/login')); 20 | }; 21 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repo-by-fullname-resolver.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ResolveFn } from '@angular/router'; 3 | 4 | import { Repository } from '@kibibit/achievibit-sdk'; 5 | 6 | import { RepositoriesApiService } from '../../services/api/repositories.service'; 7 | 8 | export const repoByFullnameResolver: ResolveFn = (route) => { 9 | const reposApiService = inject(RepositoriesApiService); 10 | const fullname = route.paramMap.get('fullname') as string; 11 | const splittedFullname = fullname.split('/'); 12 | const owner = splittedFullname[0]; 13 | const name = splittedFullname[1]; 14 | 15 | return reposApiService.getRepoByName(owner, name); 16 | }; 17 | -------------------------------------------------------------------------------- /client/src/app/shared/pipes/first-error.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'firstError', 5 | standalone: true 6 | }) 7 | export class FirstErrorPipe implements PipeTransform { 8 | transform(errors: Record | null): string | null { 9 | if (!errors) return null; 10 | 11 | const firstKey = Object.keys(errors)[0]; 12 | const value = errors[firstKey]; 13 | 14 | if (typeof value === 'string') return value; 15 | 16 | if (typeof value === 'object') { 17 | const firstNested = Object.values(value)[0]; 18 | return typeof firstNested === 'string' ? firstNested : null; 19 | } 20 | 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html body { 4 | --kb-primary-font-family: "Comfortaa", sans-serif; 5 | --kb-primary-font-weight: 400; 6 | --kb-primary-font-style: normal; 7 | --kb-secondary-font-family: "Righteous", sans-serif; 8 | --kb-secondary-font-weight: 400; 9 | --kb-secondary-font-style: normal; 10 | } 11 | 12 | html body { 13 | font-family: var(--kb-primary-font-family); 14 | font-weight: var(--kb-primary-font-weight); 15 | font-style: var(--kb-primary-font-style); 16 | } 17 | 18 | .snackbar { 19 | background: transparent; 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } -------------------------------------------------------------------------------- /server/src/auth/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './auth.controller'; 6 | export * from './auth.module'; 7 | export * from './bitbucket/bitbucket.controller'; 8 | export * from './bitbucket/bitbucket.module'; 9 | export * from './bitbucket/bitbucket.strategy'; 10 | export * from './github/github.controller'; 11 | export * from './github/github.module'; 12 | export * from './github/github.strategy'; 13 | export * from './gitlab/gitlab.controller'; 14 | export * from './gitlab/gitlab.module'; 15 | export * from './gitlab/gitlab.strategy'; 16 | export * from './jwt/jwt.module'; 17 | export * from './jwt/jwt.service'; 18 | export * from './jwt/jwt.strategy'; 19 | -------------------------------------------------------------------------------- /client/src/app/pages/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(HomeComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/users/users.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UsersComponent } from './users.component'; 4 | 5 | describe('UsersComponent', () => { 6 | let component: UsersComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ UsersComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UsersComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(HeaderComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/src/systems/systems.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { RepositoriesModule } from '@kb-repositories'; 4 | import { UsersModule } from '@kb-users'; 5 | 6 | import { BitbucketService } from './bitbucket/bitbucket.service'; 7 | import { GithubService } from './github/github.service'; 8 | import { GitlabService } from './gitlab/gitlab.service'; 9 | import { OrganizationsModule } from '@kb-organizations'; 10 | 11 | @Module({ 12 | imports: [ 13 | UsersModule, 14 | RepositoriesModule, 15 | OrganizationsModule 16 | ], 17 | providers: [ GithubService, GitlabService, BitbucketService ], 18 | exports: [ GithubService, GitlabService, BitbucketService ] 19 | }) 20 | export class SystemsModule {} 21 | -------------------------------------------------------------------------------- /server/src/models/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './Integration.entity'; 6 | export * from './achievement.entity'; 7 | export * from './api.model'; 8 | export * from './organization.entity'; 9 | export * from './pr-status.enum'; 10 | export * from './pull-request.entity'; 11 | export * from './repository.entity'; 12 | export * from './system.enum'; 13 | export * from './user-settings.entity'; 14 | export * from './user.entity'; 15 | export * from './authorization/permissions.enum'; 16 | export * from './authorization/role-permissions.map'; 17 | export * from './authorization/roles.enum'; 18 | export * from './pagination/page-meta.model'; 19 | export * from './pagination/page.model'; 20 | -------------------------------------------------------------------------------- /server/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/src/app/shared/mini-game/mini-game.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MiniGameComponent } from './mini-game.component'; 4 | 5 | describe('MiniGameComponent', () => { 6 | let component: MiniGameComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [MiniGameComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(MiniGameComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/users/users-all/users-all.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UsersAllComponent } from './users-all.component'; 4 | 5 | describe('UsersAllComponent', () => { 6 | let component: UsersAllComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ UsersAllComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UsersAllComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'supertest'; 2 | 3 | import { INestApplication } from '@nestjs/common'; 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | 6 | import { AppModule } from './../src/app.module'; 7 | 8 | describe('AppController (e2e)', () => { 9 | let app: INestApplication; 10 | 11 | beforeEach(async () => { 12 | const moduleFixture: TestingModule = await Test.createTestingModule({ 13 | imports: [ AppModule ] 14 | }).compile(); 15 | 16 | app = moduleFixture.createNestApplication(); 17 | await app.init(); 18 | }); 19 | 20 | it('/ (GET)', () => { 21 | return request(app.getHttpServer()) 22 | .get('/') 23 | .expect(200) 24 | .expect('Hello World!'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /client/src/app/pages/app-status/app-status.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AppStatusComponent } from './app-status.component'; 4 | 5 | describe('AppStatusComponent', () => { 6 | let component: AppStatusComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ AppStatusComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AppStatusComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/user-lives/user-lives.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserLivesComponent } from './user-lives.component'; 4 | 5 | describe('UserLivesComponent', () => { 6 | let component: UserLivesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ UserLivesComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UserLivesComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/src/guards/gitlab-webhook/gitlab-webhook.guard.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; 4 | 5 | import { configService } from '@kb-config'; 6 | 7 | @Injectable() 8 | export class GitLabWebhookGuard implements CanActivate { 9 | private readonly secret = configService.config.GITLAB_WEBHOOK_SECRET; 10 | 11 | canActivate(context: ExecutionContext): boolean { 12 | const request = context.switchToHttp().getRequest(); 13 | const token = request.headers['x-gitlab-token'] as string; 14 | 15 | if (!token || token !== this.secret) { 16 | throw new UnauthorizedException('Invalid token'); 17 | } 18 | 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement-one-love/achievement-one-love.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | } 3 | 4 | .achievement { 5 | display: flex; 6 | background: rgba(255, 255, 255, 0.2); 7 | padding: 0.5em; 8 | max-width: 500px; 9 | border-radius: 7px; 10 | margin: 0 auto; 11 | 12 | .achievement__info { 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | 17 | .name { 18 | font-size: 1.5em; 19 | font-weight: bold; 20 | } 21 | 22 | .description { 23 | font-size: 1em; 24 | } 25 | } 26 | 27 | .achievement__icon { 28 | display: flex; 29 | align-items: center; 30 | justify-content: center; 31 | padding: 0.7em; 32 | 33 | img { 34 | width: 50px; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /server/src/auth/jwt/jwt.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { JwtModule as NestJwtModule } from '@nestjs/jwt'; 3 | import { PassportModule } from '@nestjs/passport'; 4 | 5 | import { configService } from '@kb-config'; 6 | import { UsersModule } from '@kb-users'; 7 | 8 | import { JwtService } from './jwt.service'; 9 | import { JwtStrategy } from './jwt.strategy'; 10 | 11 | @Module({ 12 | imports: [ 13 | PassportModule, 14 | NestJwtModule.register({ 15 | secret: configService.config.JWT_SECRET, 16 | signOptions: { expiresIn: '24h' } 17 | }), 18 | UsersModule 19 | ], 20 | providers: [ 21 | JwtStrategy, 22 | JwtService 23 | ], 24 | exports: [ 25 | JwtService 26 | ] 27 | }) 28 | export class JwtModule {} 29 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-overview/profile-overview.component.ts: -------------------------------------------------------------------------------- 1 | import { AsyncPipe, NgIf } from '@angular/common'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | 5 | 6 | @Component({ 7 | selector: 'kb-profile-overview', 8 | standalone: true, 9 | imports: [ NgIf, AsyncPipe ], 10 | templateUrl: './profile-overview.component.html', 11 | styleUrl: './profile-overview.component.scss' 12 | }) 13 | export class ProfileOverviewComponent implements OnInit { 14 | private readonly dataKey = 'user'; 15 | user: any; 16 | 17 | constructor( 18 | private readonly route: ActivatedRoute 19 | ) {} 20 | 21 | ngOnInit() { 22 | this.user = this.route.snapshot.data[this.dataKey]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/user-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserProfileComponent } from './user-profile.component'; 4 | 5 | describe('UserProfileComponent', () => { 6 | let component: UserProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ UserProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UserProfileComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement/achievement.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AchievementComponent } from './achievement.component'; 4 | 5 | describe('AchievementComponent', () => { 6 | let component: AchievementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ AchievementComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AchievementComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RepositoriesComponent } from './repositories.component'; 4 | 5 | describe('RepositoriesComponent', () => { 6 | let component: RepositoriesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ RepositoriesComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(RepositoriesComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/users/user-profile/user-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserProfileComponent } from './user-profile.component'; 4 | 5 | describe('UserProfileComponent', () => { 6 | let component: UserProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ UserProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UserProfileComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/src/session-user/session-user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { RepositoriesModule } from '@kb-repositories'; 4 | import { ShieldsModule } from '@kb-shields'; 5 | import { UsersModule } from '@kb-users'; 6 | 7 | import { SystemsModule } from '../systems/systems.module'; 8 | import { SessionUserController } from './session-user.controller'; 9 | import { SessionUserService } from './session-user.service'; 10 | import { EventsModule } from '@kb-events'; 11 | 12 | @Module({ 13 | imports: [ 14 | SystemsModule, 15 | UsersModule, 16 | RepositoriesModule, 17 | ShieldsModule, 18 | EventsModule 19 | ], 20 | controllers: [ SessionUserController ], 21 | providers: [ SessionUserService ] 22 | }) 23 | export class SessionUserModule {} 24 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-requests.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PullRequestsComponent } from './pull-requests.component'; 4 | 5 | describe('PullRequestsComponent', () => { 6 | let component: PullRequestsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ PullRequestsComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(PullRequestsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/user-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { NgFor, NgIf } from '@angular/common'; 2 | import { Component } from '@angular/core'; 3 | import { ActivatedRoute, RouterModule } from '@angular/router'; 4 | 5 | 6 | @Component({ 7 | selector: 'kb-active-user-profile', 8 | standalone: true, 9 | imports: [ NgIf, NgFor, RouterModule ], 10 | templateUrl: './user-profile.component.html', 11 | styleUrl: './user-profile.component.scss' 12 | }) 13 | export class UserProfileComponent { 14 | activeTab = 'overview'; 15 | 16 | constructor( 17 | private readonly route: ActivatedRoute 18 | ) {} 19 | 20 | getActiveTabFromUrl() { 21 | this.route.firstChild?.url.subscribe((url) => { 22 | this.activeTab = url[0]?.path || 'overview'; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { OrganizationsComponent } from './organizations.component'; 4 | 5 | describe('OrganizationsComponent', () => { 6 | let component: OrganizationsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ OrganizationsComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(OrganizationsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/users/users.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { UserProfileComponent } from './user-profile/user-profile.component'; 4 | import { UsersAllComponent } from './users-all/users-all.component'; 5 | import { userByUsernameResolver } from './user-by-username-resolver'; 6 | import { UsersComponent } from './users.component'; 7 | 8 | export const usersRoutes: Routes = [ 9 | { 10 | path: 'users', 11 | component: UsersComponent, 12 | children: [ 13 | { 14 | path: '', 15 | component: UsersAllComponent 16 | }, 17 | { 18 | path: ':username', 19 | component: UserProfileComponent, 20 | resolve: { 21 | user: userByUsernameResolver 22 | } 23 | } 24 | ] 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /client/src/app/shared/an-achievement/an-achievement.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AnAchievementComponent } from './an-achievement.component'; 4 | 5 | describe('AnAchievementComponent', () => { 6 | let component: AnAchievementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [AnAchievementComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AnAchievementComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/achievibit-logo/achievibit-logo.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AchievibitLogoComponent } from './achievibit-logo.component'; 4 | 5 | describe('AchievibitLogoComponent', () => { 6 | let component: AchievibitLogoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ AchievibitLogoComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AchievibitLogoComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/directives/has-role.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; 2 | 3 | import { UserRolesEnum } from '@kibibit/achievibit-sdk'; 4 | 5 | import { AuthService } from '../../services/auth.service'; 6 | 7 | @Directive({ 8 | selector: '[kbHasRole]', 9 | standalone: true 10 | }) 11 | export class KbHasRoleDirective { 12 | @Input() set hasRole(role: keyof typeof UserRolesEnum) { 13 | const has = this.auth.hasRole(UserRolesEnum[role]); 14 | if (has) { 15 | this.vc.createEmbeddedView(this.templateRef); 16 | } else { 17 | this.vc.clear(); 18 | } 19 | } 20 | 21 | constructor( 22 | private auth: AuthService, 23 | private templateRef: TemplateRef, 24 | private vc: ViewContainerRef 25 | ) {} 26 | } 27 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-request-all/pull-request-all.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PullRequestAllComponent } from './pull-request-all.component'; 4 | 5 | describe('PullRequestAllComponent', () => { 6 | let component: PullRequestAllComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ PullRequestAllComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(PullRequestAllComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/user-lives/user-lives.component.ts: -------------------------------------------------------------------------------- 1 | import { NgFor } from '@angular/common'; 2 | import { Component, Input } from '@angular/core'; 3 | 4 | 5 | @Component({ 6 | selector: 'kb-user-lives', 7 | standalone: true, 8 | imports: [ NgFor ], 9 | templateUrl: './user-lives.component.html', 10 | styleUrl: './user-lives.component.scss' 11 | }) 12 | export class UserLivesComponent { 13 | private internalCurrentLives = 4; 14 | totalLives = 4; 15 | emptyLives = 0; 16 | // currentLives getter and setter 17 | @Input() 18 | get currentLives() { 19 | return this.internalCurrentLives; 20 | } 21 | set currentLives(value: number) { 22 | this.internalCurrentLives = value; 23 | const lifeDelta = this.totalLives - value; 24 | this.emptyLives = lifeDelta > 0 ? lifeDelta : 0; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/scripts/to-env.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'dotenv'; 2 | import { readJsonSync, writeFileSync } from 'fs-extra'; 3 | import { omit } from 'lodash'; 4 | 5 | class EnvService { 6 | fromJson(object: Record) { 7 | let envFile = ''; 8 | for (const key of Object.keys(object)) { 9 | envFile += `${ key }="${ object[key] }"\n`; 10 | } 11 | return envFile; 12 | } 13 | 14 | toJson(envFile: string) { 15 | return parse(envFile); 16 | } 17 | } 18 | 19 | (async () => { 20 | const envService = new EnvService(); 21 | 22 | const NODE_ENV = process.env.NODE_ENV || 'development'; 23 | const jsonEnv = readJsonSync(`./.env.${ NODE_ENV }.achievibit.json`); 24 | const envFile = envService.fromJson(omit(jsonEnv, '$schema')); 25 | 26 | writeFileSync('./../.env', envFile); 27 | })(); 28 | 29 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories-all/repositories-all.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RepositoriesAllComponent } from './repositories-all.component'; 4 | 5 | describe('RepositoriesAllComponent', () => { 6 | let component: RepositoriesAllComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ RepositoriesAllComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(RepositoriesAllComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-overview/profile-overview.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileOverviewComponent } from './profile-overview.component'; 4 | 5 | describe('ProfileOverviewComponent', () => { 6 | let component: ProfileOverviewComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ ProfileOverviewComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ProfileOverviewComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-settings/profile-settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileSettingsComponent } from './profile-settings.component'; 4 | 5 | describe('ProfileSettingsComponent', () => { 6 | let component: ProfileSettingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ ProfileSettingsComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ProfileSettingsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations-all/organizations-all.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { OrganizationsAllComponent } from './organizations-all.component'; 4 | 5 | describe('OrganizationsAllComponent', () => { 6 | let component: OrganizationsAllComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ OrganizationsAllComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(OrganizationsAllComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [ main, master ] 5 | pull_request: 6 | branches: [ main, master ] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: lts/* 16 | - name: Install dependencies 17 | run: npm install -g pnpm && pnpm install 18 | - name: Install Playwright Browsers 19 | run: pnpm exec playwright install --with-deps 20 | - name: Run Playwright tests 21 | run: pnpm exec playwright test 22 | - uses: actions/upload-artifact@v4 23 | if: ${{ !cancelled() }} 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 28 | -------------------------------------------------------------------------------- /client/src/app/shared/minidenticon-avatar/minidenticon-avatar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MinidenticonAvatarComponent } from './minidenticon-avatar.component'; 4 | 5 | describe('MinidenticonAvatarComponent', () => { 6 | let component: MinidenticonAvatarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [MinidenticonAvatarComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(MinidenticonAvatarComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repository-profile/repository-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RepositoryProfileComponent } from './repository-profile.component'; 4 | 5 | describe('RepositoryProfileComponent', () => { 6 | let component: RepositoryProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ RepositoryProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(RepositoryProfileComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/shared/achievement-one-love/achievement-one-love.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AchievementOneLoveComponent } from './achievement-one-love.component'; 4 | 5 | describe('AchievementOneLoveComponent', () => { 6 | let component: AchievementOneLoveComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ AchievementOneLoveComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AchievementOneLoveComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /achievements/src/dev-tools/mocks.ts: -------------------------------------------------------------------------------- 1 | export class Shall { 2 | grantedAchievements: { [username: string]: any }; 3 | 4 | grant(username, achievementObject) { 5 | this.grantedAchievements = this.grantedAchievements || {}; 6 | this.grantedAchievements[username] = achievementObject; 7 | } 8 | } 9 | 10 | export class PullRequest { 11 | title = 'this is a happy little title'; 12 | id = 'test'; 13 | number: number; 14 | url = 'url'; 15 | organization: { username: string }; 16 | description = ''; 17 | creator = { 18 | username: 'creator' 19 | }; 20 | reviewers = [ { 21 | 'username': 'reviewer' 22 | } ]; 23 | merged: any; 24 | reviews: any; 25 | comments: any[]; 26 | inlineComments: any[]; 27 | reactions: any[]; 28 | commits: any[]; 29 | labels: string[]; 30 | createdOn: Date; 31 | files: { name: string }[]; 32 | } 33 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-request-profile/pull-request-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PullRequestProfileComponent } from './pull-request-profile.component'; 4 | 5 | describe('PullRequestProfileComponent', () => { 6 | let component: PullRequestProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ PullRequestProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(PullRequestProfileComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organization-profile/organization-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { OrganizationProfileComponent } from './organization-profile.component'; 4 | 5 | describe('OrganizationProfileComponent', () => { 6 | let component: OrganizationProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ OrganizationProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(OrganizationProfileComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-integrations/profile-integrations.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileIntegrationsComponent } from './profile-integrations.component'; 4 | 5 | describe('ProfileIntegrationsComponent', () => { 6 | let component: ProfileIntegrationsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ ProfileIntegrationsComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ProfileIntegrationsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/src/events/events.service.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Inject, Injectable } from '@nestjs/common'; 2 | import { EventsGateway } from './events.gateway'; 3 | import { Achievement } from '@kb-models'; 4 | 5 | @Injectable() 6 | export class EventsService { 7 | constructor( 8 | @Inject(forwardRef(() => EventsGateway)) 9 | private readonly eventsGateway: EventsGateway 10 | ) {} 11 | 12 | sendAchievementToOrg(orgName: string, achievement: Partial) { 13 | this.eventsGateway.sendAchievementToOrg(orgName, achievement); 14 | } 15 | 16 | sendAchievementToRepo(repoId: string, achievement: Partial) { 17 | this.eventsGateway.sendAchievementToRepo(repoId, achievement); 18 | } 19 | 20 | sendAchievementToUser(username: string, achievement: Partial) { 21 | this.eventsGateway.sendAchievementToUser(username, achievement); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:jammy 2 | 3 | # Install chromium 4 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 5 | && apt-get -y install --no-install-recommends chromium-browser 6 | 7 | # fix user permissions 8 | RUN usermod -aG sudo node 9 | 10 | # [Optional] Uncomment this section to install additional OS packages. 11 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 12 | # && apt-get -y install --no-install-recommends 13 | 14 | # [Optional] Uncomment if you want to install an additional version of node using nvm 15 | # ARG EXTRA_NODE_VERSION=10 16 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 17 | 18 | # [Optional] Uncomment if you want to install more global node modules 19 | # RUN su node -c "npm install -g " 20 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organizations.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { OrganizationProfileComponent } from './organization-profile/organization-profile.component'; 4 | import { OrganizationsAllComponent } from './organizations-all/organizations-all.component'; 5 | import { orgByNameResolver } from './org-by-name-resolver'; 6 | import { OrganizationsComponent } from './organizations.component'; 7 | 8 | export const organizationsRoutes: Routes = [ 9 | { 10 | path: 'orgs', 11 | component: OrganizationsComponent, 12 | children: [ 13 | { 14 | path: '', 15 | component: OrganizationsAllComponent 16 | }, 17 | { 18 | path: ':name', 19 | component: OrganizationProfileComponent, 20 | resolve: { 21 | organization: orgByNameResolver 22 | } 23 | } 24 | ] 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /client/src/app/pages/repositories/repositories.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { RepositoriesAllComponent } from './repositories-all/repositories-all.component'; 4 | import { RepositoryProfileComponent } from './repository-profile/repository-profile.component'; 5 | import { repoByFullnameResolver } from './repo-by-fullname-resolver'; 6 | import { RepositoriesComponent } from './repositories.component'; 7 | 8 | export const repositoriesRoutes: Routes = [ 9 | { 10 | path: 'repos', 11 | component: RepositoriesComponent, 12 | children: [ 13 | { 14 | path: '', 15 | component: RepositoriesAllComponent 16 | }, 17 | { 18 | path: ':fullname', 19 | component: RepositoryProfileComponent, 20 | resolve: { 21 | repository: repoByFullnameResolver 22 | } 23 | } 24 | ] 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/never-go-full-retard.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`neverGoFullRetard achievement should be granted for all supported files 1`] = ` 4 | { 5 | "avatar": "images/achievements/neverGoFullRetard.achievement.png", 6 | "description": "merged a pull request containing only pictures. pretty!", 7 | "name": "never go full retard", 8 | "relatedPullRequest": "test", 9 | "short": "Nigga, You Just Went Full Retard", 10 | } 11 | `; 12 | 13 | exports[`neverGoFullRetard achievement should be granted to creator and reviewers 1`] = ` 14 | { 15 | "avatar": "images/achievements/neverGoFullRetard.achievement.png", 16 | "description": "merged a pull request containing only pictures. pretty!", 17 | "name": "never go full retard", 18 | "relatedPullRequest": "test", 19 | "short": "Nigga, You Just Went Full Retard", 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /server/src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | import { Controller, Get, Req, UseGuards } from '@nestjs/common'; 4 | import { ApiBearerAuth, ApiCookieAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; 5 | 6 | import { JwtAuthGuard } from '../guards/jwt-auth.guard'; 7 | 8 | @Controller('api/auth') 9 | @ApiTags('Authentication') 10 | export class AuthController { 11 | @Get('logout') 12 | @UseGuards(JwtAuthGuard) 13 | @ApiBearerAuth() 14 | @ApiCookieAuth() 15 | @ApiOperation({ 16 | summary: 'Logout', 17 | description: [ 18 | 'Clears the `kibibit-jwt` cookie. ', 19 | 'This effectively logs out the user. ', 20 | '**Note that the JWT token is still valid until it expires.**' 21 | ].join('') 22 | }) 23 | logout(@Req() req: Request) { 24 | req.res.clearCookie('kibibit-jwt'); 25 | return { message: 'Logged out' }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sdk/src/sockets/server-to-client.events.ts: -------------------------------------------------------------------------------- 1 | import { IEventMapBase, IServerToClientEventShape } from './base-event-map.type'; 2 | 3 | export enum ServerToClientEvent { 4 | Connect = 'connect', 5 | Disconnect = 'disconnect', 6 | Reconnect = 'reconnect', 7 | MessageReceived = 'message:received', 8 | userLives = 'user:lives:updated', 9 | userLevel = 'user:level:updated', 10 | userXP = 'user:xp:updated', 11 | SystemAlert = 'system:alert', 12 | MiniGameStarted = 'mini-game:started', 13 | MiniGameEnded = 'mini-game:ended', 14 | MiniGameUpdated = 'mini-game:updated' 15 | } 16 | 17 | export interface IServerToClientEventMap extends IEventMapBase { 18 | [ServerToClientEvent.MessageReceived]: { 19 | payload: { from: string; content: string }; 20 | }; 21 | [ServerToClientEvent.SystemAlert]: { 22 | payload: { message: string; type: 'info' | 'warning' | 'error' }; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /server/src/decorators/is-safe-query.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registerDecorator, 3 | ValidationOptions, 4 | ValidationArguments, 5 | } from 'class-validator'; 6 | 7 | export function IsSafeQuery(validationOptions?: ValidationOptions) { 8 | return function (object: Object, propertyName: string) { 9 | registerDecorator({ 10 | name: 'isSafeQuery', 11 | target: object.constructor, 12 | propertyName: propertyName, 13 | options: validationOptions, 14 | validator: { 15 | validate(value: any, args: ValidationArguments) { 16 | const unsafePatterns = /('|;|--|\/\*|\*\/|SELECT|DROP|INSERT|UNION|UPDATE|DELETE)/i; 17 | return typeof value === 'string' && !unsafePatterns.test(value); 18 | }, 19 | defaultMessage(args: ValidationArguments) { 20 | return 'Query contains potentially harmful input'; 21 | }, 22 | }, 23 | }); 24 | }; 25 | } -------------------------------------------------------------------------------- /achievements/src/label-baby-junior.achievement.ts: -------------------------------------------------------------------------------- 1 | import { IAchievement } from './achievement.abstract'; 2 | 3 | export const labelBabyJunior: IAchievement = { 4 | name: 'Label Baby Junior', 5 | check: function(pullRequest, shall) { 6 | if (isManyLabels(pullRequest)) { 7 | const achievement = { 8 | avatar: 'images/achievements/labelBabyJunior.achievement.jpg', 9 | name: 'The Label Maker', 10 | short: 'Is this a label maker?', 11 | description: [ 12 | 'You\'ve put many labels, thank you for organizing. ', 13 | 'You\'re a gift that keeps on re-giving' 14 | ].join(''), 15 | relatedPullRequest: pullRequest.id 16 | }; 17 | 18 | shall.grant(pullRequest.creator.username, achievement); 19 | } 20 | } 21 | }; 22 | 23 | function isManyLabels(pullRequest) { 24 | const labels = pullRequest.labels; 25 | return labels && labels.length > 5; 26 | } 27 | -------------------------------------------------------------------------------- /client/src/app/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { User, UserPermissionsEnum, UserRolesEnum } from '@kibibit/achievibit-sdk'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | private currentUserSubject = new BehaviorSubject(null); 11 | currentUser$ = this.currentUserSubject.asObservable(); 12 | 13 | setUser(user: User) { 14 | this.currentUserSubject.next(user); 15 | } 16 | 17 | getCurrentUser(): User | null { 18 | return this.currentUserSubject.value; 19 | } 20 | 21 | hasRole(role: UserRolesEnum): boolean { 22 | return this.currentUserSubject.value?.roles.includes(role) ?? false; 23 | } 24 | 25 | hasPermission(permission: UserPermissionsEnum): boolean { 26 | return this.currentUserSubject.value?.permissions.includes(permission) ?? false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/app/shared/achievibit-logo/achievibit-logo.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | position: relative; 4 | margin-right: 1em; 5 | } 6 | 7 | button.how-to-say { 8 | position: absolute; 9 | top: 0; 10 | right: 0; 11 | scale: 50%; 12 | transform: translate3d(calc(2* 90%), calc(-2* 60%), 0); 13 | } 14 | 15 | .logo { 16 | height: 30px; 17 | margin-right: 0.2em; 18 | } 19 | 20 | .logo-badge { 21 | left: 0; 22 | transform: translate3d(50px, -35px, 0); 23 | opacity: 0.5; 24 | transition: all 0.5s ease-in-out; 25 | transform-origin: center; 26 | color: #FF6684 !important; 27 | 28 | &.blink { 29 | // blink and stop 30 | animation: blinkTwice 1s forwards; 31 | } 32 | 33 | &.kb-green { 34 | color: #47C68E !important; 35 | } 36 | } 37 | 38 | @keyframes blinkTwice { 39 | 0%, 40 | 50%, 41 | 100% { 42 | opacity: 0.5; 43 | } 44 | 25%, 45 | 75% { 46 | opacity: 1; 47 | } 48 | } -------------------------------------------------------------------------------- /server/src/custom-socket-io.adapter.ts: -------------------------------------------------------------------------------- 1 | import { Server, ServerOptions } from 'socket.io'; 2 | import { instrument } from '@socket.io/admin-ui'; 3 | 4 | import { IoAdapter } from '@nestjs/platform-socket.io'; 5 | 6 | export class CustomSocketIoAdapter extends IoAdapter { 7 | createIOServer(port: number, options?: ServerOptions): Server { 8 | options = { 9 | ...options, 10 | // Use default path or adjust as needed 11 | path: '/socket.io', 12 | cors: { 13 | // Adjust for your security requirements 14 | origin: '*', 15 | methods: [ 'GET', 'POST' ] 16 | } 17 | }; 18 | 19 | const server = super.createIOServer(port, options); 20 | 21 | instrument(server, { 22 | // Adjust authentication as needed 23 | auth: false, 24 | mode: 'development' 25 | // Specify the namespace if needed 26 | // namespaceName: '/admin', 27 | }); 28 | 29 | return server; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/src/decorators/api-ok-response-paginated.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, Type } from '@nestjs/common'; 2 | import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger'; 3 | 4 | import { PageModel } from '@kb-models'; 5 | 6 | export const ApiOkResponsePaginated = >( 7 | dataModel: DataModel, 8 | description?: string 9 | ) => 10 | applyDecorators( 11 | ApiExtraModels(PageModel, dataModel), 12 | ApiOkResponse({ 13 | description: description || `A paginated list of type ${ dataModel.name }`, 14 | schema: { 15 | allOf: [ 16 | { $ref: getSchemaPath(PageModel) }, 17 | { 18 | properties: { 19 | data: { 20 | type: 'array', 21 | items: { $ref: getSchemaPath(dataModel) } 22 | } 23 | } 24 | } 25 | ] 26 | } 27 | }) 28 | ); 29 | -------------------------------------------------------------------------------- /achievements/src/cutting-edges.achievement.ts: -------------------------------------------------------------------------------- 1 | import { some } from 'lodash'; 2 | 3 | import { IAchievement, IUserAchievement } from './achievement.abstract'; 4 | 5 | export const cuttingEdge: IAchievement = { 6 | name: 'Cutting Edges', 7 | check: function(pullRequest, shall) { 8 | if (pullRequest.merged) { 9 | const anyApprovals = some(pullRequest.reviews, function(review) { 10 | return review.state === 'APPROVED'; 11 | }); 12 | 13 | if (!anyApprovals) { 14 | const achieve: IUserAchievement = { 15 | avatar: 'images/achievements/cuttingEdges.achievement.jpg', 16 | name: 'Cutting Edges', 17 | short: 'Cutting corners? I also like to live dangerously', 18 | description: 19 | 'You\'ve merged a pull request without a reviewer confirming', 20 | relatedPullRequest: pullRequest.id 21 | }; 22 | 23 | shall.grant(pullRequest.creator.username, achieve); 24 | } 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /achievements/src/member.achievement.spec.ts: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon'; 2 | 3 | import { PullRequest, Shall } from './dev-tools/mocks'; 4 | import { member } from './member.achievement'; 5 | 6 | describe('member achievement', () => { 7 | it('should not be granted if PR opened less than 2 weeks ago', () => { 8 | const testShall = new Shall(); 9 | const pullRequest = new PullRequest(); 10 | 11 | pullRequest.createdOn = DateTime.now().minus({ days: 13 }).toJSDate(); 12 | 13 | member.check(pullRequest, testShall); 14 | expect(testShall.grantedAchievements).toBeUndefined(); 15 | }); 16 | 17 | it('should be granted if PR opened more than 2 weeks ago', () => { 18 | const testShall = new Shall(); 19 | const pullRequest = new PullRequest(); 20 | 21 | pullRequest.createdOn = DateTime.now().minus({ days: 15 }).toJSDate(); 22 | 23 | member.check(pullRequest, testShall); 24 | expect(testShall.grantedAchievements.creator).toMatchSnapshot(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /client/logout-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sdk/src/generated/SocketIo.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | /* 4 | * --------------------------------------------------------------- 5 | * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## 6 | * ## ## 7 | * ## AUTHOR: acacode ## 8 | * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## 9 | * --------------------------------------------------------------- 10 | */ 11 | 12 | import { HttpClient, RequestParams } from "./http-client"; 13 | 14 | export class SocketIo extends HttpClient { 15 | /** 16 | * No description 17 | * 18 | * @name AppControllerGetSocketIo 19 | * @request GET:/socket.io 20 | */ 21 | appControllerGetSocketIo = (params: RequestParams = {}) => 22 | this.request({ 23 | path: `/socket.io`, 24 | method: "GET", 25 | ...params, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "outDir": "./dist/out-tsc", 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "ES2022", 20 | "useDefineForClassFields": false, 21 | "lib": [ 22 | "ES2022", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/login-app/logout-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .env.*.json 2 | *.private-key.pem 3 | 4 | .pnpm-store 5 | 6 | # compiled output 7 | /dist 8 | /node_modules 9 | /build 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | pnpm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | 20 | # OS 21 | .DS_Store 22 | 23 | # Tests 24 | /coverage 25 | /.nyc_output 26 | 27 | # IDEs and editors 28 | /.idea 29 | .project 30 | .classpath 31 | .c9/ 32 | *.launch 33 | .settings/ 34 | *.sublime-workspace 35 | 36 | # IDE - VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | 43 | # dotenv environment variable files 44 | .env 45 | .env.development.local 46 | .env.test.local 47 | .env.production.local 48 | .env.local 49 | 50 | # temp directory 51 | .temp 52 | .tmp 53 | 54 | # Runtime data 55 | pids 56 | *.pid 57 | *.seed 58 | *.pid.lock 59 | 60 | # Diagnostic reports (https://nodejs.org/api/report.html) 61 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 62 | -------------------------------------------------------------------------------- /server/src/decorators/req-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | 3 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 4 | 5 | import { configService } from '@kb-config'; 6 | 7 | // Custom decorator to fetch req.user and pass it to the controller method 8 | export const ReqUser = createParamDecorator( 9 | (data: unknown, ctx: ExecutionContext) => { 10 | const request = ctx.switchToHttp().getRequest(); 11 | 12 | if (request.user) { 13 | return request.user; 14 | } 15 | 16 | const kibibitJwtFromCookie = request.cookies['kibibit-jwt']; 17 | 18 | if (!kibibitJwtFromCookie) { 19 | return null; 20 | } 21 | 22 | try { 23 | // get user details from jwt token by decoding it 24 | // and attach it to the request object 25 | const userFromJwt = jwt.verify(kibibitJwtFromCookie, configService.config.JWT_SECRET); 26 | 27 | return userFromJwt; 28 | } catch (error) { 29 | return null; 30 | } 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /client/src/app/pages/pull-requests/pull-requests.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { UserRolesEnum } from '@kibibit/achievibit-sdk'; 4 | 5 | import { PullRequestAllComponent } from './pull-request-all/pull-request-all.component'; 6 | import { PullRequestProfileComponent } from './pull-request-profile/pull-request-profile.component'; 7 | import { prByIdResolver } from './pr-by-id-resolver'; 8 | import { PullRequestsComponent } from './pull-requests.component'; 9 | 10 | export const pullRequestsRoutes: Routes = [ 11 | { 12 | path: 'prs', 13 | component: PullRequestsComponent, 14 | // canActivate: [ roleGuard ], 15 | data: { roles: [ UserRolesEnum.Admin ] }, 16 | children: [ 17 | { 18 | path: '', 19 | component: PullRequestAllComponent 20 | }, 21 | { 22 | path: ':id', 23 | component: PullRequestProfileComponent, 24 | resolve: { 25 | pr: prByIdResolver 26 | } 27 | } 28 | ] 29 | } 30 | ]; 31 | -------------------------------------------------------------------------------- /server/src/auth/jwt/jwt.service.ts: -------------------------------------------------------------------------------- 1 | import { instanceToPlain } from 'class-transformer'; 2 | 3 | import { Injectable } from '@nestjs/common'; 4 | import { JwtService as NestJwtService } from '@nestjs/jwt'; 5 | 6 | import { configService } from '@kb-config'; 7 | import { SystemEnum, User } from '@kb-models'; 8 | import { UsersService } from '@kb-users'; 9 | 10 | @Injectable() 11 | export class JwtService { 12 | constructor( 13 | private usersService: UsersService, 14 | private nestJwtService: NestJwtService 15 | ) {} 16 | 17 | generateAccessToken(user: User) { 18 | const sanitizedUser = instanceToPlain(new User(user)); 19 | 20 | return { 21 | accessToken: this.nestJwtService.sign(sanitizedUser, { 22 | secret: configService.config.JWT_SECRET 23 | }) 24 | }; 25 | } 26 | 27 | async validateOauthUser(username: string, system: SystemEnum = SystemEnum.GITHUB) { 28 | const user = await this.usersService.findOneByIntegration(username, system); 29 | 30 | return user; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /achievements/src/use-github-bot.achievement.ts: -------------------------------------------------------------------------------- 1 | import { find } from 'lodash'; 2 | 3 | import { IAchievement, IUserAchievement } from './achievement.abstract'; 4 | 5 | export const githubBot: IAchievement = { 6 | name: 'use github bot', 7 | check: function(pullRequest, shall) { 8 | const isComitterGitHubWebFlow = find(pullRequest.commits, { 9 | committer: { 10 | username: 'web-flow' 11 | } 12 | }); 13 | if (pullRequest.commits && 14 | pullRequest.commits.length > 0 && 15 | isComitterGitHubWebFlow) { 16 | const achieve: IUserAchievement = { 17 | avatar: 'images/achievements/useGithubBot.achievement.jpeg', 18 | name: 'Why not bots?', 19 | short: 'Hey sexy mama, wanna kill all humans?', 20 | description: [ 21 | 'used github to create a pull request, using the web-flow bot' 22 | ].join(''), 23 | relatedPullRequest: pullRequest.id 24 | }; 25 | 26 | shall.grant(pullRequest.creator.username, achieve); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/double-review.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`doubleReview achievement should be granted if 2 reviewers excluding creator 1`] = ` 4 | { 5 | "avatar": "images/achievements/doubleReview.achievement.gif", 6 | "description": "double headed code review.
It doesn't matter who added you, apparently, both of you are needed for a one man job 😇", 7 | "name": "We're ready, master", 8 | "relatedPullRequest": "test", 9 | "short": ""This way!"-"No, that way!"", 10 | } 11 | `; 12 | 13 | exports[`doubleReview achievement should be granted if 2 reviewers excluding creator 2`] = ` 14 | { 15 | "avatar": "images/achievements/doubleReview.achievement.gif", 16 | "description": "double headed code review.
It doesn't matter who added you, apparently, both of you are needed for a one man job 😇", 17 | "name": "We're ready, master", 18 | "relatedPullRequest": "test", 19 | "short": ""This way!"-"No, that way!"", 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /achievements/src/member.achievement.ts: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon'; 2 | 3 | import { IAchievement } from './achievement.abstract'; 4 | 5 | export const member: IAchievement = { 6 | name: 'Member?', 7 | check: function(pullRequest, shall) { 8 | if (isWaitingLongTime(pullRequest)) { 9 | const achieve = { 10 | avatar: 'images/achievements/member.achievement.jpg', 11 | name: 'Member pull request #' + pullRequest.number + '?', 12 | short: 'Member Commits? member Push? member PR? ohh I member', 13 | description: [ 14 | 'A pull request you\'ve created 2 weeks ago', 15 | ' is finally merged' 16 | ].join(''), 17 | relatedPullRequest: pullRequest.id 18 | }; 19 | 20 | shall.grant(pullRequest.creator.username, achieve); 21 | } 22 | } 23 | }; 24 | 25 | function isWaitingLongTime(pullRequest) { 26 | const backThen = DateTime.fromJSDate(pullRequest.createdOn); 27 | const now = DateTime.now(); 28 | 29 | return now.diff(backThen, 'days').days >= 14; 30 | } 31 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: .. 5 | dockerfile: .devcontainer/Dockerfile 6 | volumes: 7 | - ..:/workspace:cached 8 | # Exclude node_modules directories 9 | - /workspace/node_modules 10 | - /workspace/client/node_modules 11 | - /workspace/server/node_modules 12 | command: sleep infinity 13 | # ports: 14 | # - '10102:10102' 15 | # - '10101:10101' 16 | environment: 17 | POSTGRES_HOST: db 18 | POSTGRES_PORT: 5432 19 | POSTGRES_USER: devuser 20 | POSTGRES_PASSWORD: secretpassword 21 | POSTGRES_DATABASE: achievibit 22 | PUPPETEER_SKIP_DOWNLOAD: false 23 | HUSKY: 1 24 | depends_on: 25 | - db 26 | 27 | db: 28 | image: postgres:15-alpine 29 | environment: 30 | DATABASE_PORT: 5432 31 | POSTGRES_USER: devuser 32 | POSTGRES_PASSWORD: secretpassword 33 | POSTGRES_DB: achievibit 34 | volumes: 35 | - db-data:/var/lib/postgresql/data 36 | 37 | volumes: 38 | db-data: 39 | -------------------------------------------------------------------------------- /achievements/src/the-godfather-consigliere.achievement.ts: -------------------------------------------------------------------------------- 1 | import { result } from 'lodash'; 2 | 3 | import { IAchievement, IUserAchievement } from './achievement.abstract'; 4 | 5 | export const theGodfatherConsigliere: IAchievement = { 6 | name: 'The Godfather Consigliere', 7 | check: function(pullRequest, shall) { 8 | if (result(pullRequest, 'organization.username') === 'Kibibit') { 9 | const achievement: IUserAchievement = { 10 | avatar: 'images/achievements/theGodfatherConsigliere.achievement.jpg', 11 | name: 'The Godfather Consigliere', 12 | short: 'Great men are not born great, they contribute to Kibibit . . .', 13 | description: [ 14 | '

You have contributed to Kibibit! We really ', 15 | 'appreciate it!

', 16 | '

Accept this achievement as gift on ', 17 | 'my daughter\'s wedding day

' 18 | ].join(''), 19 | relatedPullRequest: pullRequest.id 20 | }; 21 | 22 | shall.grant(pullRequest.creator.username, achievement); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ AppComponent ] 9 | }).compileComponents(); 10 | }); 11 | 12 | it('should create the app', () => { 13 | const fixture = TestBed.createComponent(AppComponent); 14 | const app = fixture.componentInstance; 15 | expect(app).toBeTruthy(); 16 | }); 17 | 18 | it('should have the \'client\' title', () => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.componentInstance; 21 | expect(app.title).toEqual('client'); 22 | }); 23 | 24 | it('should render title', () => { 25 | const fixture = TestBed.createComponent(AppComponent); 26 | fixture.detectChanges(); 27 | const compiled = fixture.nativeElement as HTMLElement; 28 | expect(compiled.querySelector('h1')?.textContent).toContain('Hello, client'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /server/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { SmeeService } from '@kb-config'; 6 | 7 | jest.mock('fs-extra', () => ({ 8 | readJSON: jest.fn().mockResolvedValue({ 9 | name: 'test', 10 | description: 'test', 11 | version: 'test', 12 | license: 'test', 13 | repository: 'test', 14 | }) 15 | })); 16 | 17 | describe('AppController', () => { 18 | let appController: AppController; 19 | 20 | beforeEach(async () => { 21 | const app: TestingModule = await Test.createTestingModule({ 22 | controllers: [ AppController ], 23 | providers: [ 24 | AppService, 25 | SmeeService 26 | ] 27 | }).compile(); 28 | 29 | appController = app.get(AppController); 30 | }); 31 | 32 | describe('root', () => { 33 | it('should return "Hello World!"', async () => { 34 | expect(appController.getApiDetails()).resolves.toMatchSnapshot(); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /server/src/guards/bitbucket-webhook/bitbucket-webhook.guard.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | import { Request } from 'express'; 4 | 5 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; 6 | 7 | import { configService } from '@kb-config'; 8 | 9 | @Injectable() 10 | export class BitbucketWebhookGuard implements CanActivate { 11 | private readonly secret = configService.config.BITBUCKET_WEBHOOK_SECRET; 12 | 13 | canActivate(context: ExecutionContext): boolean { 14 | const request = context.switchToHttp().getRequest(); 15 | const signature = request.headers['x-hub-signature'] as string; 16 | 17 | if (!signature) { 18 | throw new UnauthorizedException('No signature provided'); 19 | } 20 | 21 | const body = JSON.stringify(request.body); 22 | const hmac = crypto.createHmac('sha256', this.secret); 23 | const digest = hmac.update(body).digest('hex'); 24 | 25 | if (digest !== signature) { 26 | throw new UnauthorizedException('Invalid signature'); 27 | } 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/src/guards/github-webhook/github-webhook.guard.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | import { Request } from 'express'; 4 | 5 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; 6 | 7 | import { configService } from '@kb-config'; 8 | 9 | @Injectable() 10 | export class GitHubWebhookGuard implements CanActivate { 11 | private readonly secret = configService.config.GITHUB_WEBHOOK_SECRET; 12 | 13 | canActivate(context: ExecutionContext): boolean { 14 | const request = context.switchToHttp().getRequest(); 15 | const signature = request.headers['x-hub-signature-256'] as string; 16 | 17 | if (!signature) { 18 | throw new UnauthorizedException('No signature provided'); 19 | } 20 | 21 | const body = JSON.stringify(request.body); 22 | const hmac = crypto.createHmac('sha256', this.secret); 23 | const digest = 'sha256=' + hmac.update(body).digest('hex'); 24 | 25 | if (digest !== signature) { 26 | throw new UnauthorizedException('Invalid signature'); 27 | } 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /achievements/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './achievement.abstract'; 6 | export * from './bi-winning.achievement'; 7 | export * from './breaking-bad.achievement'; 8 | export * from './cutting-edges.achievement'; 9 | export * from './dont-yell-at-me.achievement'; 10 | export * from './double-review.achievement'; 11 | export * from './dr-claw.achievement'; 12 | export * from './helping-hand.achievement'; 13 | export * from './inspector-gadget.achievement'; 14 | export * from './label-baby-junior.achievement'; 15 | export * from './meeseek.achievement'; 16 | export * from './member.achievement'; 17 | export * from './mr-miyagi.achievement'; 18 | export * from './never-go-full-retard.achievement'; 19 | export * from './optimus-prime.achievement'; 20 | export * from './reaction-on-every-comment.achievement'; 21 | export * from './the-godfather-consigliere.achievement'; 22 | export * from './use-github-bot.achievement'; 23 | export * from './used-all-reactions-in-comment.achievement'; 24 | export * from './dev-tools/mocks'; 25 | export * from './dev-tools/utils'; 26 | -------------------------------------------------------------------------------- /server/src/logo.ts: -------------------------------------------------------------------------------- 1 | import { bgMagenta, blue, bold, red, yellow } from 'colors'; 2 | 3 | export const logo = bold(` 4 | ⣶⣶⣶ ${ red('⣰⣪⡳⣄') } ⣶⣶⣶ ${ blue('⣰⣪⡳⣄') } ⣶⣶⣶ ${ yellow('⣰⣪⡳⣄') } ⣶⣶⣶ 5 | ⣿⣿⣿ ${ red('⠪⣖⣝⠎') } ⣿⣿⣿ ${ blue('⠪⣖⣝⠎') } ⣿⣿⣿ ${ yellow('⠪⣖⣝⠎') } ⣿⣿⣿ 6 | ⣿⣿⣿ ⣀⣀⣀ ${ red('⣀⢀⡀') } ⢸⣿⣿⣁⣠⣤⣤⣤⣀ ${ blue('⣀⢀⡀') } ⣿⣿⣿⣁⣠⣤⣤⣤⣀ ${ yellow('⣀⢀⡀') } ⣿⣿⣿ 7 | ⣿⣿⣿ ⢀⣾⣿⣿⠋ ${ red('⡸⡵⡅') } ⢸⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ ${ blue('⡸⡵⡅') } ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ ${ yellow('⡸⡵⡅') } ⣺⣿⣿⣿⣿⣿⣿⣿⣿ 8 | ⣿⣿⣿ ⣰⣿⣿⡟⠁ ${ red('⣝⢮⡂') } ⢸⣿⣿⡿⠋⠁⠈⠙⢿⣿⣿⡆ ${ blue('⣝⢮⡂') } ⢸⣿⣿⡿⠋⠁⠈⠙⢿⣿⣿⡆ ${ yellow('⣝⢮⡂') } ⠈⠹⣿⣿⡏⠉⠉⠉⠉ 9 | ⣿⣿⣿⣼⣿⣿⣿⡀ ${ red('⢮⡳⡅') } ⢸⣿⣿⠃ ⠘⣿⣿⣗ ${ blue('⢮⡳⡅') } ⢸⣿⣿⠃ ⠘⣿⣿⣗ ${ yellow('⢮⡳⡅') } ⣿⣿⡇ 10 | ⣿⣿⣿⣿⡿⣿⣿⣷⡀ ${ red('⡳⡵⡅') } ⢸⣿⣿⣇ ⣸⣿⣿⡏ ${ blue('⡳⡵⡅') } ⢸⣿⣿⣇ ⣸⣿⣿⡏ ${ yellow('⡳⡵⡅') } ⣿⣿⣧ 11 | ⣿⣿⣿⠟ ⠘⣿⣿⣷⡀ ${ red('⣝⢮⡂') } ⢻⣿⣿⣷⣶⣴⣾⣿⣿⡿ ${ blue('⣝⢮⡂') } ⢿⣿⣿⣶⣦⣶⣿⣿⣿⠟ ${ yellow('⣝⢮⡂') } ⠹⣿⣿⣷⣶⣶⣶ 12 | ⠿⠿⠿ ⠘⠿⠿⠿⠟ ${ red('⠪⠳⠅') } ⠙⠻⢿⣿⣿⡿⠟ ${ blue('⠪⠳⠅') } ⠙⠿⣿⣿⣿⠿⠟ ${ yellow('⠪⠳⠅') } ⠉⠻⠿⠿⠿⠉ 13 | 14 | ${ bgMagenta.white(' ~ achievibit ~ ') }`).trim(); 15 | -------------------------------------------------------------------------------- /server/src/guards/disable-in-production/disable-in-production.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | 4 | import { configService } from '@kb-config'; 5 | 6 | @Injectable() 7 | export class DisableInProductionGuard implements CanActivate { 8 | constructor(private reflector: Reflector) {} 9 | 10 | canActivate(context: ExecutionContext): boolean { 11 | const disableInProduction = this.reflector.get('disableInProduction', context.getHandler()); 12 | 13 | // If the decorator is not present, allow the request 14 | if (!disableInProduction) { 15 | return true; 16 | } 17 | 18 | // Check if the environment is production 19 | if (configService.config.NODE_ENV === 'production') { 20 | // TODO(@thatkookooguy): potential for an achievement for finding this 21 | // Look, mom, I'm a hacker! 22 | throw new ForbiddenException('This endpoint is disabled in production'); 23 | } 24 | 25 | // Allow the request if not in production 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kibibit/achievibit-sdk", 3 | "version": "1.11.0", 4 | "main": "dist/index.js", 5 | "types": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "generate:api": "rimraf ./src/generated && swagger-typescript-api -p http://localhost:10102/api/docs-json -o ./src/generated --extract-enums --module-name-first-tag --modular --axios && npm version minor", 11 | "generate-barrels": "barrelsby --delete -d ./src -l below -q --exclude spec.ts --exclude spec.js --exclude __mocks__ && echo '> Barrels Generated'", 12 | "prebuild": "rimraf dist && npm run generate-barrels", 13 | "build": "tsup index.ts --format esm,cjs --dts", 14 | "prepare": "npm run build" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "description": "", 19 | "devDependencies": { 20 | "barrelsby": "^2.8.1", 21 | "rimraf": "^6.0.1", 22 | "swagger-typescript-api": "^13.0.23", 23 | "tsup": "^8.4.0", 24 | "typescript": "^5.0.0" 25 | }, 26 | "peerDependencies": { 27 | "axios": "^1.7.7", 28 | "rxjs": "7.8.2", 29 | "socket.io-client": "^4.8.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { configService, Logger } from '@kb-config'; 4 | import { UsersModule } from '@kb-users'; 5 | 6 | import { BitbucketModule } from './bitbucket/bitbucket.module'; 7 | import { GithubModule } from './github/github.module'; 8 | import { GitlabModule } from './gitlab/gitlab.module'; 9 | import { AuthController } from './auth.controller'; 10 | 11 | const logger = new Logger('AuthModule'); 12 | 13 | const imports = [ UsersModule ]; 14 | 15 | if (configService.isNoOauthConfigured()) { 16 | logger.error([ 17 | '\nNo OAuth providers are configured.', 18 | 'Please configure at least one OAuth provider.' 19 | ].join('\n')); 20 | process.exit(1); 21 | } 22 | 23 | if (configService.isGithubOauthConfigured()) { 24 | imports.push(GithubModule); 25 | } 26 | 27 | if (configService.isGitlabOauthConfigured()) { 28 | imports.push(GitlabModule); 29 | } 30 | 31 | if (configService.isBitbucketOauthConfigured()) { 32 | imports.push(BitbucketModule); 33 | } 34 | 35 | @Module({ 36 | imports, 37 | controllers: [ AuthController ] 38 | }) 39 | export class AuthModule {} 40 | -------------------------------------------------------------------------------- /client/src/app/services/api/general.service.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { Api, wrapWithProxy } from '@kibibit/achievibit-sdk'; 5 | 6 | import { ApiErrorHandler } from './api-error-handler'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class GeneralApiService { 12 | private readonly generalSdk = new Api({}); 13 | private readonly generalApiService = wrapWithProxy(this.generalSdk); 14 | 15 | constructor() { 16 | this.generalSdk.instance.interceptors.response.use( 17 | (response) => this.interceptResponse(response), 18 | (error) => ApiErrorHandler.handleError(error) 19 | ); 20 | } 21 | 22 | getApiDetails() { 23 | return this 24 | .generalApiService 25 | .appControllerGetApiDetails(); 26 | } 27 | 28 | getSupportedTimezones() { 29 | return this 30 | .generalApiService 31 | .appControllerGetTimezones(); 32 | } 33 | 34 | private interceptResponse(response: AxiosResponse) { 35 | // createSnackbar('hello world', { 36 | // timeout: 5000 37 | // }); 38 | 39 | return response; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.10. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /server/src/webhooks/engines/github.event.ts: -------------------------------------------------------------------------------- 1 | export class GitHubEvent { 2 | constructor( 3 | public readonly eventType: string, 4 | public readonly body: Record 5 | ) {} 6 | 7 | get isInstallationCreated() { 8 | return this.eventType === 'installation' && this.body.action === 'created'; 9 | } 10 | 11 | get isInstallationDeleted() { 12 | return this.eventType === 'installation' && this.body.action === 'deleted'; 13 | } 14 | 15 | get isInstallationRepositoriesAdded() { 16 | return this.eventType === 'installation_repositories' && this.body.action === 'added'; 17 | } 18 | 19 | get isInstallationRepositoriesRemoved() { 20 | return this.eventType === 'installation_repositories' && this.body.action === 'removed'; 21 | } 22 | 23 | get isPush() { 24 | return this.eventType === 'push'; 25 | } 26 | 27 | get isPullRequest() { 28 | return this.eventType === 'pull_request'; 29 | } 30 | 31 | get isPullRequestReview() { 32 | return this.eventType === 'pull_request_review'; 33 | } 34 | 35 | get isPullRequestReviewComment() { 36 | return this.eventType === 'pull_request_review_comment'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | pgdata 3 | keys 4 | # /sdk/src/generated/ 5 | 6 | .env.*.json 7 | *private-key.pem 8 | 9 | .pnpm-store 10 | 11 | # compiled output 12 | dist 13 | node_modules 14 | build 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | pnpm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # OS 26 | .DS_Store 27 | 28 | # Tests 29 | /coverage 30 | /.nyc_output 31 | 32 | # IDEs and editors 33 | /.idea 34 | .project 35 | .classpath 36 | .c9/ 37 | *.launch 38 | .settings/ 39 | *.sublime-workspace 40 | 41 | # IDE - VSCode 42 | .vscode/* 43 | !.vscode/settings.json 44 | !.vscode/tasks.json 45 | !.vscode/launch.json 46 | !.vscode/extensions.json 47 | 48 | # dotenv environment variable files 49 | .env 50 | .env.development.local 51 | .env.test.local 52 | .env.production.local 53 | .env.local 54 | 55 | # temp directory 56 | .temp 57 | .tmp 58 | 59 | # Runtime data 60 | pids 61 | *.pid 62 | *.seed 63 | *.pid.lock 64 | 65 | # Diagnostic reports (https://nodejs.org/api/report.html) 66 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 67 | node_modules/ 68 | /test-results/ 69 | /playwright-report/ 70 | /blob-report/ 71 | /playwright/.cache/ 72 | -------------------------------------------------------------------------------- /server/.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | 9 | volumes: 10 | - ../..:/workspaces:cached 11 | 12 | # Overrides default command so things don't shut down after the process ends. 13 | command: sleep infinity 14 | 15 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 16 | network_mode: service:db 17 | 18 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 19 | # (Adding the "ports" property to this file will not forward from a Codespace.) 20 | 21 | db: 22 | image: postgres:latest 23 | restart: unless-stopped 24 | volumes: 25 | - postgres-data:/var/lib/postgresql/data 26 | environment: 27 | POSTGRES_PASSWORD: postgres 28 | POSTGRES_USER: postgres 29 | POSTGRES_DB: postgres 30 | 31 | # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. 32 | # (Adding the "ports" property to this file will not forward from a Codespace.) 33 | 34 | volumes: 35 | postgres-data: 36 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/user-profile.component.html: -------------------------------------------------------------------------------- 1 |
2 | 38 |
39 | 40 |
41 |
-------------------------------------------------------------------------------- /achievements/src/double-review.achievement.ts: -------------------------------------------------------------------------------- 1 | import { clone, escape, forEach, remove } from 'lodash'; 2 | 3 | import { IAchievement, IUserAchievement } from './achievement.abstract'; 4 | 5 | export const doubleReview: IAchievement = { 6 | name: 'doubleReview', 7 | check: function(pullRequest, shall) { 8 | // clone the reviewers to not mutate the original pullRequest 9 | const reviewers = clone(pullRequest.reviewers); 10 | remove(reviewers, { 11 | username: pullRequest.creator.username 12 | }); 13 | if (reviewers && reviewers.length === 2) { 14 | const achieve: IUserAchievement = { 15 | avatar: 'images/achievements/doubleReview.achievement.gif', 16 | name: 'We\'re ready, master', 17 | short: escape('"This way!"-"No, that way!"'), 18 | description: [ 19 | 'double headed code review.
It doesn\'t matter who added you, ', 20 | 'apparently, both of you are needed for a one man job 😇' 21 | ].join(''), 22 | relatedPullRequest: pullRequest.id 23 | }; 24 | 25 | forEach(reviewers, function(reviewer) { 26 | shall.grant(reviewer.username, achieve); 27 | }); 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /achievements/src/use-github-bot.achievement.spec.ts: -------------------------------------------------------------------------------- 1 | import { PullRequest, Shall } from './dev-tools/mocks'; 2 | import { githubBot } from './use-github-bot.achievement'; 3 | 4 | const mockCommits = [ 5 | { 6 | author: { 7 | username: 'commit-author' 8 | }, 9 | committer: { 10 | username: 'web-flow' 11 | } 12 | } 13 | ]; 14 | 15 | describe('githubBot achievement', () => { 16 | it('should be granted if committer username is web-flow', () => { 17 | const testShall = new Shall(); 18 | const pullRequest = new PullRequest(); 19 | pullRequest.commits = mockCommits; 20 | 21 | githubBot.check(pullRequest, testShall); 22 | expect(testShall.grantedAchievements).toBeDefined(); 23 | expect(testShall.grantedAchievements.creator).toMatchSnapshot(); 24 | }); 25 | 26 | it('should not grant if committer is not web-flow', () => { 27 | const testShall = new Shall(); 28 | const pullRequest = new PullRequest(); 29 | pullRequest.commits = mockCommits; 30 | pullRequest.commits[0].committer.username = 'not-web-flow'; 31 | 32 | githubBot.check(pullRequest, testShall); 33 | expect(testShall.grantedAchievements).toBeUndefined(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /client/src/app/pages/app-status/app-status.component.ts: -------------------------------------------------------------------------------- 1 | import { AsyncPipe, DatePipe, KeyValuePipe, NgFor, NgIf } from '@angular/common'; 2 | import { Component } from '@angular/core'; 3 | 4 | import { GeneralApiService } from '../../services/api/general.service'; 5 | import { HealthApiService } from '../../services/api/health.service'; 6 | import { SocketService } from '../../services/socket.service'; 7 | 8 | @Component({ 9 | selector: 'kb-app-status', 10 | standalone: true, 11 | imports: [ NgIf, AsyncPipe, NgFor, KeyValuePipe, DatePipe ], 12 | templateUrl: './app-status.component.html', 13 | styleUrl: './app-status.component.scss' 14 | }) 15 | export class AppStatusComponent { 16 | healthCheck$ = this.healthApiService.healthCheck(); 17 | apiDetails$ = this.generalApiService.getApiDetails(); 18 | socketConnectionStatus$ = this.socketService.getSocketConnectionStatus(); 19 | 20 | constructor( 21 | private readonly healthApiService: HealthApiService, 22 | private readonly generalApiService: GeneralApiService, 23 | private readonly socketService: SocketService 24 | ) {} 25 | 26 | triggerMiniGame() { 27 | this.socketService.emit('test-test-test', {}, () => {}); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/src/app/shared/mini-game/mini-game.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | opacity: 0; 9 | background: #212121; 10 | z-index: 10000000000; 11 | pointer-events: none; 12 | 13 | &.kb-show { 14 | pointer-events: auto; 15 | animation: fade-in 0.5s ease-in-out forwards; 16 | } 17 | } 18 | 19 | .player-stats { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | display: flex; 24 | align-items: center; 25 | flex-direction: row-reverse; 26 | height: 64px; 27 | padding: 0 1rem; 28 | gap: 0.5em; 29 | 30 | img.avatar { 31 | width: 40px; 32 | height: 40px; 33 | border-radius: 50%; 34 | } 35 | } 36 | 37 | .mini-game-info { 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | right: 0; 42 | height: 64px; 43 | display: flex; 44 | align-items: center; 45 | gap: 0.5em; 46 | padding: 0 1rem; 47 | } 48 | 49 | iframe { 50 | width: 100%; 51 | height: calc(100% - 64px); 52 | border: none; 53 | margin-top: 64px; 54 | } 55 | 56 | // animation to fade in the mini-game 57 | @keyframes fade-in { 58 | 0% { 59 | opacity: 0; 60 | } 61 | 100% { 62 | opacity: 1; 63 | } 64 | } -------------------------------------------------------------------------------- /server/src/guards/jwt-auth-optional.guard.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | 3 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 4 | 5 | import { configService } from '@kb-config'; 6 | import { UsersService } from '@kb-users'; 7 | 8 | @Injectable() 9 | export class JwtAuthOptionalGuard implements CanActivate { 10 | constructor(private usersService: UsersService) { } 11 | 12 | async canActivate(context: ExecutionContext): Promise { 13 | try { 14 | const request = context.switchToHttp().getRequest(); 15 | const kibibitJwtFromCookie = request.cookies['kibibit-jwt']; 16 | 17 | // get user details from jwt token by decoding it 18 | // and attach it to the request object 19 | const userFromJwt = jwt.verify(kibibitJwtFromCookie, configService.config.JWT_SECRET) as { username: string }; 20 | 21 | const dbUser = await this.usersService.findOne(userFromJwt.username); 22 | 23 | request.user = dbUser; 24 | // If you want to allow the request even if auth fails, always return true 25 | return true; 26 | } catch (error) { 27 | // If you want to allow the request even if auth fails, always return true 28 | return true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/app/pages/organizations/organization-profile/organization-profile.component.html: -------------------------------------------------------------------------------- 1 | 6 |
7 | cover-image 8 |
9 |
10 | 21 |
22 |
{{ org.name }}
23 | 24 | 28 | 29 |
30 |
-------------------------------------------------------------------------------- /server/src/filters/http-exception/db-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { StatusCodes } from 'http-status-codes'; 3 | import { QueryFailedError } from 'typeorm'; 4 | 5 | import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; 6 | 7 | import { Logger } from '@kb-config'; 8 | import { User } from '@kb-models'; 9 | 10 | @Catch(QueryFailedError) 11 | export class DbExceptionFilter implements ExceptionFilter { 12 | private readonly logger = new Logger(DbExceptionFilter.name); 13 | catch(exception: QueryFailedError, host: ArgumentsHost) { 14 | const ctx = host.switchToHttp(); 15 | const response = ctx.getResponse(); 16 | const request = ctx.getRequest(); 17 | // const next = ctx.getNext(); 18 | 19 | this.logger.error(exception, { 20 | sessionId: request.sessionID, 21 | username: (request.user as User)?.username 22 | }); 23 | 24 | response 25 | .status(StatusCodes.INTERNAL_SERVER_ERROR) 26 | .json({ 27 | statusCode: StatusCodes.INTERNAL_SERVER_ERROR, 28 | timestamp: new Date().toISOString(), 29 | message: 'Internal Server Error', 30 | sessionId: request.sessionID 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { HomeComponent } from './pages/home/home.component'; 4 | 5 | export const routes: Routes = [ 6 | { path: 'home', component: HomeComponent }, 7 | { 8 | path: '', 9 | redirectTo: 'home', 10 | pathMatch: 'full' 11 | }, 12 | { 13 | path: '', 14 | loadChildren: () => import('./pages/app-status/app-status.routes') 15 | .then((m) => m.appStatusRoutes) 16 | }, 17 | { 18 | path: '', 19 | loadChildren: () => import('./pages/user-profile/user-profile.routes') 20 | .then((m) => m.userProfileRoutes) 21 | }, 22 | { 23 | path: '', 24 | loadChildren: () => import('./pages/organizations/organizations.routes') 25 | .then((m) => m.organizationsRoutes) 26 | }, 27 | { 28 | path: '', 29 | loadChildren: () => import('./pages/users/users.routes') 30 | .then((m) => m.usersRoutes) 31 | }, 32 | { 33 | path: '', 34 | loadChildren: () => import('./pages/repositories/repositories.routes') 35 | .then((m) => m.repositoriesRoutes) 36 | }, 37 | { 38 | path: '', 39 | loadChildren: () => import('./pages/pull-requests/pull-requests.routes') 40 | .then((m) => m.pullRequestsRoutes) 41 | } 42 | ]; 43 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/profile-settings/profile-settings.component.html: -------------------------------------------------------------------------------- 1 |
Settings
2 |
3 |
4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /client/src/app/pages/users/user-profile/user-profile.component.html: -------------------------------------------------------------------------------- 1 | 6 |
7 | cover-image 8 |
9 |
10 | 21 |
22 |
{{ user.username }}
23 | 24 | 28 | 29 |
30 |
-------------------------------------------------------------------------------- /client/src/app/pages/repositories/repository-profile/repository-profile.component.html: -------------------------------------------------------------------------------- 1 | 6 |
7 | cover-image 8 |
9 |
10 | 21 |
22 |
{{ repo.fullname }}
23 | 24 | 28 | 29 |
30 |
-------------------------------------------------------------------------------- /achievements/src/bi-winning.achievement.ts: -------------------------------------------------------------------------------- 1 | import { every, isEmpty } from 'lodash'; 2 | 3 | import { IAchievement, IUserAchievement } from './achievement.abstract'; 4 | 5 | export const biWinning: IAchievement = { 6 | name: 'bi-winning', 7 | check: function(pullRequest, shall) { 8 | if (!isEmpty(pullRequest.commits) && 9 | every(pullRequest.commits, allStatusesPassed)) { 10 | const achievement: IUserAchievement = { 11 | avatar: 'images/achievements/biWinning.achievement.jpg', 12 | name: 'BI-WINNING!', 13 | short: 'I\'m bi-winning. I win here and I win there', 14 | description: [ 15 | '

All the commits in your pull-request have passing statuses! ', 16 | 'WINNING!

', 17 | '

I\'m different. I have a different constitution, I have a ', 18 | 'different brain, I have a different heart. I got tiger blood, man. ', 19 | 'Dying\'s for fools, dying\'s for amateurs.

' 20 | ].join(''), 21 | relatedPullRequest: pullRequest._id 22 | }; 23 | 24 | shall.grant(pullRequest.creator.username, achievement); 25 | } 26 | } 27 | }; 28 | 29 | function allStatusesPassed(commit) { 30 | return !isEmpty(commit.statuses) && 31 | every(commit.statuses, { state: 'success' }); 32 | } 33 | -------------------------------------------------------------------------------- /client/src/app/pages/user-profile/user-profile.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { activeUserResolver } from '../../services/active-user-resolver'; 4 | import { ProfileIntegrationsComponent } from './profile-integrations/profile-integrations.component'; 5 | import { ProfileOverviewComponent } from './profile-overview/profile-overview.component'; 6 | import { ProfileSettingsComponent } from './profile-settings/profile-settings.component'; 7 | import { UserProfileComponent } from './user-profile.component'; 8 | 9 | export const userProfileRoutes: Routes = [ 10 | { 11 | path: 'profile', 12 | component: UserProfileComponent, 13 | children: [ 14 | { 15 | path: '', 16 | redirectTo: 'overview', 17 | pathMatch: 'full' 18 | }, 19 | { 20 | path: 'overview', 21 | component: ProfileOverviewComponent, 22 | resolve: { user: activeUserResolver } 23 | }, 24 | { 25 | path: 'integrations', 26 | component: ProfileIntegrationsComponent, 27 | resolve: { user: activeUserResolver } 28 | }, 29 | { 30 | path: 'settings', 31 | component: ProfileSettingsComponent, 32 | resolve: { user: activeUserResolver } 33 | } 34 | ] 35 | } 36 | ]; 37 | -------------------------------------------------------------------------------- /achievements/src/__snapshots__/dont-yell-at-me.achievement.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`dontYellAtMe achievement should be granted to PR creator if both reasons 1`] = ` 4 | { 5 | "avatar": "images/achievements/dontYellAtMe.achievement.jpg", 6 | "description": "You've used ALL CAPS and 3 or more exclamation marks in your Pull Request title", 7 | "name": "Don't Yell At Me!!!", 8 | "relatedPullRequest": "test", 9 | "short": "I don't know what we're yelling about", 10 | } 11 | `; 12 | 13 | exports[`dontYellAtMe achievement should be granted to PR creator if more than 2 '!' 1`] = ` 14 | { 15 | "avatar": "images/achievements/dontYellAtMe.achievement.jpg", 16 | "description": "You've used 3 or more exclamation marks in your Pull Request title", 17 | "name": "Don't Yell At Me!!!", 18 | "relatedPullRequest": "test", 19 | "short": "I don't know what we're yelling about", 20 | } 21 | `; 22 | 23 | exports[`dontYellAtMe achievement should be granted to PR creator if title is all caps 1`] = ` 24 | { 25 | "avatar": "images/achievements/dontYellAtMe.achievement.jpg", 26 | "description": "You've used ALL CAPS in your Pull Request title", 27 | "name": "Don't Yell At Me!!!", 28 | "relatedPullRequest": "test", 29 | "short": "I don't know what we're yelling about", 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /server/src/auth/jwt/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { ExtractJwt, Strategy } from 'passport-jwt'; 3 | 4 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 5 | import { PassportStrategy } from '@nestjs/passport'; 6 | 7 | import { configService } from '@kb-config'; 8 | import { UsersService } from '@kb-users'; 9 | 10 | @Injectable() 11 | export class JwtStrategy extends PassportStrategy(Strategy) { 12 | constructor( 13 | private readonly usersService: UsersService 14 | ) { 15 | super({ 16 | jwtFromRequest: ExtractJwt.fromExtractors([ 17 | JwtStrategy.extractJWT, 18 | ExtractJwt.fromAuthHeaderAsBearerToken() 19 | ]), 20 | ignoreExpiration: false, 21 | secretOrKey: configService.config.JWT_SECRET 22 | }); 23 | } 24 | 25 | async validate(payload: any) { 26 | const dbUser = await this.usersService.findOne(payload.username); 27 | if (!dbUser) { 28 | throw new UnauthorizedException(); 29 | } 30 | 31 | return dbUser; 32 | } 33 | 34 | private static extractJWT(req: Request): string | null { 35 | if ( 36 | req.cookies && 37 | 'kibibit-jwt' in req.cookies && 38 | req.cookies['kibibit-jwt'].length > 0 39 | ) { 40 | return req.cookies['kibibit-jwt']; 41 | } 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /achievements/src/reaction-on-every-comment.achievement.ts: -------------------------------------------------------------------------------- 1 | import { every, get, isArray } from 'lodash'; 2 | 3 | import { IAchievement } from './achievement.abstract'; 4 | 5 | export const reactionOnEveryComment: IAchievement = { 6 | name: 'Royal Flush', 7 | check: function(pullRequest, shall) { 8 | const doesCommentsExist = 9 | get(pullRequest.inlineComments, 'length') > 0 || 10 | get(pullRequest.comments, 'length') > 0; 11 | const isReactionOnEverything = 12 | every(pullRequest.inlineComments, haveReactions) && 13 | every(pullRequest.comments, haveReactions) && 14 | every([ pullRequest ], haveReactions); 15 | 16 | if (doesCommentsExist && isReactionOnEverything) { 17 | shall.grant(pullRequest.creator.username, { 18 | avatar: 'images/achievements/reactionOnEveryComment.achievement.png', 19 | name: 'royal flush', 20 | short: 'emojis on all of the comments', 21 | description: [ 22 | 'got for having at least one comment\\inline comment, ', 23 | 'and all of them (including the PR description) had reactions' 24 | ].join(''), 25 | relatedPullRequest: pullRequest.id 26 | }); 27 | } 28 | } 29 | }; 30 | 31 | function haveReactions(comment) { 32 | return isArray(comment.reactions) && 33 | get(comment.reactions, 'length') !== 0; 34 | } 35 | -------------------------------------------------------------------------------- /server/scripts/migration-create.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ts-node 2 | 3 | import { execSync } from 'child_process'; 4 | 5 | import { bgRed, blue, green } from 'colors'; 6 | import { readdirSync } from 'fs-extra'; 7 | 8 | const migrationName = process.argv[2]; 9 | 10 | if (!migrationName) { 11 | console.error('Please provide a migration name.'); 12 | process.exit(1); 13 | } 14 | 15 | const command = `npm run typeorm migration:create src/migrations/${ migrationName }`; 16 | 17 | try { 18 | execSync(command, { stdio: 'inherit' }); 19 | const generatedMigration = findGeneratedMigration(migrationName); 20 | const lintCreatedMigration = `npm run lint:fix src/migrations/${ generatedMigration }`; 21 | execSync(lintCreatedMigration, { stdio: null }); 22 | console.log(green(`Migration ${ blue('linted') } successfully.`)); 23 | } catch (error) { 24 | console.error(bgRed.white('Error creating migration:')); 25 | console.error(error); 26 | process.exit(1); 27 | } 28 | 29 | function findGeneratedMigration(migrationName: string) { 30 | const migrationFolderContent = readdirSync('src/migrations'); 31 | const migrationFile = migrationFolderContent.find((file) => file.endsWith(`-${ migrationName }.ts`)); 32 | 33 | if (!migrationFile) { 34 | console.error('Could not find the generated migration file.'); 35 | process.exit(1); 36 | } 37 | 38 | return migrationFile; 39 | } 40 | --------------------------------------------------------------------------------