├── .env.example ├── .github ├── COMMIT_CONVENTION.md └── workflows │ └── unit-test.yml ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── inspectionProfiles │ └── Project_Default.xml └── vcs.xml ├── LICENSE ├── README.md ├── apps ├── api │ ├── .env.example │ ├── Dockerfile │ ├── biome.json │ ├── env.d.ts │ ├── nest-cli.json │ ├── package.json │ ├── prisma │ │ ├── dbml │ │ │ └── schema.dbml │ │ ├── migrations │ │ │ ├── 20230714190314_init │ │ │ │ └── migration.sql │ │ │ ├── 20230715112235_organization │ │ │ │ └── migration.sql │ │ │ ├── 20230716033116_org_member_user_mapping │ │ │ │ └── migration.sql │ │ │ ├── 20230801023517_added_events │ │ │ │ └── migration.sql │ │ │ ├── 20231019150437_wyswyg │ │ │ │ └── migration.sql │ │ │ ├── 20231104165757_events │ │ │ │ └── migration.sql │ │ │ ├── 20231106124429_npm_run_nest_dev │ │ │ │ └── migration.sql │ │ │ ├── 20231108172146_npm_run_nest_dev │ │ │ │ └── migration.sql │ │ │ ├── 20231113174329_event_date │ │ │ │ └── migration.sql │ │ │ ├── 20231113174511_optioanl_events │ │ │ │ └── migration.sql │ │ │ ├── 20231113175751_event_ticket │ │ │ │ └── migration.sql │ │ │ ├── 20231203091321_last_date │ │ │ │ └── migration.sql │ │ │ ├── 20240106192643_builder │ │ │ │ └── migration.sql │ │ │ ├── 20240210121024_forms │ │ │ │ └── migration.sql │ │ │ ├── 20240214191116_coverimage │ │ │ │ └── migration.sql │ │ │ ├── 20240216173647_saml_oauth │ │ │ │ └── migration.sql │ │ │ ├── 20240216173805_rename │ │ │ │ └── migration.sql │ │ │ ├── 20240216174111_uniwue │ │ │ │ └── migration.sql │ │ │ ├── 20240217150659_form │ │ │ │ └── migration.sql │ │ │ ├── 20240218161849_payment │ │ │ │ └── migration.sql │ │ │ ├── 20240218180341_stripe │ │ │ │ └── migration.sql │ │ │ ├── 20240218183007_stripe_product │ │ │ │ └── migration.sql │ │ │ ├── 20240218183649_stripe │ │ │ │ └── migration.sql │ │ │ ├── 20240221161707_response │ │ │ │ └── migration.sql │ │ │ ├── 20240319134219_ticket │ │ │ │ └── migration.sql │ │ │ ├── 20240319134318_time │ │ │ │ └── migration.sql │ │ │ ├── 20240323153154_slug │ │ │ │ └── migration.sql │ │ │ ├── 20240324115128_ │ │ │ │ └── migration.sql │ │ │ ├── 20240330180131_options │ │ │ │ └── migration.sql │ │ │ ├── 20240330183017_comments │ │ │ │ └── migration.sql │ │ │ ├── 20240330184341_user_comment │ │ │ │ └── migration.sql │ │ │ ├── 20240407160532_add_pgvector_extension │ │ │ │ └── migration.sql │ │ │ ├── 20240407164324_idk │ │ │ │ └── migration.sql │ │ │ ├── 20240407185306_fix_migration │ │ │ │ └── migration.sql │ │ │ ├── 20240407191338_some_changes │ │ │ │ └── migration.sql │ │ │ ├── 20240407194731_active │ │ │ │ └── migration.sql │ │ │ ├── 20240407194857_nest │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ ├── schema.prisma │ │ └── seed.ts │ ├── src │ │ ├── BaseContext.ts │ │ ├── app.module.ts │ │ ├── controllers │ │ │ ├── ai.controller.ts │ │ │ ├── auth.controller.ts │ │ │ ├── cloud.controller.ts │ │ │ ├── events.controller.ts │ │ │ ├── form.controller.ts │ │ │ ├── kanban.controller.ts │ │ │ ├── org-events.controller.ts │ │ │ ├── org-invite.controller.ts │ │ │ ├── org-member.controller.ts │ │ │ ├── organization.controller.ts │ │ │ ├── public-events.controller.ts │ │ │ └── user.controller.ts │ │ ├── db │ │ │ ├── migrations │ │ │ │ ├── 01.user.ts │ │ │ │ ├── 02.account.ts │ │ │ │ ├── 03.organization.ts │ │ │ │ ├── 04.organization-member.ts │ │ │ │ ├── 05.organization-invite.ts │ │ │ │ ├── 06.events.ts │ │ │ │ ├── 07.event-ticket.ts │ │ │ │ ├── 08.form-field.ts │ │ │ │ ├── 09.form.ts │ │ │ │ ├── 10.form-field-options.ts │ │ │ │ ├── 11.form-response.ts │ │ │ │ ├── 12.ticket.ts │ │ │ │ ├── 13.kanban.ts │ │ │ │ ├── 14.kanban-card.ts │ │ │ │ ├── 15.comment.ts │ │ │ │ └── 16.field_options_id.ts │ │ │ └── schema │ │ │ │ ├── account.ts │ │ │ │ ├── comment.ts │ │ │ │ ├── event-ticket.ts │ │ │ │ ├── events.ts │ │ │ │ ├── form-field-options.ts │ │ │ │ ├── form-fields.ts │ │ │ │ ├── form-response.ts │ │ │ │ ├── form.ts │ │ │ │ ├── index.ts │ │ │ │ ├── kanban-card.ts │ │ │ │ ├── kanban.ts │ │ │ │ ├── organization-invite.ts │ │ │ │ ├── organization-member.ts │ │ │ │ ├── organization.ts │ │ │ │ ├── tickets.ts │ │ │ │ └── user.ts │ │ ├── dto │ │ │ ├── ai.dto.ts │ │ │ ├── events.dto.ts │ │ │ ├── form-field.dto.ts │ │ │ ├── form.dto.ts │ │ │ └── update-task.dto.ts │ │ ├── error.ts │ │ ├── filters │ │ │ └── global-error.filter.ts │ │ ├── main.ts │ │ ├── middleware │ │ │ └── .gitkeep │ │ ├── models │ │ │ ├── Account.ts │ │ │ ├── BaseModel.ts │ │ │ ├── Comment.ts │ │ │ ├── EventTicket.ts │ │ │ ├── Events.ts │ │ │ ├── Form.ts │ │ │ ├── FormFieldOptions.ts │ │ │ ├── FormFields.ts │ │ │ ├── FormResponse.ts │ │ │ ├── Kanban.ts │ │ │ ├── KanbanCard.ts │ │ │ ├── Org.ts │ │ │ ├── OrgInvite.ts │ │ │ ├── OrgMember.ts │ │ │ ├── Ticket.ts │ │ │ ├── User.ts │ │ │ └── index.ts │ │ ├── modules │ │ │ └── root.module.ts │ │ ├── services │ │ │ ├── ai.service.ts │ │ │ ├── auth │ │ │ │ ├── auth.service.ts │ │ │ │ ├── cookieHandler.ts │ │ │ │ ├── decorators │ │ │ │ │ ├── role.decorator.ts │ │ │ │ │ └── user.decorator.ts │ │ │ │ ├── guards │ │ │ │ │ ├── github-oauth.guard.ts │ │ │ │ │ ├── google-oauth.guard.ts │ │ │ │ │ └── refresh.guard.ts │ │ │ │ └── strategy │ │ │ │ │ ├── github.strategy.ts │ │ │ │ │ ├── google.strategy.ts │ │ │ │ │ ├── jwt.strategy.ts │ │ │ │ │ └── refresh.strategy.ts │ │ │ ├── cloud.service.ts │ │ │ ├── decorator │ │ │ │ └── roles.decorator.ts │ │ │ ├── dto │ │ │ │ ├── cover-dto.ts │ │ │ │ ├── create-events.dto.ts │ │ │ │ ├── create-org.dto.ts │ │ │ │ ├── create-task.dto.ts │ │ │ │ ├── delete-event.dto.ts │ │ │ │ ├── delete-org.dto.ts │ │ │ │ ├── generic-org.dto.ts │ │ │ │ ├── get-events.dto.ts │ │ │ │ ├── member-remove.dto.ts │ │ │ │ ├── publish-form.dto.ts │ │ │ │ ├── publishForm.dto.ts │ │ │ │ ├── register-event.dto.ts │ │ │ │ ├── remove-user.dto.ts │ │ │ │ ├── update-event.dto.ts │ │ │ │ ├── update-org.dto.ts │ │ │ │ ├── update-role.dto.ts │ │ │ │ ├── update-user.dto.ts │ │ │ │ └── user-invite.dto.ts │ │ │ ├── events.service.ts │ │ │ ├── form.service.ts │ │ │ ├── guards │ │ │ │ └── rbac-member.guard.ts │ │ │ ├── kanban.service.ts │ │ │ ├── mail │ │ │ │ ├── mail.service.ts │ │ │ │ └── templates │ │ │ │ │ ├── org-invite.tsx │ │ │ │ │ ├── payment-succesfull.tsx │ │ │ │ │ └── welcome.tsx │ │ │ ├── org-events.service.ts │ │ │ ├── org-invite.service.ts │ │ │ ├── org-member.service.ts │ │ │ ├── organization.service.ts │ │ │ ├── prisma.service.ts │ │ │ └── user.service.ts │ │ ├── types │ │ │ ├── express.ts │ │ │ └── knex.d.ts │ │ ├── utils │ │ │ ├── db.ts │ │ │ ├── envSchema.ts │ │ │ ├── error.ts │ │ │ ├── exclude.ts │ │ │ ├── hyphenate.ts │ │ │ ├── index.ts │ │ │ └── stripUndefined.ts │ │ └── validation │ │ │ ├── zod.validation.decorator.ts │ │ │ └── zod.validation.pipe.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── web │ ├── .env.example │ ├── .eslintrc │ ├── .gitignore │ ├── .prettierignore │ ├── .storybook │ ├── main.js │ └── preview.js │ ├── __tests__ │ ├── Home.test.tsx │ └── components │ │ └── Button.test.tsx │ ├── components.json │ ├── components │ ├── Error.tsx │ ├── editor │ │ ├── components │ │ │ ├── Editor.tsx │ │ │ ├── MenuBar.tsx │ │ │ ├── constants.ts │ │ │ └── extentions.ts │ │ └── index.ts │ ├── events │ │ ├── components │ │ │ ├── Card.tsx │ │ │ ├── NoData.tsx │ │ │ └── PreLoader.tsx │ │ └── index.ts │ ├── preloaders │ │ ├── components │ │ │ ├── EventsLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── OrgLoader.tsx │ │ │ └── PageLoader.tsx │ │ └── index.ts │ └── ui │ │ ├── Button │ │ ├── Button.stories.tsx │ │ ├── Button.tsx │ │ └── index.ts │ │ ├── Tooltip │ │ ├── Tooltip.tsx │ │ └── index.ts │ │ └── Truncate │ │ ├── Truncate.stories.tsx │ │ ├── Truncate.tsx │ │ └── index.ts │ ├── config │ ├── ENV.ts │ ├── handler.ts │ └── index.ts │ ├── context │ ├── Auth │ │ ├── context.tsx │ │ └── index.ts │ └── Event │ │ ├── .gitkeep │ │ └── README.md │ ├── env.d.ts │ ├── hooks │ ├── api │ │ ├── Auth │ │ │ ├── index.ts │ │ │ └── useLogout.ts │ │ ├── Events │ │ │ ├── index.ts │ │ │ ├── useAllEvents.ts │ │ │ ├── useEvent.ts │ │ │ ├── useEventStats.ts │ │ │ ├── useEventStatus.ts │ │ │ └── usePublicOrgEvents.ts │ │ ├── form │ │ │ ├── index.ts │ │ │ ├── useAddForm.ts │ │ │ ├── useAddSchema.ts │ │ │ ├── useAllForms.ts │ │ │ ├── useFormSchema.ts │ │ │ └── useUpdateForm.ts │ │ ├── kanban │ │ │ ├── index.ts │ │ │ ├── useKanban.ts │ │ │ └── useUpdateTask.ts │ │ ├── org │ │ │ ├── index.ts │ │ │ ├── useAddOrg.ts │ │ │ ├── useMembers.ts │ │ │ ├── useOrg.ts │ │ │ ├── useOrgEvents.ts │ │ │ ├── useOrgInfo.ts │ │ │ ├── useParticipants.ts │ │ │ └── useRemoveMember.ts │ │ └── user │ │ │ ├── index.ts │ │ │ └── useTickets.ts │ ├── index.ts │ ├── useAuth.ts │ ├── useDebounce.ts │ ├── useMediaQuery.ts │ ├── useOutsideClick.ts │ ├── useRoles.ts │ └── useToggle.ts │ ├── layout │ ├── AuthModal.tsx │ ├── DashboardLayout.tsx │ ├── FormBuilderLayout.tsx │ ├── HomeLayout.tsx │ ├── components │ │ ├── DashBoardRoute.tsx │ │ ├── DashboardNav.tsx │ │ ├── FormBuilderNav.tsx │ │ ├── MainNav.tsx │ │ ├── UserNav.tsx │ │ └── constants.ts │ └── index.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── next.d.ts │ ├── package.json │ ├── pages │ ├── 404.tsx │ ├── [org].tsx │ ├── _app.tsx │ ├── events │ │ ├── [id].tsx │ │ └── index.tsx │ ├── index.tsx │ ├── org │ │ ├── [id] │ │ │ ├── [eventid] │ │ │ │ ├── event.tsx │ │ │ │ ├── form │ │ │ │ │ ├── [formid] │ │ │ │ │ │ ├── builder.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── participants.tsx │ │ │ │ ├── revenue.tsx │ │ │ │ └── tasks.tsx │ │ │ └── index.tsx │ │ └── index.tsx │ ├── tickets │ │ └── index.tsx │ └── verify.tsx │ ├── postcss.config.js │ ├── public │ ├── 404.svg │ ├── banner.png │ ├── event.jpg │ ├── foss.png │ ├── logo.svg │ ├── lottie │ │ ├── coming.json │ │ ├── empty.json │ │ ├── flight.json │ │ └── ticketsLottie.json │ ├── main.png │ └── placeholder.png │ ├── store │ └── useFormState.ts │ ├── tailwind.config.js │ ├── theme │ └── style.css │ ├── tsconfig.json │ ├── types │ └── index.ts │ ├── ui │ ├── components │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── checkbox.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── skeleton.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx │ └── lib │ │ └── utils.ts │ ├── utils │ ├── convert2base64.ts │ ├── debounce.ts │ ├── index.ts │ ├── isProd.ts │ └── pluralize.ts │ ├── views │ ├── dashboard │ │ ├── components │ │ │ ├── ComingSoon.tsx │ │ │ ├── lottie │ │ │ │ └── TicketLottie.tsx │ │ │ ├── participants │ │ │ │ ├── FormDrawer.tsx │ │ │ │ ├── Participants.tsx │ │ │ │ └── RemoveUserModal.tsx │ │ │ ├── settings │ │ │ │ ├── DeleteEvent.tsx │ │ │ │ ├── NewEventModal.tsx │ │ │ │ └── PublishModal.tsx │ │ │ └── teams │ │ │ │ └── Teams.tsx │ │ └── index.ts │ ├── form │ │ ├── components │ │ │ ├── builder │ │ │ │ ├── AvailableFields.tsx │ │ │ │ ├── BuilderConfig.tsx │ │ │ │ ├── BuilderPreview.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormDescription.tsx │ │ │ │ └── RenderField.tsx │ │ │ ├── common.ts │ │ │ ├── dashboard │ │ │ │ ├── FormsLoader.tsx │ │ │ │ └── NewFormModal.tsx │ │ │ ├── public │ │ │ │ ├── PublicFormModal.tsx │ │ │ │ └── SchemaPreview.tsx │ │ │ └── types.ts │ │ ├── constants.ts │ │ └── index.ts │ ├── home │ │ ├── components │ │ │ └── Animate.tsx │ │ └── index.ts │ ├── org │ │ ├── components │ │ │ ├── dashboard │ │ │ │ ├── NewOrgDialog.tsx │ │ │ │ ├── OrgCard.tsx │ │ │ │ └── SkeltonCard.tsx │ │ │ ├── events │ │ │ │ └── Events.tsx │ │ │ ├── settings │ │ │ │ ├── DeleteModal.tsx │ │ │ │ ├── DeleteOrg.tsx │ │ │ │ ├── LeaveModal.tsx │ │ │ │ ├── LeaveOrg.tsx │ │ │ │ └── OrgSettings.tsx │ │ │ └── teams │ │ │ │ ├── InviteModal.tsx │ │ │ │ ├── Members.tsx │ │ │ │ └── RemoveMemberModal.tsx │ │ └── index.ts │ ├── profile │ │ ├── components │ │ │ └── ProfileModal.tsx │ │ └── index.ts │ ├── tasks │ │ ├── components │ │ │ ├── Kanban.tsx │ │ │ ├── KanbanTask.tsx │ │ │ ├── TaskPane.tsx │ │ │ └── TaskPreviewPane.tsx │ │ └── index.ts │ └── tickets │ │ ├── components │ │ ├── TicketCard.tsx │ │ └── TicketModal.tsx │ │ └── index.ts │ └── vitest.config.ts ├── docker-compose.yaml ├── package.json ├── packages ├── docs │ ├── .gitkeep │ ├── all-events.png │ ├── editor.png │ ├── event.png │ ├── members.png │ ├── orgs.png │ ├── particpents.png │ ├── profile.png │ └── settings.png └── infra │ ├── .terraform.lock.hcl │ ├── main.tf │ └── provide.tf ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json └── turbo.json /.env.example: -------------------------------------------------------------------------------- 1 | DB_USER=fossfolio 2 | DB_PASS=fossfolio 3 | DB_NAME=fossfolio -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: Run all unit test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [dev] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-20.04 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x] # Update to a version that satisfies pnpm requirements 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup pnpm 22 | uses: pnpm/action-setup@v2 23 | with: 24 | version: 8 25 | 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: 'pnpm' 31 | 32 | - name: Install dependencies and run tests 33 | run: | 34 | pnpm --filter web install --no-frozen-lockfile 35 | pnpm --filter web test:unit 36 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 FossLabs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/api/.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT_ID= 2 | GITHUB_CLIENT_SECRET= 3 | GITHUB_SCOPE=user:email 4 | 5 | SESSION_SECRET=secret 6 | JWT_SECRET=secret 7 | DATABASE_URL=postgres://fossfolio:fossfolio@localhost:5432/fossfolio 8 | 9 | ACCESS_TOKEN_VALIDITY=10s 10 | CLIENT_REDIRECT_URI=http://localhost:3000/auth 11 | API_BASE_URL=http://localhost:8000 12 | WEB_URL=http://localhost:3000 13 | 14 | GOOGLE_CLIENT_ID= 15 | GOOGLE_CLIENT_SECRET= 16 | GOOGLE_CALLBACK_URL=http://localhost:8000/auth/google/callback 17 | GOOGLE_SCOPE=profile,email 18 | 19 | 20 | 21 | MAIL_FROM=info@fossfolio.com 22 | MAIL_HOST= 23 | MAIL_PASSWORD= 24 | MAIL_PORT=587 25 | MAIL_USER= 26 | 27 | 28 | AWS_ACCESS_KEY = '' 29 | AWS_SECRET_KEY = '' 30 | AWS_REGION = "" 31 | 32 | 33 | 34 | STRIPE_SECRET_KEY = "" 35 | STRIPE_WEBHOOK_SECRET = "" 36 | 37 | AI_KEY = "" -------------------------------------------------------------------------------- /apps/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node 2 | 3 | RUN npm install -g pnpm 4 | 5 | COPY package.json package.json 6 | COPY .env .env 7 | COPY src src 8 | 9 | RUN pnpm install 10 | RUN pnpm run start 11 | 12 | ENTRYPOINT ["pnpm", "start:prod"] 13 | -------------------------------------------------------------------------------- /apps/api/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.7.0/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "security": { 11 | "recommended": true 12 | }, 13 | "complexity": { 14 | "noThisInStatic": "off" 15 | }, 16 | "performance": { 17 | "recommended": true 18 | }, 19 | "style": { 20 | "useImportType": "off" 21 | } 22 | } 23 | }, 24 | "javascript": { 25 | "parser": { 26 | "unsafeParameterDecoratorsEnabled": true 27 | }, 28 | "formatter": { 29 | "enabled": true, 30 | "semicolons": "always", 31 | "quoteStyle": "single" 32 | } 33 | }, 34 | "json": { 35 | "formatter": { 36 | "trailingCommas": "none" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/api/env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | export interface ProcessEnv { 3 | DATABASE_URL: string; 4 | GITHUB_CLIENT_ID: string; 5 | GITHUB_CLIENT_SECRET: string; 6 | GITHUB_CALLBACK_URL: string; 7 | GITHUB_SCOPE: string; 8 | ACCESS_TOKEN_VALIDITY: string; 9 | API_BASE_URL: string; 10 | GOOGLE_CLIENT_ID: string; 11 | GOOGLE_CLIENT_SECRET: string; 12 | GOOGLE_CALLBACK_URL: string; 13 | GOOGLE_SCOPE: string; 14 | WEB_URL: string; 15 | MAIL_HOST: string; 16 | MAIL_PORT: number; 17 | MAIL_USER: string; 18 | MAIL_PASSWORD: string; 19 | AWS_ACCESS_KEY: string; 20 | AWS_SECRET_KEY: string; 21 | AWS_REGION: string; 22 | STRIPE_SECRET_KEY: string; 23 | STRIPE_WEBHOOK_SECRET: string; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "builder": { 7 | "type": "swc", 8 | "options": { 9 | "extensions": [".jsx", ".tsx", ".js", ".ts"] 10 | } 11 | }, 12 | "jsc": { 13 | "parser": { 14 | "syntax": "typescript", 15 | "decorators": true, 16 | "tsx": true, 17 | "dynamicImport": true 18 | } 19 | }, 20 | "typeCheck": true, 21 | "deleteOutDir": true, 22 | "assets": ["mail/templates/**/*"], 23 | "watchAssets": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20230714190314_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "uid" TEXT NOT NULL, 4 | "email" TEXT, 5 | "displayName" TEXT, 6 | "slug" TEXT, 7 | "photoURL" TEXT, 8 | "refreshToken" TEXT, 9 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | 11 | CONSTRAINT "User_pkey" PRIMARY KEY ("uid") 12 | ); 13 | 14 | -- CreateTable 15 | CREATE TABLE "Account" ( 16 | "id" TEXT NOT NULL, 17 | "userId" TEXT NOT NULL, 18 | "provider" TEXT NOT NULL, 19 | "providerAccountId" TEXT NOT NULL, 20 | "providerAccessToken" TEXT, 21 | "providerRefreshToken" TEXT, 22 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 23 | "updatedAt" TIMESTAMP(3) NOT NULL, 24 | 25 | CONSTRAINT "Account_pkey" PRIMARY KEY ("id") 26 | ); 27 | 28 | -- CreateIndex 29 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 30 | 31 | -- CreateIndex 32 | CREATE UNIQUE INDEX "User_slug_key" ON "User"("slug"); 33 | 34 | -- CreateIndex 35 | CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); 36 | 37 | -- AddForeignKey 38 | ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 39 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20230716033116_org_member_user_mapping/migration.sql: -------------------------------------------------------------------------------- 1 | -- AddForeignKey 2 | ALTER TABLE "OrganizationMember" ADD CONSTRAINT "OrganizationMember_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20230801023517_added_events/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Events" ( 3 | "id" TEXT NOT NULL DEFAULT (gen_random_uuid())::text, 4 | "name" TEXT NOT NULL, 5 | "description" TEXT NOT NULL, 6 | "website" TEXT, 7 | "location" TEXT NOT NULL, 8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | "lastDate" TIMESTAMP(3) NOT NULL, 10 | "organizationId" TEXT NOT NULL, 11 | 12 | CONSTRAINT "Events_pkey" PRIMARY KEY ("id") 13 | ); 14 | 15 | -- CreateTable 16 | CREATE TABLE "_EventsToUser" ( 17 | "A" TEXT NOT NULL, 18 | "B" TEXT NOT NULL 19 | ); 20 | 21 | -- CreateIndex 22 | CREATE UNIQUE INDEX "Events_id_key" ON "Events"("id"); 23 | 24 | -- CreateIndex 25 | CREATE UNIQUE INDEX "_EventsToUser_AB_unique" ON "_EventsToUser"("A", "B"); 26 | 27 | -- CreateIndex 28 | CREATE INDEX "_EventsToUser_B_index" ON "_EventsToUser"("B"); 29 | 30 | -- AddForeignKey 31 | ALTER TABLE "Events" ADD CONSTRAINT "Events_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; 32 | 33 | -- AddForeignKey 34 | ALTER TABLE "_EventsToUser" ADD CONSTRAINT "_EventsToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Events"("id") ON DELETE CASCADE ON UPDATE CASCADE; 35 | 36 | -- AddForeignKey 37 | ALTER TABLE "_EventsToUser" ADD CONSTRAINT "_EventsToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 38 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231019150437_wyswyg/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The `description` column on the `Events` table would be dropped and recreated. This will lead to data loss if there is data in the column. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Events" ADD COLUMN "isPublished" BOOLEAN NOT NULL DEFAULT false, 9 | DROP COLUMN "description", 10 | ADD COLUMN "description" JSONB; 11 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231104165757_events/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "collegeName" TEXT, 3 | ADD COLUMN "isCollegeEvent" BOOLEAN NOT NULL DEFAULT true, 4 | ADD COLUMN "isTeamEvent" BOOLEAN NOT NULL DEFAULT false, 5 | ADD COLUMN "maxTeamSize" INTEGER NOT NULL DEFAULT 1, 6 | ADD COLUMN "minTeamSize" INTEGER NOT NULL DEFAULT 1; 7 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231106124429_npm_run_nest_dev/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ALTER COLUMN "isCollegeEvent" SET DEFAULT false; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231108172146_npm_run_nest_dev/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `collegeName` on the `Events` table. All the data in the column will be lost. 5 | - Added the required column `updatedAt` to the `Events` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Events" DROP COLUMN "collegeName", 10 | ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; 11 | 12 | -- AlterTable 13 | ALTER TABLE "User" ADD COLUMN "collegeName" TEXT, 14 | ADD COLUMN "isStudent" BOOLEAN; 15 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231113174329_event_date/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `eventDate` to the `Events` table without a default value. This is not possible if the table is not empty. 5 | - Added the required column `ticketMaxCount` to the `Events` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Events" ADD COLUMN "eventDate" TIMESTAMP(3) NOT NULL, 10 | ADD COLUMN "ticketMaxCount" INTEGER NOT NULL; 11 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231113174511_optioanl_events/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ALTER COLUMN "eventDate" DROP NOT NULL, 3 | ALTER COLUMN "ticketMaxCount" DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231113175751_event_ticket/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `ticketMaxCount` on the `Events` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Events" DROP COLUMN "ticketMaxCount", 9 | ADD COLUMN "maxTicketCount" INTEGER; 10 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20231203091321_last_date/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ALTER COLUMN "lastDate" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240106192643_builder/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "FieldType" AS ENUM ('SingleLineText', 'LongText', 'SingleSelect', 'MultiSelect', 'Checkbox', 'Number', 'Email', 'URL', 'PhoneNumber', 'Attachment'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "Field" ( 6 | "id" TEXT NOT NULL, 7 | "title" TEXT NOT NULL, 8 | "label" TEXT, 9 | "placeholder" TEXT, 10 | "options" TEXT, 11 | "required" BOOLEAN NOT NULL DEFAULT false, 12 | "type" "FieldType" NOT NULL, 13 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 14 | "updatedAt" TIMESTAMP(3) NOT NULL, 15 | 16 | CONSTRAINT "Field_pkey" PRIMARY KEY ("id") 17 | ); 18 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240210121024_forms/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `title` on the `Field` table. All the data in the column will be lost. 5 | - Made the column `label` on table `Field` required. This step will fail if there are existing NULL values in that column. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Field" DROP COLUMN "title", 10 | ADD COLUMN "eventsId" TEXT, 11 | ALTER COLUMN "label" SET NOT NULL; 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "Field" ADD CONSTRAINT "Field_eventsId_fkey" FOREIGN KEY ("eventsId") REFERENCES "Events"("id") ON DELETE SET NULL ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240214191116_coverimage/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "coverImage" TEXT; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240216173647_saml_oauth/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "SAMLProviders" ( 3 | "id" TEXT NOT NULL, 4 | "issuer" TEXT NOT NULL, 5 | "cert" TEXT NOT NULL, 6 | "entryPoint" TEXT NOT NULL, 7 | "wantAssertionsSigned" BOOLEAN NOT NULL, 8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | "updatedAt" TIMESTAMP(3) NOT NULL, 10 | 11 | CONSTRAINT "SAMLProviders_pkey" PRIMARY KEY ("id") 12 | ); 13 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240216173805_rename/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `SAMLProviders` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropTable 8 | DROP TABLE "SAMLProviders"; 9 | 10 | -- CreateTable 11 | CREATE TABLE "SamlConfig" ( 12 | "id" TEXT NOT NULL, 13 | "issuer" TEXT NOT NULL, 14 | "cert" TEXT NOT NULL, 15 | "entryPoint" TEXT NOT NULL, 16 | "wantAssertionsSigned" BOOLEAN NOT NULL, 17 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 18 | "updatedAt" TIMESTAMP(3) NOT NULL, 19 | 20 | CONSTRAINT "SamlConfig_pkey" PRIMARY KEY ("id") 21 | ); 22 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240216174111_uniwue/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[issuer]` on the table `SamlConfig` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "SamlConfig_issuer_key" ON "SamlConfig"("issuer"); 9 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240217150659_form/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "isFormPublished" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240218161849_payment/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "ticketPrice" INTEGER NOT NULL DEFAULT 0; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240218180341_stripe/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "stripe_price_object" TEXT; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240218183007_stripe_product/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "stripe_prdouct_object" TEXT; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240218183649_stripe/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `stripe_prdouct_object` on the `Events` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Events" DROP COLUMN "stripe_prdouct_object", 9 | ADD COLUMN "stripe_product_object" TEXT; 10 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240221161707_response/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Response" ( 3 | "id" TEXT NOT NULL, 4 | "data" JSONB NOT NULL, 5 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "updatedAt" TIMESTAMP(3) NOT NULL, 7 | "userUid" TEXT NOT NULL, 8 | "eventsId" TEXT, 9 | 10 | CONSTRAINT "Response_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "Response" ADD CONSTRAINT "Response_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "Response" ADD CONSTRAINT "Response_eventsId_fkey" FOREIGN KEY ("eventsId") REFERENCES "Events"("id") ON DELETE SET NULL ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240319134219_ticket/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `_EventsToUser` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "_EventsToUser" DROP CONSTRAINT "_EventsToUser_A_fkey"; 9 | 10 | -- DropForeignKey 11 | ALTER TABLE "_EventsToUser" DROP CONSTRAINT "_EventsToUser_B_fkey"; 12 | 13 | -- DropTable 14 | DROP TABLE "_EventsToUser"; 15 | 16 | -- CreateTable 17 | CREATE TABLE "Ticket" ( 18 | "id" TEXT NOT NULL, 19 | "eventsId" TEXT NOT NULL, 20 | "userUid" TEXT NOT NULL, 21 | 22 | CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id") 23 | ); 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_eventsId_fkey" FOREIGN KEY ("eventsId") REFERENCES "Events"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | 28 | -- AddForeignKey 29 | ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 30 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240319134318_time/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `updatedAt` to the `Ticket` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Ticket" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; 10 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240323153154_slug/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[slug]` on the table `Events` will be added. If there are existing duplicate values, this will fail. 5 | - Added the required column `slug` to the `Events` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Events" ADD COLUMN IF NOT EXISTS "slug" TEXT UNIQUE; 10 | 11 | UPDATE "Events" SET "slug" = CONCAT('event_', id); 12 | 13 | ALTER TABLE "Events" ALTER COLUMN "slug" SET NOT NULL; 14 | 15 | -- CreateIndex 16 | CREATE UNIQUE INDEX IF NOT EXISTS "Events_slug_key" ON "Events"("slug"); 17 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240324115128_/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Kanban" ( 3 | "id" TEXT NOT NULL, 4 | "title" TEXT NOT NULL, 5 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "updatedAt" TIMESTAMP(3) NOT NULL, 7 | "userUid" TEXT NOT NULL, 8 | "eventsId" TEXT, 9 | 10 | CONSTRAINT "Kanban_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateTable 14 | CREATE TABLE "Task" ( 15 | "id" TEXT NOT NULL, 16 | "title" TEXT NOT NULL, 17 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 18 | "updatedAt" TIMESTAMP(3) NOT NULL, 19 | "kanbanId" TEXT, 20 | "userUid" TEXT NOT NULL, 21 | 22 | CONSTRAINT "Task_pkey" PRIMARY KEY ("id") 23 | ); 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "Kanban" ADD CONSTRAINT "Kanban_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | 28 | -- AddForeignKey 29 | ALTER TABLE "Kanban" ADD CONSTRAINT "Kanban_eventsId_fkey" FOREIGN KEY ("eventsId") REFERENCES "Events"("id") ON DELETE SET NULL ON UPDATE CASCADE; 30 | 31 | -- AddForeignKey 32 | ALTER TABLE "Task" ADD CONSTRAINT "Task_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 33 | 34 | -- AddForeignKey 35 | ALTER TABLE "Task" ADD CONSTRAINT "Task_kanbanId_fkey" FOREIGN KEY ("kanbanId") REFERENCES "Kanban"("id") ON DELETE SET NULL ON UPDATE CASCADE; 36 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240330180131_options/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The `options` column on the `Field` table would be dropped and recreated. This will lead to data loss if there is data in the column. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Field" DROP COLUMN "options", 9 | ADD COLUMN "options" TEXT[]; 10 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240330183017_comments/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Comment" ( 3 | "id" TEXT NOT NULL, 4 | "taskId" TEXT NOT NULL, 5 | "data" JSONB NOT NULL, 6 | 7 | CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "Comment" ADD CONSTRAINT "Comment_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240330184341_user_comment/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `userUid` to the `Comment` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Comment" ADD COLUMN "userUid" TEXT NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "Comment" ADD CONSTRAINT "Comment_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240407160532_add_pgvector_extension/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateExtension 2 | CREATE EXTENSION IF NOT EXISTS "vector" WITH SCHEMA "public"; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240407164324_idk/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Events" ADD COLUMN "embedding" vector; -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240407185306_fix_migration/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `embedding` on the `Events` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Events" DROP COLUMN "embedding", 9 | ADD COLUMN "embedding_description" vector(768), 10 | ADD COLUMN "embedding_title" vector(768); 11 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240407191338_some_changes/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "FieldType" ADD VALUE 'DateTime'; 3 | 4 | create index if not exists "index_embedding_description" on "Events" using hnsw (embedding_description vector_cosine_ops); 5 | create index if not exists "index_embedding_title" on "Events" using hnsw (embedding_title vector_cosine_ops); -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240407194731_active/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "index_embedding_description"; 3 | 4 | -- DropIndex 5 | DROP INDEX "index_embedding_title"; 6 | 7 | -- AlterTable 8 | ALTER TABLE "Organization" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true; 9 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/20240407194857_nest/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Organization" ALTER COLUMN "isActive" SET DEFAULT false; 3 | -------------------------------------------------------------------------------- /apps/api/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /apps/api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RootModule } from './modules/root.module'; 3 | 4 | @Module({ 5 | imports: [RootModule], 6 | }) 7 | export class AppModule {} 8 | -------------------------------------------------------------------------------- /apps/api/src/controllers/ai.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body } from '@nestjs/common'; 2 | import { AiService } from '../services/ai.service'; 3 | import { ZodValidator } from '@api/validation/zod.validation.decorator'; 4 | import { AIFormSchema, AIFormDto } from '@api/dto/ai.dto'; 5 | 6 | @Controller('ai') 7 | export class AiController { 8 | constructor(private readonly aiService: AiService) {} 9 | 10 | @Post('form') 11 | @ZodValidator({ 12 | body: AIFormSchema, 13 | }) 14 | async generateForm(@Body() aiFormDto: AIFormDto) { 15 | return this.aiService.gptComplete(aiFormDto.prompt, aiFormDto.messages); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/api/src/controllers/cloud.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Post, 4 | UseInterceptors, 5 | UploadedFile, 6 | ParseFilePipe, 7 | UseGuards, 8 | FileTypeValidator, 9 | MaxFileSizeValidator, 10 | } from '@nestjs/common'; 11 | import { S3Service } from '@api/services/cloud.service'; 12 | import { ApiOperation, ApiTags } from '@nestjs/swagger'; 13 | import { AuthGuard } from '@nestjs/passport'; 14 | import { RbacGuard } from '@api/services/guards/rbac-member.guard'; 15 | import { FileInterceptor } from '@nestjs/platform-express'; 16 | 17 | @Controller('/cloud') 18 | @ApiTags('Cloud') 19 | export class CloudController { 20 | constructor(private readonly s3Service: S3Service) {} 21 | 22 | @Post('/upload') 23 | @UseGuards(AuthGuard('jwt'), RbacGuard) 24 | @UseInterceptors(FileInterceptor('file')) 25 | @ApiOperation({ summary: 'Upload image for event cover page' }) 26 | async uploadFile( 27 | @UploadedFile( 28 | new ParseFilePipe({ 29 | // file size validators 30 | validators: [ 31 | new FileTypeValidator({ fileType: '.(png|jpeg|jpg)' }), // support png,jpg,peg 32 | new MaxFileSizeValidator({ maxSize: 1024 * 1024 * 4 }), // File size 4 megabytes 33 | ], 34 | }), 35 | ) 36 | file: Express.Multer.File, 37 | ) { 38 | const image = await this.s3Service.uploadFile(file); 39 | return { 40 | file: image, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/api/src/controllers/org-invite.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | Query, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { RbacGuard } from '../services/guards/rbac-member.guard'; 11 | import { Roles } from '../services/decorator/roles.decorator'; 12 | import { OrganizationInviteService } from '../services/org-invite.service'; 13 | import { AuthUser } from '../services/auth/decorators/user.decorator'; 14 | import { AuthGuard } from '@nestjs/passport'; 15 | import { Role } from '@api/utils/db'; 16 | import { User } from '@api/db/schema/user'; 17 | import type { OrgInvite } from '../services/dto/user-invite.dto'; 18 | import { ApiTags } from '@nestjs/swagger'; 19 | 20 | @Controller('org/:orgId/invite') 21 | @ApiTags('Org - Invite') 22 | export class OrgInviteController { 23 | constructor(private readonly service: OrganizationInviteService) {} 24 | 25 | @Post('/') 26 | @Roles(Role.ADMIN) 27 | @UseGuards(AuthGuard('jwt'), RbacGuard) 28 | async sendInvite( 29 | @AuthUser() user: User, 30 | @Body() data: OrgInvite, 31 | @Param('orgId') orgId: string, 32 | ) { 33 | return this.service.inviteToOrg({ 34 | email: data.email, 35 | userId: user.id, 36 | role: data.role, 37 | orgId: orgId, 38 | }); 39 | } 40 | 41 | @Get('/verify') 42 | @UseGuards(AuthGuard('jwt'), RbacGuard) 43 | async verfyEmail(@AuthUser() user: User, @Query() { id }) { 44 | return this.service.verifyEmailInvite(id, user.id, user.email); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /apps/api/src/controllers/public-events.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { ApiOperation, ApiTags } from '@nestjs/swagger'; 4 | import type { User } from '@prisma/client'; 5 | import { EventsService } from '../services/events.service'; 6 | import { AuthUser } from '../services/auth/decorators/user.decorator'; 7 | import { RbacGuard } from '../services/guards/rbac-member.guard'; 8 | import type { RegisterEventDto } from '../services/dto/register-event.dto'; 9 | 10 | @Controller() 11 | @ApiTags('Events - Public View') 12 | export class PublicEventsController { 13 | constructor(private readonly events: EventsService) {} 14 | 15 | @Post('/register') 16 | @ApiTags('events') 17 | @ApiOperation({ summary: 'Register for specific event' }) 18 | @UseGuards(AuthGuard('jwt'), RbacGuard) 19 | async registerEvent(@Body() data: RegisterEventDto, @AuthUser() user: User) { 20 | return await this.events.registerEvent(data.eventId, user.uid); 21 | } 22 | 23 | @Get('/ticket/:eventId') 24 | @ApiTags('events') 25 | @ApiOperation({ summary: 'return ticket info' }) 26 | async getTicketInfo(@Param('eventId') eventId: string) { 27 | return await this.events.getTicketInfo(eventId); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/api/src/controllers/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Patch, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { AuthUser } from '../services/auth/decorators/user.decorator'; 4 | import { OrganizationService } from '../services/organization.service'; 5 | import { UserService } from '../services/user.service'; 6 | import { User } from '@api/db/schema'; 7 | import type { UpdateUserDto } from '../services/dto/update-user.dto'; 8 | import { OrgModel } from '@api/models'; 9 | import { ApiTags } from '@nestjs/swagger'; 10 | 11 | @Controller('user') 12 | @ApiTags('Users') 13 | export class UserController { 14 | constructor( 15 | private readonly organizationService: OrganizationService, 16 | private readonly userService: UserService, 17 | ) {} 18 | 19 | @Get('/orgs') 20 | @UseGuards(AuthGuard('jwt')) 21 | async findOrgs(@AuthUser() user: User) { 22 | return OrgModel.getOrgsWithUserAsMember(user.id); 23 | } 24 | 25 | @Get('/') 26 | @UseGuards(AuthGuard('jwt')) 27 | async getUser(@AuthUser() user: User) { 28 | return this.userService.findUserById(user.id); 29 | } 30 | 31 | @Patch('/') 32 | @UseGuards(AuthGuard('jwt')) 33 | async updateUser( 34 | @Body() updateUserDto: UpdateUserDto, 35 | @AuthUser() user: User, 36 | ) { 37 | return this.userService.updateUser(user, updateUserDto); 38 | } 39 | 40 | @Get('/tickets') 41 | @UseGuards(AuthGuard('jwt')) 42 | async getUserTickets(@AuthUser() user: User) { 43 | return this.userService.getReservedTickets(user.id); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/01.user.ts: -------------------------------------------------------------------------------- 1 | import type { Knex } from 'knex'; 2 | 3 | import { SystemTable } from '../../utils/db'; 4 | 5 | export async function up(knex: Knex): Promise { 6 | const isTableExists = await knex.schema.hasTable(SystemTable.User); 7 | 8 | if (!isTableExists) { 9 | await knex.schema.createTable(SystemTable.User, (table) => { 10 | table.string('id', 25).primary(); 11 | 12 | table.string('email').notNullable().unique(); 13 | 14 | table.string('display_name'); 15 | 16 | table.string('slug').unique(); 17 | 18 | table.text('photo_url'); 19 | 20 | table.text('college_name'); 21 | 22 | table.boolean('is_student'); 23 | 24 | table.text('refresh_token'); 25 | 26 | table.boolean('is_deleted').defaultTo(false); 27 | 28 | table.timestamps(true, true); 29 | }); 30 | } 31 | } 32 | 33 | export async function down(knex: Knex): Promise { 34 | await knex.schema.dropTableIfExists(SystemTable.User); 35 | } 36 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/02.account.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Account); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Account, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_user_id', 25).notNullable(); 12 | 13 | table.string('provider').notNullable(); 14 | table.string('provider_account_id').notNullable(); 15 | 16 | table.text('provider_access_token'); 17 | table.text('provider_refresh_token'); 18 | 19 | table.boolean('is_deleted').defaultTo(false); 20 | 21 | table.timestamps(true, true); 22 | }); 23 | } 24 | } 25 | 26 | export async function down(knex: Knex): Promise { 27 | await knex.schema.dropTableIfExists(SystemTable.Account); 28 | } 29 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/03.organization.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Org); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Org, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('name').notNullable().unique(); 12 | 13 | table.string('slug').unique(); 14 | 15 | table.boolean('is_verified').defaultTo(false); 16 | 17 | table.boolean('is_deleted').defaultTo(false); 18 | 19 | table.timestamps(true, true); 20 | }); 21 | } 22 | } 23 | 24 | export async function down(knex: Knex): Promise { 25 | await knex.schema.dropTableIfExists(SystemTable.Org); 26 | } 27 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/04.organization-member.ts: -------------------------------------------------------------------------------- 1 | import { Role, SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.OrgMember); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.OrgMember, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_organization_id', 25); 12 | 13 | table.string('fk_user_id', 25); 14 | 15 | table.enum('role', [Role.ADMIN, Role.EDITOR, Role.VIEWER]); 16 | 17 | table.boolean('is_deleted').defaultTo(false); 18 | 19 | table.timestamps(true, true); 20 | }); 21 | } 22 | } 23 | 24 | export async function down(knex: Knex): Promise { 25 | await knex.schema.dropTableIfExists(SystemTable.OrgMember); 26 | } 27 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/05.organization-invite.ts: -------------------------------------------------------------------------------- 1 | import { Role, SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.OrgInvite); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.OrgInvite, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_organization_id', 25); 12 | 13 | table.string('invitee_email', 255); 14 | 15 | table.string('inviter_uid', 25); 16 | 17 | table.enum('role', [Role.ADMIN, Role.EDITOR, Role.VIEWER]); 18 | 19 | table.boolean('is_deleted').defaultTo(false); 20 | 21 | table.timestamps(true, true); 22 | }); 23 | } 24 | } 25 | 26 | export async function down(knex: Knex): Promise { 27 | await knex.schema.dropTableIfExists(SystemTable.OrgInvite); 28 | } 29 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/06.events.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Events); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Events, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('name', 255); 12 | 13 | table.string('slug', 255); 14 | 15 | table.string('fk_organization_id', 25); 16 | 17 | table.string('description'); 18 | 19 | table.string('website').nullable(); 20 | 21 | table.string('cover_image').nullable(); 22 | 23 | table.string('location', 255); 24 | 25 | table.timestamp('event_date'); 26 | 27 | table.boolean('is_published').defaultTo(false); 28 | 29 | table.boolean('is_deleted').defaultTo(false); 30 | 31 | table.timestamps(true, true); 32 | }); 33 | } 34 | } 35 | 36 | export async function down(knex: Knex): Promise { 37 | await knex.schema.dropTableIfExists(SystemTable.Events); 38 | } 39 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/07.event-ticket.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.EventTicket); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.EventTicket, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_event_id', 25); 12 | 13 | table.string('name', 255); 14 | 15 | table.string('description'); 16 | 17 | table.decimal('price', 10, 2); 18 | 19 | table.integer('quantity'); 20 | 21 | table.json('stripe_price_object').nullable(); 22 | 23 | table.json('stripe_product_object').nullable(); 24 | 25 | table.boolean('is_active').defaultTo(false); 26 | 27 | table.timestamps(true, true); 28 | }); 29 | } 30 | } 31 | 32 | export async function down(knex: Knex): Promise { 33 | await knex.schema.dropTableIfExists(SystemTable.EventTicket); 34 | } 35 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/08.form-field.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable, FormInput } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.FormFields); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.FormFields, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_form_id', 25); 12 | 13 | table.string('name', 255); 14 | 15 | table.string('placeholder'); 16 | 17 | table.string('description'); 18 | 19 | table.boolean('required').defaultTo('false'); 20 | 21 | table.enum('type', FormInput); 22 | 23 | table.boolean('is_deleted').defaultTo(false); 24 | 25 | table.timestamps(true, true); 26 | }); 27 | } 28 | } 29 | 30 | export async function down(knex: Knex): Promise { 31 | await knex.schema.dropTableIfExists(SystemTable.FormFields); 32 | } 33 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/09.form.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Form); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Form, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_event_id', 25); 12 | 13 | table.string('title', 255); 14 | 15 | table.string('description'); 16 | 17 | table.string('logo_url'); 18 | 19 | table.string('banner_url'); 20 | 21 | table.string('confirmation_message'); 22 | 23 | table.json('misc'); 24 | 25 | table.boolean('is_default_form').defaultTo(false); 26 | 27 | table.boolean('is_published').defaultTo(false); 28 | 29 | table.boolean('is_deleted').defaultTo(false); 30 | 31 | table.timestamps(true, true); 32 | }); 33 | } 34 | } 35 | 36 | export async function down(knex: Knex): Promise { 37 | await knex.schema.dropTableIfExists(SystemTable.Form); 38 | } 39 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/10.form-field-options.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable, FormInput } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable( 6 | SystemTable.FormFieldOptions, 7 | ); 8 | 9 | if (!isTableExists) { 10 | await knex.schema.createTable(SystemTable.FormFieldOptions, (table) => { 11 | table.string('id', 25).primary(); 12 | 13 | table.string('fk_form_id', 25); 14 | 15 | table.string('option', 255); 16 | 17 | table.boolean('is_deleted').defaultTo(false); 18 | 19 | table.timestamps(true, true); 20 | }); 21 | } 22 | } 23 | 24 | export async function down(knex: Knex): Promise { 25 | await knex.schema.dropTableIfExists(SystemTable.FormFieldOptions); 26 | } 27 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/11.form-response.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.FormResponse); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.FormResponse, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_form_id', 25); 12 | 13 | table.string('fk_event_id', 25); 14 | 15 | table.string('fk_user_id', 25).nullable(); 16 | 17 | table.json('response'); 18 | 19 | table.boolean('is_deleted').defaultTo(false); 20 | 21 | table.timestamps(true, true); 22 | }); 23 | } 24 | } 25 | 26 | export async function down(knex: Knex): Promise { 27 | await knex.schema.dropTableIfExists(SystemTable.FormResponse); 28 | } 29 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/12.ticket.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Ticket); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Ticket, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_event_id', 25); 12 | 13 | table.string('fk_user_id', 25).nullable(); 14 | 15 | table.string('fk_event_ticket_id', 25); 16 | 17 | table.float('paid_amount'); 18 | 19 | table.boolean('is_deleted').defaultTo(false); 20 | 21 | table.timestamps(true, true); 22 | }); 23 | } 24 | } 25 | 26 | export async function down(knex: Knex): Promise { 27 | await knex.schema.dropTableIfExists(SystemTable.Ticket); 28 | } 29 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/13.kanban.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Kanban); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Kanban, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_event_id', 25); 12 | 13 | table.string('title'); 14 | 15 | table.boolean('is_deleted').defaultTo(false); 16 | 17 | table.timestamps(true, true); 18 | }); 19 | } 20 | } 21 | 22 | export async function down(knex: Knex): Promise { 23 | await knex.schema.dropTableIfExists(SystemTable.Kanban); 24 | } 25 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/14.kanban-card.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.KanbanCard); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.KanbanCard, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_kanban_id', 25); 12 | 13 | table.string('title'); 14 | 15 | table.string('description'); 16 | 17 | table.string('fk_user_id', 25); 18 | 19 | table.boolean('is_deleted').defaultTo(false); 20 | 21 | table.timestamps(true, true); 22 | }); 23 | } 24 | } 25 | 26 | export async function down(knex: Knex): Promise { 27 | await knex.schema.dropTableIfExists(SystemTable.KanbanCard); 28 | } 29 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/15.comment.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable(SystemTable.Comment); 6 | 7 | if (!isTableExists) { 8 | await knex.schema.createTable(SystemTable.Comment, (table) => { 9 | table.string('id', 25).primary(); 10 | 11 | table.string('fk_user_id', 25); 12 | 13 | table.string('fk_event_id', 25); 14 | 15 | table.string('fk_kanban_card_id', 25); 16 | 17 | table.string('fk_kanban_id', 25); 18 | 19 | table.string('comment'); 20 | 21 | table.boolean('is_deleted').defaultTo(false); 22 | 23 | table.timestamps(true, true); 24 | }); 25 | } 26 | } 27 | 28 | export async function down(knex: Knex): Promise { 29 | await knex.schema.dropTableIfExists(SystemTable.Comment); 30 | } 31 | -------------------------------------------------------------------------------- /apps/api/src/db/migrations/16.field_options_id.ts: -------------------------------------------------------------------------------- 1 | import { SystemTable } from '../../utils/db'; 2 | import type { Knex } from 'knex'; 3 | 4 | export async function up(knex: Knex): Promise { 5 | const isTableExists = await knex.schema.hasTable( 6 | SystemTable.FormFieldOptions, 7 | ); 8 | 9 | if (!isTableExists) { 10 | await knex.schema.alterTable(SystemTable.FormFieldOptions, (table) => { 11 | table.string('fk_field_id', 25); 12 | }); 13 | } 14 | } 15 | 16 | export async function down(knex: Knex): Promise { 17 | await knex.schema.alterTable(SystemTable.FormFieldOptions, (table) => { 18 | table.dropColumn('fk_field_id'); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/account.ts: -------------------------------------------------------------------------------- 1 | import type { SystemFields } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const AccountSchema = z.object({ 5 | id: z.string().length(25), 6 | 7 | fk_user_id: z.string().length(25), 8 | 9 | provider: z.string(), 10 | provider_account_id: z.string(), 11 | 12 | provider_access_token: z.string().optional(), 13 | provider_refresh_token: z.string().optional(), 14 | 15 | created_at: z.date(), 16 | updated_at: z.date(), 17 | 18 | is_deleted: z.boolean().default(false), 19 | }); 20 | 21 | export type Account = z.infer; 22 | 23 | export type AccountCreateSchema = Omit< 24 | z.input, 25 | SystemFields 26 | >; 27 | 28 | export type AccountUpdateSchema = Partial; 29 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/comment.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import type { SystemFields } from '../../utils/db'; 4 | 5 | export const CommentSchema = z.object({ 6 | id: z.string().length(25), 7 | 8 | fk_user_id: z.string().length(25), 9 | 10 | fk_event_id: z.string().length(25), 11 | 12 | fk_kanban_card_id: z.string().length(25), 13 | 14 | fk_kanban_id: z.string().length(25), 15 | 16 | comment: z.string().min(3).max(255), 17 | 18 | is_deleted: z.boolean().default(false), 19 | 20 | created_at: z.date(), 21 | 22 | updated_at: z.date(), 23 | }); 24 | 25 | export type Comment = z.infer; 26 | 27 | export type CommentCreateSchema = Omit< 28 | z.input, 29 | SystemFields 30 | >; 31 | 32 | export type CommentUpdateSchema = Partial; 33 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/event-ticket.ts: -------------------------------------------------------------------------------- 1 | import type { SystemFields } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const EventTicketSchema = z.object({ 5 | id: z.string().length(25), 6 | 7 | fk_event_id: z.string().length(25), 8 | 9 | // id of the user who is registering 10 | fk_user_id: z.string(), 11 | 12 | name: z.string().min(3).max(255), 13 | 14 | description: z.string(), 15 | 16 | price: z.number().min(0), 17 | 18 | quantity: z.number().min(0), 19 | 20 | stripe_price_object: z.record(z.unknown()).optional(), 21 | 22 | stripe_product_object: z.record(z.unknown()).optional(), 23 | 24 | is_active: z.boolean().default(false), 25 | 26 | is_deleted: z.boolean().default(false), 27 | 28 | created_at: z.date(), 29 | 30 | updated_at: z.date(), 31 | }); 32 | 33 | export type EventTicket = z.infer; 34 | 35 | export type EventTicketCreateSchema = Omit< 36 | z.input, 37 | SystemFields 38 | >; 39 | 40 | export type EventTicketUpdateSchema = Partial; 41 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/events.ts: -------------------------------------------------------------------------------- 1 | import type { SystemFields } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const EventSchema = z.object({ 5 | id: z.string().length(25), 6 | 7 | name: z.string().min(3).max(255), 8 | 9 | slug: z.string().min(3).max(255), 10 | 11 | fk_organization_id: z.string().length(25), 12 | 13 | description: z.string().optional(), 14 | 15 | website: z.string().url().optional(), 16 | 17 | cover_image: z.string().url().optional(), 18 | 19 | location: z.string().min(3).max(255), 20 | 21 | event_date: z.date(), 22 | 23 | is_published: z.boolean().default(false), 24 | 25 | is_deleted: z.boolean().default(false), 26 | 27 | created_at: z.date(), 28 | 29 | updated_at: z.date(), 30 | }); 31 | 32 | export type Event = z.infer; 33 | 34 | export type EventCreateSchema = Omit, SystemFields>; 35 | 36 | export type EventUpdateSchema = Partial; 37 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/form-field-options.ts: -------------------------------------------------------------------------------- 1 | import type { SystemFields } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const FormFieldOptionsSchems = z.object({ 5 | id: z.string().length(25), 6 | 7 | fk_form_id: z.string().length(25), 8 | 9 | fk_field_id: z.string().length(25), 10 | 11 | option: z.string(), 12 | 13 | is_deleted: z.boolean().default(false), 14 | 15 | created_at: z.date(), 16 | 17 | updated_at: z.date(), 18 | }); 19 | 20 | export type FormFieldOptions = z.infer; 21 | 22 | export type FormFieldOptionsCreateSchema = Omit< 23 | z.input, 24 | SystemFields 25 | >; 26 | 27 | export type FormFieldOptionsUpdateSchema = 28 | Partial; 29 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/form-fields.ts: -------------------------------------------------------------------------------- 1 | import { SystemFields, FormInput } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const FormFieldSchema = z.object({ 5 | id: z.string().length(25), 6 | 7 | fk_form_id: z.string().length(25), 8 | 9 | name: z.string().min(3).max(255), 10 | 11 | placeholder: z.string().optional(), 12 | 13 | description: z.string().optional(), 14 | 15 | required: z.boolean().default(false), 16 | 17 | type: z.enum(FormInput), 18 | 19 | is_deleted: z.boolean().default(false), 20 | 21 | created_at: z.date(), 22 | 23 | updated_at: z.date(), 24 | }); 25 | 26 | export type FormField = z.infer; 27 | 28 | export type FormFieldCreateSchema = Omit< 29 | z.input, 30 | SystemFields 31 | >; 32 | 33 | export type FormFieldUpdateSchema = Partial; 34 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/form-response.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import type { SystemFields } from '../../utils/db'; 4 | 5 | export const FormResponseSchema = z.object({ 6 | id: z.string().length(25), 7 | 8 | fk_form_id: z.string().length(25), 9 | 10 | fk_event_id: z.string().length(25), 11 | 12 | fk_user_id: z.string().length(25).optional(), 13 | 14 | response: z.record(z.unknown()), 15 | 16 | is_deleted: z.boolean().default(false), 17 | 18 | created_at: z.date(), 19 | 20 | updated_at: z.date(), 21 | }); 22 | 23 | export type FormResponse = z.infer; 24 | 25 | export type FormResponseCreateSchema = Omit< 26 | z.input, 27 | SystemFields 28 | >; 29 | 30 | export type FormResponseUpdateSchema = Partial; 31 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/form.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import type { SystemFields } from '../../utils/db'; 4 | 5 | export const FormSchema = z.object({ 6 | id: z.string().length(25), 7 | 8 | fk_event_id: z.string().length(25), 9 | 10 | title: z.string().min(3).max(255), 11 | 12 | description: z.string(), 13 | 14 | logo_url: z.string().url().optional(), 15 | 16 | banner_url: z.string().url().optional(), 17 | 18 | confirmation_message: z.string().optional(), 19 | 20 | misc: z.record(z.unknown()).optional(), 21 | 22 | is_default_form: z.boolean().default(true), 23 | 24 | is_published: z.boolean().default(false), 25 | 26 | is_deleted: z.boolean().default(false), 27 | 28 | created_at: z.date(), 29 | 30 | updated_at: z.date(), 31 | }); 32 | 33 | export type Form = z.infer; 34 | 35 | export type FormCreateSchema = Omit, SystemFields>; 36 | 37 | export type FormUpdateSchema = Partial; 38 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/kanban-card.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import type { SystemFields } from '../../utils/db'; 4 | 5 | export const KanbanCardSchema = z.object({ 6 | id: z.string().length(25), 7 | 8 | fk_kanban_id: z.string().length(25), 9 | 10 | title: z.string().min(3).max(255), 11 | 12 | description: z.string().optional(), 13 | 14 | is_deleted: z.boolean().default(false), 15 | 16 | fk_user_id: z.string().length(25), 17 | 18 | created_at: z.date(), 19 | 20 | updated_at: z.date(), 21 | }); 22 | 23 | export type KanbanCard = z.infer; 24 | 25 | export type KanbanCardCreateSchema = Omit< 26 | z.input, 27 | SystemFields 28 | >; 29 | 30 | export type KanbanCardUpdateSchema = Partial; 31 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/kanban.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import type { SystemFields } from '../../utils/db'; 3 | 4 | export const KanbanSchema = z.object({ 5 | id: z.string().length(25), 6 | 7 | fk_event_id: z.string().length(25), 8 | 9 | title: z.string().min(3).max(255), 10 | 11 | is_deleted: z.boolean().default(false), 12 | 13 | created_at: z.date(), 14 | 15 | updated_at: z.date(), 16 | }); 17 | 18 | export type Kanban = z.infer; 19 | 20 | export type KanbanCreateSchema = Omit< 21 | z.input, 22 | SystemFields 23 | >; 24 | 25 | export type KanbanUpdateSchema = Partial; 26 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/organization-invite.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import type { SystemFields } from '../../utils/db'; 4 | import { Role } from '../../utils/db'; 5 | 6 | export const OrganizationInviteSchema = z.object({ 7 | id: z.string().length(25), 8 | 9 | fk_organization_id: z.string().length(25), 10 | 11 | invitee_email: z.string().email(), 12 | 13 | inviter_uid: z.string().length(25), 14 | 15 | role: z.enum([Role.ADMIN, Role.EDITOR, Role.VIEWER]), 16 | 17 | created_at: z.date(), 18 | 19 | updated_at: z.date(), 20 | 21 | is_deleted: z.boolean().default(false), 22 | }); 23 | 24 | export type OrganizationInvite = z.infer; 25 | 26 | export type OrganizationInviteCreateSchema = Omit< 27 | z.input, 28 | SystemFields 29 | >; 30 | 31 | export type OrganizationInviteUpdateSchema = 32 | Partial; 33 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/organization-member.ts: -------------------------------------------------------------------------------- 1 | import { Role, type SystemFields } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const OrganizationMemberSchema = z.object({ 5 | id: z.string().length(25), 6 | 7 | fk_organization_id: z.string().length(25), 8 | 9 | fk_user_id: z.string().length(25), 10 | 11 | role: z.enum([Role.ADMIN, Role.EDITOR, Role.VIEWER]), 12 | 13 | created_at: z.date(), 14 | 15 | updated_at: z.date(), 16 | 17 | is_deleted: z.boolean().default(false), 18 | }); 19 | 20 | export type OrganizationMember = z.infer; 21 | 22 | export type OrganizationMemberCreateSchema = Omit< 23 | z.input, 24 | SystemFields 25 | >; 26 | 27 | export type OrganizationMemberUpdateSchema = 28 | Partial; 29 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/organization.ts: -------------------------------------------------------------------------------- 1 | import type { SystemFields } from '../../utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const OrganizationSchema = z.object({ 5 | id: z.string().length(25), 6 | name: z.string(), 7 | slug: z.string(), 8 | 9 | is_verified: z.boolean().default(false), 10 | 11 | is_deleted: z.boolean().default(false), 12 | 13 | created_at: z.date(), 14 | updated_at: z.date(), 15 | }); 16 | 17 | export type Organization = z.infer; 18 | 19 | export type OrganizationCreateSchema = Omit< 20 | z.input, 21 | SystemFields 22 | >; 23 | 24 | export type OrganizationUpdateSchema = Partial; 25 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/tickets.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import type { SystemFields } from '../../utils/db'; 4 | 5 | export const TicketSchema = z.object({ 6 | id: z.string().length(25), 7 | 8 | fk_event_id: z.string().length(25), 9 | 10 | fk_user_id: z.string().length(25).optional(), 11 | 12 | fk_event_ticket_id: z.string().length(25), 13 | 14 | paid_amount: z.number(), 15 | 16 | is_deleted: z.boolean().default(false), 17 | 18 | created_at: z.date(), 19 | 20 | updated_at: z.date(), 21 | }); 22 | 23 | export type Ticket = z.infer; 24 | 25 | export type TicketCreateSchema = Omit< 26 | z.input, 27 | SystemFields 28 | >; 29 | 30 | export type TicketUpdateSchema = Partial; 31 | -------------------------------------------------------------------------------- /apps/api/src/db/schema/user.ts: -------------------------------------------------------------------------------- 1 | import { SystemFields } from '@api/utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const UserSchema = z.object({ 5 | id: z.string().length(25), 6 | email: z.string().email(), 7 | display_name: z.string(), 8 | slug: z.string(), 9 | photo_url: z.string().optional(), 10 | college_name: z.string().optional(), 11 | is_student: z.boolean().optional(), 12 | 13 | refresh_token: z.string(), 14 | 15 | created_at: z.date(), 16 | updated_at: z.date(), 17 | is_deleted: z.boolean().default(false), 18 | }); 19 | 20 | export type User = z.infer; 21 | 22 | export type UserCreateSchema = Omit; 23 | 24 | export type UserUpdateSchema = Partial; 25 | -------------------------------------------------------------------------------- /apps/api/src/dto/ai.dto.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export const AIFormSchema = z.object({ 3 | prompt: z.string(), 4 | messages: z.array( 5 | z.object({ 6 | text: z.string(), 7 | ai: z.boolean(), 8 | }), 9 | ), 10 | }); 11 | 12 | export type AIFormDto = z.infer; 13 | -------------------------------------------------------------------------------- /apps/api/src/dto/events.dto.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const CreateEventSchema = z.object({ 4 | name: z.string(), 5 | 6 | description: z.string().optional(), 7 | 8 | website: z.string(), 9 | 10 | location: z.string(), 11 | 12 | cover_image: z.string(), 13 | 14 | event_date: z.string().transform((str) => new Date(str)), 15 | }); 16 | 17 | export const CreateEventParamsSchema = z.object({ 18 | orgId: z.string(), 19 | }); 20 | 21 | export const DashBoardEventParamsSchema = z.object({ 22 | eventId: z.string(), 23 | }); 24 | 25 | export const EventParamsSchema = z.object({ 26 | eventId: z.string(), 27 | orgId: z.string(), 28 | }); 29 | 30 | export const PublicEventParamsSchema = z.object({ 31 | slug: z.string(), 32 | }); 33 | 34 | export const UpdateEventSchema = z.object({ 35 | name: z.string().optional(), 36 | 37 | description: z.string().optional(), 38 | 39 | website: z.string().url().optional(), 40 | 41 | location: z.string().optional(), 42 | 43 | cover_image: z.string().url().optional(), 44 | 45 | is_published: z.boolean().optional(), 46 | }); 47 | 48 | export type UpdateEventDto = z.infer; 49 | 50 | export type CreateEventDto = z.infer; 51 | 52 | export type DashBoardEventParams = z.infer; 53 | 54 | export type PublicEventParams = z.infer; 55 | 56 | export type EventParams = z.infer; 57 | -------------------------------------------------------------------------------- /apps/api/src/dto/form-field.dto.ts: -------------------------------------------------------------------------------- 1 | import { FormInput } from '@api/utils/db'; 2 | import { z } from 'zod'; 3 | 4 | export const CreateFormFieldSchema = z.object({ 5 | name: z.string(), 6 | placeholder: z.string().optional(), 7 | required: z.boolean(), 8 | type: z.enum(FormInput), 9 | description: z.string().optional(), 10 | options: z.string().array().optional(), 11 | }); 12 | 13 | export const EditFormFieldSchema = z.object({ 14 | name: z.string().optional(), 15 | placeholder: z.string().optional(), 16 | required: z.boolean().optional(), 17 | type: z.enum(FormInput).optional(), 18 | description: z.string().optional(), 19 | options: z.string().array().optional(), 20 | }); 21 | 22 | export type CreateFormFieldDto = z.infer; 23 | 24 | export type EditFormFieldDto = z.infer; 25 | -------------------------------------------------------------------------------- /apps/api/src/dto/form.dto.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export const NewFormSchema = z.object({ 3 | title: z.string(), 4 | description: z.string(), 5 | }); 6 | 7 | export const updateFormSchema = z.object({ 8 | title: z.string().optional(), 9 | description: z.string().optional(), 10 | }); 11 | 12 | export type NewFormDto = z.infer; 13 | 14 | export type UpdateFormDto = z.infer; 15 | -------------------------------------------------------------------------------- /apps/api/src/dto/update-task.dto.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const UpdateTaskBoard = z.object({ 4 | kanbanId: z.string(), 5 | }); 6 | 7 | export type IUpdateTaskBoard = z.infer; 8 | -------------------------------------------------------------------------------- /apps/api/src/error.ts: -------------------------------------------------------------------------------- 1 | export const ORG_EXISTS = { 2 | ok: false, 3 | message: 'An organization with that slug already exists', 4 | error: 'ORG_EXISTS', 5 | }; 6 | export const ORG_NOT_FOUND = { 7 | ok: false, 8 | message: 'An organization with that slug does not exist', 9 | error: 'ORG_NOT_FOUND', 10 | }; 11 | 12 | export const ORG_MEMBER_NOT_FOUND = { 13 | ok: false, 14 | message: 'An organization member with that user id does not exist', 15 | error: 'ORG_MEMBER_NOT_FOUND', 16 | }; 17 | 18 | export const ROLE_UPDATE_FAILED = { 19 | ok: false, 20 | message: 'Failed to update role', 21 | error: 'ROLE_UPDATE_FAILED', 22 | }; 23 | 24 | export const USER_NOT_FOUND = { 25 | ok: false, 26 | message: 'User Doesnt Exist', 27 | error: 'USER_NOT_FOUND', 28 | }; 29 | 30 | export const USER_UPDATE_ERROR = { 31 | ok: false, 32 | message: 'Cant Update User', 33 | error: 'USER_UPDATE_ERROR', 34 | }; 35 | 36 | export const ORG_ID_NOT_FOUND = { 37 | ok: false, 38 | message: "couldn't find the org", 39 | error: 'please provide an organizationId', 40 | }; 41 | 42 | export const NO_ROLE_ACCESS = { 43 | ok: false, 44 | message: 'you are not authorized to visit the page', 45 | error: 'insufficent role access', 46 | }; 47 | -------------------------------------------------------------------------------- /apps/api/src/middleware/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/api/src/middleware/.gitkeep -------------------------------------------------------------------------------- /apps/api/src/models/Account.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { Account } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class AccountModel extends BaseModel( 7 | SystemTable.Account, 8 | ) { 9 | constructor() { 10 | const logger = new Logger('Account Model'); 11 | super(logger); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/models/Comment.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { Comment } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class CommentModel extends BaseModel( 7 | SystemTable.Comment, 8 | ) { 9 | constructor() { 10 | const logger = new Logger('Comment Model'); 11 | super(logger); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/models/Events.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { Event } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class EventModel extends BaseModel( 7 | SystemTable.Events, 8 | ) { 9 | constructor() { 10 | const logger = new Logger('Event Model'); 11 | super(logger); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/models/Form.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { Form } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | import BaseContext from '@api/BaseContext'; 6 | import { FFError } from '@api/utils/error'; 7 | import { Knex } from 'knex'; 8 | 9 | export class FormModel extends BaseModel( 10 | SystemTable.Form, 11 | ) { 12 | constructor() { 13 | const logger = new Logger('Form Model'); 14 | super(logger); 15 | } 16 | 17 | static async getAllFormsWithSubmissionsCount(eventId: string, trx?: Knex) { 18 | try { 19 | const qb = trx ?? BaseContext.knex; 20 | 21 | const data = await qb(SystemTable.Form) 22 | .select( 23 | '*', 24 | qb.raw( 25 | `(SELECT COUNT(*)::integer FROM ${SystemTable.FormResponse} e WHERE e.fk_form_id = ${SystemTable.Form}.id AND e.is_deleted = false) as total_submissions`, 26 | ), 27 | ) 28 | .orderBy('created_at', 'asc'); 29 | 30 | return data; 31 | } catch (error) { 32 | FFError.databaseError( 33 | `${SystemTable.Form}: ${SystemTable.FormResponse}: Query Failed : `, 34 | error, 35 | ); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/api/src/models/FormFieldOptions.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { FormFieldOptions } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class FormFieldOptionsModel extends BaseModel< 7 | SystemTable.FormFieldOptions, 8 | FormFieldOptions 9 | >(SystemTable.FormFieldOptions) { 10 | constructor() { 11 | const logger = new Logger('FormFieldOptions Model'); 12 | super(logger); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/models/FormFields.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { FormField } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | import { Knex } from 'knex'; 6 | import BaseContext from '@api/BaseContext'; 7 | import { FFError } from '@api/utils/error'; 8 | import { FormModel } from './Form'; 9 | 10 | export class FormFieldsModel extends BaseModel< 11 | SystemTable.FormFields, 12 | FormField 13 | >(SystemTable.FormFields) { 14 | constructor() { 15 | const logger = new Logger('FormFields Model'); 16 | super(logger); 17 | } 18 | 19 | public static async getFieldWithOptions(formId: string, trx?: Knex) { 20 | try { 21 | const qb = trx ?? BaseContext.knex; 22 | const schema = await qb(SystemTable.FormFields) 23 | .select( 24 | '*', 25 | BaseContext.knex.raw( 26 | `(SELECT json_agg(option) FROM ${SystemTable.FormFieldOptions} WHERE ${SystemTable.FormFieldOptions}.fk_form_id = ${SystemTable.FormFields}.id) as options`, 27 | ), 28 | ) 29 | .where('fk_form_id', formId) 30 | .andWhere('is_deleted', false); 31 | 32 | const formInfo = await FormModel.findById(formId); 33 | 34 | return { 35 | schema, 36 | data: formInfo, 37 | }; 38 | } catch (error) { 39 | FFError.databaseError( 40 | `${SystemTable.FormFields}: Query Failed : `, 41 | error, 42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/api/src/models/FormResponse.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { FormResponse } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class FormResponseModel extends BaseModel< 7 | SystemTable.FormResponse, 8 | FormResponse 9 | >(SystemTable.FormResponse) { 10 | constructor() { 11 | const logger = new Logger('FormResponse Model'); 12 | super(logger); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/models/Kanban.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from "@api/models/BaseModel"; 2 | import { Kanban } from "@api/db/schema"; 3 | import { Logger } from "@nestjs/common"; 4 | import { SystemTable } from "@api/utils/db"; 5 | import { Knex } from "knex"; 6 | import BaseContext from "@api/BaseContext"; 7 | import { FFError } from "@api/utils/error"; 8 | 9 | export class KanbanModal extends BaseModel( 10 | SystemTable.Kanban 11 | ) { 12 | constructor() { 13 | const logger = new Logger("Kanban Model"); 14 | super(logger); 15 | } 16 | 17 | static async findKanbanBoardsByEvent(id: string, trx?: Knex) { 18 | try { 19 | const qb = trx ?? BaseContext.knex; 20 | 21 | const kanban = await qb(SystemTable.Kanban) 22 | .where("fk_event_id", id) 23 | .select( 24 | "*", 25 | qb.raw( 26 | `(SELECT COALESCE(json_agg(row_to_json(${SystemTable.KanbanCard})), '[]'::json) FROM ${SystemTable.KanbanCard} WHERE ${SystemTable.KanbanCard}.fk_kanban_id = ${SystemTable.Kanban}.id) as tasks` 27 | ) 28 | ); 29 | 30 | return kanban; 31 | } catch (error) { 32 | FFError.databaseError(`${SystemTable.Kanban}: Query Failed : `, error); 33 | throw error; // Make sure to re-throw the error after logging it 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/api/src/models/KanbanCard.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { KanbanCard } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class KanbanCardModal extends BaseModel< 7 | SystemTable.KanbanCard, 8 | KanbanCard 9 | >(SystemTable.KanbanCard) { 10 | constructor() { 11 | const logger = new Logger('KanbanCard Model'); 12 | super(logger); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/models/OrgInvite.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { OrganizationInvite } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class OrgInviteModel extends BaseModel< 7 | SystemTable.OrgInvite, 8 | OrganizationInvite 9 | >(SystemTable.OrgInvite) { 10 | constructor() { 11 | const logger = new Logger('OrgInvite Model'); 12 | super(logger); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/models/Ticket.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { Ticket } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | 6 | export class TicketModel extends BaseModel( 7 | SystemTable.Ticket, 8 | ) { 9 | constructor() { 10 | const logger = new Logger('Ticket Model'); 11 | super(logger); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/models/User.ts: -------------------------------------------------------------------------------- 1 | import BaseModel from '@api/models/BaseModel'; 2 | import { OrganizationMember, User } from '@api/db/schema'; 3 | import { Logger } from '@nestjs/common'; 4 | import { SystemTable } from '@api/utils/db'; 5 | import { Knex } from 'knex'; 6 | import BaseContext from '@api/BaseContext'; 7 | import { FFError } from '@api/utils/error'; 8 | 9 | export class UserModel extends BaseModel( 10 | SystemTable.User, 11 | ) { 12 | constructor() { 13 | const logger = new Logger('UserModel'); 14 | super(logger); 15 | } 16 | 17 | static async findUserByEmail(email: string, trx?: Knex) { 18 | try { 19 | const user = await (trx ?? BaseContext.knex)( 20 | SystemTable.User, 21 | ) 22 | .where({ email }) 23 | .first(); 24 | return user; 25 | } catch (error) { 26 | FFError.databaseError(`${SystemTable.User}: Query Failed : `, error); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/api/src/models/index.ts: -------------------------------------------------------------------------------- 1 | import { AccountModel } from './Account'; 2 | import { CommentModel } from './Comment'; 3 | import { UserModel } from './User'; 4 | import { EventModel } from './Events'; 5 | import { EventTicketModel } from './EventTicket'; 6 | import { FormModel } from './Form'; 7 | import { FormFieldOptionsModel } from './FormFieldOptions'; 8 | import { FormFieldsModel } from './FormFields'; 9 | import { FormResponseModel } from './FormResponse'; 10 | import { KanbanModal } from './Kanban'; 11 | import { KanbanCardModal } from './KanbanCard'; 12 | import { OrgModel } from './Org'; 13 | import { OrgInviteModel } from './OrgInvite'; 14 | import { TicketModel } from './Ticket'; 15 | import { OrgMemberModel } from './OrgMember'; 16 | 17 | export { 18 | AccountModel, 19 | UserModel, 20 | EventModel, 21 | OrgMemberModel, 22 | CommentModel, 23 | EventTicketModel, 24 | FormModel, 25 | FormFieldOptionsModel, 26 | FormFieldsModel, 27 | FormResponseModel, 28 | KanbanModal, 29 | KanbanCardModal, 30 | OrgModel, 31 | OrgInviteModel, 32 | TicketModel, 33 | }; 34 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/cookieHandler.ts: -------------------------------------------------------------------------------- 1 | import type { Response } from 'express'; 2 | 3 | import BaseContext from '@api/BaseContext'; 4 | 5 | export const cookieHandler = ( 6 | res: Response, 7 | authToken: { 8 | accessToken: string; 9 | refreshToken: string; 10 | }, 11 | redirect?: boolean, 12 | ) => { 13 | res.cookie('access_token', authToken.accessToken, { 14 | httpOnly: true, 15 | secure: BaseContext.config.get('NODE_ENV') === 'production', 16 | maxAge: 1000 * 60 * 60 * 24, // 1 day 17 | }); 18 | res.cookie('refresh_token', authToken.refreshToken, { 19 | httpOnly: true, 20 | secure: BaseContext.config.get('NODE_ENV') === 'production', 21 | maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week 22 | }); 23 | 24 | if (!redirect) { 25 | return res.status(200).json({ 26 | message: 'Success', 27 | }); 28 | } 29 | 30 | return res 31 | .status(200) 32 | .redirect(BaseContext.config.get('CLIENT_REDIRECT_URI')); 33 | }; 34 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/decorators/role.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; 2 | import type { Role } from '@prisma/client'; 3 | 4 | export const UserRole = createParamDecorator( 5 | (_, ctx: ExecutionContext): Role => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | 8 | return request.role; 9 | }, 10 | ); 11 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/decorators/user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; 2 | 3 | export const AuthUser = createParamDecorator( 4 | (data: unknown, ctx: ExecutionContext) => { 5 | const request = ctx.switchToHttp().getRequest(); 6 | 7 | return request.user; 8 | }, 9 | ); 10 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/guards/github-oauth.guard.ts: -------------------------------------------------------------------------------- 1 | import { type ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class GithubAuthGuard extends AuthGuard('github') { 6 | getAuthenticateOptions(context: ExecutionContext) { 7 | const req = context.switchToHttp().getRequest(); 8 | return { 9 | state: { 10 | redirect_uri: req.query.redirect_uri, 11 | }, 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/guards/google-oauth.guard.ts: -------------------------------------------------------------------------------- 1 | import { type ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class GoogleAuthGuard extends AuthGuard('google') { 6 | getAuthenticateOptions(context: ExecutionContext) { 7 | const req = context.switchToHttp().getRequest(); 8 | return { 9 | state: { 10 | redirect_uri: req.query.redirect_uri, 11 | }, 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/guards/refresh.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class RefreshGuard extends AuthGuard('refresh') {} 6 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/strategy/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import type { Request } from 'express'; 5 | import { ExtractJwt, Strategy } from 'passport-jwt'; 6 | import { UserService } from '@api/services/user.service'; 7 | import { FFError } from '@api/utils/error'; 8 | import { UserModel } from '@api/models'; 9 | 10 | @Injectable() 11 | export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { 12 | constructor( 13 | private readonly configService: ConfigService, 14 | private readonly userService: UserService, 15 | ) { 16 | super({ 17 | ignoreExpiration: false, 18 | secretOrKey: configService.get('JWT_SECRET'), 19 | jwtFromRequest: ExtractJwt.fromExtractors([ 20 | (request: Request) => { 21 | const data = request?.cookies.access_token; 22 | if (!data) { 23 | FFError.unauthorized('Access Token not found'); 24 | } 25 | return data; 26 | }, 27 | ]), 28 | }); 29 | } 30 | 31 | async validate(payload: { iss: string; sub: string; iat: number }) { 32 | const user = await UserModel.findById(payload.sub); 33 | 34 | if (!user) { 35 | FFError.unauthorized('Invalid user'); 36 | } 37 | return user; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/api/src/services/auth/strategy/refresh.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import type { Request } from 'express'; 5 | import { ExtractJwt, Strategy } from 'passport-jwt'; 6 | import { FFError } from '@api/utils/error'; 7 | import { UserModel } from '@api/models'; 8 | 9 | @Injectable() 10 | export class RefreshStrategy extends PassportStrategy(Strategy, 'refresh') { 11 | constructor(configService: ConfigService) { 12 | super({ 13 | ignoreExpiration: true, 14 | passReqToCallback: true, 15 | secretOrKey: configService.get('JWT_SECRET'), 16 | jwtFromRequest: ExtractJwt.fromExtractors([ 17 | (request: Request) => { 18 | const data = request?.cookies.refresh_token; 19 | if (!data) { 20 | FFError.unauthorized('Invalid refresh token'); 21 | } 22 | return data; 23 | }, 24 | ]), 25 | }); 26 | } 27 | 28 | async validate( 29 | _req: Request, 30 | payload: { 31 | sub: string; 32 | iat: number; 33 | exp: number; 34 | }, 35 | ) { 36 | const user = await UserModel.findById(payload.sub); 37 | if (!user) { 38 | FFError.unauthorized('Invalid refresh token'); 39 | } 40 | 41 | return user; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/api/src/services/cloud.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; 3 | import { FFError } from '@api/utils/error'; 4 | import { ConfigService } from '@nestjs/config'; 5 | 6 | @Injectable() 7 | export class S3Service { 8 | private readonly s3Client: S3Client; 9 | 10 | constructor(private readonly configService: ConfigService) { 11 | this.s3Client = new S3Client({ 12 | region: this.configService.get('AWS_REGION') as string, 13 | credentials: { 14 | accessKeyId: this.configService.get('AWS_ACCESS_KEY') as string, 15 | secretAccessKey: this.configService.get('AWS_SECRET_KEY') as string, 16 | }, 17 | }); 18 | } 19 | 20 | async uploadFile(file: Express.Multer.File) { 21 | try { 22 | const { mimetype, originalname } = file; 23 | const command = new PutObjectCommand({ 24 | // TODO: @DarkPhoenix2704 - Make the Bucket name Configurable 25 | Bucket: 'fossfolio', 26 | Key: originalname.split(' ').join('-'), 27 | Body: file.buffer, 28 | ContentType: mimetype, 29 | }); 30 | const data = await this.s3Client.send(command); 31 | 32 | return `https://${command.input.Bucket}.s3.amazonaws.com/${command.input.Key}`; 33 | } catch (error) { 34 | FFError.uploadAttachmentError('Error uploading attachment'); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/api/src/services/decorator/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { Role } from '@api/utils/db'; 3 | 4 | export const Roles = (...roles: Role[]) => SetMetadata('roles', roles); 5 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/cover-dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class ImageUpload { 4 | @ApiProperty() 5 | organizationId: string; 6 | 7 | @ApiProperty() 8 | eventId: string; 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/create-events.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 3 | 4 | export class CreateEventDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | organizationId: string; 9 | @ApiProperty() 10 | @IsString() 11 | website: string; 12 | 13 | @ApiProperty() 14 | @IsString() 15 | @IsNotEmpty() 16 | location: string; 17 | 18 | @ApiProperty() 19 | @IsNumber() 20 | ticketPrice: number; 21 | 22 | @ApiProperty() 23 | @IsString() 24 | @IsNotEmpty() 25 | eventDate: string; 26 | 27 | @ApiProperty() 28 | @IsString() 29 | @IsNotEmpty() 30 | isPaidEvent: string; 31 | 32 | @ApiProperty() 33 | @IsString() 34 | @IsNotEmpty() 35 | lastDate: string; 36 | 37 | @ApiProperty() 38 | @IsNumber() 39 | @IsNotEmpty() 40 | maxTicketCount: number; 41 | } 42 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/create-org.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class CreateOrgDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | name: string; 9 | 10 | @ApiProperty() 11 | @IsString() 12 | @IsNotEmpty() 13 | slug: string; 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/create-task.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import type { Prisma } from '@prisma/client'; 3 | import { IsNotEmpty, IsString } from 'class-validator'; 4 | 5 | export class CreateTask { 6 | @ApiProperty() 7 | @IsString() 8 | @IsNotEmpty() 9 | title: string; 10 | 11 | @ApiProperty() 12 | @IsNotEmpty() 13 | data: Prisma.JsonValue; 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/delete-event.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsString } from 'class-validator'; 3 | 4 | export class DeleteEventDto { 5 | @ApiProperty() 6 | @IsString() 7 | organizationId: string; 8 | } 9 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/delete-org.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class DeleteOrgDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | organizationId: string; 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/generic-org.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class GenericOrgDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | organizationId: string; 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/get-events.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class GetEventByOrgDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsString() 8 | slug: string; 9 | } 10 | export class GetEventByOrgIdDto { 11 | @ApiProperty() 12 | @IsNotEmpty() 13 | @IsString() 14 | id: string; 15 | } 16 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/member-remove.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class RemoveMember { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsString() 8 | memberId: string; 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/publish-form.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsString, IsBoolean, IsNotEmpty } from 'class-validator'; 3 | 4 | export class ToggleFormPublishStatus { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | organizationId: string; 9 | 10 | @ApiProperty() 11 | @IsBoolean() 12 | @IsNotEmpty() 13 | shouldFormPublish: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/publishForm.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsString, IsBoolean, IsNotEmpty } from 'class-validator'; 3 | 4 | export class ToggleFormPublishStatus { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | organizationId: string; 9 | 10 | @ApiProperty() 11 | @IsBoolean() 12 | @IsNotEmpty() 13 | shouldFormPublish: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/register-event.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class RegisterEventDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsString() 8 | eventId: string; 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/remove-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsString } from 'class-validator'; 3 | 4 | export class RemoveUserDto { 5 | @ApiProperty() 6 | @IsString() 7 | organizationId: string; 8 | 9 | @ApiProperty() 10 | @IsString() 11 | eventId: string; 12 | 13 | @ApiProperty() 14 | @IsString() 15 | userId: string; 16 | } 17 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/update-event.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class UpdateEventDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | organizationId: string; 9 | 10 | @ApiProperty() 11 | @IsString() 12 | @IsNotEmpty() 13 | eventSlug: string; 14 | 15 | @ApiProperty() 16 | name: string; 17 | 18 | @ApiProperty() 19 | description?: Record; 20 | 21 | @ApiProperty() 22 | website: string; 23 | 24 | @ApiProperty() 25 | location: string; 26 | 27 | @ApiProperty() 28 | lastDate: Date; 29 | 30 | @ApiProperty() 31 | isPublished: boolean; 32 | 33 | @ApiProperty() 34 | maxTeamSize: number; 35 | 36 | @ApiProperty() 37 | minTeamSize: number; 38 | 39 | @ApiProperty() 40 | isCollegeEvent: boolean; 41 | 42 | @ApiProperty() 43 | maxTicketCount: number; 44 | 45 | @ApiProperty() 46 | eventDate: Date; 47 | 48 | @ApiProperty() 49 | file: string; 50 | } 51 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/update-org.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class UpdateOrgDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | name: string; 9 | 10 | @ApiProperty() 11 | @IsString() 12 | @IsNotEmpty() 13 | slug: string; 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/update-role.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class UpdateMemberRole { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsString() 8 | organizationId: string; 9 | 10 | @ApiProperty() 11 | @IsNotEmpty() 12 | @IsString() 13 | memberId: string; 14 | 15 | @ApiProperty() 16 | @IsNotEmpty() 17 | @IsString() 18 | role: string; 19 | } 20 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsBoolean, IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class UpdateUserDto { 5 | @ApiProperty() 6 | @IsString() 7 | displayName?: string; 8 | 9 | @ApiProperty() 10 | @IsString() 11 | @IsNotEmpty() 12 | slug?: string; 13 | 14 | photoUrl?: string; 15 | 16 | @ApiProperty() 17 | @IsBoolean() 18 | isCollegeStudent?: boolean; 19 | 20 | collegeName?: string; 21 | } 22 | -------------------------------------------------------------------------------- /apps/api/src/services/dto/user-invite.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Role } from '@prisma/client'; 3 | import { IsNotEmpty, IsString } from 'class-validator'; 4 | 5 | export class OrgInvite { 6 | @ApiProperty() 7 | @IsNotEmpty() 8 | @IsString() 9 | email: string; 10 | 11 | @ApiProperty({ 12 | enum: Role, 13 | }) 14 | @IsNotEmpty() 15 | role: Role; 16 | } 17 | -------------------------------------------------------------------------------- /apps/api/src/services/guards/rbac-member.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type CanActivate, 3 | type ExecutionContext, 4 | Injectable, 5 | ForbiddenException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { NO_ROLE_ACCESS } from '../../error'; 9 | import { FFError } from '@api/utils/error'; 10 | import { OrgMemberModel } from '@api/models'; 11 | import { User } from '@api/db/schema'; 12 | import { Request } from 'express'; 13 | import { Role } from '@api/utils/db'; 14 | 15 | @Injectable() 16 | export class RbacGuard implements CanActivate { 17 | constructor(private reflector: Reflector) {} 18 | 19 | async canActivate(context: ExecutionContext): Promise { 20 | const roles = this.reflector.get('roles', context.getHandler()); 21 | if (!roles) { 22 | return true; 23 | } 24 | 25 | const request: IncomingRequest = context.switchToHttp().getRequest(); 26 | const user = request.user as User | undefined; 27 | const organizationId = request.params.orgId; 28 | 29 | if (!organizationId || !user) { 30 | FFError.forbidden(''); 31 | } 32 | 33 | const organizationMember = await OrgMemberModel.findOne({ 34 | fk_organization_id: organizationId, 35 | fk_user_id: user.id, 36 | }); 37 | 38 | if (!organizationMember) { 39 | throw new ForbiddenException(NO_ROLE_ACCESS); 40 | } 41 | request.role = organizationMember.role; 42 | 43 | return roles.includes(organizationMember.role); 44 | } 45 | } 46 | 47 | interface IncomingRequest extends Request { 48 | role: Role; 49 | } 50 | -------------------------------------------------------------------------------- /apps/api/src/services/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, type OnModuleInit } from '@nestjs/common'; 2 | import { PrismaClient } from '@prisma/client'; 3 | 4 | @Injectable() 5 | export class PrismaService extends PrismaClient implements OnModuleInit { 6 | async onModuleInit() { 7 | await this.$connect(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/types/express.ts: -------------------------------------------------------------------------------- 1 | import { User as FFUser } from '@api/db/schema'; 2 | import { Role } from '@api/utils/db'; 3 | 4 | declare namespace Express { 5 | export interface Request { 6 | user?: FFUser; 7 | role?: Role; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/src/utils/envSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const envSchema = z.object({ 4 | DATABASE_URL: z 5 | .string({ 6 | required_error: 'DATABASE_URL is required in the environment variables', 7 | }) 8 | .default('postgres://fossfolio:fossfolio@localhost:5432/fossfolio'), 9 | GITHUB_CLIENT_ID: z.string(), 10 | GITHUB_CLIENT_SECRET: z.string(), 11 | GITHUB_CALLBACK_URL: z.string().url(), 12 | GITHUB_SCOPE: z.string().default('user:email'), 13 | ACCESS_TOKEN_VALIDITY: z.string().default('10h'), 14 | API_BASE_URL: z.string().default('http://localhost:8000'), 15 | GOOGLE_CLIENT_ID: z.string(), 16 | GOOGLE_CLIENT_SECRET: z.string(), 17 | GOOGLE_CALLBACK_URL: z.string().url(), 18 | GOOGLE_SCOPE: z.string().default('profile,email'), 19 | 20 | WEB_URL: z.string().default('http://localhost:3000'), 21 | 22 | MAIL_HOST: z.string(), 23 | MAIL_PORT: z.string().default('587'), 24 | MAIL_USER: z.string(), 25 | MAIL_PASSWORD: z.string(), 26 | 27 | AWS_ACCESS_KEY: z.string(), 28 | AWS_SECRET_KEY: z.string(), 29 | AWS_REGION: z.string(), 30 | 31 | STRIPE_SECRET_KEY: z.string(), 32 | STRIPE_WEBHOOK_SECRET: z.string(), 33 | 34 | JWT_SECRET: z.string(), 35 | 36 | AI_KEY: z.string(), 37 | 38 | NODE_ENV: z.string().default('development'), 39 | 40 | HOST: z.string().default('0.0.0.0'), 41 | PORT: z 42 | .string() 43 | .refine( 44 | (v) => { 45 | const n = Number(v); 46 | return !Number.isNaN(n) && v?.length > 0; 47 | }, 48 | { message: 'Invalid PORT number' }, 49 | ) 50 | .default('8080'), 51 | 52 | DB_HOST: z.string().default('localhost'), 53 | DB_USER: z.string().default('fossfolio'), 54 | DB_PASSWORD: z.string().default('fossfolio'), 55 | DB_NAME: z.string().default('fossfolio'), 56 | DB_PORT: z.string().default('5432'), 57 | }); 58 | -------------------------------------------------------------------------------- /apps/api/src/utils/exclude.ts: -------------------------------------------------------------------------------- 1 | function exclude(k: K, keys: Key[]): Omit { 2 | for (const key of keys) { 3 | delete k[key]; 4 | } 5 | return k; 6 | } 7 | 8 | export default exclude; 9 | -------------------------------------------------------------------------------- /apps/api/src/utils/hyphenate.ts: -------------------------------------------------------------------------------- 1 | export const hyphenate = (text: string): string => { 2 | return text.trim().replace(/ /g, '-'); 3 | }; 4 | -------------------------------------------------------------------------------- /apps/api/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { stripUndefinedOrNull } from '@api/utils/stripUndefined'; 2 | import { hyphenate } from './hyphenate'; 3 | import exclude from './exclude'; 4 | 5 | export { stripUndefinedOrNull, hyphenate, exclude }; 6 | -------------------------------------------------------------------------------- /apps/api/src/utils/stripUndefined.ts: -------------------------------------------------------------------------------- 1 | type NonUndefined = T extends undefined ? never : T; 2 | type NonNull = T extends null ? never : T; 3 | type NonNullableObject = { 4 | [K in keyof T]: NonUndefined>; 5 | }; 6 | 7 | type Prettify = { 8 | [K in keyof T]: T[K]; 9 | } & {}; 10 | 11 | export const stripUndefinedOrNull = ( 12 | obj: T, 13 | ): Prettify> => { 14 | const strip = (input: unknown): unknown => { 15 | return Array.isArray(input) 16 | ? input.map(strip) 17 | : input !== null && typeof input === 'object' 18 | ? Object.entries(input) 19 | .filter(([, value]) => value !== undefined && value !== null) 20 | .reduce((acc, [key, value]) => { 21 | acc[key as keyof typeof acc] = strip(value); 22 | return acc; 23 | }, {} as Record) 24 | : input; 25 | }; 26 | 27 | return strip(obj) as Prettify>; 28 | }; -------------------------------------------------------------------------------- /apps/api/src/validation/zod.validation.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, UsePipes } from '@nestjs/common'; 2 | import { ZodSchema } from 'zod'; 3 | import { ZodValidationPipe } from './zod.validation.pipe'; 4 | 5 | interface ZodValidationOptions { 6 | body?: ZodSchema; 7 | query?: ZodSchema; 8 | params?: ZodSchema; 9 | } 10 | 11 | export function ZodValidator(options: ZodValidationOptions) { 12 | const decorators: ClassDecorator[] = [] as unknown as ClassDecorator[]; 13 | if (options.body) { 14 | decorators.push(UsePipes(new ZodValidationPipe(options.body, 'body'))); 15 | } 16 | 17 | if (options.query) { 18 | decorators.push(UsePipes(new ZodValidationPipe(options.query, 'query'))); 19 | } 20 | if (options.params) { 21 | decorators.push(UsePipes(new ZodValidationPipe(options.params, 'param'))); 22 | } 23 | 24 | return applyDecorators(...decorators); 25 | } 26 | -------------------------------------------------------------------------------- /apps/api/src/validation/zod.validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { PipeTransform, ArgumentMetadata } from '@nestjs/common'; 2 | import { ZodSchema } from 'zod'; 3 | import { FFError } from '@api/utils/error'; 4 | 5 | export class ZodValidationPipe implements PipeTransform { 6 | constructor( 7 | private schema: ZodSchema, 8 | private type: 'body' | 'query' | 'param', 9 | ) {} 10 | 11 | transform(value: unknown, metadata: ArgumentMetadata) { 12 | if (metadata.type !== this.type) { 13 | return value; 14 | } 15 | 16 | try { 17 | const parsedValue = this.schema.parse(value); 18 | return parsedValue; 19 | } catch (error: unknown) { 20 | FFError.badRequest(`Invalid request ${this.type}:${error}`); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "strict": true 5 | }, 6 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx"], 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "removeComments": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "allowSyntheticDefaultImports": true, 11 | "target": "esNext", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./", 15 | "incremental": true, 16 | "skipLibCheck": true, 17 | "strict": false, 18 | "strictNullChecks": true, 19 | "noImplicitAny": false, 20 | "strictBindCallApply": false, 21 | "forceConsistentCasingInFileNames": false, 22 | "noFallthroughCasesInSwitch": true, 23 | "paths": { 24 | "@api/*": ["src/*"] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_URL = '' 2 | NEXT_PUBLIC_WEBSITE_DOMAIN = '' 3 | 4 | AWS_ACCESS_KEY = '' 5 | AWS_SECRET_KEY = '' 6 | 7 | MISTRAL_API_KEY = "" 8 | 9 | NEXT_PUBLIC_POSTHOG_KEY = "" 10 | NEXT_PUBLIC_POSTHOG_HOST = "" 11 | -------------------------------------------------------------------------------- /apps/web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /test-results -------------------------------------------------------------------------------- /apps/web/.prettierignore: -------------------------------------------------------------------------------- 1 | .next -------------------------------------------------------------------------------- /apps/web/.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | module.exports = { 3 | stories: ["../**/*.mdx", "../**/*.stories.@(js|jsx|ts|tsx)"], 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-essentials", 7 | "@storybook/addon-interactions", 8 | "storybook-dark-mode", 9 | { 10 | name: "@storybook/addon-styling", 11 | options: { 12 | postCss: { 13 | implementation: require("postcss"), 14 | }, 15 | }, 16 | }, 17 | ], 18 | framework: { 19 | name: "@storybook/nextjs", 20 | options: {}, 21 | }, 22 | core: { 23 | disableTelemetry: true, 24 | }, 25 | docs: { 26 | autodocs: "tag", 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /apps/web/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { themes } from "@storybook/theming"; 2 | import "../theme/style.css"; 3 | 4 | export const parameters = { 5 | actions: { argTypesRegex: "^on[A-Z].*" }, 6 | controls: { 7 | matchers: { 8 | color: /(background|color)$/i, 9 | date: /Date$/, 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /apps/web/__tests__/Home.test.tsx: -------------------------------------------------------------------------------- 1 | import { expect, it, describe } from "vitest"; 2 | import { render, screen } from "@testing-library/react"; 3 | import Page from "@app/pages/index"; 4 | 5 | describe("Home page", () => { 6 | it("Join Button is visible", () => { 7 | render(); 8 | 9 | const loginSpan = screen 10 | .getByRole("button", { name: /join Event/i }) 11 | .querySelector("span"); 12 | expect(loginSpan).toBeTruthy(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /apps/web/__tests__/components/Button.test.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@app/components/ui/Button"; 2 | import { render, screen } from "@testing-library/react"; 3 | import { describe, expect, it } from "vitest"; 4 | 5 | describe("Button Test in design system", () => { 6 | it("Button is disabled", () => { 7 | render(); 8 | 9 | const btn = screen.getByText("Test").closest("button"); 10 | 11 | expect(btn?.disabled).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "theme/style.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "ui/components", 14 | "utils": "ui/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/components/Error.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { Button } from "@app/components/ui/Button"; 3 | import Image from "next/image"; 4 | 5 | export const Error = () => { 6 | const router = useRouter(); 7 | return ( 8 |
9 | A 404 IMAGE 10 |

11 | Looks Like You're Lost 12 |

13 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /apps/web/components/editor/components/Editor.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@app/ui/lib/utils"; 2 | import { MenuBar } from "./MenuBar"; 3 | import { defaultContent } from "./constants"; 4 | import { extensions } from "./extentions"; 5 | import { EditorContent, useEditor } from "@tiptap/react"; 6 | import React, { forwardRef } from "react"; 7 | 8 | interface Props { 9 | defaultValue?: string; 10 | className?: string; 11 | contentClassName?: string; 12 | onChange?: (e: React.FormEvent) => void; 13 | } 14 | 15 | export const Editor: React.FC = forwardRef( 16 | ({ defaultValue, className, contentClassName, onChange }, ref) => { 17 | const editor = useEditor({ 18 | extensions: extensions, 19 | content: defaultValue || defaultContent, 20 | }); 21 | 22 | return ( 23 |
27 | 28 | 34 |
35 | ); 36 | } 37 | ); 38 | 39 | Editor.displayName = "Editor"; 40 | -------------------------------------------------------------------------------- /apps/web/components/editor/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const defaultContent = ` 2 |

3 | Hi there, 4 |

5 |
6 |

7 | Please Enter Your Event Description Here. 8 |

9 | `; 10 | -------------------------------------------------------------------------------- /apps/web/components/editor/components/extentions.ts: -------------------------------------------------------------------------------- 1 | import Document from "@tiptap/extension-document"; 2 | import Paragraph from "@tiptap/extension-paragraph"; 3 | import Text from "@tiptap/extension-text"; 4 | import TextStyle from "@tiptap/extension-text-style"; 5 | import Bold from "@tiptap/extension-bold"; 6 | import List from "@tiptap/extension-list-item"; 7 | import Italic from "@tiptap/extension-italic"; 8 | import Strike from "@tiptap/extension-strike"; 9 | import Heading from "@tiptap/extension-heading"; 10 | 11 | export const extensions = [ 12 | Document, 13 | Paragraph, 14 | Text, 15 | TextStyle, 16 | Bold, 17 | List, 18 | Italic, 19 | Strike, 20 | Heading.configure({ 21 | levels: [1, 2, 3, 4], 22 | }), 23 | ]; 24 | -------------------------------------------------------------------------------- /apps/web/components/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { Editor } from "./components/Editor"; 2 | -------------------------------------------------------------------------------- /apps/web/components/events/components/NoData.tsx: -------------------------------------------------------------------------------- 1 | import { useLottie } from "lottie-react"; 2 | import Empty from "@app/public/lottie/empty.json"; 3 | 4 | export const NoData = () => { 5 | const defaultOptions = { 6 | loop: true, 7 | autoplay: true, 8 | animationData: Empty, 9 | rendererSettings: { 10 | preserveAspectRatio: "xMidYMid slice", 11 | }, 12 | }; 13 | 14 | const { View } = useLottie(defaultOptions); 15 | return ( 16 |
17 |
{<>{View}}
18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/components/events/components/PreLoader.tsx: -------------------------------------------------------------------------------- 1 | import { EventsLoader } from "@app/components/preloaders"; 2 | 3 | type Prop = { 4 | count: number; 5 | }; 6 | 7 | export const PreLoader = ({ count = 16 }: Prop) => { 8 | return ( 9 |
10 | {new Array(count).fill(0).map((_, index) => ( 11 | 12 | ))} 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/events/index.ts: -------------------------------------------------------------------------------- 1 | export { EventCard } from "./components/Card"; 2 | export { PreLoader } from "./components/PreLoader"; 3 | export { NoData } from "./components/NoData"; 4 | -------------------------------------------------------------------------------- /apps/web/components/preloaders/components/EventsLoader.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@app/ui/components/skeleton"; 2 | 3 | export const EventsLoader = () => ( 4 |
5 | 6 |
7 | 8 | 9 | 10 |
11 |
12 | ); 13 | -------------------------------------------------------------------------------- /apps/web/components/preloaders/components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import { RiLoaderFill } from "react-icons/ri"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | interface LoaderProp { 5 | className?: string; 6 | } 7 | 8 | export const Loader = ({ className = "h-screen" }: LoaderProp) => { 9 | return ( 10 |
16 | 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/components/preloaders/components/OrgLoader.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@app/ui/components/skeleton"; 2 | 3 | export const OrgLoader = ({ count = 9 }) => { 4 | return ( 5 | <> 6 | {new Array(count).fill(0).map((_, index) => ( 7 | 11 | ))} 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /apps/web/components/preloaders/components/PageLoader.tsx: -------------------------------------------------------------------------------- 1 | import { MainNav } from "@app/layout/components/MainNav"; 2 | import { useLottie } from "lottie-react"; 3 | 4 | import Flight from "@app/public/lottie/flight.json"; 5 | 6 | export const PageLoader = () => { 7 | const defaultOptions = { 8 | loop: true, 9 | autoplay: true, 10 | animationData: Flight, 11 | rendererSettings: { 12 | preserveAspectRatio: "xMidYMid slice", 13 | }, 14 | }; 15 | 16 | const { View } = useLottie(defaultOptions); 17 | return ( 18 | <> 19 | 20 |
21 | {typeof window !== undefined && <>{View}} 22 |
23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /apps/web/components/preloaders/index.ts: -------------------------------------------------------------------------------- 1 | export { OrgLoader } from "./components/OrgLoader"; 2 | export { EventsLoader } from "./components/EventsLoader"; 3 | export { PageLoader } from "./components/PageLoader"; 4 | export { Loader } from "./components/Loader"; 5 | -------------------------------------------------------------------------------- /apps/web/components/ui/Button/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { AiFillCaretLeft, AiFillCaretRight } from "react-icons/ai"; 3 | import { Button } from "./Button"; 4 | 5 | const meta: Meta = { 6 | title: "Components/Button", 7 | component: Button, 8 | tags: ["v2"], 9 | }; 10 | 11 | export default meta; 12 | type Story = StoryObj; 13 | 14 | // More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args 15 | export const Primary: Story = { 16 | args: { 17 | children: "Hello Fossfolio", 18 | }, 19 | }; 20 | 21 | export const Outline: Story = { 22 | args: { 23 | children: "Hello Fossfolio", 24 | variant: "outline", 25 | }, 26 | }; 27 | 28 | export const leftIcon: Story = { 29 | args: { 30 | children: "Hello Fossfolio", 31 | leftIcon: , 32 | }, 33 | }; 34 | export const rightIcon: Story = { 35 | args: { 36 | children: "Hello Fossfolio", 37 | rightIcon: , 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /apps/web/components/ui/Button/index.ts: -------------------------------------------------------------------------------- 1 | export type { ButtonProps } from "./Button"; 2 | export { Button } from "./Button"; 3 | -------------------------------------------------------------------------------- /apps/web/components/ui/Tooltip/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Tooltip as ShadcnToolTip, 3 | TooltipContent, 4 | TooltipProvider, 5 | TooltipTrigger, 6 | } from "@app/ui/components/tooltip"; 7 | 8 | interface Prop { 9 | content: string; 10 | toolTipMessage: string; 11 | } 12 | 13 | export const Tooltip = ({ content, toolTipMessage }: Prop) => { 14 | return ( 15 | 16 | 17 | {toolTipMessage} 18 | {content} 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/web/components/ui/Tooltip/index.ts: -------------------------------------------------------------------------------- 1 | export { Tooltip } from "./Tooltip"; 2 | -------------------------------------------------------------------------------- /apps/web/components/ui/Truncate/Truncate.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { Truncate } from "./Truncate"; 3 | 4 | const meta: Meta = { 5 | title: "Components/Truncate", 6 | component: Truncate, 7 | tags: ["v2"], 8 | }; 9 | 10 | export default meta; 11 | 12 | type Story = StoryObj; 13 | 14 | export const Primary: Story = { 15 | args: { 16 | text: "This is a long long text", 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /apps/web/components/ui/Truncate/Truncate.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Tooltip, 3 | TooltipContent, 4 | TooltipProvider, 5 | TooltipTrigger, 6 | } from "@app/ui/components/tooltip"; 7 | 8 | interface Prop { 9 | text: string; 10 | size?: number; 11 | className?: string; 12 | children?: React.ReactNode; 13 | } 14 | 15 | export const Truncate = ({ text, size = 10, className, children }: Prop) => { 16 | if (text.length > size) { 17 | return ( 18 | 19 | 20 | 21 | {text.substring(0, size - 1)}... 22 | 23 | {children ? children :

{text}

}
24 |
25 |
26 | ); 27 | } else { 28 | return

{text}

; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /apps/web/components/ui/Truncate/index.ts: -------------------------------------------------------------------------------- 1 | export { Truncate } from "./Truncate"; 2 | -------------------------------------------------------------------------------- /apps/web/config/ENV.ts: -------------------------------------------------------------------------------- 1 | export const ENV = { 2 | api_base: 3 | process.env.NEXT_PUBLIC_API_URL + "/api" || "http://localhost:3001/api", 4 | web_base_url: 5 | process.env.NEXT_PUBLIC_WEBSITE_DOMAIN || "http://localhost:3000", 6 | aws_access_key: process.env.AWS_ACCESS_KEY, 7 | aws_secret_key: process.env.AWS_SECRET_KEY, 8 | post_hog_key: process.env.NEXT_PUBLIC_POSTHOG_KEY, 9 | post_hog_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/config/index.ts: -------------------------------------------------------------------------------- 1 | export { apiHandler } from "./handler"; 2 | export { ENV } from "./ENV"; 3 | -------------------------------------------------------------------------------- /apps/web/context/Auth/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthContext, AuthCtx, AuthGuard } from "./context"; 2 | -------------------------------------------------------------------------------- /apps/web/context/Event/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/web/context/Event/.gitkeep -------------------------------------------------------------------------------- /apps/web/context/Event/README.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | When user click on the event card from org dashboard event data should be loaded in this context 4 | 5 | - [ ] create project context 6 | 7 | - [ ] create react query hook to call the event info 8 | -------------------------------------------------------------------------------- /apps/web/env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | export interface ProcessEnv { 3 | NEXT_PUBLIC_WEBSITE_DOMAIN: string; 4 | NEXT_PUBLIC_API_URL: string; 5 | MISTRAL_API_KEY: string; 6 | NEXT_PUBLIC_POSTHOG_KEY: string; 7 | NEXT_PUBLIC_POSTHOG_HOST: string; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Auth/index.ts: -------------------------------------------------------------------------------- 1 | export { useLogOut } from "./useLogout"; 2 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Auth/useLogout.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useState } from "react"; 3 | import { useQueryClient } from "@tanstack/react-query"; 4 | import { useAuth } from "@app/hooks/useAuth"; 5 | 6 | type LogOut = { 7 | isLoading: boolean; 8 | logOut: () => Promise; 9 | }; 10 | 11 | export const useLogOut = (): LogOut => { 12 | const [isLoading, setLoading] = useState(false); 13 | const queryClient = useQueryClient(); 14 | const { clearData } = useAuth(); 15 | const logOut = async (): Promise => { 16 | try { 17 | setLoading(true); 18 | // clearing user context 19 | clearData(); 20 | // clearing all the disk cache 21 | queryClient.clear(); 22 | await apiHandler.get("/auth/logout"); 23 | } catch (e) { 24 | console.error(e); 25 | } finally { 26 | setLoading(false); 27 | } 28 | }; 29 | return { logOut, isLoading }; 30 | }; 31 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Events/index.ts: -------------------------------------------------------------------------------- 1 | export { useAllEvents } from "./useAllEvents"; 2 | export { useEvent } from "./useEvent"; 3 | export { useUserRegistrationStatus } from "./useEventStatus"; 4 | export { useEventStats } from "./useEventStats"; 5 | export { usePublicOrgEvents } from "./usePublicOrgEvents"; 6 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Events/useAllEvents.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { OrgEvents } from "@app/types"; 3 | import { useQuery } from "@tanstack/react-query"; 4 | 5 | const getAllEvents = async (query: string) => { 6 | const { data } = await apiHandler.get("/events" + "?search=" + query); 7 | return data; 8 | }; 9 | 10 | export const useAllEvents = (query: string) => { 11 | const queryKey = ["events", query]; 12 | 13 | const events = useQuery>({ 14 | queryKey: queryKey, 15 | queryFn: () => getAllEvents(query), 16 | }); 17 | 18 | return events; 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Events/useEvent.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | import type { OrgEvents } from "@app/types"; 6 | 7 | /* 8 | This hook can be used in both event page in dashboard and event page user facing page 9 | eventid is the primary key of event in dashboard 10 | ID is the primary key of event in Event page 11 | 12 | */ 13 | 14 | const getEvent = async (id: string, type: Fetch = "event") => { 15 | const url = type === "public" ? `/events/${id}` : `/events/org/${id}`; 16 | const { data } = await apiHandler.get(url); 17 | return data; 18 | }; 19 | 20 | export type Fetch = "event" | "public"; 21 | 22 | export const useEvent = (type: Fetch = "event") => { 23 | const router = useRouter(); 24 | const [Id, setId] = useState(""); 25 | const { id, eventid } = router.query; 26 | 27 | useEffect(() => { 28 | // id is the primary key of event in events page 29 | // eventid is the primary key of event in org dashboard page 30 | 31 | if (router.isReady) { 32 | // this is done to reuse same function event info page and org dashboard 33 | if (type === "event") { 34 | setId(eventid as string); 35 | } 36 | if (type === "public") { 37 | setId(id as string); 38 | } 39 | } 40 | // eslint-disable-next-line react-hooks/exhaustive-deps 41 | }, [router.isReady]); 42 | 43 | const eventQueryKey = ["all-events", Id]; 44 | 45 | const events = useQuery({ 46 | queryKey: eventQueryKey, 47 | queryFn: () => getEvent(Id, type), 48 | // query is disabled until the query param is available 49 | enabled: !!Id, 50 | }); 51 | 52 | return events; 53 | }; 54 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Events/useEventStats.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | import { ServerResponse } from "@app/types"; 6 | 7 | type Data = { 8 | totalRevenue: number; 9 | totalTickets: number; 10 | insights: Record; 11 | }; 12 | 13 | type IData = ServerResponse; 14 | 15 | const getEventStats = async (id: string) => { 16 | const { data } = await apiHandler.get(`/events/stats/${id}`); 17 | return data; 18 | }; 19 | 20 | export const useEventStats = () => { 21 | const router = useRouter(); 22 | const [Id, setId] = useState(""); 23 | const queryKey = ["events", "stats", Id]; 24 | 25 | useEffect(() => { 26 | // id is the primary key of event in events page 27 | // eventid is the primary key of event in org dashboard page 28 | 29 | if (router.isReady) { 30 | const { eventid } = router.query; 31 | setId(eventid as string); 32 | } 33 | // eslint-disable-next-line react-hooks/exhaustive-deps 34 | }, [router.isReady]); 35 | 36 | const events = useQuery({ 37 | queryKey: queryKey, 38 | queryFn: () => getEventStats(Id), 39 | // query is disabled until the query param is available 40 | enabled: !!Id, 41 | }); 42 | 43 | return events; 44 | }; 45 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Events/useEventStatus.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useAuth } from "@app/hooks/useAuth"; 3 | import { useQuery } from "@tanstack/react-query"; 4 | import { useRouter } from "next/router"; 5 | import { useEffect, useState } from "react"; 6 | 7 | const getStatus = async (id: string) => { 8 | const { data } = await apiHandler.get(`/events/status/${id}`); 9 | return data; 10 | }; 11 | 12 | type Status = { 13 | isRegistred: boolean; 14 | ok: boolean; 15 | message: string; 16 | }; 17 | 18 | export const useUserRegistrationStatus = () => { 19 | const { user } = useAuth(); 20 | const [id, setId] = useState(""); 21 | const router = useRouter(); 22 | 23 | useEffect(() => { 24 | // id is the primary key of event in events page 25 | if (router.isReady) { 26 | const { id } = router.query; 27 | setId(id as string); 28 | } 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, [router.isReady]); 31 | const status = useQuery({ 32 | queryKey: ["eveny-status", id], 33 | queryFn: () => getStatus(id), 34 | enabled: !!id && Boolean(user), 35 | }); 36 | return status; 37 | }; 38 | -------------------------------------------------------------------------------- /apps/web/hooks/api/Events/usePublicOrgEvents.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | import type { OrgEvents } from "@app/types"; 6 | 7 | type IData = { 8 | data: { 9 | events: OrgEvents[]; 10 | name: string; 11 | }; 12 | ok: boolean; 13 | message: string; 14 | }; 15 | 16 | const getEvent = async (id: string) => { 17 | const { data } = await apiHandler.get(`/org/events/public/${id}`); 18 | return data; 19 | }; 20 | 21 | export const usePublicOrgEvents = () => { 22 | const router = useRouter(); 23 | const [Id, setId] = useState(""); 24 | const eventQueryKey = ["events", Id]; 25 | 26 | useEffect(() => { 27 | if (router.isReady) { 28 | const { org } = router.query; 29 | setId(org as string); 30 | } 31 | // eslint-disable-next-line react-hooks/exhaustive-deps 32 | }, [router.isReady]); 33 | 34 | const events = useQuery({ 35 | queryKey: eventQueryKey, 36 | queryFn: () => getEvent(Id), 37 | // query is disabled until the query param is available 38 | enabled: !!Id, 39 | }); 40 | 41 | return events; 42 | }; 43 | -------------------------------------------------------------------------------- /apps/web/hooks/api/form/index.ts: -------------------------------------------------------------------------------- 1 | export { useFormSchema } from "./useFormSchema"; 2 | export { useAllForms } from "./useAllForms"; 3 | export { useAddSchema } from "./useAddSchema"; 4 | export { useAddForm } from "./useAddForm"; 5 | export { useUpdateForm } from "./useUpdateForm"; 6 | -------------------------------------------------------------------------------- /apps/web/hooks/api/form/useAddForm.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 3 | import { useRouter } from "next/router"; 4 | import { toast } from "sonner"; 5 | 6 | const addForm = async ( 7 | orgId: string, 8 | eventId: string, 9 | data: UseAddFormProps 10 | ) => { 11 | return await apiHandler.post(`/events/form/${orgId}/${eventId}`, data); 12 | }; 13 | 14 | interface UseAddFormProps { 15 | title: string; 16 | description: string; 17 | } 18 | 19 | export const useAddForm = () => { 20 | const queryClient = useQueryClient(); 21 | const router = useRouter(); 22 | const { id, eventid } = router.query; 23 | 24 | return useMutation( 25 | (data: UseAddFormProps) => addForm(id as string, eventid as string, data), 26 | { 27 | onSettled: () => { 28 | queryClient.invalidateQueries(["events", "form", eventid]); 29 | }, 30 | onError: (error) => { 31 | console.error("Error adding Form:", error); 32 | toast.error("Error adding form"); 33 | }, 34 | } 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /apps/web/hooks/api/form/useAllForms.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | import { AllForms } from "@app/types"; 6 | 7 | const getForm = async (id: string, eventid: string) => { 8 | const { data } = await apiHandler.get(`/events/form/${id}/${eventid}`); 9 | return data; 10 | }; 11 | 12 | export const useAllForms = () => { 13 | const router = useRouter(); 14 | const [Id, setId] = useState(""); 15 | const [eventid, seteventid] = useState(""); 16 | const formQueryKey = ["events", "form", eventid]; 17 | 18 | useEffect(() => { 19 | // id is the primary key of org 20 | // eventid is the primary key of event 21 | 22 | if (router.isReady) { 23 | const { id, eventid } = router.query; 24 | setId(id as string); 25 | seteventid(eventid as string); 26 | } 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, [router.isReady]); 29 | 30 | const events = useQuery({ 31 | queryKey: formQueryKey, 32 | queryFn: () => getForm(Id, eventid), 33 | // query is disabled until the query param is available 34 | enabled: !!Id, 35 | }); 36 | 37 | return events; 38 | }; 39 | -------------------------------------------------------------------------------- /apps/web/hooks/api/form/useFormSchema.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | import { IFormInput } from "@app/views/form"; 6 | import { AllForms } from "@app/types"; 7 | 8 | export interface Schema { 9 | id: string; 10 | fk_form_id: string; 11 | name: string; 12 | placeholder: string; 13 | description: string; 14 | required: boolean; 15 | type: IFormInput; 16 | is_deleted: boolean; 17 | created_at: Date; 18 | updated_at: Date; 19 | options?: Array; 20 | } 21 | 22 | export interface FormResponse { 23 | schema: Schema[]; 24 | data: AllForms; 25 | } 26 | 27 | const getForm = async (id: string, formId: string) => { 28 | const { data } = await apiHandler.get(`/events/form/${id}/schema/${formId}`); 29 | return data; 30 | }; 31 | 32 | export const useFormSchema = () => { 33 | const router = useRouter(); 34 | const { id, eventid, formid } = router.query; 35 | const [Id, setId] = useState(""); 36 | const [formPk, setFormId] = useState(""); 37 | const formQueryKey = ["events", "form", eventid, formid]; 38 | 39 | useEffect(() => { 40 | // id is the primary key of org 41 | 42 | if (router.isReady) { 43 | setId(id as string); 44 | setFormId(formid as string); 45 | } 46 | // eslint-disable-next-line react-hooks/exhaustive-deps 47 | }, [router.isReady]); 48 | 49 | const events = useQuery({ 50 | queryKey: formQueryKey, 51 | queryFn: () => getForm(Id, formPk), 52 | // query is disabled until the query param is available 53 | enabled: !!Id, 54 | }); 55 | 56 | return events; 57 | }; 58 | -------------------------------------------------------------------------------- /apps/web/hooks/api/form/useUpdateForm.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 3 | import { useRouter } from "next/router"; 4 | import { toast } from "sonner"; 5 | import { NewFormSchema } from "@app/views/form/components/common"; 6 | 7 | const updateForm = async ( 8 | orgId: string, 9 | eventId: string, 10 | formId: string, 11 | data: NewFormSchema 12 | ) => { 13 | return await apiHandler.patch( 14 | `/events/form/${orgId}/${eventId}/${formId}`, 15 | data 16 | ); 17 | }; 18 | 19 | export const useUpdateForm = () => { 20 | const queryClient = useQueryClient(); 21 | const router = useRouter(); 22 | const { id, eventid, formid } = router.query; 23 | 24 | return useMutation( 25 | (data: NewFormSchema) => 26 | updateForm(id as string, eventid as string, formid as string, data), 27 | { 28 | onSettled: () => { 29 | queryClient.invalidateQueries(["events", "form", eventid]); 30 | queryClient.invalidateQueries(["events", "form", eventid, formid]); 31 | }, 32 | onError: (error) => { 33 | console.error("Error adding Form:", error); 34 | toast.error("Error adding form"); 35 | }, 36 | } 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /apps/web/hooks/api/kanban/index.ts: -------------------------------------------------------------------------------- 1 | export { useUpdateTask } from "./useUpdateTask"; 2 | export { useKanban } from "./useKanban"; 3 | -------------------------------------------------------------------------------- /apps/web/hooks/api/kanban/useKanban.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | import { useRouter } from "next/router"; 4 | import { useEffect, useState } from "react"; 5 | import { Kanban, ServerResponse } from "@app/types"; 6 | 7 | export type KanbanResponse = ServerResponse; 8 | 9 | const getKanban = async (slug: string): Promise => { 10 | const { data } = await apiHandler.get(`/events/kanban/${slug}`); 11 | return data; 12 | }; 13 | 14 | export const useKanban = () => { 15 | const [slug, setSlug] = useState(""); 16 | const router = useRouter(); 17 | 18 | useEffect(() => { 19 | if (router.isReady) { 20 | const { eventid } = router.query; 21 | setSlug(eventid as string); 22 | } 23 | // eslint-disable-next-line react-hooks/exhaustive-deps 24 | }, [router.isReady]); 25 | 26 | const key = ["event", "kanaban", slug]; 27 | 28 | const apiData = useQuery({ 29 | queryFn: () => getKanban(slug), 30 | queryKey: key, 31 | enabled: !!slug, 32 | }); 33 | 34 | return apiData; 35 | }; 36 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/index.ts: -------------------------------------------------------------------------------- 1 | export { useOrgs } from "./useOrg"; 2 | export { useAddOrg } from "./useAddOrg"; 3 | export { useMembers } from "./useMembers"; 4 | export { useOrgEvents } from "./useOrgEvents"; 5 | export { useOrgInfo } from "./useOrgInfo"; 6 | export { 7 | useEventParticipants, 8 | useEventParticipantsFormSubmissions, 9 | } from "./useParticipants"; 10 | export { useRemoveMember } from "./useRemoveMember"; 11 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/useAddOrg.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useRouter } from "next/router"; 3 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 4 | import { toast } from "sonner"; 5 | 6 | type IOrgPayload = { 7 | name: string; 8 | slug: string; 9 | }; 10 | 11 | const addOrg = async (payload: IOrgPayload) => { 12 | return await apiHandler.post("/org", payload); 13 | }; 14 | 15 | // when user crateates a new org we need to update all ui with new org 16 | // optimistic ui rendering need to be done 17 | export const useAddOrg = () => { 18 | const queryClient = useQueryClient(); 19 | const router = useRouter(); 20 | return useMutation({ 21 | mutationFn: addOrg, 22 | onSuccess: (result, variables) => { 23 | queryClient.setQueryData(["org"], (old) => ({ 24 | // @ts-ignore 25 | ...old, 26 | })); 27 | toast.success("Organisation created successfully"); 28 | // pushing to the new org page 29 | router.push(`/org/${result.data.id}`); 30 | }, 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/useMembers.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useRouter } from "next/router"; 3 | import { apiHandler } from "@app/config"; 4 | import type { Member } from "@app/types"; 5 | 6 | const getAllMembers = async (id: string): Promise => { 7 | const { data } = await apiHandler.get(`/org/${id}/member`); 8 | return data; 9 | }; 10 | 11 | export const useMembers = () => { 12 | const router = useRouter(); 13 | const { id } = router.query; 14 | const queryKey = ["org-members", id]; 15 | const members = useQuery({ 16 | queryKey: queryKey, 17 | queryFn: () => getAllMembers(id as string), 18 | }); 19 | return members; 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/useOrg.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | import type { Organization } from "@app/types"; 4 | 5 | const getAllOrg = async () => { 6 | try { 7 | const { data, status } = await apiHandler.get("/user/orgs"); 8 | // handling unauth 9 | if (status === 401) { 10 | throw new Error("Unauthorized user"); 11 | } 12 | return data; 13 | } catch { 14 | console.error("Login failed"); 15 | } 16 | }; 17 | 18 | export const useOrgs = () => { 19 | const orgs = useQuery({ 20 | queryKey: ["orgs]"], 21 | queryFn: getAllOrg, 22 | }); 23 | return orgs; 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/useOrgEvents.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | import type { OrgEvents, Roles } from "@app/types"; 6 | import { useAuth } from "@app/hooks/useAuth"; 7 | 8 | export type IOrgEvents = { 9 | event: OrgEvents[]; 10 | role: Roles; 11 | }; 12 | 13 | const getAllEventsInOrg = async (id: string) => { 14 | const { data } = await apiHandler.get(`/org/${id}/events`); 15 | return data; 16 | }; 17 | 18 | export const useOrgEvents = () => { 19 | const router = useRouter(); 20 | const { setRole } = useAuth(); 21 | const [orgId, setOrgId] = useState(""); 22 | const orgEventQueryKey = ["org-events", orgId]; 23 | useEffect(() => { 24 | if (router.isReady) { 25 | const { id } = router.query; 26 | setOrgId(id as string); 27 | } 28 | // eslint-disable-next-line react-hooks/exhaustive-deps 29 | }, [router.isReady]); 30 | 31 | const events = useQuery({ 32 | queryKey: orgEventQueryKey, 33 | queryFn: () => getAllEventsInOrg(orgId), 34 | onSuccess: (data) => { 35 | setRole(data.role); 36 | }, 37 | // query is disabled until the query param is available 38 | enabled: !!orgId, 39 | }); 40 | 41 | return events; 42 | }; 43 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/useOrgInfo.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useEffect, useState } from "react"; 3 | import { useRouter } from "next/router"; 4 | import { apiHandler } from "@app/config"; 5 | 6 | type IData = { 7 | id: string; 8 | slug: string; 9 | name: string; 10 | created_at: string; 11 | updated_at: string; 12 | is_verified: boolean; 13 | is_deleted: boolean; 14 | }; 15 | 16 | const getOrg = async (id: string) => { 17 | const { data } = await apiHandler.get(`/org/${id}`); 18 | return data; 19 | }; 20 | 21 | export const useOrgInfo = (canCall: boolean) => { 22 | const router = useRouter(); 23 | const [orgId, setOrgId] = useState(""); 24 | const orgEventQueryKey = ["org-info", orgId]; 25 | useEffect(() => { 26 | if (router.isReady) { 27 | const { id } = router.query; 28 | setOrgId(id as string); 29 | } 30 | // eslint-disable-next-line react-hooks/exhaustive-deps 31 | }, [router.isReady]); 32 | 33 | const events = useQuery({ 34 | queryKey: orgEventQueryKey, 35 | queryFn: () => getOrg(orgId), 36 | // query is disabled until the query param is available 37 | enabled: !!orgId && canCall, 38 | }); 39 | 40 | return events; 41 | }; 42 | -------------------------------------------------------------------------------- /apps/web/hooks/api/org/useRemoveMember.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 3 | import { apiHandler } from "@app/config/handler"; 4 | import { toast } from "sonner"; 5 | 6 | interface Payload { 7 | memberId: string; 8 | orgId: string; 9 | } 10 | 11 | const handleRemoveClick = async ({ memberId, orgId }: Payload) => { 12 | return await apiHandler.patch(`/org/${orgId}/member/remove`, { 13 | memberId: memberId, 14 | }); 15 | }; 16 | 17 | export const useRemoveMember = () => { 18 | const router = useRouter(); 19 | const queryClient = useQueryClient(); 20 | 21 | const { id } = router.query; 22 | 23 | return useMutation( 24 | (memberId: string) => 25 | handleRemoveClick({ 26 | memberId, 27 | orgId: router.query?.id as string, 28 | }), 29 | { 30 | onSettled: () => { 31 | queryClient.invalidateQueries(["org-members", id]); 32 | }, 33 | onError: (error) => { 34 | console.error("Error removing user:", error); 35 | toast.error("Error Removing User"); 36 | }, 37 | } 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /apps/web/hooks/api/user/index.ts: -------------------------------------------------------------------------------- 1 | export { useTickets } from "./useTickets"; 2 | -------------------------------------------------------------------------------- /apps/web/hooks/api/user/useTickets.ts: -------------------------------------------------------------------------------- 1 | import { apiHandler } from "@app/config"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | 4 | export type Info = { 5 | name: string; 6 | eventDate: Date; 7 | location: string; 8 | id: string; 9 | coverImage: string; 10 | }; 11 | 12 | export interface Data { 13 | ok: boolean; 14 | message: string; 15 | data: Info[] | undefined; 16 | } 17 | 18 | const getAllTickets = async () => { 19 | const { data } = await apiHandler.get("/user/tickets"); 20 | return data; 21 | }; 22 | 23 | export const useTickets = () => { 24 | const key = ["tickets"]; 25 | 26 | const res = useQuery({ 27 | queryKey: key, 28 | queryFn: getAllTickets, 29 | }); 30 | 31 | return res; 32 | }; 33 | -------------------------------------------------------------------------------- /apps/web/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useDebounce } from "./useDebounce"; 2 | export { useToggle } from "./useToggle"; 3 | export { useAuth } from "./useAuth"; 4 | export { useRoles } from "./useRoles"; 5 | export { useMediaQuery } from "./useMediaQuery"; 6 | export { useOutsideClick } from "./useOutsideClick"; 7 | -------------------------------------------------------------------------------- /apps/web/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { AuthCtx } from "@app/context/Auth"; 3 | 4 | export const useAuth = () => { 5 | const ctx = useContext(AuthCtx); 6 | if (ctx === null) { 7 | throw new Error("Unable to access the auth context"); 8 | } 9 | 10 | return ctx; 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | 3 | const debounce = (fn: Function, ms = 2000) => { 4 | let timeoutId: ReturnType; 5 | // eslint-disable-next-line func-names 6 | return function (this: any, ...args: any[]) { 7 | clearTimeout(timeoutId); 8 | timeoutId = setTimeout(() => fn.apply(this, args), ms); 9 | }; 10 | }; 11 | 12 | export const useDebounce = () => 13 | useCallback((func: Function) => debounce(func), []); 14 | -------------------------------------------------------------------------------- /apps/web/hooks/useMediaQuery.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [value, setValue] = React.useState(false); 5 | 6 | React.useEffect(() => { 7 | function onChange(event: MediaQueryListEvent) { 8 | setValue(event.matches); 9 | } 10 | 11 | const result = matchMedia(query); 12 | result.addEventListener("change", onChange); 13 | setValue(result.matches); 14 | 15 | return () => result.removeEventListener("change", onChange); 16 | }, [query]); 17 | 18 | return value; 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/hooks/useOutsideClick.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, RefObject } from "react"; 2 | 3 | export const useOutsideClick = ( 4 | ref: RefObject, 5 | callback: () => void 6 | ): void => { 7 | const handleClick = (e: MouseEvent) => { 8 | if (ref.current && !ref.current.contains(e.target as Node)) { 9 | callback(); 10 | } 11 | }; 12 | 13 | useEffect(() => { 14 | document.addEventListener("click", handleClick); 15 | return () => { 16 | document.removeEventListener("click", handleClick); 17 | }; 18 | }, [ref, callback]); 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/hooks/useRoles.ts: -------------------------------------------------------------------------------- 1 | import { useAuth } from "./useAuth"; 2 | 3 | const Properties = { 4 | admin: { 5 | canDeleteOrg: true, 6 | canSendInvite: true, 7 | canCreateEvent: true, 8 | canEditEvent: true, 9 | canRemoveOrgUser: true, 10 | canViewDashboard: true, 11 | canChangeParticipantStatus: true, 12 | canSeeRevenue: true, 13 | canDeleteEvent: true, 14 | canEditOrg: true, 15 | }, 16 | editor: { 17 | canDeleteOrg: false, 18 | canEditEvent: true, 19 | canCreateEvent: true, 20 | canViewDashboard: true, 21 | canSendInvite: false, 22 | canRemoveOrgUser: false, 23 | canChangeParticipantStatus: true, 24 | canSeeRevenue: false, 25 | canDeleteEvent: false, 26 | canEditOrg: false, 27 | }, 28 | viewer: { 29 | canDeleteOrg: false, 30 | canEditEvent: false, 31 | canCreateEvent: false, 32 | canSendInvite: false, 33 | canRemoveOrgUser: false, 34 | canViewDashboard: true, 35 | canChangeParticipantStatus: true, 36 | canSeeRevenue: false, 37 | canDeleteEvent: false, 38 | canEditOrg: false, 39 | }, 40 | no_access: { 41 | canDeleteOrg: false, 42 | canEditEvent: false, 43 | canCreateEvent: false, 44 | canSendInvite: false, 45 | canRemoveOrgUser: false, 46 | canViewDashboard: true, 47 | canChangeParticipantStatus: false, 48 | canSeeRevenue: false, 49 | canDeleteEvent: false, 50 | canEditOrg: false, 51 | }, 52 | }; 53 | 54 | export const useRoles = () => { 55 | const { role } = useAuth(); 56 | 57 | if (!role) { 58 | return Properties["no_access"]; 59 | } 60 | return Properties[role]; 61 | }; 62 | -------------------------------------------------------------------------------- /apps/web/hooks/useToggle.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | type VoidFn = () => void; 4 | 5 | export type Toggle = { 6 | on: VoidFn; 7 | off: VoidFn; 8 | toggle: VoidFn; 9 | }; 10 | 11 | type UseToggleReturn = [boolean, Toggle]; 12 | 13 | export const useToggle = (initialState = false): UseToggleReturn => { 14 | const [value, setValue] = useState(initialState); 15 | 16 | const on = useCallback(() => { 17 | setValue(true); 18 | }, []); 19 | 20 | const off = useCallback(() => { 21 | setValue(false); 22 | }, []); 23 | 24 | const toggle = useCallback((isOpen?: boolean) => { 25 | setValue((prev) => (typeof isOpen === "boolean" ? isOpen : !prev)); 26 | }, []); 27 | 28 | return [value, { on, off, toggle }]; 29 | }; 30 | -------------------------------------------------------------------------------- /apps/web/layout/AuthModal.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { Button } from "@app/ui/components/button"; 3 | import { 4 | Dialog, 5 | DialogContent, 6 | DialogDescription, 7 | DialogHeader, 8 | DialogTitle, 9 | } from "@app/ui/components/dialog"; 10 | import { ENV } from "@app/config"; 11 | import { useAuth } from "@app/hooks"; 12 | 13 | export const AuthModal = () => { 14 | const router = useRouter(); 15 | const { isAuthModalOpen, toggleModal } = useAuth(); 16 | return ( 17 | 18 | 19 | 20 | Login 21 | 22 |
By Signing in, you agree to our Terms and Services
23 |
24 | 32 | 40 |
41 |
42 |
43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /apps/web/layout/DashboardLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Child } from "@app/types"; 2 | import { DashNav } from "./components/DashboardNav"; 3 | 4 | export const DashboardLayout = ({ children }: Child) => { 5 | return ( 6 |
7 | 8 |
{children}
9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/layout/FormBuilderLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Child } from "@app/types"; 2 | import { FormBuilderNav } from "./components/FormBuilderNav"; 3 | 4 | export const FormBuilderLayout = ({ children }: Child) => { 5 | return ( 6 |
7 | 8 |
{children}
9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/layout/HomeLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Child } from "@app/types"; 2 | import { MainNav } from "./components/MainNav"; 3 | 4 | export const HomeLayout = ({ children }: Child) => { 5 | return ( 6 | <> 7 | 8 | {children} 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/layout/components/DashBoardRoute.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { IconType } from "react-icons"; 3 | 4 | export const DashBoardRoute = ({ Icon, name }: Props) => { 5 | const router = useRouter(); 6 | 7 | const { id, eventid } = router.query; 8 | return ( 9 |
router.push(`/org/${id}/${eventid}/${name.toLowerCase()}`)} 12 | > 13 | 14 | {name} 15 |
16 | ); 17 | }; 18 | 19 | interface Props { 20 | Icon: IconType; 21 | name: string; 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/layout/components/MainNav.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { useRouter } from "next/router"; 3 | import { Button } from "@app/components/ui/Button"; 4 | import { useAuth } from "@app/hooks"; 5 | import { AuthModal } from "../AuthModal"; 6 | import { UserNav } from "./UserNav"; 7 | 8 | export const MainNav = () => { 9 | const router = useRouter(); 10 | const { user, toggleModal } = useAuth(); 11 | return ( 12 |
13 |
14 |
15 |

16 | fossfolio 17 |

18 | 19 |
20 | 21 |

router.push("/")} 24 | > 25 | Home 26 |

27 | 28 | 29 |

Events

30 | 31 |
32 |
33 | {user ? ( 34 | 35 | ) : ( 36 | 39 | )} 40 |
41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /apps/web/layout/components/constants.ts: -------------------------------------------------------------------------------- 1 | import { FiHome, FiUser, FiSettings, FiBarChart } from "react-icons/fi"; 2 | import { FaWpforms } from "react-icons/fa"; 3 | import { BsPeople } from "react-icons/bs"; 4 | 5 | export const ROUTES = [ 6 | { 7 | name: "Event", 8 | icon: FiHome, 9 | }, 10 | { 11 | name: "Participants", 12 | icon: FiUser, 13 | }, 14 | { 15 | name: "Form", 16 | icon: FaWpforms, 17 | }, 18 | { 19 | name: "Tasks", 20 | icon: BsPeople, 21 | }, 22 | { 23 | name: "Revenue", 24 | icon: FiBarChart, 25 | }, 26 | ]; 27 | 28 | export const FORM_BUILDER_ROUTES = [ 29 | { 30 | name: "Builder", 31 | icon: FiHome, 32 | }, 33 | { 34 | name: "Responses", 35 | icon: FiUser, 36 | }, 37 | { 38 | name: "Analytics", 39 | icon: FaWpforms, 40 | }, 41 | ]; 42 | -------------------------------------------------------------------------------- /apps/web/layout/index.ts: -------------------------------------------------------------------------------- 1 | export { HomeLayout } from "./HomeLayout"; 2 | export { DashboardLayout } from "./DashboardLayout"; 3 | export { FormBuilderLayout } from "./FormBuilderLayout"; 4 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | typescript: { 6 | ignoreBuildErrors: true, 7 | }, 8 | images: { 9 | domains: [ 10 | "img.freepik.com", 11 | "fossfolio.s3.amazonaws.com", 12 | "loremflickr.com", 13 | "picsum.photos", 14 | "fossfolio.s3.us-east-1.amazonaws.com", 15 | ], 16 | }, 17 | }; 18 | 19 | module.exports = nextConfig; 20 | -------------------------------------------------------------------------------- /apps/web/next.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NextComponentType, 3 | NextPageContext, 4 | NextLayoutComponentType, 5 | NextPage, 6 | } from "next"; 7 | import type { AppProps } from "next/app"; 8 | import { Child } from "./types"; 9 | 10 | declare module "next" { 11 | type NextLayoutComponentType

= NextComponentType< 12 | NextPageContext, 13 | any, 14 | P 15 | > & { 16 | Layout?: (page: ReactNode) => ReactNode; 17 | RequireAuth: boolean; 18 | }; 19 | 20 | type NextPageWithLayout = NextPage & { 21 | Layout: (arg0: Child) => JSX.Element; 22 | RequireAuth: boolean; 23 | }; 24 | } 25 | 26 | declare module "next/app" { 27 | type AppLayoutProps

= AppProps & { 28 | Component: NextLayoutComponentType; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /apps/web/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { NextPageWithLayout } from "next"; 2 | import { HomeLayout } from "@app/layout"; 3 | import { Error as ErrorPage } from "@app/components/Error"; 4 | const Error: NextPageWithLayout = () => ; 5 | 6 | Error.Layout = HomeLayout; 7 | Error.RequireAuth = false; 8 | export default Error; 9 | -------------------------------------------------------------------------------- /apps/web/pages/[org].tsx: -------------------------------------------------------------------------------- 1 | import { EventCard, NoData, PreLoader } from "@app/components/events"; 2 | import { usePublicOrgEvents } from "@app/hooks/api/Events"; 3 | import { HomeLayout } from "@app/layout"; 4 | import { NextPageWithLayout } from "next"; 5 | 6 | const Event: NextPageWithLayout = () => { 7 | const { data, isLoading } = usePublicOrgEvents(); 8 | 9 | if (isLoading) { 10 | return ; 11 | } 12 | 13 | if (data?.data.events.length === 0) { 14 | return ( 15 |

16 |

Find Events

17 |
18 | 19 |
20 |
21 | ); 22 | } 23 | 24 | return ( 25 |
26 |

{data?.data.name.toUpperCase()}

27 |
28 | {data?.data.events.map((el) => ( 29 | 40 | ))} 41 |
42 |
43 | ); 44 | }; 45 | 46 | Event.Layout = HomeLayout; 47 | Event.RequireAuth = false; 48 | export default Event; 49 | -------------------------------------------------------------------------------- /apps/web/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayout } from "@app/layout"; 2 | import { motion } from "framer-motion"; 3 | import Image from "next/image"; 4 | import Link from "next/link"; 5 | import { AnimatedCharacters } from "@app/views/home"; 6 | import { NextPageWithLayout } from "next"; 7 | import { Button } from "@app/components/ui/Button"; 8 | 9 | const container = { 10 | visible: { 11 | transition: { 12 | staggerChildren: 0.025, 13 | }, 14 | }, 15 | }; 16 | 17 | const Head = ["Discover, host and manage Events, Hackathons all in one place."]; 18 | 19 | const Home: NextPageWithLayout = () => { 20 | return ( 21 |
22 |
23 | 29 | {Head.map((el, key) => ( 30 | 31 | ))} 32 | 33 |
34 | 37 | 40 |
41 |
42 |
43 | an event card image 49 |
50 |
51 | ); 52 | }; 53 | 54 | Home.Layout = HomeLayout; 55 | Home.RequireAuth = false; 56 | export default Home; 57 | -------------------------------------------------------------------------------- /apps/web/pages/org/[id]/[eventid]/form/[formid]/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageLoader } from "@app/components/preloaders"; 2 | import { HomeLayout } from "@app/layout"; 3 | 4 | export const Index = () => { 5 | return ; 6 | }; 7 | export async function getServerSideProps(ctx: any) { 8 | const accesToken = ctx.req.cookies["access_token"]; 9 | const url = ctx.req.url as string; 10 | const length = url.split("/").length; 11 | const formId = url.split("/")[length - 1]; 12 | const eventid = url.split("/")[length - 2]; 13 | const orgId = url.split("/")[length - 3]; 14 | if (!accesToken) { 15 | return { 16 | redirect: { 17 | destination: "/", 18 | permanent: false, 19 | }, 20 | }; 21 | } 22 | 23 | return { 24 | redirect: { 25 | destination: `/org/${orgId}/${eventid}/form/${formId}/builder`, 26 | permanent: false, 27 | }, 28 | }; 29 | } 30 | 31 | Index.Layout = HomeLayout; 32 | Index.RequireAuth = true; 33 | 34 | export default Index; 35 | -------------------------------------------------------------------------------- /apps/web/pages/org/[id]/[eventid]/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageLoader } from "@app/components/preloaders"; 2 | import { HomeLayout } from "@app/layout"; 3 | 4 | export const Index = () => { 5 | return ; 6 | }; 7 | export async function getServerSideProps(ctx: any) { 8 | const accesToken = ctx.req.cookies["access_token"]; 9 | const url = ctx.req.url as string; 10 | const length = url.split("/").length; 11 | const eventid = url.split("/")[length - 1]; 12 | const orgId = url.split("/")[length - 2]; 13 | if (!accesToken) { 14 | return { 15 | redirect: { 16 | destination: "/", 17 | permanent: false, 18 | }, 19 | }; 20 | } 21 | 22 | return { 23 | redirect: { 24 | destination: `/org/${orgId}/${eventid}/event`, 25 | permanent: false, 26 | }, 27 | }; 28 | } 29 | 30 | Index.Layout = HomeLayout; 31 | Index.RequireAuth = true; 32 | 33 | export default Index; 34 | -------------------------------------------------------------------------------- /apps/web/pages/org/[id]/[eventid]/participants.tsx: -------------------------------------------------------------------------------- 1 | import { NextPageWithLayout } from "next"; 2 | import { DashboardLayout } from "@app/layout"; 3 | import { Participants } from "@app/views/dashboard"; 4 | import { useEventParticipants } from "@app/hooks/api/org"; 5 | import { useEvent } from "@app/hooks/api/Events"; 6 | import { Loader } from "@app/components/preloaders"; 7 | import { TicketLottie } from "@app/views/dashboard"; 8 | 9 | const Dashboard: NextPageWithLayout = () => { 10 | const { isLoading, data, refetch, error } = useEventParticipants(); 11 | const { data: eventData } = useEvent("event"); 12 | 13 | if (error) { 14 | return ; 15 | } 16 | if (isLoading) { 17 | return ; 18 | } 19 | 20 | if (data) { 21 | return ( 22 |
23 |

24 | All Registered Participants 25 |

26 | 32 |
33 | ); 34 | } 35 | }; 36 | 37 | Dashboard.Layout = DashboardLayout; 38 | Dashboard.RequireAuth = true; 39 | export default Dashboard; 40 | -------------------------------------------------------------------------------- /apps/web/pages/org/[id]/[eventid]/tasks.tsx: -------------------------------------------------------------------------------- 1 | import { useKanban } from "@app/hooks/api/kanban"; 2 | import { DashboardLayout } from "@app/layout"; 3 | import { Separator } from "@app/ui/components/separator"; 4 | import { Kanban } from "@app/views/tasks"; 5 | import { Loader } from "@app/components/preloaders"; 6 | import { HTML5Backend } from "react-dnd-html5-backend"; 7 | import { DndProvider } from "react-dnd"; 8 | import { Button } from "@app/components/ui/Button"; 9 | 10 | const Tasks = () => { 11 | const { data, isLoading, isError } = useKanban(); 12 | 13 | if (isLoading) { 14 | return ; 15 | } 16 | 17 | if (isError) { 18 | return

Error

; 19 | } 20 | return ( 21 |
22 |
23 |

Board

24 | 25 |
26 | 27 | 28 |
29 | {data?.data.map((board) => ( 30 |
31 | 32 | 33 |
34 | ))} 35 |
36 |
37 |
38 | ); 39 | }; 40 | 41 | Tasks.Layout = DashboardLayout; 42 | Tasks.RequireAuth = true; 43 | export default Tasks; 44 | -------------------------------------------------------------------------------- /apps/web/pages/verify.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useRouter } from "next/router"; 3 | import { HomeLayout } from "@app/layout"; 4 | import { apiHandler } from "@app/config"; 5 | import { QueryClient } from "@tanstack/react-query"; 6 | 7 | type Prop = { 8 | ok: boolean; 9 | orgId: string; 10 | }; 11 | 12 | const Verify = ({ orgId }: Prop) => { 13 | const router = useRouter(); 14 | 15 | const queryClient = new QueryClient(); 16 | useEffect(() => { 17 | const verify = async () => { 18 | try { 19 | await apiHandler.get(`/org/invite/verify?id=${orgId}`); 20 | queryClient.invalidateQueries({ 21 | queryKey: ["orgs"], 22 | }); 23 | router.push(`/org`); 24 | } catch { 25 | router.push("/events?invite_failed=true"); 26 | } 27 | }; 28 | verify(); 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, []); 31 | 32 | return ( 33 |
34 |

35 | Verifying your email 36 |

37 |
38 | ); 39 | }; 40 | 41 | // used to verify the email for org 42 | export async function getServerSideProps(ctx: any) { 43 | return { 44 | props: { 45 | ok: true, 46 | orgId: ctx.query.id, 47 | }, 48 | }; 49 | } 50 | 51 | Verify.Layout = HomeLayout; 52 | Verify.RequireAuth = true; 53 | 54 | export default Verify; 55 | -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/web/public/banner.png -------------------------------------------------------------------------------- /apps/web/public/event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/web/public/event.jpg -------------------------------------------------------------------------------- /apps/web/public/foss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/web/public/foss.png -------------------------------------------------------------------------------- /apps/web/public/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/web/public/main.png -------------------------------------------------------------------------------- /apps/web/public/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foss-labs/fossfolio/e460e337c1a2ca3e5854bde346aafa7e3a235252/apps/web/public/placeholder.png -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@app/*": ["./*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/ui/components/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"; 5 | 6 | import { cn } from "../lib/utils"; 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )); 21 | Avatar.displayName = AvatarPrimitive.Root.displayName; 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )); 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )); 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 49 | 50 | export { Avatar, AvatarImage, AvatarFallback }; 51 | -------------------------------------------------------------------------------- /apps/web/ui/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 3 | import { FaCheck } from "react-icons/fa"; 4 | 5 | import { cn } from "ui/lib/utils"; 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | )); 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 27 | 28 | export { Checkbox }; 29 | -------------------------------------------------------------------------------- /apps/web/ui/components/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "../lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | } 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /apps/web/ui/components/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as LabelPrimitive from "@radix-ui/react-label"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "../lib/utils"; 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 " 9 | ); 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )); 22 | Label.displayName = LabelPrimitive.Root.displayName; 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /apps/web/ui/components/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 3 | 4 | import { cn } from "../lib/utils"; 5 | 6 | const Popover = PopoverPrimitive.Root; 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger; 9 | 10 | const PopoverContent = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 14 | 15 | 25 | 26 | )); 27 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 28 | 29 | export { Popover, PopoverTrigger, PopoverContent }; 30 | -------------------------------------------------------------------------------- /apps/web/ui/components/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 3 | 4 | import { cn } from "../lib/utils"; 5 | 6 | const Separator = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >( 10 | ( 11 | { className, orientation = "horizontal", decorative = true, ...props }, 12 | ref 13 | ) => ( 14 | 25 | ) 26 | ); 27 | Separator.displayName = SeparatorPrimitive.Root.displayName; 28 | 29 | export { Separator }; 30 | -------------------------------------------------------------------------------- /apps/web/ui/components/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "../lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /apps/web/ui/components/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "ui/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |