├── .github └── workflows │ ├── ci.yml │ └── docker.yml ├── .gitignore ├── CODEOWNERS ├── Makefile ├── README.md ├── backend ├── .dockerignore ├── Dockerfile ├── Dockerfile.development ├── README.md ├── algorithms │ ├── README.md │ ├── main.go │ ├── misc.go │ ├── strings.go │ └── tests │ │ ├── strings_test.go │ │ └── util.go ├── database │ ├── contexts │ │ ├── context_test.go │ │ ├── db_fetcher.go │ │ ├── live_context.go │ │ ├── main.go │ │ ├── schema.go │ │ └── testing_context.go │ ├── main.go │ └── repositories │ │ ├── dockerfilesystem.go │ │ ├── filesystem.go │ │ ├── frontends.go │ │ ├── groups.go │ │ ├── main.go │ │ ├── mocks │ │ ├── models_mock.go │ │ └── repositories_mock.go │ │ ├── person.go │ │ ├── repository_interfaces.go │ │ ├── tests │ │ ├── dockerfilesystem_test.go │ │ └── filesystem_test.go │ │ └── types.go ├── docs │ └── WritingTests.md ├── editor │ ├── OT │ │ ├── OTClient │ │ │ ├── .eslintrc.js │ │ │ ├── .prettierrc │ │ │ ├── client.ts │ │ │ ├── jest.config.js │ │ │ ├── operation.test.ts │ │ │ ├── operation.ts │ │ │ ├── operationQueue.ts │ │ │ ├── package.json │ │ │ ├── transform.test.ts │ │ │ ├── transform.ts │ │ │ ├── tsconfig.json │ │ │ ├── util.ts │ │ │ └── yarn.lock │ │ ├── client_view.go │ │ ├── datamodel │ │ │ ├── README.md │ │ │ ├── component.go │ │ │ ├── document.go │ │ │ ├── image.go │ │ │ ├── main.go │ │ │ └── paragraph.go │ │ ├── document_server.go │ │ ├── main.go │ │ ├── operations │ │ │ ├── application.go │ │ │ ├── array_operation.go │ │ │ ├── boolean_operation.go │ │ │ ├── integer_operation.go │ │ │ ├── json_config.go │ │ │ ├── noop_operation.go │ │ │ ├── object_operation.go │ │ │ ├── operation_model.go │ │ │ ├── string_operation.go │ │ │ ├── tests │ │ │ │ ├── operation_application_test.go │ │ │ │ ├── string_operation_test.go │ │ │ │ └── transform_test.go │ │ │ └── transform.go │ │ ├── server_factory.go │ │ ├── testing_framework.go │ │ ├── tests │ │ │ └── integration_test.go │ │ └── worker.go │ ├── diffSync │ │ ├── README.md │ │ ├── document │ │ │ ├── document.go │ │ │ ├── documentState.go │ │ │ ├── extension.go │ │ │ ├── extensionHead.go │ │ │ └── manager.go │ │ ├── html │ │ │ ├── README.md │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ └── diffmatchpatch │ │ │ │ │ └── index.js │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ ├── main.go │ │ ├── service │ │ │ ├── autosave_extension.go │ │ │ ├── broker.go │ │ │ ├── client_extension.go │ │ │ └── extension_stub.go │ │ └── tests │ │ │ └── main.go │ └── pessimistic │ │ ├── editorServer.go │ │ ├── main.go │ │ └── tsclient │ │ └── client.ts ├── endpoints │ ├── auth_endpoints.go │ ├── dependency_factory.go │ ├── editor_endpoints.go │ ├── filesystem_endpoints.go │ ├── form_parsers.go │ ├── main.go │ ├── mocks │ │ └── dependency_factory_mock.go │ ├── models │ │ ├── editor_models.go │ │ ├── filesystem_models.go │ │ ├── user_models.go │ │ └── volume_models.go │ ├── registration.go │ ├── tests │ │ ├── auth_test.go │ │ ├── filesystem_test.go │ │ ├── handler_test.go │ │ ├── login_test.go │ │ ├── parsers_test.go │ │ └── volume_test.go │ └── volume_endpoints.go ├── environment │ ├── config.go │ └── main.go ├── go.mod ├── go.sum ├── internal │ ├── data │ │ └── test.txt │ ├── logger │ │ └── main.go │ ├── session │ │ └── session.go │ └── storage │ │ └── main.go ├── main.go └── pkg │ └── cmsjson │ ├── README.md │ ├── ast.go │ ├── ast_constructors.go │ ├── ast_marshaller.go │ ├── ast_unmarshaller.go │ ├── cmsjson_test.go │ ├── config.go │ ├── marshall.go │ └── unmarshall.go ├── config ├── .env.dev.example └── README.md ├── docker-compose.yml ├── frontend ├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── .nginx │ └── nginx.conf ├── .storybook │ ├── main.js │ └── preview.js ├── Dockerfile ├── Dockerfile.development ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.tsx │ ├── assets │ │ └── moveable-content-dots.svg │ ├── cse-testing-lib │ │ ├── README.md │ │ ├── custom-queries.ts │ │ ├── index.ts │ │ └── test-utils.tsx │ ├── cse-ui-kit │ │ ├── ClearLayeredGlassContainer │ │ │ ├── ClearLayeredGlassContainer-Styled.tsx │ │ │ ├── ClearLayeredGlassContainer.stories.tsx │ │ │ └── ClearLayeredGlassContainer.tsx │ │ ├── CreateCodeBlock_button │ │ │ ├── CreateCodeBlock-Styled.tsx │ │ │ ├── CreateCodeBlock.stories.tsx │ │ │ ├── CreateCodeBlock.tsx │ │ │ └── index.ts │ │ ├── CreateContentBlock_button │ │ │ ├── CreateContentBlock-Styled.tsx │ │ │ ├── CreateContentBlock.stories.tsx │ │ │ ├── CreateContentBlock.tsx │ │ │ └── index.ts │ │ ├── CreateHeadingBlock_button │ │ │ ├── CreateHeadingBlock-Styled.tsx │ │ │ ├── CreateHeadingBlock.stories.tsx │ │ │ ├── CreateHeadingBlock.tsx │ │ │ └── index.ts │ │ ├── CreateMediaBlock_button │ │ │ ├── CreateMediaBlock-Styled.tsx │ │ │ ├── CreateMediaBlock.stories.tsx │ │ │ ├── CreateMediaBlock.tsx │ │ │ └── index.ts │ │ ├── EditableTitle_textbox │ │ │ ├── EditableTitle-Styled.tsx │ │ │ ├── EditableTitle.tsx │ │ │ └── index.ts │ │ ├── MediaContentBlock │ │ │ ├── MediaContent-Styled.tsx │ │ │ ├── MediaContentBlock.stories.tsx │ │ │ └── MediaContentBlock.tsx │ │ ├── PublishDocument_button │ │ │ ├── PublishDocument-Styled.tsx │ │ │ ├── PublishDocument.tsx │ │ │ └── index.ts │ │ ├── README.md │ │ ├── SyncDocument_button │ │ │ ├── SyncDocument-Styled.tsx │ │ │ ├── SyncDocument.tsx │ │ │ └── index.ts │ │ ├── assets │ │ │ ├── bold-button.svg │ │ │ ├── centeralign-button.svg │ │ │ ├── code-button.svg │ │ │ ├── default.png │ │ │ ├── italics-button.svg │ │ │ ├── leftrightalign-button.svg │ │ │ ├── media-icon.svg │ │ │ ├── new_post.png │ │ │ ├── quote-button.svg │ │ │ ├── underline-button.svg │ │ │ └── upload-content.svg │ │ ├── buttons │ │ │ ├── Button-Styled.tsx │ │ │ ├── Button.stories.tsx │ │ │ ├── Button.tsx │ │ │ └── index.ts │ │ ├── codeblock │ │ │ ├── codecontentblock-Styled.tsx │ │ │ ├── codecontentblock-wrapper.tsx │ │ │ └── codecontentblock.stories.tsx │ │ ├── contentBlockPopup │ │ │ ├── contentBlockPopup-Styled.tsx │ │ │ ├── contentBlockPopup.stories.tsx │ │ │ └── contentBlockPopup.tsx │ │ ├── contentblock │ │ │ ├── contentblock-Styled.tsx │ │ │ ├── contentblock-wrapper.tsx │ │ │ └── contentblock.stories.tsx │ │ ├── dottted_container │ │ │ ├── dotted_container-Styled.tsx │ │ │ ├── dotted_container.stories.tsx │ │ │ ├── dotted_container.tsx │ │ │ └── index.ts │ │ ├── helpers │ │ │ └── Storybook.ts │ │ ├── small_buttons │ │ │ ├── BoldButton.tsx │ │ │ ├── CenterAlignButton.tsx │ │ │ ├── CodeButton.tsx │ │ │ ├── ItalicButton.tsx │ │ │ ├── LeftAlignButton.tsx │ │ │ ├── QuoteButton.tsx │ │ │ ├── RightAlignButton.tsx │ │ │ ├── UnderlineButton.tsx │ │ │ ├── small_buttons-Styled.tsx │ │ │ └── small_buttons.stories.tsx │ │ ├── spheres │ │ │ ├── Sphere-Styled.tsx │ │ │ ├── Sphere.stories.tsx │ │ │ └── Sphere.tsx │ │ ├── styles │ │ │ └── GlobalStyles.ts │ │ └── text_alignment_buttons │ │ │ ├── LeftAlign.tsx │ │ │ ├── MiddleAlign.tsx │ │ │ ├── RightAlign.tsx │ │ │ ├── text-alignment-Styled.tsx │ │ │ └── text_alignment_buttons.stories.tsx │ ├── deprecated │ │ ├── Dashboard_OLD.tsx │ │ ├── components │ │ │ ├── Editor │ │ │ │ ├── EditorFile.tsx │ │ │ │ ├── EditorHeader.tsx │ │ │ │ └── EditorSidebar.tsx │ │ │ ├── FileRenderer_OLD │ │ │ │ ├── FileContainer.tsx │ │ │ │ ├── FileRenderer.tsx │ │ │ │ ├── FolderContainer.tsx │ │ │ │ └── Renamable.tsx │ │ │ └── NewDialogue │ │ │ │ ├── NewDialogue.tsx │ │ │ │ ├── TemplateChip.tsx │ │ │ │ └── TemplateSelector.tsx │ │ ├── data │ │ │ └── templates.json │ │ └── types │ │ │ └── FileFormat.tsx │ ├── index.tsx │ ├── packages │ │ ├── dashboard │ │ │ ├── Dashboard.tsx │ │ │ ├── api │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── components │ │ │ │ ├── ConfirmationModal │ │ │ │ │ ├── ConfirmationWindow.test.tsx │ │ │ │ │ └── ConfirmationWindow.tsx │ │ │ │ ├── Directory.tsx │ │ │ │ ├── FileRenderer │ │ │ │ │ ├── FileContainer.tsx │ │ │ │ │ ├── FolderContainer.tsx │ │ │ │ │ ├── Renamable.tsx │ │ │ │ │ └── Renderer.tsx │ │ │ │ └── SideBar │ │ │ │ │ ├── SideBar.test.tsx │ │ │ │ │ └── SideBar.tsx │ │ │ └── state │ │ │ │ └── folders │ │ │ │ ├── actions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── initial-state.ts │ │ │ │ ├── reducers.ts │ │ │ │ ├── sagas.ts │ │ │ │ ├── selectors.ts │ │ │ │ └── types.ts │ │ └── editor │ │ │ ├── EditorBlock.test.tsx │ │ │ ├── api │ │ │ ├── OTClient │ │ │ │ ├── README.md │ │ │ │ ├── client.ts │ │ │ │ ├── operation.ts │ │ │ │ ├── operationQueue.ts │ │ │ │ └── util.ts │ │ │ └── cmsFS │ │ │ │ └── volumes.ts │ │ │ ├── componentFactory.tsx │ │ │ ├── components │ │ │ ├── CodeBlock.tsx │ │ │ ├── EditorBlock.tsx │ │ │ ├── HeadingBlock.tsx │ │ │ ├── buttons │ │ │ │ ├── EditorBoldButton.tsx │ │ │ │ ├── EditorCenterAlignButton.tsx │ │ │ │ ├── EditorCodeButton.tsx │ │ │ │ ├── EditorItalicButton.tsx │ │ │ │ ├── EditorLeftAlignButton.tsx │ │ │ │ ├── EditorQuoteButton.tsx │ │ │ │ ├── EditorRightAlignButton.tsx │ │ │ │ ├── EditorSelectFont.tsx │ │ │ │ ├── EditorUnderlineButton.tsx │ │ │ │ └── buttonHelpers.ts │ │ │ ├── styles │ │ │ │ └── PrismTheme.tsx │ │ │ └── util │ │ │ │ └── normalize-tokens.ts │ │ │ ├── index.tsx │ │ │ ├── operationManager.tsx │ │ │ ├── state │ │ │ ├── actions.ts │ │ │ ├── helpers.ts │ │ │ ├── index.ts │ │ │ ├── initial-state.ts │ │ │ ├── reducers.ts │ │ │ ├── selectors.ts │ │ │ └── types.ts │ │ │ ├── types.ts │ │ │ └── websocketClient.ts │ ├── react-app-env.d.ts │ ├── redux-state │ │ ├── configure-store.ts │ │ ├── docs.md │ │ ├── index.ts │ │ └── reducers.ts │ ├── reportWebVitals.ts │ └── setupTests.ts └── tsconfig.json ├── next ├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── Dockerfile.development ├── README.md ├── env.d.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public │ ├── assets │ │ ├── CSESocEventsCP.png │ │ ├── WebsitesIcon.png │ │ ├── close_icon.svg │ │ ├── csesoc_discord.png │ │ ├── csesoc_facebook.png │ │ ├── csesoc_instagram.png │ │ ├── csesoc_spotify.png │ │ ├── csesoc_youtube.png │ │ ├── execs │ │ │ ├── 2007.png │ │ │ ├── 2008.png │ │ │ ├── 2009.png │ │ │ ├── 2010.png │ │ │ ├── 2011.png │ │ │ ├── 2012.png │ │ │ ├── 2013.png │ │ │ ├── 2014.png │ │ │ ├── 2015.png │ │ │ ├── 2016.png │ │ │ ├── 2017.png │ │ │ ├── 2018.png │ │ │ ├── 2019.png │ │ │ ├── 2020.png │ │ │ └── 2021.png │ │ ├── logo.svg │ │ ├── logo_white.svg │ │ ├── menu_icon.svg │ │ ├── socials │ │ │ ├── discord.svg │ │ │ ├── discord_coloured.svg │ │ │ ├── facebook.svg │ │ │ ├── facebook_coloured.svg │ │ │ ├── instagram.svg │ │ │ ├── instagram_coloured.svg │ │ │ ├── spotify.svg │ │ │ ├── spotify_coloured.svg │ │ │ ├── youtube.svg │ │ │ └── youtube_coloured.svg │ │ ├── sponsors │ │ │ ├── ak.webp │ │ │ ├── amz.webp │ │ │ ├── app.webp │ │ │ ├── arc.webp │ │ │ ├── aris.webp │ │ │ ├── atl.webp │ │ │ ├── aw.webp │ │ │ ├── bkk.webp │ │ │ ├── canva.webp │ │ │ ├── cbx.webp │ │ │ ├── cog.webp │ │ │ ├── ctd.webp │ │ │ ├── del.webp │ │ │ ├── disp.webp │ │ │ ├── dlb.webp │ │ │ ├── fl.webp │ │ │ ├── goog.webp │ │ │ ├── imc.webp │ │ │ ├── jds.webp │ │ │ ├── jst.webp │ │ │ ├── knapp.webp │ │ │ ├── mqb.webp │ │ │ ├── msft.webp │ │ │ ├── mtl.webp │ │ │ ├── mvc.webp │ │ │ ├── nine.webp │ │ │ ├── nm.webp │ │ │ ├── opt.webp │ │ │ ├── pal.webp │ │ │ ├── pear.webp │ │ │ ├── prsp.webp │ │ │ ├── quant.webp │ │ │ ├── qube.webp │ │ │ ├── rokt.webp │ │ │ ├── rpt.webp │ │ │ ├── rs.webp │ │ │ ├── sig.webp │ │ │ ├── unsw.webp │ │ │ ├── wgg.webp │ │ │ └── zip.webp │ │ └── sponsors_white │ │ │ ├── atl.svg │ │ │ ├── deloitte.svg │ │ │ ├── imc.svg │ │ │ └── js.svg │ ├── favicon.ico │ └── vercel.svg ├── src │ ├── assets │ │ ├── example.png │ │ ├── execs.js │ │ ├── firstday.png │ │ ├── makingfriends.png │ │ ├── sponsors.js │ │ ├── studytips.png │ │ └── wishiknew.png │ ├── components │ │ ├── aboutus │ │ │ ├── AboutUs-Styled.tsx │ │ │ ├── ReusableSpheres.tsx │ │ │ └── Sphere-Styled.tsx │ │ ├── blog │ │ │ ├── Blog-styled.tsx │ │ │ ├── Blog.tsx │ │ │ └── types.ts │ │ ├── eventspage │ │ │ ├── ClearLayeredGlassContainer-Styled.tsx │ │ │ └── ClearLayeredGlassContainer.tsx │ │ ├── footer │ │ │ └── Footer.tsx │ │ ├── navbar │ │ │ ├── HamburgerMenu-styled.tsx │ │ │ ├── HamburgerMenu.tsx │ │ │ ├── Navbar-styled.tsx │ │ │ ├── Navbar.tsx │ │ │ └── types.ts │ │ ├── resources │ │ │ ├── Carousel.tsx │ │ │ └── image-data.tsx │ │ ├── sponsors │ │ │ └── Sponsors-Styled.tsx │ │ └── start │ │ │ ├── advice │ │ │ ├── AdviceView.tsx │ │ │ └── InfoCard.tsx │ │ │ ├── assets │ │ │ ├── circles.svg │ │ │ ├── enrolment-guide-banner.png │ │ │ ├── notangles.svg │ │ │ ├── peer-mentoring.png │ │ │ └── unsw.svg │ │ │ ├── connect │ │ │ └── ConnectView.tsx │ │ │ ├── enrolment │ │ │ └── EnrolmentView.tsx │ │ │ ├── events │ │ │ ├── EventTab.tsx │ │ │ └── EventsView.tsx │ │ │ ├── timeline │ │ │ ├── Timeline-styled.tsx │ │ │ └── Timeline.tsx │ │ │ ├── view │ │ │ ├── View.tsx │ │ │ └── types.ts │ │ │ └── welcome │ │ │ ├── Countdown.tsx │ │ │ └── WelcomeView.tsx │ ├── hooks │ │ └── TimelineScroll.tsx │ ├── pages │ │ ├── AboutUs.tsx │ │ ├── ExecDescription.tsx │ │ ├── MiniAboutUs.tsx │ │ ├── MiniEvents.tsx │ │ ├── MiniHomepage.tsx │ │ ├── MiniResources.tsx │ │ ├── MiniSupport.tsx │ │ ├── Sponsors.tsx │ │ ├── Sponsors2.tsx │ │ ├── Start.tsx │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── blog │ │ │ ├── [bid].tsx │ │ │ └── index.tsx │ │ └── index.tsx │ ├── styles │ │ ├── device.ts │ │ ├── globalStyles.ts │ │ ├── motion.tsx │ │ └── theme.ts │ └── svgs │ │ ├── 2022.png │ │ ├── BottomRect.svg │ │ ├── DC.svg │ │ ├── FB.svg │ │ ├── HPCurve.tsx │ │ ├── RCurve.svg │ │ ├── RectangleCurve.tsx │ │ ├── SPOT.svg │ │ ├── TopRect.svg │ │ ├── YT.svg │ │ ├── degree_planner.svg │ │ ├── jobs_board.svg │ │ ├── notangles.svg │ │ └── otter.png ├── tsconfig.json └── yarn.lock ├── package-lock.json ├── postgres ├── Dockerfile ├── dbver.txt ├── down │ └── down.sql ├── migrate.py ├── requirements.txt └── up │ ├── 01-create_migrations_table.sql │ ├── 02-create_groups_table.sql │ ├── 03-create_person_table.sql │ ├── 04-create_filesystem_table.sql │ ├── 05-create_frontend_table.sql │ └── 06-create_dummy_data.sql ├── renovate.json ├── services └── events-service │ ├── Dockerfile │ ├── docker-compose.yml │ ├── fetcher.go │ ├── go.mod │ ├── main.go │ ├── response_cache.go │ └── service.go └── utilities ├── createUsers.sh └── ghost-exporter ├── .config └── dotnet-tools.json ├── .paket └── Paket.Restore.targets ├── Converters.fs ├── Main.fs ├── Models ├── CMSSyntax.fs └── GhostSyntax.fs ├── examples ├── example_format.json └── example_format__exported.json ├── ghost-exporter.fsproj ├── paket.dependencies ├── paket.lock └── paket.references /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [main, prototype, "renovate/*"] 10 | pull_request: 11 | branches: [main, prototype] 12 | 13 | env: 14 | PG_PORT: 5432 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | backend_testing: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/setup-go@v4 22 | with: 23 | go-version: 1.18 24 | - uses: actions/checkout@v3 25 | - name: Building docker containers using docker-compose 26 | run: GO_MOD=go.mod docker-compose up -d --build 27 | - name: Golang Tests 28 | run: go test ./... 29 | working-directory: ./backend 30 | frontend_testing: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - name: type check 35 | run: cd frontend && npm install && npm run types 36 | - name: react tests 37 | run: cd frontend && npm test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | *.o 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | # End of https://www.toptal.com/developers/gitignore/api/go 27 | 28 | node_modules 29 | 30 | # nextjs bundled files 31 | .next 32 | 33 | # ignore compiled main go file and compiled typescript 34 | backend/main 35 | backend/dist 36 | 37 | 38 | # ignore .env files 39 | config/.env.dev 40 | 41 | # System files 42 | .DS_Store 43 | 44 | ### fsharp ### 45 | lib/debug 46 | lib/release 47 | Debug 48 | *.suo 49 | *.user 50 | obj 51 | bin 52 | /build/ 53 | *.exe 54 | !.paket/paket.bootstrapper.exe 55 | .ionide/ 56 | .fake 57 | .paket/* 58 | paket-files/* -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | backend/ @ShunyaoLiang 2 | postgres/ @ShunyaoLiang 3 | utilities/ @ShunyaoLiang 4 | services/ @ShunyaoLiang 5 | 6 | frontend/ @lauraw0 @james-teng-0 7 | frontend/src/packages/editor @lauraw0 @james-teng-0 @ShunyaoLiang 8 | next/ @lauraw0 @james-teng-0 9 | 10 | .github/ @csesoc/technical 11 | renovate.json @csesoc/technical 12 | Dockerfile @csesoc/technical 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SERVICE := go-hotreload 2 | 3 | dev: 4 | docker-compose \ 5 | --env-file=./config/.env.dev \ 6 | up \ 7 | -d 8 | 9 | dev-build: 10 | docker-compose \ 11 | --env-file=./config/.env.dev \ 12 | up --build 13 | 14 | pg: 15 | docker volume prune; \ 16 | docker-compose \ 17 | --env-file=./config/.env.dev \ 18 | up --build 19 | 20 | cms-only: 21 | docker-compose \ 22 | --env-file=./config/.env.dev \ 23 | up frontend backend db 24 | 25 | cms-build: 26 | docker-compose \ 27 | --env-file=./config/.env.dev \ 28 | up --build frontend backend db 29 | 30 | next-only: 31 | docker-compose \ 32 | --env-file=./config/.env.dev \ 33 | up next backend db 34 | 35 | next-build: 36 | docker-compose \ 37 | --env-file=./config/.env.dev \ 38 | up --build next backend db 39 | 40 | clean: 41 | docker-compose \ 42 | --env-file=./config/.env.dev \ 43 | down 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cms.csesoc.unsw.edu.au 2 | 3 | Welcome to the CSESoc CMS Git Repo!! :D 4 | 5 | 6 | ## Build Instructions 7 | Building the app fresh after installation 8 | run: 9 | `make dev-build` 10 | 11 | all subsequent running of the app you can 12 | run: 13 | `make` 14 | 15 | what to do after you are done programming? 16 | run: 17 | `make clean` 18 | 19 | when dependencies have changed i.e. you installed a new package, updated version of a package 20 | run: 21 | `make dev-build` 22 | 23 | ## Environment Variables 24 | 25 | Please note that you have to add /Config/.env.dev and include the env secrets. Please contact `laurlala#1696`, `jumbo#9999` or `Mae#6758` on discord for these if you don't have them :) 26 | 27 | ## Postgres Instructions 28 | Access interactive terminal by running `docker exec -it pg_container bash` 29 | now run this command `psql -d test_db -f infile` to load the dummy data we have prepared in ./postgres/infile 30 | or run `make pg` 31 | 32 | 33 | ## FAQs: 34 | - Q: Something is broken what to do? 35 | - A: Run `make clean` then run `make dev-build` again, should fix it, if it is still broken then manually remove all images in the docker desktop GUI and re-run `make dev-build`. 36 | 37 | - Q: Something has gone wrong with Docker, where can I find docker resources? 38 | - A: Docker is rather well documented, check out the docker website: https://www.docker.com/ to try and resolve your issue. 39 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | Dockerfile.development -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19-alpine as app-builder 2 | WORKDIR /go/src/app 3 | COPY . . 4 | RUN apk add git 5 | # Static build required so that we can safely copy the binary over. 6 | # `-tags timetzdata` embeds zone info from the "time/tzdata" package. 7 | RUN CGO_ENABLED=0 go install -ldflags '-extldflags "-static"' -tags timetzdata 8 | 9 | FROM scratch 10 | # the test program: 11 | COPY --from=app-builder /go/bin/cms.csesoc.unsw.edu.au /cms.csesoc.unsw.edu.au 12 | # the tls certificates: 13 | # NB: this pulls directly from the upstream image, which already has ca-certificates: 14 | COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 15 | ENTRYPOINT ["/cms.csesoc.unsw.edu.au"] -------------------------------------------------------------------------------- /backend/Dockerfile.development: -------------------------------------------------------------------------------- 1 | FROM golang:1.19-alpine as build-env 2 | 3 | # make current working directory /app 4 | WORKDIR /go/src/cms.csesoc.unsw.edu.au 5 | 6 | # copy go.mod and go.sum 7 | COPY go.mod go.sum ./ 8 | 9 | COPY . . 10 | 11 | # downloding dependencies 12 | RUN go mod download 13 | RUN apk add --update npm 14 | RUN go get github.com/githubnemo/CompileDaemon 15 | 16 | EXPOSE 8080 17 | 18 | # export environment variable for gopath 19 | ENV GOPATH /go 20 | 21 | # might start the app 22 | CMD ["go", "run", "main.go"] 23 | 24 | # For some reason compile daemon is broken 25 | # ENTRYPOINT CompileDaemon --build="go build main.go" --command="./main" -------------------------------------------------------------------------------- /backend/algorithms/README.md: -------------------------------------------------------------------------------- 1 | # Algorithms 2 | 3 | This is a small package that contains many of the algorithms used by specifically the editor, it is its own package so that it can be cross compiled with the `GOOS=JS` and web-assembly target and inserted into the frontend. 4 | 5 | ## WASM Frontend 6 | This package is cross-compiled to target WASM, this exposes all these algorithms to the JS client to prevent re-implementation, the algorithms are primarily used for the diff/match/patch operations for the differential synchronistaion algorithm. 7 | 8 | ## Resources 9 | Our editor uses quite a few algorithms so below is a list of resources you can use to learn about them and hopefully contribute to the editor :) 10 | - [Differential Synchronisation Algorithm](https://neil.fraser.name/writing/sync/eng047-fraser.pdf) 11 | - [Myer's Difference Algorithm](http://www.xmailserver.org/diff2.pdf) 12 | - [A cool blog post](https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/) 13 | - [String Difference Strategies](https://neil.fraser.name/writing/diff/) 14 | -------------------------------------------------------------------------------- /backend/algorithms/main.go: -------------------------------------------------------------------------------- 1 | package algorithms 2 | 3 | // Config for some of our algorithms 4 | // controls the minimum size of a job 5 | // aka: the job that isnt broken up into subproblems 6 | // computed concurrently 7 | const concurrentBatchSize int = 2000 8 | 9 | // threshold before which the concurrent implementation 10 | // of an algorithm starts running 11 | const concurrencyThreshold int = 2000 12 | 13 | // limits the amount of go routines that can be 14 | // spawned for a concurrent computation, the limit 15 | // prevents excessive context switching :) 16 | const concurrentSpawnLimit int = 8 17 | -------------------------------------------------------------------------------- /backend/algorithms/misc.go: -------------------------------------------------------------------------------- 1 | package algorithms 2 | 3 | func min(a, b int) int { 4 | if a < b { 5 | return a 6 | } 7 | return b 8 | } 9 | -------------------------------------------------------------------------------- /backend/algorithms/tests/util.go: -------------------------------------------------------------------------------- 1 | package algorithms 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | // generates a random word of length n 9 | func randomWord(n int) string { 10 | rand.Seed(time.Now().UnixNano()) 11 | result := make([]byte, n) 12 | 13 | for i := 0; i < n; i++ { 14 | asciiBase := rand.Int31() 15 | result[i] = byte(asciiBase % 256) 16 | } 17 | 18 | return string(result) 19 | } 20 | -------------------------------------------------------------------------------- /backend/database/contexts/main.go: -------------------------------------------------------------------------------- 1 | package contexts 2 | 3 | import ( 4 | "log" 5 | 6 | "cms.csesoc.unsw.edu.au/environment" 7 | "github.com/jackc/pgx/v4" 8 | ) 9 | 10 | // Constants regarding database connections 11 | var USER = environment.GetDBUser() 12 | var HOST = environment.GetDBHost() 13 | var PASSWORD = environment.GetDBPassword() 14 | var DATABASE = environment.GetDB() 15 | 16 | const TEST_USER = "postgres" 17 | const TEST_PASSWORD = "test" 18 | const TESTING_DB_NAME = "cms_testing_db" 19 | 20 | const TEST_DB_EXPIRY_TIME = 180 21 | 22 | // DatabaseContext exposes 3 methods to the user, by using an interface 23 | // it allows us to easilly swap what context (and consequently database) a method is actually using. 24 | // Any connection to the database implements the database context interface 25 | type DatabaseContext interface { 26 | Query(query string, sqlArgs []interface{}, resultOutput ...interface{}) error 27 | QueryRow(query string, sqlArgs []interface{}) (pgx.Rows, error) 28 | Exec(query string, sqlArgs []interface{}) error 29 | Close() 30 | } 31 | 32 | // returns a new database context based on the current environment 33 | func GetDatabaseContext() DatabaseContext { 34 | if environment.IsTestingEnvironment() { 35 | return newTestingContext() 36 | } 37 | 38 | context, err := newLiveContext() 39 | if err != nil { 40 | log.Fatalf("failed to fetch database context: %v", err) 41 | } 42 | 43 | return context 44 | } 45 | -------------------------------------------------------------------------------- /backend/database/contexts/schema.go: -------------------------------------------------------------------------------- 1 | // TITLE: Schema 2 | // Created by (Varun: Varun-Sethu) (09/21) 3 | // Last modified by (Varun: Varun-Sethu) (1/10/21) 4 | // # # # 5 | /* 6 | File is just a small utility file for fetching the startup script for our databases. 7 | **/ 8 | package contexts 9 | 10 | import ( 11 | "io/ioutil" 12 | "path/filepath" 13 | 14 | _ "github.com/lib/pq" 15 | ) 16 | 17 | // importSchema just loads the startup script stored in Postgres/create_tables.sql 18 | func importSchema() (string, error) { 19 | // TODO: no.... just no.... 20 | absPath, _ := filepath.Abs("../../../Postgres/create_tables.sql") 21 | 22 | contents, err := ioutil.ReadFile(absPath) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | return string(contents), nil 28 | } 29 | -------------------------------------------------------------------------------- /backend/database/main.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | // This is just a small file containing some documentaiton 4 | // repositories/ 5 | // contains all our repositories to interface with the database, can be mocked 6 | // contexts/ 7 | // containins structures relating to an underlying connection to a database 8 | -------------------------------------------------------------------------------- /backend/database/repositories/frontends.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | type frontendsRepository struct { 10 | embeddedContext 11 | } 12 | 13 | const InvalidFrontend = -1 14 | 15 | func NewFrontendRepo(logicalName string, URL string, embeddedContext embeddedContext) (filesystemRepository, error) { 16 | rows, err := embeddedContext.ctx.QueryRow("SELECT * from new_frontend($1, $2)", []interface{}{logicalName, URL}) 17 | if err != nil { 18 | return filesystemRepository{}, fmt.Errorf("Error setting up frontend in Postgres (new_frontend): %w", err) 19 | } 20 | 21 | defer rows.Close() 22 | 23 | var frontendID uuid.UUID 24 | var frontendRoot uuid.UUID 25 | 26 | if rows.Next() { 27 | err := rows.Scan(&frontendID, &frontendRoot) 28 | if err != nil { 29 | return filesystemRepository{}, fmt.Errorf("Error scanning columns within new_frontend: %w", err) 30 | } 31 | } 32 | 33 | return filesystemRepository{ 34 | frontendID, 35 | frontendRoot, 36 | logicalName, 37 | URL, 38 | embeddedContext, 39 | }, nil 40 | } 41 | 42 | // GetFrontendFromURL is the implementation of the frontend repository for frontendRepository 43 | func (rep frontendsRepository) GetFrontendFromURL(url string) int { 44 | var frontendId int 45 | err := rep.ctx.Query("SELECT ID from frontend where URL = $1;", []interface{}{url}, &frontendId) 46 | if err != nil { 47 | return InvalidFrontend 48 | } 49 | 50 | return frontendId 51 | } 52 | -------------------------------------------------------------------------------- /backend/database/repositories/groups.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | // Implements IGroupRepository 8 | type groupsRepository struct { 9 | embeddedContext 10 | } 11 | 12 | func (rep groupsRepository) GetGroupInfo(g Groups) Groups { 13 | var result Groups 14 | err := rep.ctx.Query("SELECT * from groups where name = $1;", []interface{}{g.Name}, 15 | &g.UID, &g.Name, &g.Permission) 16 | if err != nil { 17 | log.Print("get permissions error", err.Error()) 18 | } 19 | return result 20 | } 21 | -------------------------------------------------------------------------------- /backend/database/repositories/person.go: -------------------------------------------------------------------------------- 1 | // TITLE: user database repository layer 2 | // Author: (Jacky: FafnirZ) (09/21) 3 | // Refactor into database package: Varun 4 | 5 | package repositories 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | // Implements IPersonRepository 14 | type personRepository struct { 15 | frontEndID uuid.UUID 16 | embeddedContext 17 | } 18 | 19 | func (rep personRepository) PersonExists(p Person) bool { 20 | var result int 21 | err := rep.ctx.Query("SELECT count(*) from person where email = $1 and frontendid = $2 and password = $3;", []interface{}{p.Email, rep.frontEndID, p.Password}, &result) 22 | if err != nil { 23 | log.Println("credentials match err", err.Error()) 24 | } 25 | 26 | return result == 1 27 | } 28 | 29 | func (rep personRepository) GetPersonWithDetails(p Person) Person { 30 | var result Person 31 | err := rep.ctx.Query("SELECT * from person where email = $1 and frontendid = $2 and password = $3;", []interface{}{p.Email, rep.frontEndID, p.Password}, 32 | &result.UID, &result.Email, &result.FirstName, &result.Password, &result.GroupID, &result.FrontEndID) 33 | if err != nil { 34 | log.Print("get permissions error", err.Error()) 35 | } 36 | return result 37 | } 38 | -------------------------------------------------------------------------------- /backend/database/repositories/types.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "cms.csesoc.unsw.edu.au/database/contexts" 5 | "cms.csesoc.unsw.edu.au/environment" 6 | ) 7 | 8 | // User groups configurations 9 | const ( 10 | GROUPS_ADMIN int = 1 11 | GROUPS_USER int = 2 12 | ) 13 | 14 | // internal ID for holding potentially null 15 | // foreign keys 16 | // implements scannable interface 17 | type nullableID struct { 18 | ID *int 19 | } 20 | 21 | // Implementation of the scan interface 22 | func (ni nullableID) Scan(src interface{}) error { 23 | switch v := src.(type) { 24 | case int: 25 | *ni.ID = v 26 | case nil: 27 | *ni.ID = -1 28 | default: 29 | break 30 | } 31 | 32 | return nil 33 | } 34 | 35 | // small struct that can be embedded into repository implementations 36 | // contains some methods for exposing a test context 37 | type embeddedContext struct { 38 | ctx contexts.DatabaseContext 39 | } 40 | 41 | // utility function for testing 42 | func (rep embeddedContext) GetContext() contexts.DatabaseContext { 43 | if !environment.IsTestingEnvironment() { 44 | panic("not in a testing environment") 45 | } 46 | 47 | return rep.ctx 48 | } 49 | -------------------------------------------------------------------------------- /backend/editor/OT/OTClient/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: 'standard-with-typescript', 7 | overrides: [ 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 'latest', 11 | sourceType: 'module' 12 | }, 13 | rules: { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/editor/OT/OTClient/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true 5 | } 6 | -------------------------------------------------------------------------------- /backend/editor/OT/OTClient/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /backend/editor/OT/OTClient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cms_backend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "socket.io": "4.5.1", 7 | "socket.io-client": "4.5.1" 8 | }, 9 | "devDependencies": { 10 | "@types/jest": "^29.0.3", 11 | "@types/socket.io": "3.0.2", 12 | "@typescript-eslint/eslint-plugin": "5.33.1", 13 | "@typescript-eslint/parser": "5.26.0", 14 | "eslint": "8.16.0", 15 | "eslint-config-standard": "17.0.0", 16 | "eslint-config-standard-with-typescript": "22.0.0", 17 | "eslint-plugin-import": "2.26.0", 18 | "eslint-plugin-n": "15.3.0", 19 | "eslint-plugin-promise": "6.0.0", 20 | "jest": "^29.0.3", 21 | "ts-jest": "^29.0.1", 22 | "typescript": "*" 23 | }, 24 | "scripts": { 25 | "test": "jest" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/editor/OT/OTClient/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Bind operation in functional programming 3 | * 4 | * @param f - the function to apply on the value 5 | * @param y - the value 6 | * @returns the return value of the function with y passed in if y is not undefined else undefined 7 | */ 8 | export const bind = (f: (x: T) => V, y: T | undefined): V | undefined => 9 | y !== undefined ? f(y) : undefined; 10 | -------------------------------------------------------------------------------- /backend/editor/OT/datamodel/README.md: -------------------------------------------------------------------------------- 1 | # Data/Models 2 | 3 | Data/Models is where you define the core layout of the data format that you wish to use the collaborative editor with, currently there are only two supported data models, the CMS datamodel (used by the CMS frontend) and the Learning Platform datamodel (used by the learning platform). 4 | 5 | ## Creating Models 6 | Note that in order to expose your new datamodel you must implement the `IsExposed()` method, this is to prevent passing the wrong models into the CMS functions (so hopefully this can be caught at compile time as opposed to runtime). 7 | 8 | ## Details 9 | TODO :sunglasses: -------------------------------------------------------------------------------- /backend/editor/OT/datamodel/component.go: -------------------------------------------------------------------------------- 1 | package datamodel 2 | 3 | import "reflect" 4 | 5 | type Component interface { 6 | Get(string) (reflect.Value, error) 7 | Set(string, reflect.Value) error 8 | } 9 | -------------------------------------------------------------------------------- /backend/editor/OT/datamodel/document.go: -------------------------------------------------------------------------------- 1 | package datamodel 2 | 3 | // Document is the main datamodel type of the CMS model, it implements the DataModel interface 4 | type Document struct { 5 | DocumentName string 6 | DocumentId string 7 | Content []Component 8 | } 9 | 10 | // IsExposed is the required registration for our type 11 | func (d Document) IsExposed() bool { return true } 12 | -------------------------------------------------------------------------------- /backend/editor/OT/datamodel/image.go: -------------------------------------------------------------------------------- 1 | package datamodel 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | // @implements the Component interface 9 | type Image struct { 10 | ImageDocumentID string 11 | ImageSource string 12 | } 13 | 14 | // Get returns the reflect.Value corresponding to a specific field 15 | func (i Image) Get(field string) (reflect.Value, error) { 16 | return reflect.ValueOf(i).FieldByName(field), nil 17 | } 18 | 19 | // Set sets a reflect.Value given a specific field 20 | func (i Image) Set(field string, value reflect.Value) error { 21 | reflectionField := reflect.ValueOf(i).FieldByName(field) 22 | if reflectionField.IsValid() { 23 | reflectionField.Set(value) 24 | return nil 25 | } 26 | 27 | return errors.New("invalid field provided") 28 | } 29 | -------------------------------------------------------------------------------- /backend/editor/OT/datamodel/main.go: -------------------------------------------------------------------------------- 1 | package datamodel 2 | 3 | // DataModel is an interface that all wannabe datamodels must implement, 4 | // for now the only required method is IsExposed(), the point of the interface 5 | // is to prevent accidentally providing non-registered types into the CMS 6 | type DataModel interface { 7 | IsExposed() bool 8 | } 9 | 10 | // DataType is a series of types that can be used within your datamodel, they require registration in the 11 | // json configuration file 12 | type DataType interface{} 13 | -------------------------------------------------------------------------------- /backend/editor/OT/datamodel/paragraph.go: -------------------------------------------------------------------------------- 1 | package datamodel 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | // @implements Component 9 | type Paragraph struct { 10 | ParagraphID string 11 | ParagraphAlign string 12 | ParagraphChildren []Text 13 | } 14 | 15 | type Text struct { 16 | Text string 17 | Link string 18 | Bold bool 19 | Italic bool 20 | Underline bool 21 | } 22 | 23 | // Get returns the reflect.Value corresponding to a specific field 24 | func (p Paragraph) Get(field string) (reflect.Value, error) { 25 | return reflect.ValueOf(p).FieldByName(field), nil 26 | } 27 | 28 | // Set sets a reflect.Value given a specific field 29 | func (p Paragraph) Set(field string, value reflect.Value) error { 30 | inputType := value.Kind() 31 | if field == "ParagraphAlign" { 32 | fieldValue := value.String() 33 | isValidAllignment := inputType == reflect.String && 34 | (fieldValue == "left" || fieldValue == "right" || fieldValue == "center") 35 | if !isValidAllignment { 36 | return errors.New("ParagraphAlign data must be a string of 'left', 'right' or 'center'") 37 | } 38 | } 39 | reflectionField := reflect.ValueOf(p).FieldByName(field) 40 | reflectionField.Set(value) 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /backend/editor/OT/main.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/google/uuid" 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | // This file just defines some of the endpoints for the editor 12 | // and ties together its various disparate components 13 | var Upgrader = websocket.Upgrader{ 14 | ReadBufferSize: 1024, 15 | WriteBufferSize: 1024, 16 | CheckOrigin: func(r *http.Request) bool { 17 | return true 18 | }, 19 | } 20 | 21 | // Actual edit endpoint 22 | func EditEndpoint(w http.ResponseWriter, r *http.Request) { 23 | requestedDocument, ok := r.URL.Query()["document"] 24 | if !ok || len(requestedDocument[0]) < 1 { 25 | w.WriteHeader(400) 26 | return 27 | } 28 | 29 | ws, err := Upgrader.Upgrade(w, r, nil) 30 | if err != nil { 31 | log.Println(err) 32 | } 33 | 34 | wsClient := newClient(ws) 35 | targetServer := GetDocumentServerFactoryInstance().FetchDocumentServer(uuid.MustParse(requestedDocument[0])) 36 | commPipe, terminatePipe := targetServer.connectClient(wsClient) 37 | 38 | go wsClient.run(commPipe, terminatePipe) 39 | } 40 | -------------------------------------------------------------------------------- /backend/editor/OT/operations/application.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "fmt" 5 | 6 | "cms.csesoc.unsw.edu.au/pkg/cmsjson" 7 | ) 8 | 9 | // Traverse returns the second last value and the last value pointed at by a specific path 10 | func Traverse(document cmsjson.AstNode, subpaths []int) (cmsjson.AstNode, cmsjson.AstNode, error) { 11 | curr := document 12 | var prev cmsjson.AstNode = nil 13 | lastNode := len(subpaths) - 1 14 | 15 | for pathIndex, pathValue := range subpaths { 16 | prev = curr 17 | // If not last node 18 | if node, _ := curr.JsonObject(); node != nil { 19 | curr = node[pathValue] 20 | } else if node, _ := curr.JsonArray(); node != nil { 21 | curr = node[pathValue] 22 | } else if node, _ := curr.JsonPrimitive(); node != nil { 23 | if pathIndex != lastNode { 24 | return nil, nil, fmt.Errorf("primitive types must appear at the end of the path") 25 | } 26 | } 27 | } 28 | 29 | return prev, curr, nil 30 | } 31 | 32 | func (op Operation) ApplyTo(document cmsjson.AstNode) (cmsjson.AstNode, error) { 33 | parent, _, err := Traverse(document, op.Path) 34 | if err != nil { 35 | return nil, fmt.Errorf("failed to apply operation %v at target site: %w", op, err) 36 | } 37 | 38 | applicationIndex := op.Path[len(op.Path)-1] 39 | return op.Operation.Apply(parent, applicationIndex, op.OperationType) 40 | } 41 | -------------------------------------------------------------------------------- /backend/editor/OT/operations/array_operation.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "cms.csesoc.unsw.edu.au/pkg/cmsjson" 8 | ) 9 | 10 | // ArrayOperation is an operation on an array type 11 | // @implements OperationModel 12 | type ArrayOperation struct { 13 | NewValue float64 14 | } 15 | 16 | // TransformAgainst is the ArrayOperation implementation of the operationModel interface 17 | func (arrOp ArrayOperation) TransformAgainst(operation OperationModel, applicationType EditType) (OperationModel, OperationModel) { 18 | return arrOp, operation 19 | } 20 | 21 | // Apply is the ArrayOperation implementation of the OperationModel interface, it does nothing 22 | func (arrOp ArrayOperation) Apply(parentNode cmsjson.AstNode, applicationIndex int, applicationType EditType) (cmsjson.AstNode, error) { 23 | var err error = nil 24 | if children, _ := parentNode.JsonArray(); children != nil { 25 | if applicationIndex < 0 || applicationIndex > len(children) { 26 | return nil, fmt.Errorf("invalid application index, index %d out of bounds for array of size %d", applicationIndex, len(children)) 27 | } 28 | 29 | if applicationType == Insert { 30 | operandAsAst := cmsjson.ASTFromValue(arrOp.NewValue) 31 | err = parentNode.UpdateOrAddArrayElement(applicationIndex, operandAsAst) 32 | } else { 33 | err = parentNode.RemoveArrayElement(applicationIndex) 34 | } 35 | 36 | return parentNode, err 37 | } 38 | 39 | return nil, errors.New("invalid application of an array operation, expected parent node to be an array") 40 | } 41 | -------------------------------------------------------------------------------- /backend/editor/OT/operations/boolean_operation.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "cms.csesoc.unsw.edu.au/pkg/cmsjson" 8 | ) 9 | 10 | // BooleanOperations represents an operation on a boolean type 11 | // @implements OperationModel 12 | type BooleanOperation struct { 13 | NewValue bool 14 | } 15 | 16 | // TransformAgainst is the BooleanOperation implementation of the operationModel interface 17 | func (boolOp BooleanOperation) TransformAgainst(operation OperationModel, applicationType EditType) (OperationModel, OperationModel) { 18 | return boolOp, operation 19 | } 20 | 21 | // Apply is the BooleanOperation implementation of the OperationModel interface, it does nothing 22 | func (boolOp BooleanOperation) Apply(parentNode cmsjson.AstNode, applicationIndex int, applicationType EditType) (cmsjson.AstNode, error) { 23 | var err error = nil 24 | if child, childType := parentNode.JsonPrimitive(); child != nil { 25 | if childType.Kind() != reflect.Bool { 26 | return nil, errors.New("invalid application of a primitive operation, expected child node to be a boolean") 27 | } 28 | operandAsAst := cmsjson.ASTFromValue(boolOp.NewValue) 29 | err = parentNode.UpdateOrAddPrimitiveElement(operandAsAst) 30 | return parentNode, err 31 | } 32 | return nil, errors.New("invalid application of a primitive operation, expected parent node to be a primitive") 33 | } 34 | -------------------------------------------------------------------------------- /backend/editor/OT/operations/integer_operation.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "cms.csesoc.unsw.edu.au/pkg/cmsjson" 8 | ) 9 | 10 | // IntegerOperation represents an operation on an integer type 11 | // @implementations of OperationModel 12 | type IntegerOperation struct { 13 | NewValue int 14 | } 15 | 16 | // TransformAgainst is the IntegerOperation implementation of the operationModel interface 17 | func (intOp IntegerOperation) TransformAgainst(operation OperationModel, applicationType EditType) (OperationModel, OperationModel) { 18 | return intOp, operation 19 | } 20 | 21 | // Apply is the IntegerOperation implementation of the OperationModel interface, it does nothing 22 | func (intOp IntegerOperation) Apply(parentNode cmsjson.AstNode, applicationIndex int, applicationType EditType) (cmsjson.AstNode, error) { 23 | var err error = nil 24 | if child, childType := parentNode.JsonPrimitive(); child != nil { 25 | if childType.Kind() != reflect.Int { 26 | return nil, errors.New("invalid application of a primitive operation, expected child node to be an integer") 27 | } 28 | operandAsAst := cmsjson.ASTFromValue(intOp.NewValue) 29 | err = parentNode.UpdateOrAddPrimitiveElement(operandAsAst) 30 | return parentNode, err 31 | } 32 | return nil, errors.New("invalid application of a primitive operation, expected parent node to be a primitive") 33 | } 34 | -------------------------------------------------------------------------------- /backend/editor/OT/operations/noop_operation.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import "cms.csesoc.unsw.edu.au/pkg/cmsjson" 4 | 5 | // Noop represents a non-existent operation 6 | // @implements OperationModel 7 | type Noop struct{} 8 | 9 | // TransformAgainst is the noop implementation of the operationModel interface 10 | func (noop Noop) TransformAgainst(operation OperationModel, applicationType EditType) (OperationModel, OperationModel) { 11 | return noop, operation 12 | } 13 | 14 | // Apply is the noop implementation of the OperationModel interface, it does nothing 15 | func (noop Noop) Apply(parentNode cmsjson.AstNode, applicationIndex int, applicationType EditType) (cmsjson.AstNode, error) { 16 | return parentNode, nil 17 | } 18 | -------------------------------------------------------------------------------- /backend/editor/OT/operations/operation_model.go: -------------------------------------------------------------------------------- 1 | package operations 2 | 3 | import ( 4 | "cms.csesoc.unsw.edu.au/pkg/cmsjson" 5 | ) 6 | 7 | type ( 8 | // EditType is the underlying type of an edit 9 | EditType int 10 | 11 | // OperationModel defines an simple interface an operation must implement 12 | OperationModel interface { 13 | TransformAgainst(op OperationModel, applicationType EditType) (OperationModel, OperationModel) 14 | Apply(parentNode cmsjson.AstNode, applicationIndex int, applicationType EditType) (cmsjson.AstNode, error) 15 | } 16 | 17 | // Operation is the fundamental incoming type from the frontend 18 | Operation struct { 19 | Path []int 20 | OperationType EditType 21 | AcknowledgedServerOps int 22 | 23 | IsNoOp bool 24 | Operation OperationModel 25 | } 26 | ) 27 | 28 | // EditType enum constants 29 | const ( 30 | Insert EditType = iota 31 | Delete 32 | ) 33 | 34 | // NoOperation is a special constant that signifies an operation that does nothing 35 | var NoOperation = Operation{IsNoOp: true, Operation: Noop{}} 36 | 37 | // Parse is a utility function that takes a JSON stream and parses the input into 38 | // a Request object 39 | func ParseOperation(request string) (Operation, error) { 40 | var operation Operation 41 | if err := cmsjson.Unmarshall[Operation](CmsJsonConf, &operation, []byte(request)); err != nil { 42 | return Operation{}, err 43 | } else { 44 | return operation, nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /backend/editor/OT/tests/integration_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | // This test suite performs full integration tests on the entire concurrent editor 4 | // the test suite is probably super fragile (as is the nature of a lot of integration tests :( ) but should give you assurance during any refactoring job you do 5 | -------------------------------------------------------------------------------- /backend/editor/OT/worker.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | // worker is a single goroutine that listens for work to execute, it also optionally listens for a termination 4 | // signal the reason we have this is because we spin up a goroutine for each incoming keystroke from the client 5 | // so my suspicion is that the overhead of allocating a stack and then de-allocating it on each keystroke 6 | // is rather costly (despite goroutines being rather lightweight) 7 | // TODO: in the future benchmark the actual impact of just spinning a goroutine on each keystroke 8 | // and remove this if it isn't actually as bad as suspected 9 | // NOTE: each client is allocated one worker they can push work to :D 10 | 11 | type empty struct{} 12 | 13 | // createAndStartWorker is the body of a worker goroutine 14 | // it can be called via: go startWorker(handles) 15 | func createAndStartWorker(workHandle chan func(), killHandle chan empty) { 16 | for { 17 | select { 18 | case work := <-workHandle: 19 | // do the work and then return ourselves to the worker pool 20 | work() 21 | 22 | case <-killHandle: 23 | // just die :( 24 | return 25 | 26 | default: 27 | // do nothing 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/editor/diffSync/document/documentState.go: -------------------------------------------------------------------------------- 1 | package document 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/google/uuid" 7 | "github.com/sergi/go-diff/diffmatchpatch" 8 | ) 9 | 10 | type documentState struct { 11 | // Shadows maps an extension ID to an shadow 12 | // Connected extensions maps an extension ID to an extension 13 | baseText string 14 | shadows map[uuid.UUID]*string 15 | connectedExtensions map[uuid.UUID]*Extension 16 | 17 | readingExtensions sync.RWMutex 18 | } 19 | 20 | // Types: 21 | // syncPayload defines the arguments for a sync operation against 22 | // a document, the signature refers to the ID of the extension sending 23 | // this payload 24 | type syncPayload struct { 25 | patches []diffmatchpatch.Patch 26 | signature uuid.UUID 27 | } 28 | 29 | // terminatePayload represents a request to terminate an extensions 30 | // connect to a document, marks it for garbage collection later 31 | type terminatePayload struct { 32 | signature uuid.UUID 33 | } 34 | -------------------------------------------------------------------------------- /backend/editor/diffSync/document/extensionHead.go: -------------------------------------------------------------------------------- 1 | package document 2 | 3 | import "github.com/sergi/go-diff/diffmatchpatch" 4 | 5 | // Defines an exteion interface, all extensions must satisfy this set of required 6 | // functions to be useable and considered an interface 7 | type ExtensionHead interface { 8 | // Synchronisation mechanisms 9 | Synchronise([]diffmatchpatch.Patch) 10 | 11 | // LifeCycle operations 12 | // Just note that init is passed a method that it can use 13 | // to try and synchronise with the document 14 | // it is also given a method the extension head must call to signal 15 | // to the document that it wishes to die (how tragic) 16 | // the idea is that the ExtensionHead has no idea wat it is attached to 17 | // it only knows how to communicate with it 18 | Init(func([]diffmatchpatch.Patch), func(), *string) 19 | Destroy(*string) // destroy is given the current state of the document 20 | 21 | // Special functions regarding the service 22 | IsService() bool 23 | Spin() 24 | Stop() 25 | } 26 | -------------------------------------------------------------------------------- /backend/editor/diffSync/html/README.md: -------------------------------------------------------------------------------- 1 | # HTML Tests 2 | This is just a really small file for performing some functional tests on the editor :), contains a tiny implemementation of the client side editor code. -------------------------------------------------------------------------------- /backend/editor/diffSync/html/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "codemirror": { 6 | "version": "5.65.7", 7 | "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.7.tgz", 8 | "integrity": "sha512-zb67cXzgugIQmb6tfD4G11ILjYoMfTjwcjn+cWsa4GewlI2adhR/h3kolkoCQTm1msD/1BuqVTKuO09ELsS++A==" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/editor/diffSync/html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "codemirror": "5.65.7" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /backend/editor/diffSync/main.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "cms.csesoc.unsw.edu.au/editor/diffSync/service" 8 | "github.com/google/uuid" 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | // This file just defines some of the endpoints for the editor 13 | // and ties togher its various disparate components 14 | var broker = service.NewBroker() 15 | 16 | var Upgrader = websocket.Upgrader{ 17 | ReadBufferSize: 1024, 18 | WriteBufferSize: 1024, 19 | CheckOrigin: func(r *http.Request) bool { 20 | return true 21 | }, 22 | } 23 | 24 | // Actual edit endpoint 25 | func EditEndpoint(w http.ResponseWriter, r *http.Request) { 26 | requestedDocument, ok := r.URL.Query()["document"] 27 | if !ok || len(requestedDocument[0]) < 1 { 28 | w.WriteHeader(400) 29 | return 30 | } 31 | var err error 32 | var ws *websocket.Conn 33 | ws, err = Upgrader.Upgrade(w, r, nil) 34 | if err != nil { 35 | log.Println(err) 36 | } 37 | 38 | var documentID uuid.UUID 39 | documentID, err = uuid.Parse(requestedDocument[0]) 40 | if err == nil { 41 | err = broker.ConnectOrOpenDocument(documentID, ws) 42 | } 43 | if err != nil { 44 | log.Println(err) 45 | ws.Close() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /backend/editor/diffSync/service/extension_stub.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/sergi/go-diff/diffmatchpatch" 4 | 5 | // Defines some basic methods for synchronising with a document 6 | // and maintaining an internal shadow 7 | type ExtensionStub struct { 8 | serverShadow *string 9 | dmp *diffmatchpatch.DiffMatchPatch 10 | baseText string 11 | sendToDoc func([]diffmatchpatch.Patch) 12 | } 13 | 14 | // Diff match patch implementation 15 | func (stub ExtensionStub) Synchronise(patches []diffmatchpatch.Patch) { 16 | newText, _ := stub.dmp.PatchApply(patches, stub.baseText) 17 | newShadow, _ := stub.dmp.PatchApply(patches, *stub.serverShadow) 18 | 19 | stub.baseText = newText 20 | *stub.serverShadow = newShadow 21 | 22 | // compute the difference between the base text and the shadow 23 | // and propogate the diff to the document 24 | diff := stub.dmp.PatchMake(*stub.serverShadow, stub.baseText) 25 | *stub.serverShadow = stub.baseText 26 | 27 | stub.sendToDoc(diff) 28 | } 29 | -------------------------------------------------------------------------------- /backend/editor/diffSync/tests/main.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | // TODO :P 4 | -------------------------------------------------------------------------------- /backend/endpoints/editor_endpoints.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | editor "cms.csesoc.unsw.edu.au/editor/pessimistic" 8 | . "cms.csesoc.unsw.edu.au/endpoints/models" 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | // Upgrader is a websocket upgrader 13 | var Upgrader = websocket.Upgrader{ 14 | ReadBufferSize: 1024, 15 | WriteBufferSize: 1024, 16 | CheckOrigin: func(r *http.Request) bool { 17 | return true 18 | }, 19 | } 20 | 21 | // EditHandler is the HTTP handler responsible for dealing with incoming requests to edit a document 22 | // for the most part this is passed over to the editor package 23 | func EditHandler(form ValidEditRequest, w http.ResponseWriter, r *http.Request, df DependencyFactory) handlerResponse[empty] { 24 | unpublishedVol := df.GetUnpublishedVolumeRepo() 25 | log := df.GetLogger() 26 | 27 | ws, err := Upgrader.Upgrade(w, r, nil) 28 | if err != nil { 29 | log.Write("failed to upgrade websocket connection") 30 | return handlerResponse[empty]{ 31 | Status: http.StatusInternalServerError, 32 | } 33 | } 34 | 35 | // note: this blocks until completion 36 | log.Write("starting editor loop") 37 | err = editor.EditorClientLoop(form.DocumentID, unpublishedVol, ws) 38 | if err != nil { 39 | log.Write(fmt.Sprintf("ending editor loop, message: %v", err.Error())) 40 | return handlerResponse[empty]{ 41 | Status: http.StatusInternalServerError, 42 | } 43 | } 44 | 45 | fmt.Print("hello!: D") 46 | return handlerResponse[empty]{Status: http.StatusOK} 47 | } 48 | -------------------------------------------------------------------------------- /backend/endpoints/models/editor_models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/google/uuid" 4 | 5 | // ValidEditRequest represents a valid request that can be send to the editor endpoint 6 | type ( 7 | ValidEditRequest struct { 8 | DocumentID uuid.UUID 9 | } 10 | ) 11 | -------------------------------------------------------------------------------- /backend/endpoints/models/user_models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "crypto/sha256" 5 | "regexp" 6 | 7 | "cms.csesoc.unsw.edu.au/database/repositories" 8 | ) 9 | 10 | type ( 11 | User struct { 12 | Email string `schema:"Email"` 13 | Password string `schema:"Password"` 14 | } 15 | ) 16 | 17 | // ValidEmail checks to see if the username is valid 18 | // - email must be > 2 characters before the domain 19 | // - white lists a few domains which are allowed 20 | // - match alphanumeric greater than 2 letters 21 | // - followed by optional (.something) greater than 0 times 22 | // TODO: fix for {z8} and {z6} cases 23 | func (u *User) IsValidEmail() bool { 24 | regex := `^(z[0-9]{7}|([a-zA-Z0-9]{2,})+(\.[a-zA-Z0-9]+)*)@(gmail.com|ad.unsw.edu|student.unsw.edu|hotmail.com|outlook.com)(.au)?$` 25 | r, _ := regexp.Compile(regex) 26 | if match := r.MatchString(u.Email); !match { 27 | return false 28 | } 29 | 30 | return true 31 | } 32 | 33 | // UserExists just checks if a user has a valid password 34 | func (u *User) UserExists(personRepo repositories.PersonRepository) bool { 35 | hashedPassword := u.HashPassword() 36 | 37 | return personRepo.PersonExists(repositories.Person{ 38 | Email: u.Email, 39 | Password: hashedPassword, 40 | }) 41 | } 42 | 43 | // HashPassword hashes a user's password 44 | func (u *User) HashPassword() string { 45 | hashedBytes := sha256.Sum256([]byte(u.Password)) 46 | return string(hashedBytes[:]) 47 | } 48 | -------------------------------------------------------------------------------- /backend/endpoints/models/volume_models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "mime/multipart" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | // Request models outline the general model that an incoming request to a handler must satisfy 10 | type ( 11 | // ValidImageUploadRequest is the request model for an handler that uploads an IMAGE to a docker volume 12 | ValidImageUploadRequest struct { 13 | Parent uuid.UUID 14 | LogicalName string `schema:"LogicalName,required"` 15 | OwnerGroup int `schema:"OwnerGroup,required"` 16 | Image multipart.File 17 | } 18 | 19 | // ValidImageUploadRequest is the request model for an handler that uploads an IMAGE to a docker volume 20 | ValidDocumentUploadRequest struct { 21 | Parent uuid.UUID `schema:"Parent,required"` 22 | DocumentName string `schema:"DocumentName,required"` 23 | Content string `schema:"Content,required"` // TODO: Add check that content is valid JSON 24 | } 25 | 26 | // ValidPublishDocumentRequest is the request model for any handler that publishes a document 27 | ValidPublishDocumentRequest struct { 28 | DocumentID uuid.UUID `schema:"DocumentID,required"` 29 | } 30 | 31 | // ValidGetPublishedDocumentRequest is the response model for any handler that fetches information from 32 | // the published volume 33 | ValidGetPublishedDocumentRequest struct { 34 | DocumentID uuid.UUID `schema:"DocumentID,required"` 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /backend/endpoints/tests/handler_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | repMocks "cms.csesoc.unsw.edu.au/database/repositories/mocks" 9 | "cms.csesoc.unsw.edu.au/endpoints" 10 | mock_endpoints "cms.csesoc.unsw.edu.au/endpoints/mocks" 11 | "cms.csesoc.unsw.edu.au/endpoints/models" 12 | "cms.csesoc.unsw.edu.au/internal/logger" 13 | "github.com/golang/mock/gomock" 14 | "github.com/google/uuid" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | // Currently failing, because the websocket upgrade thing fails and will send back a 500 error. 19 | func TestEditHandler(t *testing.T) { 20 | controller := gomock.NewController(t) 21 | assert := assert.New(t) 22 | defer controller.Finish() 23 | 24 | // Test Setup 25 | documentID := uuid.New() 26 | form := models.ValidEditRequest{DocumentID: documentID} 27 | responseRecorder := httptest.NewRecorder() 28 | request := httptest.NewRequest("GET", "/editor", nil) 29 | 30 | //Make unpublishedvolumes 31 | mockUnpublishedVolume := repMocks.NewMockIUnpublishedVolumeRepository(controller) 32 | 33 | // Make logger 34 | log := logger.OpenLog("New Handler Log") 35 | mockDepFactory := mock_endpoints.NewMockDependencyFactory(controller) 36 | mockDepFactory.EXPECT().GetLogger().Return(log) 37 | mockDepFactory.EXPECT().GetUnpublishedVolumeRepo().Return(mockUnpublishedVolume) 38 | 39 | // Test execution 40 | response := endpoints.EditHandler(form, responseRecorder, request, mockDepFactory) 41 | assert.Equal(response.Status, http.StatusInternalServerError) 42 | } 43 | -------------------------------------------------------------------------------- /backend/environment/config.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func GetFrontendURI() string { 8 | return os.Getenv("FRONTEND_URI") 9 | } 10 | 11 | func GetDBUser() string { 12 | return os.Getenv("POSTGRES_USER") 13 | } 14 | 15 | func GetDBHost() string { 16 | return os.Getenv("POSTGRES_HOST") 17 | } 18 | 19 | func GetDBPassword() string { 20 | return os.Getenv("POSTGRES_PASSWORD") 21 | } 22 | 23 | func GetDB() string { 24 | return os.Getenv("POSTGRES_DB") 25 | } 26 | 27 | func GetDBPort() string { 28 | return os.Getenv("PG_PORT") 29 | } 30 | -------------------------------------------------------------------------------- /backend/environment/main.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import ( 4 | "flag" 5 | "testing" 6 | ) 7 | 8 | func init() { 9 | testing.Init() 10 | flag.Parse() 11 | } 12 | 13 | // package to quickly grab information regarding the execution environment 14 | func IsTestingEnvironment() bool { 15 | return flag.Lookup("test.v") != nil 16 | } 17 | -------------------------------------------------------------------------------- /backend/internal/data/test.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /backend/internal/logger/main.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | type Log struct { 11 | startingMessage string 12 | logBuffer strings.Builder 13 | logLock sync.Mutex 14 | } 15 | 16 | // OpenLog creates a new log to be passed 17 | // down the call chain, u can flush the logBuffer with close or flush 18 | func OpenLog(startingMessage string) *Log { 19 | return &Log{ 20 | startingMessage: startingMessage, 21 | logBuffer: strings.Builder{}, 22 | logLock: sync.Mutex{}, 23 | } 24 | } 25 | 26 | // Write writes to an open log 27 | func (l *Log) Write(log string) { 28 | l.logLock.Lock() 29 | // TODO: investigate if this is slower than just maintaining an array of byte buffers 30 | l.logBuffer.WriteString(fmt.Sprintf(" %s\n", log)) 31 | l.logLock.Unlock() 32 | } 33 | 34 | // flush flushes the buffer using Go's inbuilt logging library 35 | func (l *Log) Flush() { 36 | l.logLock.Lock() 37 | log.Printf("== %s ==\n", l.startingMessage) 38 | log.Print(l.logBuffer.String()) 39 | l.logLock.Unlock() 40 | } 41 | 42 | // Close closes the buffer, basically just flushing it 43 | func (l *Log) Close() { 44 | l.Flush() 45 | log.Printf("== log end ==\n") 46 | } 47 | -------------------------------------------------------------------------------- /backend/internal/storage/main.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/build" 7 | "os" 8 | ) 9 | 10 | func Read(id, dir string) string { 11 | // super insecure but who cares? issa prototype 12 | gopath := build.Default.GOPATH 13 | file, err := os.Open(fmt.Sprintf("%s/src/cms.csesoc.unsw.edu.au/internal/%s/%s", gopath, dir, id)) 14 | defer file.Close() 15 | 16 | if err != nil { 17 | panic(err) 18 | } 19 | fileBuffer := new(bytes.Buffer) 20 | fileBuffer.ReadFrom(file) 21 | 22 | return fileBuffer.String() 23 | } 24 | 25 | // pretty pleasee close ur file pointers owo, meant to override file contents 26 | func Open(id, dir string) (*os.File, error) { 27 | file, err := os.Create(fmt.Sprintf("storage/%s", id)) 28 | return file, err 29 | } 30 | -------------------------------------------------------------------------------- /backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "cms.csesoc.unsw.edu.au/endpoints" 8 | "cms.csesoc.unsw.edu.au/environment" 9 | 10 | "github.com/rs/cors" 11 | ) 12 | 13 | func main() { 14 | mux := http.NewServeMux() 15 | 16 | endpoints.RegisterFilesystemEndpoints(mux) 17 | endpoints.RegisterAuthenticationEndpoints(mux) 18 | endpoints.RegisterEditorEndpoints(mux) 19 | 20 | // whitelisted URLs 21 | frontend_URI := environment.GetFrontendURI() 22 | 23 | // CORS middleware added 24 | c := cors.New(cors.Options{ 25 | // for testing purposes 26 | AllowedOrigins: []string{frontend_URI}, 27 | AllowedMethods: []string{http.MethodGet, http.MethodPost}, 28 | AllowCredentials: true, 29 | }) 30 | handler := cors.Default().Handler(mux) 31 | handler = c.Handler(handler) 32 | 33 | log.Fatal(http.ListenAndServe(":8080", handler)) 34 | } 35 | -------------------------------------------------------------------------------- /backend/pkg/cmsjson/ast_marshaller.go: -------------------------------------------------------------------------------- 1 | package cmsjson 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Marshall can potentially be a rather expensive operation 10 | // why? Strings in go are immutable, if we recursively modify 11 | // a string each invocation ends up allocation a new string on the heap 12 | // resulting in GC overhead :P 13 | func (c Configuration) MarshallAST(source AstNode) string { 14 | asPrimitive, _ := source.JsonPrimitive() 15 | asObject, _ := source.JsonObject() 16 | asArray, _ := source.JsonArray() 17 | 18 | switch { 19 | case asPrimitive != nil: 20 | return stringifyPrimitive(asPrimitive) 21 | case asObject != nil: 22 | result := strings.Builder{} 23 | for _, node := range asObject { 24 | result.WriteString(fmt.Sprintf("\"%s\": %s", node.GetKey(), c.MarshallAST(node))) 25 | } 26 | 27 | return fmt.Sprintf("{%s}", result.String()) 28 | default: 29 | result := strings.Builder{} 30 | for _, node := range asArray { 31 | result.WriteString(c.MarshallAST(node) + ",") 32 | } 33 | 34 | return fmt.Sprintf("[%s]", result.String()) 35 | } 36 | } 37 | 38 | func stringifyPrimitive(primitive interface{}) string { 39 | switch parsedPrimitive := primitive.(type) { 40 | case string: 41 | return "\"" + parsedPrimitive + "\"" 42 | case bool: 43 | return strconv.FormatBool(parsedPrimitive) 44 | default: 45 | return strconv.FormatInt(parsedPrimitive.(int64), 10) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /backend/pkg/cmsjson/config.go: -------------------------------------------------------------------------------- 1 | package cmsjson 2 | 3 | import "reflect" 4 | 5 | // What is this? Well the default go marshaller does not support interface types 6 | // this is a partial custom implementation that adds the support for interface types 7 | // accepted types are defined in the RegisteredType field in the configuration 8 | // for the most part this is just a bunch of reflection bashing + using a better json parser 9 | 10 | type Configuration struct { 11 | // registeredTypes maps registered interface types to their implementation 12 | // forcing registration restricts the set of types we can marshall into thus 13 | // improving security 14 | RegisteredTypes map[reflect.Type]map[string]reflect.Type 15 | } 16 | 17 | type typeCategory int 18 | 19 | const ( 20 | _primitive = iota 21 | _interface 22 | _struct 23 | _array 24 | _slice 25 | _astRequest 26 | ) 27 | 28 | // resolveType takes a reflection field and determines what "type category" it falls into 29 | // we have differing logic for differing type categories 30 | func resolveType(t reflect.Type) typeCategory { 31 | if t == reflect.TypeOf((*jsonNode)(nil)) { 32 | return _astRequest 33 | } 34 | 35 | switch t.Kind() { 36 | case reflect.Struct: 37 | return _struct 38 | case reflect.Interface: 39 | return _interface 40 | case reflect.Slice: 41 | return _slice 42 | case reflect.Array: 43 | return _array 44 | } 45 | 46 | return _primitive 47 | } 48 | -------------------------------------------------------------------------------- /config/.env.dev.example: -------------------------------------------------------------------------------- 1 | BACKEND_URI=http://localhost:8080/ 2 | FRONTEND_URI=http://localhost:3000 3 | PG_USER=postgres 4 | PG_PASSWORD=postgres 5 | PG_DB=test_db 6 | PG_PORT=5432 7 | PG_HOST=db:5432 8 | COMPOSE_HTTP_TIMEOUT=200 9 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | # Config folder 2 | this folder contains environment variables and shall not be committed to the repo 3 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/npm-debug.log 3 | build 4 | .dockerignore 5 | Dockerfile 6 | Dockerfile.prod 7 | Dockerfile.development 8 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "project": ["./tsconfig.json"], 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint", 17 | "react" 18 | ], 19 | "rules": { 20 | // "@typescript-eslint/strict-boolean-expressions": [ 21 | // 2, 22 | // { 23 | // "allowString" : false, 24 | // "allowNumber" : false 25 | // } 26 | // ], 27 | "@typescript-eslint/strict-boolean-expressions": "off", 28 | "@typescript-eslint/no-explicit-any": "off" 29 | }, 30 | "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] 31 | } -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/.nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 4; 2 | 3 | events { worker_connections 1024; } 4 | 5 | http { 6 | server { 7 | listen 80; 8 | root /usr/share/nginx/html; 9 | include /etc/nginx/mime.types; 10 | 11 | location / { 12 | try_files $uri /index.html; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /frontend/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/cse-ui-kit/**/*.stories.mdx", 4 | "../src/cse-ui-kit/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/preset-create-react-app" 10 | ], 11 | "framework": "@storybook/react", 12 | "core": { 13 | "builder": "webpack5" 14 | } 15 | } -------------------------------------------------------------------------------- /frontend/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | } -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Grab the latest Node base image 2 | FROM node:20.2.0-alpine as builder 3 | 4 | # Set the current working directory inside the container 5 | WORKDIR /app 6 | 7 | COPY package.json package-lock.json ./ 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | # nginx state for serving content 15 | FROM nginx:1.23.1-alpine 16 | COPY ./.nginx/nginx.conf /etc/nginx/nginx.conf 17 | # Set working directory to nginx asset directory 18 | WORKDIR /usr/share/nginx/html 19 | # Remove default nginx static assets 20 | RUN rm -rf ./* 21 | # Copy static assets from builder stage 22 | COPY --from=builder /app/build . 23 | 24 | EXPOSE 80 25 | 26 | # Containers run nginx with global directives and daemon off 27 | ENTRYPOINT ["nginx", "-g", "daemon off;"] 28 | -------------------------------------------------------------------------------- /frontend/Dockerfile.development: -------------------------------------------------------------------------------- 1 | # pulling official base image 2 | FROM node:20.2.0-alpine 3 | 4 | # Setting working directory 5 | WORKDIR /app 6 | 7 | # exposing ports 8 | EXPOSE 3000 9 | 10 | # npm start 11 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Routes, Route } from 'react-router-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { GlobalStore, persistor } from 'src/redux-state/index'; 5 | import { PersistGate } from 'redux-persist/integration/react'; 6 | 7 | // imports 8 | import Dashboard from './packages/dashboard/Dashboard'; 9 | import Editor from './packages/editor'; 10 | import GlobalStyle from './cse-ui-kit/styles/GlobalStyles'; 11 | 12 | const App: React.FC = () => { 13 | return ( 14 |
15 | 16 | 17 | 18 | 19 | } /> 20 | } /> 21 | 22 | 23 | 24 |
25 | ); 26 | }; 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /frontend/src/assets/moveable-content-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/cse-testing-lib/README.md: -------------------------------------------------------------------------------- 1 | ## CSESoc Website's testing library 2 | 3 | Placed in this folder are the key helper functions you can import to call in your jest tests -------------------------------------------------------------------------------- /frontend/src/cse-testing-lib/custom-queries.ts: -------------------------------------------------------------------------------- 1 | import { queryHelpers, buildQueries, GetAllBy } from '@testing-library/react' 2 | 3 | // The queryAllByAttribute is a shortcut for attribute-based matchers 4 | // You can also use document.querySelector or a combination of existing 5 | // testing library utilities to find matching nodes for your query 6 | 7 | const queryAllByDataAnchor: GetAllBy<[dataAnchorValue: any]> = (...args: any[]) => { 8 | // @ts-ignore 9 | return queryHelpers.queryAllByAttribute('data-anchor', ...args) 10 | } 11 | 12 | const getMultipleError = (c: HTMLElement, dataAnchorValue: any) => 13 | `Found multiple elements with the data-anchor attribute of: ${dataAnchorValue}` 14 | const getMissingError = (c: HTMLElement, dataAnchorValue: any) => 15 | `Unable to find an element with the data-anchor attribute of: ${dataAnchorValue}` 16 | 17 | const [ 18 | queryByDataAnchor, 19 | getAllByDataAnchor, 20 | getByDataAnchor, 21 | findAllByDataAnchor, 22 | findByDataAnchor, 23 | ] = buildQueries(queryAllByDataAnchor, getMultipleError, getMissingError) 24 | 25 | export { 26 | queryByDataAnchor, 27 | queryAllByDataAnchor, 28 | getByDataAnchor, 29 | getAllByDataAnchor, 30 | findAllByDataAnchor, 31 | findByDataAnchor, 32 | } -------------------------------------------------------------------------------- /frontend/src/cse-testing-lib/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | render, 3 | } from "./test-utils" 4 | 5 | import { fireEvent, act } from "@testing-library/react" 6 | 7 | export { 8 | render, 9 | fireEvent, 10 | act 11 | }; -------------------------------------------------------------------------------- /frontend/src/cse-testing-lib/test-utils.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux'; 3 | import { GlobalStore } from 'src/redux-state/index'; 4 | import {render, queries, RenderOptions} from '@testing-library/react' 5 | import * as customQueries from './custom-queries' 6 | import { BrowserRouter } from 'react-router-dom'; 7 | 8 | // with redux 9 | const AllTheProviders: React.FC = ({ children }) => { 10 | return ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ) 17 | } 18 | 19 | const customRender = ( 20 | ui: React.ReactElement, 21 | options?: Omit, 22 | ) => render( 23 | ui, { 24 | queries: {...queries, ...customQueries}, 25 | wrapper: AllTheProviders, 26 | ...options 27 | }) 28 | 29 | export * from '@testing-library/react' 30 | export {customRender as render} -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/ClearLayeredGlassContainer/ClearLayeredGlassContainer-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type positionProps = { 4 | position: string; 5 | top?: number; 6 | left?: number; 7 | colour: string; 8 | }; 9 | 10 | export const StyledContainer = styled.div` 11 | position: ${(props) => props.position}; 12 | width: 47vw; 13 | height: 25vw; 14 | top: ${(props) => props.top}vw; 15 | left: ${(props) => props.left}vw; 16 | border-radius: 1vw; 17 | background-color: ${(props) => props.colour}; 18 | border-width: 0.15vw; 19 | border-style: solid; 20 | border-color: #FAFCFF; 21 | `; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/ClearLayeredGlassContainer/ClearLayeredGlassContainer.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import ClearLayeredGlass from './ClearLayeredGlassContainer'; 5 | 6 | export default { 7 | title: 'CSE-UIKIT/ClearLayeredGlass', 8 | component: ClearLayeredGlass, 9 | } as ComponentMeta; 10 | 11 | const Template: ComponentStory = () => 12 | ( 13 | 14 | ) 15 | 16 | export const Primary = Template.bind({}); -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/ClearLayeredGlassContainer/ClearLayeredGlassContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledContainer } from './ClearLayeredGlassContainer-Styled'; 3 | 4 | // The colour listed first below (#71c0f882) represents blue whilst the 5 | // second colour (#f8717182) represents red 6 | export default function ClearLayeredGlass() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateCodeBlock_button/CreateCodeBlock-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | export const StyledButton = styled.div` 7 | width: 175px; 8 | height: 45px; 9 | margin: 5px; 10 | background: ${(props) => props.background}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | user-select: none; 17 | 18 | &:hover { 19 | background: #efeef3; 20 | color: black; 21 | transform: scale(1.04); 22 | } 23 | &:active { 24 | background: #c8d1fa; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateCodeBlock_button/CreateCodeBlock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import CreateCodeBlock from './CreateCodeBlock'; 5 | 6 | import { AiFillEdit } from "react-icons/ai"; 7 | 8 | export default { 9 | title: 'CSE-UIKIT/CreateCodeBlockButton', 10 | component: CreateCodeBlock, 11 | 12 | argTypes: { 13 | backgroundColor: { control: 'color' }, 14 | }, 15 | } as ComponentMeta; 16 | 17 | const Template: ComponentStory = (args) => 18 | ( 19 |
24 | Insert Button 25 | 26 |
27 | ) 28 | 29 | export const Primary = Template.bind({}); 30 | Primary.args = { 31 | background: "#90c2e7", 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateCodeBlock_button/CreateCodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledButton, buttonProps } from "./CreateCodeBlock-Styled"; 3 | import { AiOutlineCode } from "react-icons/ai"; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | } & buttonProps; 8 | 9 | export default function CreateCodeBlock({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 16 | 17 | Insert Code Block 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateCodeBlock_button/index.ts: -------------------------------------------------------------------------------- 1 | import CreateCodeBlock from './CreateCodeBlock'; 2 | 3 | export default CreateCodeBlock; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateContentBlock_button/CreateContentBlock-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | export const StyledButton = styled.div` 7 | width: 175px; 8 | height: 45px; 9 | margin: 5px; 10 | background: ${(props) => props.background}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | user-select: none; 17 | 18 | &:hover { 19 | background: #efeef3; 20 | color: black; 21 | transform: scale(1.04); 22 | } 23 | &:active { 24 | background: #c8d1fa; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateContentBlock_button/CreateContentBlock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import CreateContentBlock from './CreateContentBlock'; 5 | 6 | import { AiFillEdit } from "react-icons/ai"; 7 | 8 | export default { 9 | title: 'CSE-UIKIT/CreateContentBlockButton', 10 | component: CreateContentBlock, 11 | 12 | argTypes: { 13 | backgroundColor: { control: 'color' }, 14 | }, 15 | } as ComponentMeta; 16 | 17 | const Template: ComponentStory = (args) => 18 | ( 19 |
24 | Insert Button 25 | 26 |
27 | ) 28 | 29 | export const Primary = Template.bind({}); 30 | Primary.args = { 31 | background: "#90c2e7", 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateContentBlock_button/CreateContentBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledButton, buttonProps } from "./CreateContentBlock-Styled"; 3 | import { AiFillEdit } from "react-icons/ai"; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | } & buttonProps; 8 | 9 | export default function CreateContentBlock({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 16 | 17 | Insert Content Block 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateContentBlock_button/index.ts: -------------------------------------------------------------------------------- 1 | import CreateContentBlock from './CreateContentBlock'; 2 | 3 | export default CreateContentBlock; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateHeadingBlock_button/CreateHeadingBlock-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | export const StyledButton = styled.div` 7 | width: 175px; 8 | height: 45px; 9 | margin: 5px; 10 | background: ${(props) => props.background}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | user-select: none; 17 | 18 | &:hover { 19 | background: #efeef3; 20 | color: black; 21 | transform: scale(1.04); 22 | } 23 | &:active { 24 | background: #c8d1fa; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateHeadingBlock_button/CreateHeadingBlock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import CreateHeadingBlock from './CreateHeadingBlock'; 5 | 6 | import { AiFillEdit } from "react-icons/ai"; 7 | 8 | export default { 9 | title: 'CSE-UIKIT/CreateTitleBlockButton', 10 | component: CreateHeadingBlock, 11 | 12 | argTypes: { 13 | backgroundColor: { control: 'color' }, 14 | }, 15 | } as ComponentMeta; 16 | 17 | const Template: ComponentStory = (args) => 18 | ( 19 |
24 | Insert Button 25 | 26 |
27 | ) 28 | 29 | export const Primary = Template.bind({}); 30 | Primary.args = { 31 | background: "#90c2e7", 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateHeadingBlock_button/CreateHeadingBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledButton, buttonProps } from "./CreateHeadingBlock-Styled"; 3 | import { AiFillEdit } from "react-icons/ai"; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | } & buttonProps; 8 | 9 | export default function CreateHeadingBlock({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 12 | 13 | Insert Heading Block 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateHeadingBlock_button/index.ts: -------------------------------------------------------------------------------- 1 | import CreateHeadingBlock from './CreateHeadingBlock'; 2 | 3 | export default CreateHeadingBlock; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateMediaBlock_button/CreateMediaBlock-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | export const StyledButton = styled.div` 7 | width: 175px; 8 | height: 45px; 9 | margin: 5px; 10 | background: ${(props) => props.background}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | user-select: none; 17 | 18 | &:hover { 19 | background: #efeef3; 20 | color: black; 21 | transform: scale(1.04); 22 | } 23 | &:active { 24 | background: #c8d1fa; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateMediaBlock_button/CreateMediaBlock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import CreateMediaBlock from './CreateMediaBlock'; 5 | 6 | import { AiFillPicture } from "react-icons/ai"; 7 | 8 | export default { 9 | title: 'CSE-UIKIT/CreateMediaBlockButton', 10 | component: CreateMediaBlock, 11 | 12 | argTypes: { 13 | backgroundColor: { control: 'color' }, 14 | }, 15 | } as ComponentMeta; 16 | 17 | const Template: ComponentStory = (args) => 18 | ( 19 |
24 | Insert Button 25 | 26 |
27 | ) 28 | 29 | export const Primary = Template.bind({}); 30 | Primary.args = { 31 | background: "#90c2e7", 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateMediaBlock_button/CreateMediaBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledButton, buttonProps } from "./CreateMediaBlock-Styled"; 3 | import { AiFillPicture } from "react-icons/ai"; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | } & buttonProps; 8 | 9 | export default function CreateMediaBlock({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 16 | 17 | Insert Media Block 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/CreateMediaBlock_button/index.ts: -------------------------------------------------------------------------------- 1 | import CreateMediaBlock from './CreateMediaBlock'; 2 | 3 | export default CreateMediaBlock; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/EditableTitle_textbox/EditableTitle-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const StyledTextBox = styled.input` 4 | background: transparent; 5 | 6 | border: none; 7 | border-color: transparent; 8 | 9 | 10 | padding: 0.5em; 11 | 12 | font-size: inherit; 13 | 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | user-select: none; 18 | 19 | &:hover { 20 | cursor: text; 21 | color: black; 22 | transform: scale(1.04); 23 | } 24 | 25 | cursor: pointer; 26 | `; 27 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/EditableTitle_textbox/EditableTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StyledTextBox } from "./EditableTitle-Styled"; 3 | 4 | type Props = { 5 | onChange?: React.ChangeEventHandler; 6 | onBlur?: React.FocusEventHandler 7 | value: string 8 | }; 9 | 10 | export default function EditableTitle({ onChange, onBlur, value, ...styleProps }: Props) { 11 | return ( 12 | 13 | ); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/EditableTitle_textbox/index.ts: -------------------------------------------------------------------------------- 1 | import EditableTitle from './EditableTitle'; 2 | 3 | export default EditableTitle; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/MediaContentBlock/MediaContent-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | 7 | export const StyledContent = styled.div` 8 | items: center; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | `; 14 | 15 | export const Text = styled.p` 16 | word-wrap: initial; 17 | display: flex; 18 | align-items: bottom; 19 | `; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/MediaContentBlock/MediaContentBlock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import MediaContentBlock from './MediaContentBlock'; 5 | 6 | export default { 7 | title: 'CSE-UIKIT/MediaContentBlock', 8 | component: MediaContentBlock, 9 | 10 | argTypes: { 11 | backgroundColor: { control: 'color' }, 12 | }, 13 | } as ComponentMeta; 14 | 15 | const Template: ComponentStory = (args) => 16 | ( 17 |
22 | Insert Media Content Block 23 | 24 |
25 | ) 26 | 27 | export const Primary = Template.bind({}); -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/MediaContentBlock/MediaContentBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledContent, buttonProps, Text} from "./MediaContent-Styled"; 3 | import { ReactComponent as Media } from 'src/cse-ui-kit/assets/media-icon.svg'; 4 | import MoveableContentBlock from '../contentblock/contentblock-wrapper'; 5 | 6 | type Props = { 7 | onClick?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function MediaContentBlock({ onClick, ...styleProps }: Props) { 11 | return ( 12 | 13 | 14 | 17 | Upload Images/Gifs 18 | 19 | 20 | ); 21 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/PublishDocument_button/PublishDocument-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | export const StyledButton = styled.div` 7 | width: 175px; 8 | height: 45px; 9 | margin: 5px; 10 | background: ${(props) => props.background}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | user-select: none; 17 | 18 | &:hover { 19 | background: #efeef3; 20 | color: black; 21 | transform: scale(1.04); 22 | } 23 | &:active { 24 | background: #c8d1fa; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/PublishDocument_button/PublishDocument.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledButton, buttonProps } from "./PublishDocument-Styled"; 3 | import { MdPublish } from "react-icons/md"; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | } & buttonProps; 8 | 9 | export default function PublishDocument({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 12 | 13 | Publish Document 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/PublishDocument_button/index.ts: -------------------------------------------------------------------------------- 1 | import PublishDocument from "./PublishDocument"; 2 | 3 | export default PublishDocument; 4 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/README.md: -------------------------------------------------------------------------------- 1 | ## CSESOC UI Kit 2 | 3 | Utilises Storybook 4 | 5 | References: 6 | For default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export 7 | For argTypes: https://storybook.js.org/docs/react/api/argtypes 8 | For component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args 9 | For args: https://storybook.js.org/docs/react/writing-stories/args 10 | For flexDirection type casting: https://stackoverflow.com/questions/62432985/typescript-saying-a-string-is-invalid-even-though-its-in-the-union -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/SyncDocument_button/SyncDocument-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | }; 6 | export const StyledButton = styled.div` 7 | width: 175px; 8 | height: 45px; 9 | margin: 5px; 10 | background: ${(props) => props.background}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | user-select: none; 17 | 18 | &:hover { 19 | background: #efeef3; 20 | color: black; 21 | transform: scale(1.04); 22 | } 23 | &:active { 24 | background: #c8d1fa; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/SyncDocument_button/SyncDocument.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from "react"; 2 | import { StyledButton, buttonProps } from "./SyncDocument-Styled"; 3 | import { AiOutlineSync } from "react-icons/ai"; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | } & buttonProps; 8 | 9 | export default function SyncDocument({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 12 | 13 | Sync Document 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/SyncDocument_button/index.ts: -------------------------------------------------------------------------------- 1 | import SyncDocument from "./SyncDocument"; 2 | 3 | export default SyncDocument; 4 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/bold-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/centeralign-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/code-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/frontend/src/cse-ui-kit/assets/default.png -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/italics-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/leftrightalign-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/new_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/frontend/src/cse-ui-kit/assets/new_post.png -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/quote-button.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/assets/underline-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/buttons/Button-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type buttonProps = { 4 | background?: string; 5 | } 6 | export const StyledButton = styled.div` 7 | width: 80px; 8 | height: 45px; 9 | background: ${props => props.background }; 10 | color: white; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | border-radius: 10px; 16 | 17 | &:hover { 18 | background: #EFEEF3; 19 | color: black; 20 | transform: scale(1.04); 21 | } 22 | &:active { 23 | background: #C8D1FA; 24 | color: #7482CB; 25 | transform: scale(0.96); 26 | } 27 | 28 | cursor: pointer; 29 | `; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/buttons/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import Button from './Button'; 5 | 6 | // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export 7 | export default { 8 | title: 'CSE-UIKIT/Button', 9 | component: Button, 10 | // More on argTypes: https://storybook.js.org/docs/react/api/argtypes 11 | argTypes: { 12 | backgroundColor: { control: 'color' }, 13 | }, 14 | } as ComponentMeta; 15 | 16 | // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args 17 | const Template: ComponentStory = (args) => 18 | ( 19 |
24 | Basic Button 25 | 26 |
27 | ) 28 | 29 | export const Primary = Template.bind({}); 30 | Primary.args = { 31 | background: "#90c2e7", 32 | } 33 | 34 | export const Secondary = Template.bind({}); 35 | Secondary.args = { 36 | background: "#a799b7" 37 | } 38 | // More on args: https://storybook.js.org/docs/react/writing-stories/args 39 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/buttons/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledButton, buttonProps } from './Button-Styled'; 3 | 4 | 5 | type Props = { 6 | children?: React.ReactElement | any; 7 | onClick?: (...args: any) => void; 8 | } & buttonProps; 9 | 10 | export default function Button({ children, onClick, ...styleProps }: Props) { 11 | return ( 12 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/buttons/index.ts: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | 3 | export default Button; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/codeblock/codecontentblock-Styled.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */ 2 | import styled from "styled-components"; 3 | 4 | export type StyledProps = { 5 | focused?: boolean; 6 | } 7 | 8 | export const StyledCodeContent = styled.div` 9 | width: 100%; 10 | max-width: 600px; 11 | background: #f7f7f7; 12 | color: #000000; 13 | box-shadow: ${(props) => props.focused && '0px 2px 3px rgba(0, 0, 0, 0.25);'} 14 | padding: 30px 20px; 15 | display: flex; 16 | flex-direction: row; 17 | justify-content: space-between; 18 | align-items: center; 19 | border-radius: 5px; 20 | margin: 5px; 21 | padding: 5px; 22 | font-family: monospace; 23 | `; 24 | 25 | export const StyledCodeContentDots = styled.div` 26 | height: 100; 27 | cursor: pointer; 28 | `; 29 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/codeblock/codecontentblock-wrapper.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react'; 3 | import { StyledCodeContent, StyledCodeContentDots, StyledProps } from './codecontentblock-Styled'; 4 | import { ReactComponent as Dots } from '../../assets/moveable-content-dots.svg'; 5 | import { Box } from "@mui/material"; 6 | 7 | type Props = { 8 | children?: React.ReactElement | any; 9 | onClick?: (...args: any) => void; 10 | } & StyledProps; 11 | 12 | export default function MoveableContentBlock({ children, onClick, ...styleProps }: Props) { 13 | const { focused } = styleProps; 14 | return ( 15 | 20 |
26 | {children} 27 |
28 | 29 | { (focused == true) && 30 | 34 | } 35 | 36 |
37 | ); 38 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/codeblock/codecontentblock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import MoveableCodeContentContentBlock from './codecontentblock-wrapper'; 5 | 6 | export default { 7 | title: 'CSE-UIKIT/CodeContentContentBlock', 8 | component: MoveableCodeContentContentBlock, 9 | } as ComponentMeta; 10 | 11 | const Template: ComponentStory = (args) => 12 | ( 13 |
18 | Moveable Content Block 19 | 20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 21 | 22 |
23 | ) 24 | 25 | export const Primary = Template.bind({}); -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/contentBlockPopup/contentBlockPopup-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const StyledContainer = styled.div` 4 | width: max-content; 5 | height: max-content; 6 | background: #ffffff; 7 | color: #000000; 8 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.25); 9 | border-radius: 5px; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | `; 14 | 15 | export const StyledDottedContainer = styled.div` 16 | margin: 3vw 5vw; 17 | background: #ffffff; 18 | color: #a1a1a1; 19 | margin: 3vw 4vw; 20 | border-radius: 5px; 21 | border-width: 1vw; 22 | outline-style: dashed; 23 | `; 24 | 25 | export const StyledContent = styled.div` 26 | margin: 3vw 7vw; 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: space-around; 30 | align-items: center; 31 | `; 32 | 33 | export const MainText = styled.div` 34 | color: #5F5F5F; 35 | font-family: 'Arial'; 36 | font-weight: 1vw; 37 | font-size: 2vw; 38 | margin: 2vw 0 0; 39 | text-align: center; 40 | `; 41 | 42 | export const BoldText = styled.span` 43 | font-weight: bold; 44 | ` -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/contentBlockPopup/contentBlockPopup.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react'; 3 | 4 | import { 5 | StyledContainer, 6 | StyledDottedContainer, 7 | StyledContent, 8 | MainText, 9 | BoldText 10 | } from './contentBlockPopup-Styled'; 11 | import { ReactComponent as ContentUpload } from 'src/cse-ui-kit/assets/upload-content.svg'; 12 | 13 | type Props = { 14 | children?: React.ReactElement | any; 15 | }; 16 | 17 | export default function ContentBlockPopup({ children }: Props) { 18 | return ( 19 | 20 | 21 | 22 | 27 | 28 | 29 | Drag and Drop 30 | or 31 | click here 32 | 33 | 34 | 35 | to upload your image 36 | 37 | {children} 38 | 39 | 40 | 41 | ) 42 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/contentblock/contentblock-Styled.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */ 2 | import styled from "styled-components"; 3 | 4 | export type StyledProps = { 5 | focused?: boolean; 6 | } 7 | 8 | export const StyledContent = styled.div` 9 | width: 100%; 10 | max-width: 600px; 11 | background: #ffffff; 12 | color: #000000; 13 | box-shadow: ${(props) => props.focused && '0px 2px 3px rgba(0, 0, 0, 0.25);'} 14 | padding: 30px 20px; 15 | display: flex; 16 | flex-direction: row; 17 | justify-content: space-between; 18 | align-items: center; 19 | border-radius: 5px; 20 | margin: 5px; 21 | `; 22 | 23 | export const StyledContentDots = styled.div` 24 | height: 100; 25 | cursor: pointer; 26 | `; 27 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/contentblock/contentblock-wrapper.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react'; 3 | import { StyledContent, StyledContentDots, StyledProps } from './contentblock-Styled'; 4 | import { ReactComponent as Dots } from '../../assets/moveable-content-dots.svg'; 5 | import { Box } from "@mui/material"; 6 | 7 | type Props = { 8 | children?: React.ReactElement | any; 9 | onClick?: (...args: any) => void; 10 | } & StyledProps; 11 | 12 | export default function MoveableContentBlock({ children, onClick, ...styleProps }: Props) { 13 | const { focused } = styleProps; 14 | return ( 15 | 20 |
26 | {children} 27 |
28 | 29 | { (focused == true) && 30 | 34 | } 35 | 36 |
37 | ); 38 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/contentblock/contentblock.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import MoveableContentBlock from './contentblock-wrapper'; 5 | 6 | export default { 7 | title: 'CSE-UIKIT/ContentBlock', 8 | component: MoveableContentBlock, 9 | } as ComponentMeta; 10 | 11 | const Template: ComponentStory = (args) => 12 | ( 13 |
18 | Moveable Content Block 19 | 20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 21 | 22 |
23 | ) 24 | 25 | export const Primary = Template.bind({}); -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/dottted_container/dotted_container-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type containerProps = { 4 | background?: string; 5 | } 6 | export const StyledContainer = styled.div` 7 | background-color: white; 8 | width: 300px; 9 | min-height: 200px; 10 | margin: 30px auto; 11 | box-sizing: border-box; 12 | padding: 30px; 13 | border: 35px solid #F0F0F0; 14 | outline-style:dashed; 15 | outline-color:#909090; 16 | outline-offset: -35px; 17 | `; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/dottted_container/dotted_container.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import DottedContainer from './dotted_container'; 5 | 6 | export default { 7 | title: 'CSE-UIKIT/DottedContainer', 8 | component: DottedContainer, 9 | 10 | } as ComponentMeta; 11 | 12 | const Template: ComponentStory = (args) => 13 | ( 14 |
19 | 20 |
21 | ) 22 | export const Primary = Template.bind({}); -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/dottted_container/dotted_container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledContainer, containerProps } from './dotted_container-Styled'; 3 | 4 | 5 | type Props = { 6 | children?: React.ReactElement | any; 7 | className?: string; 8 | } & containerProps; 9 | 10 | export default function DottedContainer({ children, className}: Props) { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/dottted_container/index.ts: -------------------------------------------------------------------------------- 1 | import DottedContainer from './dotted_container'; 2 | 3 | export default DottedContainer; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/helpers/Storybook.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/react"; 2 | 3 | const STORYBOOK_TITLE = 'cse-ui-kit' 4 | export const generateStories = (name: string) => 5 | storiesOf(`${STORYBOOK_TITLE} / ${name}`, module); -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/BoldButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as Bold } from 'src/cse-ui-kit/assets/bold-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function BoldButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/CenterAlignButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as CenterAlign } from 'src/cse-ui-kit/assets/centeralign-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function CenterAlignButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/CodeButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as Code } from 'src/cse-ui-kit/assets/code-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function CodeButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/ItalicButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as Italic } from 'src/cse-ui-kit/assets/italics-button.svg'; 4 | 5 | 6 | type Props = { 7 | onClick?: MouseEventHandler; 8 | onMouseDown?: MouseEventHandler; 9 | } & buttonProps; 10 | 11 | export default function ItalicButton({ 12 | onClick, 13 | onMouseDown, 14 | ...styleProps 15 | }: Props) { 16 | return ( 17 | 18 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/LeftAlignButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as LeftAlign } from 'src/cse-ui-kit/assets/leftrightalign-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function LeftAlignButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/QuoteButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as Quote } from 'src/cse-ui-kit/assets/quote-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function QuoteButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/RightAlignButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as RightAlign } from 'src/cse-ui-kit/assets/leftrightalign-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function RightAlignButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/UnderlineButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './small_buttons-Styled'; 3 | import { ReactComponent as Underline } from 'src/cse-ui-kit/assets/underline-button.svg'; 4 | 5 | type Props = { 6 | onClick?: MouseEventHandler; 7 | onMouseDown?: MouseEventHandler; 8 | } & buttonProps; 9 | 10 | export default function UnderlineButton({ 11 | onClick, 12 | onMouseDown, 13 | ...styleProps 14 | }: Props) { 15 | return ( 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/small_buttons-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const scaleRate = 0.5; 4 | 5 | export type buttonProps = { 6 | background?: string; 7 | size: number; 8 | }; 9 | 10 | export const StyledButton = styled.div` 11 | height: ${(props) => props.size}px; 12 | width: ${(props) => props.size}px; 13 | background: ${(props) => props.background}; 14 | color: #000000; 15 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | margin: 5px; 20 | border-radius: ${(props) => props.size / 10}px; 21 | &:hover { 22 | background: #efeef3; 23 | transform: scale(1.04); 24 | } 25 | &:active { 26 | background: #c8d1fa; 27 | transform: scale(0.96); 28 | } 29 | cursor: pointer; 30 | `; 31 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/small_buttons/small_buttons.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | 4 | import BoldButton from './BoldButton'; 5 | import ItalicButton from './ItalicButton'; 6 | import UnderlineButton from './UnderlineButton'; 7 | import { Box } from "@mui/material"; 8 | 9 | // More on flexDirection type casting: https://stackoverflow.com/questions/62432985/typescript-saying-a-string-is-invalid-even-though-its-in-the-union 10 | const BoxContainerStyle = { 11 | display: "flex", 12 | flexDirection: "column" as const, 13 | alignItems: "center" 14 | } 15 | 16 | export default { 17 | title: 'CSE-UIKIT/SmallButtons', 18 | component: BoldButton, 19 | } as ComponentMeta; 20 | 21 | const Template: ComponentStory = (args) => 22 | ( 23 | 31 | 32 | Bold Button 33 | 34 | 35 | 36 | 37 | Italic Button 38 | 39 | 40 | 41 | 42 | Underline Button 43 | 44 | 45 | 46 | ) 47 | 48 | export const Primary = Template.bind({}); 49 | Primary.args = { 50 | background: "#E2E1E7", 51 | size: 45 52 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/spheres/Sphere-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export type sphereProps = { 4 | size?: number; 5 | colourMain?: string; 6 | colourSecondary?: string; 7 | startMainPoint?: number; 8 | startSecondaryPoint?: number; 9 | angle?: number; 10 | blur?: number; 11 | rotation?: number; 12 | } 13 | 14 | export const StyledSphere = styled.div` 15 | width: ${props => props.size}px; 16 | height: ${props => props.size}px; 17 | background: linear-gradient( 18 | ${props => props.angle}deg, 19 | ${props => props.colourMain} ${props => props.startMainPoint}%, 20 | ${props => props.colourSecondary} ${props => props.startSecondaryPoint}% 21 | ); 22 | filter: blur(${props => props.blur}px); 23 | transform: rotate(${props => props.rotation}deg); 24 | mix-blend-mode: normal; 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | border-radius: 50%; 29 | `; 30 | 31 | // The default main colour (#969DC7) represents violet whilst the default 32 | // secondary colour (#E8CAFF) represents pink 33 | StyledSphere.defaultProps = { 34 | size: 100, 35 | colourMain: "#9B9BE1", 36 | colourSecondary: "#E8CAFF", 37 | startMainPoint: 0, 38 | startSecondaryPoint: 100, 39 | angle: 0, 40 | blur: 0, 41 | rotation: 0, 42 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/spheres/Sphere.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledSphere, sphereProps } from './Sphere-Styled'; 3 | 4 | type Props = sphereProps; 5 | 6 | export default function Sphere({ ...styleProps }: Props) { 7 | return ( 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/styles/GlobalStyles.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | 3 | export default createGlobalStyle` 4 | :root { 5 | /*global variables*/ 6 | } 7 | 8 | body { 9 | margin: 0; 10 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 11 | } 12 | `; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/text_alignment_buttons/LeftAlign.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './text-alignment-Styled'; 3 | import { ReactComponent as LeftAlign } from 'src/cse-ui-kit/assets/leftalign-button.svg'; 4 | 5 | type Props = { 6 | onClick?: (...args: any) => void; 7 | } & buttonProps; 8 | 9 | export default function LeftAlignButton({ onClick, ...styleProps }: Props) { 10 | return ( 11 | 15 | 19 | 20 | ); 21 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/text_alignment_buttons/MiddleAlign.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './text-alignment-Styled'; 3 | import { ReactComponent as MiddleAlign } from 4 | 'src/cse-ui-kit/assets/middlealign-button.svg'; 5 | 6 | type Props = { 7 | onClick?: (...args: any) => void; 8 | } & buttonProps; 9 | 10 | export default function MiddleAlignButton({ onClick, ...styleProps }: Props) { 11 | return ( 12 | 16 | 20 | 21 | ); 22 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/text_alignment_buttons/RightAlign.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledButton, buttonProps, scaleRate } from './text-alignment-Styled'; 3 | import { ReactComponent as RightAlign } from 4 | 'src/cse-ui-kit/assets/rightalign-button.svg'; 5 | 6 | type Props = { 7 | onClick?: (...args: any) => void; 8 | } & buttonProps; 9 | 10 | export default function RightAlignButton({ onClick, ...styleProps }: Props) { 11 | return ( 12 | 16 | 20 | 21 | ); 22 | } -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/text_alignment_buttons/text-alignment-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const scaleRate = 0.65; 4 | 5 | export type buttonProps = { 6 | variant?: string; 7 | size: number; 8 | } 9 | export const StyledButton = styled.div` 10 | height: ${props => props.size}px; 11 | width: ${props => props.size}px; 12 | background: ${props => 13 | props.variant == "middle" ? "#2B3648" : "#FFFFFF"}; 14 | color: ${props => props.variant == "middle" ? "#FFFFFF" : "#2B3648"}; 15 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | border-radius: ${props => props.size / 10}px; 20 | 21 | &:hover { /*if middle: else left/right variant */ 22 | background: ${props => 23 | props.variant == "middle" ? "#5B687D" : "#EFEEF3"}; 24 | color: ${props => props.variant == "middle" ? "#FFFFFF" : "#2B3648"}; 25 | transform: scale(1.04); 26 | } 27 | &:active { 28 | background: #C8D1FA; 29 | color: #7482CB; 30 | transform: scale(0.96); 31 | } 32 | 33 | cursor: pointer; 34 | `; -------------------------------------------------------------------------------- /frontend/src/cse-ui-kit/text_alignment_buttons/text_alignment_buttons.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box } from "@mui/material"; 3 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 4 | 5 | import LeftAlignButton from './LeftAlign'; 6 | import MiddleAlignButton from './MiddleAlign'; 7 | import RightAlignButton from './RightAlign'; 8 | 9 | const BoxContainerStyle = { 10 | display: "flex", 11 | flexDirection: "column" as const, 12 | alignItems: "center" 13 | } 14 | 15 | export default { 16 | title: 'CSE-UIKIT/TextAlignButtons', 17 | component: LeftAlignButton, 18 | } as ComponentMeta; 19 | 20 | const Template: ComponentStory = (args) => 21 | ( 22 | 30 | 31 | Left Alignment Button 32 | 33 | 34 | 35 | 36 | Middle Alignment Button 37 | 38 | 39 | 40 | 41 | Right Alignment Button 42 | 43 | 44 | 45 | 46 | ) 47 | 48 | export const Primary = Template.bind({}); 49 | Primary.args = { 50 | size: 45 51 | } -------------------------------------------------------------------------------- /frontend/src/deprecated/components/Editor/EditorSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Button } from "@mui/material"; 4 | import { EditorState, RichUtils } from "draft-js"; 5 | 6 | const Container = styled.div` 7 | background: #eeeeee; 8 | width: 15vw; 9 | overflow-y: scroll; 10 | ` 11 | 12 | interface EditorProps { 13 | editorState: EditorState, 14 | setEditorState: React.Dispatch> 15 | } 16 | 17 | // Placeholder 18 | const EditorSidebar: React.FC = ({ editorState, setEditorState }) => { 19 | const toggle = (style: string) => { 20 | setEditorState(RichUtils.toggleInlineStyle(editorState, style)); 21 | } 22 | 23 | const hasStyle = (style: string) => { 24 | return editorState.getCurrentInlineStyle().includes(style); 25 | } 26 | 27 | return ( 28 | 29 | This is the sidebar 30 | 36 | 37 | ); 38 | }; 39 | 40 | export default EditorSidebar; 41 | -------------------------------------------------------------------------------- /frontend/src/deprecated/components/FileRenderer_OLD/Renamable.tsx: -------------------------------------------------------------------------------- 1 | // Renamable text field 2 | // Created by Hanyuan Li, @hanyuone (09/2021) 3 | // # # # 4 | // A component that can be double-clicked and edited 5 | 6 | import React, { useState } from "react"; 7 | 8 | interface RenamableProps { 9 | name: string, 10 | onRename: (newName: string) => void 11 | } 12 | 13 | const Renamable: React.FC = ({ name, onRename }) => { 14 | const [toggle, setToggle] = useState(true); 15 | const [inputName, setInputName] = useState(name); 16 | 17 | return ( 18 | <> 19 | {toggle ? ( 20 |

setToggle(false)}> 22 | {name} 23 |

24 | ) : ( 25 | setInputName(event.target.value)} 29 | onKeyDown={event => { 30 | if (event.key === "Enter" || event.key === "Escape") { 31 | if (event.key === "Enter") { 32 | onRename(inputName); 33 | } 34 | 35 | setToggle(true); 36 | event.preventDefault(); 37 | event.stopPropagation(); 38 | } 39 | }} /> 40 | )} 41 | 42 | ); 43 | }; 44 | 45 | export default Renamable; 46 | -------------------------------------------------------------------------------- /frontend/src/deprecated/components/NewDialogue/TemplateChip.tsx: -------------------------------------------------------------------------------- 1 | // Individul chip representing a single template in the Template Selector 2 | // Matthew Rossouw, @omeh-a (09/2021) 3 | // # # # 4 | // Renders a MaterialUI chip for a template. A material UI tooltip contains 5 | // the description for the template. 6 | 7 | 8 | import { Avatar, Chip, Tooltip } from '@mui/material'; 9 | import React from 'react'; 10 | 11 | 12 | interface TemplateProps { 13 | name : string, 14 | description : string, 15 | img: string, // todo - find some way to link this image to a thumbnail contained by backend 16 | isSelected: boolean, 17 | click: (name : string) => void 18 | } 19 | 20 | /** 21 | * Representation of a single template. Returns its name to the Selector 22 | * upon click to let it know which one is selected. 23 | */ 24 | const TemplateChip : React.FC = ({name, description, img, click, isSelected}) => { 25 | return ( 26 | 27 | {name.substring(0,1)}} 31 | onClick = {() => {click(name)}} 32 | color = {isSelected ? "primary" : "default"} 33 | style = {{margin: "3px"}} 34 | /> 35 | 36 | 37 | ) 38 | } 39 | 40 | 41 | export default TemplateChip -------------------------------------------------------------------------------- /frontend/src/deprecated/data/templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates" : [ 3 | { 4 | "name" : "Simple blog", 5 | "description" : "A simple blog templates with a header, background image and body.", 6 | "img" : "./blog.jpg" 7 | }, 8 | { 9 | "name" : "Blank page", 10 | "description" : "A full-page content box", 11 | "img" : "./blog.jpg" 12 | }, 13 | { 14 | "name" : "Fullscreen image", 15 | "description" : "A full page with only an image container for posting poggers memes.", 16 | "img" : "./blog.jpg" 17 | }, 18 | { 19 | "name" : "Simple blog 2", 20 | "description" : "Here to demo scrollbox", 21 | "img" : "./blog.jpg" 22 | }, 23 | { 24 | "name" : "Blank page 2", 25 | "description" : "Here to demo scrollbox", 26 | "img" : "./blog.jpg" 27 | }, 28 | { 29 | "name" : "Fullscreen image 2", 30 | "description" : "Here to demo scrollbox", 31 | "img" : "./blog.jpg" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /frontend/src/deprecated/types/FileFormat.tsx: -------------------------------------------------------------------------------- 1 | // deprecated 2 | interface FileFormat { 3 | id: number, 4 | filename: string, 5 | isDocument: boolean 6 | } 7 | 8 | export type { FileFormat }; 9 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | import { BrowserRouter } from 'react-router-dom'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | 14 | document.getElementById('root') 15 | ); 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals(); 21 | -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/api/helpers.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { RootState } from "../../../redux-state/reducers"; 3 | 4 | import { JSONFileFormat } from "./types"; 5 | import { folderSelectors } from "../state/folders"; 6 | import { FileEntity, sliceState } from "../state/folders/types"; 7 | 8 | 9 | // Converts a backend response to the File or Folder type 10 | export function toFileOrFolder(json: JSONFileFormat): FileEntity { 11 | const {EntityID, EntityName, IsDocument, Parent} = json; 12 | 13 | return { 14 | id: EntityID, 15 | name: EntityName, 16 | parentId: Parent, 17 | type: IsDocument ? "File" : "Folder", 18 | } as FileEntity 19 | } 20 | 21 | export function getFolderState(): sliceState { 22 | return useSelector((state: RootState) => ( 23 | folderSelectors.getFolderState(state) 24 | )); 25 | } -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/api/types.ts: -------------------------------------------------------------------------------- 1 | export type JSONFileFormat = { 2 | EntityID: string; 3 | EntityName: string; 4 | Parent: string; 5 | IsDocument: boolean; 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/components/ConfirmationModal/ConfirmationWindow.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'src/cse-testing-lib'; 2 | import ConfirmationWindow from './ConfirmationWindow'; 3 | 4 | describe("Confirmation window tests", () => { 5 | it("Confirmation window rendered when New Page button is clicked", () => { 6 | // TODO 7 | }); 8 | }) 9 | -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/components/SideBar/SideBar.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "src/cse-testing-lib"; 2 | import { queryByDataAnchor } from "src/cse-testing-lib/custom-queries"; 3 | import SideBar from "./SideBar"; 4 | import React from "react"; 5 | 6 | describe("Side bar tests", () => { 7 | it("Side bar is rendered with proper buttons", () => { 8 | const mockSetOpen = jest.fn(); 9 | 10 | const mockSetModalState = jest.fn(); 11 | const mockSelectedFileID = "5"; 12 | const { queryByDataAnchor } = render( 13 | 19 | ); 20 | 21 | expect(queryByDataAnchor("NewPageButton")).toBeTruthy(); 22 | expect(queryByDataAnchor("NewFolderButton")).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/state/folders/index.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit'; 2 | import { initialState } from './initial-state'; 3 | import * as reducerFns from './reducers'; 4 | import * as folderSelectors from './selectors'; 5 | import * as foldersActions from './actions'; 6 | import * as foldersSagas from './sagas'; 7 | 8 | const reducer = createReducer(initialState, (builder) => { 9 | // addCase(action, reducer); 10 | builder.addCase( 11 | foldersActions.addFolderItemAction, 12 | reducerFns.addFolderItems 13 | ); 14 | builder.addCase(foldersActions.addFileItemAction, reducerFns.addFileItems); 15 | builder.addCase( 16 | foldersActions.deleteFileEntityAction, 17 | reducerFns.deleteFileEntityItems 18 | ); 19 | builder.addCase( 20 | foldersActions.renameFileEntityAction, 21 | reducerFns.renameFileEntity 22 | ); 23 | // init reducers 24 | builder.addCase(foldersActions.initItemsAction, reducerFns.setItems); 25 | builder.addCase(foldersActions.setDirectory, reducerFns.setDirectory); 26 | }); 27 | 28 | export { reducer, foldersActions, folderSelectors, foldersSagas }; 29 | -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/state/folders/initial-state.ts: -------------------------------------------------------------------------------- 1 | import { sliceState } from "./types"; 2 | 3 | const ROOT_UUID = "00000000-0000-0000-0000-000000000000"; 4 | 5 | export const initialState: sliceState = { 6 | parentFolder: ROOT_UUID, 7 | path: [{ folderName: "root", folderId: ROOT_UUID }], 8 | items: [], 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/state/folders/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from 'src/redux-state/reducers'; 2 | 3 | export const getFolderState = (state: RootState) => state.folders; -------------------------------------------------------------------------------- /frontend/src/packages/dashboard/state/folders/types.ts: -------------------------------------------------------------------------------- 1 | export type baseEntity = { 2 | id: string; 3 | name: string; 4 | parentId: string; 5 | type: string; 6 | }; 7 | 8 | export type Folder = baseEntity; 9 | export type File = { 10 | image?: string; 11 | } & baseEntity; 12 | 13 | // FileEntity is the type which contains both 14 | // folders and files 15 | export type FileEntity = Folder | File; 16 | 17 | // PathObject is the type which specifies the name AND id of the 18 | // folder we are currently in 19 | export type PathObject = { 20 | folderName: string; 21 | folderId: string; 22 | }; 23 | 24 | export type sliceState = { 25 | parentFolder: string; 26 | path: PathObject[]; 27 | items: FileEntity[]; 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/api/OTClient/README.md: -------------------------------------------------------------------------------- 1 | # CMS OT Client 2 | 3 | TODO: there really needs to be a better way of doing this, currently this code is copied directly from the backend folder. In the future there should maybe be some shared folder between frontend and backend JUST for stuff like API clients? -------------------------------------------------------------------------------- /frontend/src/packages/editor/api/OTClient/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Bind operation in functional programming 3 | * 4 | * @param f - the function to apply on the value 5 | * @param y - the value 6 | * @returns the return value of the function with y passed in if y is not undefined else undefined 7 | */ 8 | export const bind = (f: (x: T) => V, y: T | undefined): V | undefined => 9 | y !== undefined ? f(y) : undefined; 10 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/api/cmsFS/volumes.ts: -------------------------------------------------------------------------------- 1 | // TODO: remove this and replace with API client once thats complete, see: 2 | // https://github.com/csesoc/website/pull/238 3 | export const publishDocument = (documentId: string) => { 4 | fetch("/api/filesystem/publish-document", { 5 | method: "POST", 6 | headers: { 7 | "Content-Type": "application/x-www-form-urlencoded", 8 | }, 9 | body: new URLSearchParams({ 10 | DocumentID: `${documentId}`, 11 | }), 12 | }); 13 | } -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorBoldButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import BoldButton from "src/cse-ui-kit/small_buttons/BoldButton"; 4 | import { toggleMark } from "./buttonHelpers"; 5 | 6 | const EditorBoldButton: FC = () => { 7 | const editor = useSlate(); 8 | return ( 9 | { 12 | event.preventDefault(); 13 | toggleMark(editor, "bold"); 14 | }} 15 | /> 16 | ); 17 | }; 18 | 19 | export default EditorBoldButton; 20 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorCenterAlignButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import { toggleMark } from "./buttonHelpers"; 4 | import { Editor } from "slate"; 5 | import CenterAlignButton from "src/cse-ui-kit/small_buttons/CenterAlignButton"; 6 | 7 | const EditorCenterAlignButton: FC = () => { 8 | const editor = useSlate(); 9 | return ( 10 | { 13 | event.preventDefault(); 14 | Editor.addMark(editor, "align", "center") 15 | }} 16 | /> 17 | ); 18 | }; 19 | 20 | export default EditorCenterAlignButton; 21 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorCodeButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import CodeButton from "src/cse-ui-kit/small_buttons/CodeButton"; 4 | import { toggleMark } from "./buttonHelpers"; 5 | 6 | const EditorCodeButton: FC = () => { 7 | const editor = useSlate(); 8 | return ( 9 | { 12 | event.preventDefault(); 13 | toggleMark(editor, "code"); 14 | }} 15 | /> 16 | ); 17 | }; 18 | 19 | export default EditorCodeButton; 20 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorItalicButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import { toggleMark } from "./buttonHelpers"; 4 | import ItalicButton from "src/cse-ui-kit/small_buttons/ItalicButton"; 5 | 6 | const EditorItalicButton: FC = () => { 7 | const editor = useSlate(); 8 | return ( 9 | { 12 | event.preventDefault(); 13 | toggleMark(editor, "italic"); 14 | }} 15 | /> 16 | ); 17 | }; 18 | 19 | export default EditorItalicButton; 20 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorLeftAlignButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import { toggleMark } from "./buttonHelpers"; 4 | import { Editor } from "slate"; 5 | import LeftAlignButton from "src/cse-ui-kit/small_buttons/LeftAlignButton"; 6 | 7 | const EditorLeftAlignButton: FC = () => { 8 | const editor = useSlate(); 9 | return ( 10 | { 13 | event.preventDefault(); 14 | Editor.addMark(editor, "align", "left") 15 | }} 16 | /> 17 | ); 18 | }; 19 | 20 | export default EditorLeftAlignButton; 21 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorQuoteButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useSlate } from 'slate-react'; 3 | import QuoteButton from 'src/cse-ui-kit/small_buttons/QuoteButton'; 4 | import { toggleMark } from './buttonHelpers'; 5 | 6 | const EditorQuoteButton: FC = () => { 7 | const editor = useSlate(); 8 | return ( 9 | { 12 | event.preventDefault(); 13 | toggleMark(editor, "quote"); 14 | }} 15 | /> 16 | ); 17 | }; 18 | 19 | export default EditorQuoteButton; 20 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorRightAlignButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import { toggleMark } from "./buttonHelpers"; 4 | import { Editor } from "slate"; 5 | import RightAlignButton from "src/cse-ui-kit/small_buttons/RightAlignButton"; 6 | 7 | const EditorRightAlignButton: FC = () => { 8 | const editor = useSlate(); 9 | return ( 10 | { 13 | event.preventDefault(); 14 | Editor.addMark(editor, "align", "right") 15 | }} 16 | /> 17 | ); 18 | }; 19 | 20 | export default EditorRightAlignButton; 21 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorSelectFont.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSlate } from "slate-react"; 3 | import { Editor } from "slate"; 4 | 5 | const selectFont = () => { 6 | const editor = useSlate(); 7 | 8 | return ( 9 | 23 | ); 24 | }; 25 | 26 | export default selectFont; 27 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/components/buttons/EditorUnderlineButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { useSlate } from "slate-react"; 3 | import UnderlineButton from "src/cse-ui-kit/small_buttons/UnderlineButton"; 4 | import { toggleMark } from "./buttonHelpers"; 5 | 6 | const EditorUnderlineButton: FC = () => { 7 | const editor = useSlate(); 8 | return ( 9 | { 12 | event.preventDefault(); 13 | toggleMark(editor, "underline"); 14 | }} 15 | /> 16 | ); 17 | }; 18 | 19 | export default EditorUnderlineButton; 20 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/operationManager.tsx: -------------------------------------------------------------------------------- 1 | // operationManager is a centralized location for dealing with captured operations from anywhere within the editor 2 | // these operations are shoved along and propagated to the server :) 3 | 4 | import { BaseOperation } from "slate"; 5 | import { CMSOperation } from "./api/OTClient/operation"; 6 | import { BlockData } from "./types"; 7 | 8 | export class OperationManager { 9 | public pushToServer = (operation: CMSOperation) => { 10 | // todo: reformat the operation to follow the structure of a CMS operation 11 | // drawing upon the editor content as a relative data source 12 | 13 | // todo: remove console.logs after completion 14 | // console.log("operation: ", operation); 15 | } 16 | } 17 | 18 | export const slateToCmsOperation = (editorContent: BlockData, operation: BaseOperation[]) : CMSOperation => { 19 | // TODO: remove console.logs after full completion :D 20 | console.log("content: ", editorContent); 21 | console.log("operation: ", operation); 22 | 23 | // TODO: implement me :D 24 | return { 25 | Path: [1, 2, 3], 26 | OperationType: "insert", 27 | IsNoOp: false, 28 | Operation: { 29 | $type: "noop", 30 | noop: {} 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from "@reduxjs/toolkit"; 2 | import { BlockInfo } from "./types"; 3 | 4 | /** 5 | * Content actions 6 | */ 7 | export const addContentBlock = createAction("editor/createContentBlock"); 8 | export const updateContent = createAction("editor/updateContent"); 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/helpers.ts: -------------------------------------------------------------------------------- 1 | import { getContents } from "./selectors"; 2 | import { Descendant } from "slate"; 3 | 4 | export const defaultContent: Descendant[] = [ 5 | { 6 | type: "paragraph", 7 | children: [{ text: "" }], 8 | }, 9 | ]; 10 | 11 | export const headingContent: Descendant[] = [ 12 | { 13 | type: "heading", 14 | children: [{ text: "" }], 15 | }, 16 | ]; 17 | 18 | export const getBlockContent = (id: number) => { 19 | const contents = getContents(); 20 | 21 | contents.map((content) => { 22 | if (content.id == id) { 23 | return content.data 24 | } 25 | }) 26 | return defaultContent 27 | }; -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/index.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from "@reduxjs/toolkit"; 2 | import { initialState } from './initial-state'; 3 | import * as reducerFns from './reducers'; 4 | import * as editorActions from './actions'; 5 | 6 | const reducer = createReducer(initialState, (builder) => { 7 | builder.addCase(editorActions.addContentBlock, reducerFns.addContentBlock); 8 | builder.addCase(editorActions.updateContent, reducerFns.updateContent); 9 | }) 10 | 11 | export { 12 | reducer 13 | }; -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/initial-state.ts: -------------------------------------------------------------------------------- 1 | import { editorState } from "./types"; 2 | 3 | export const initialState: editorState = { 4 | contents: [] 5 | } -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/reducers.ts: -------------------------------------------------------------------------------- 1 | import { PayloadAction } from "@reduxjs/toolkit"; 2 | import { editorState, BlockInfo } from "./types"; 3 | 4 | 5 | export function addContentBlock(state: editorState, action: PayloadAction): editorState { 6 | return { 7 | contents: [ 8 | ...state.contents, 9 | action.payload, 10 | ] 11 | } 12 | } 13 | 14 | export function updateContent(state: editorState, action: PayloadAction): editorState { 15 | const { id, data } = action.payload; 16 | return { 17 | contents: state.contents.map((block) => { 18 | if (block.id == id) { 19 | return ({ 20 | ...block, 21 | data: data, 22 | }) 23 | } 24 | return block; 25 | }) 26 | } 27 | } -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/selectors.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { RootState } from "../../../redux-state/reducers"; 3 | import { BlockInfo } from "./types"; 4 | 5 | export function getContents(): BlockInfo[] { 6 | return useSelector((state: RootState) => state.editor.contents) 7 | } -------------------------------------------------------------------------------- /frontend/src/packages/editor/state/types.ts: -------------------------------------------------------------------------------- 1 | import { Descendant } from "slate"; 2 | 3 | export type BlockInfo = { 4 | id: number 5 | data: Descendant[] 6 | } 7 | 8 | export type editorState = { 9 | contents: (BlockInfo)[] 10 | } -------------------------------------------------------------------------------- /frontend/src/packages/editor/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactEditor } from "slate-react"; 2 | import { BaseEditor, BaseOperation, Range, Descendant, BaseRange } from "slate"; 3 | 4 | export type BlockData = Descendant[]; 5 | 6 | export type OpPropagator = (id: number, update: BlockData, operation: BaseOperation[]) => void; 7 | export type UpdateCallback = (id: number, update: BlockData) => void; 8 | 9 | export type CustomText = { 10 | textSize?: number; 11 | text: string; 12 | bold?: boolean; 13 | italic?: boolean; 14 | underline?: boolean; 15 | quote?: boolean; 16 | type?: string; 17 | align?: string; 18 | code?: string; 19 | }; 20 | 21 | export type CustomElement = { 22 | type: "paragraph" | "heading" | "code"; 23 | language?: string, 24 | children: Descendant[] 25 | }; 26 | 27 | export interface CMSBlockProps { 28 | update: OpPropagator; 29 | initialValue: BlockData; 30 | id: number; 31 | showToolBar: boolean; 32 | language?: string; 33 | onEditorClick: () => void; 34 | } 35 | 36 | export type CustomEditor = BaseEditor & 37 | ReactEditor & { 38 | nodeToDecorations?: Map 39 | } 40 | 41 | declare module "slate" { 42 | interface CustomTypes { 43 | Editor: CustomEditor; 44 | Element: CustomElement; 45 | Text: CustomText; 46 | Range: BaseRange & { 47 | [key: string] : unknown 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/redux-state/docs.md: -------------------------------------------------------------------------------- 1 | ## Top level global redux store 2 | 3 | Reduces the state slices into 1 global redux storage. 4 | 5 | ### How do we use redux here at CSE Websites? 6 | 7 | Our Redux is primarily built using a package called `redux-toolkit` and utilises its capabilities, namely these four main functions 8 | - CombineReducers 9 | - ConfigureStore 10 | - CreateReducers 11 | - createAction 12 | 13 | Furthermore, we utilise a middleware called `redux-sagas` to handle asynchronous side effects such as API calls, which result in a redux action which has been triggered, by user interaction 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/redux-state/index.ts: -------------------------------------------------------------------------------- 1 | import { GlobalStore, persistor } from './configure-store'; 2 | import { RootState, rootReducer } from './reducers'; 3 | 4 | export { GlobalStore, persistor, rootReducer }; 5 | -------------------------------------------------------------------------------- /frontend/src/redux-state/reducers.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "@reduxjs/toolkit"; 2 | import { reducer as foldersReducer} from 'src/packages/dashboard/state/folders/index'; 3 | import { reducer as editorReducer } from 'src/packages/editor/state/index'; 4 | 5 | export const rootReducer = combineReducers({ 6 | folders: foldersReducer, 7 | editor: editorReducer 8 | }); 9 | 10 | export type RootState = ReturnType -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "baseUrl": ".", 23 | "paths": { 24 | "test-utils": ["src/cse-testing/lib/test-utils"] 25 | } 26 | }, 27 | "include": [ 28 | "src", 29 | "src/**/*.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /next/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | Dockerfile.development -------------------------------------------------------------------------------- /next/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /next/Dockerfile: -------------------------------------------------------------------------------- 1 | # Install dependencies only when needed 2 | FROM node:20.2.0-alpine AS deps 3 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 4 | RUN apk add --no-cache libc6-compat 5 | WORKDIR /app 6 | COPY package.json package-lock.json ./ 7 | RUN npm ci 8 | 9 | # Rebuild the source code only when needed 10 | FROM node:20.2.0-alpine AS builder 11 | WORKDIR /app 12 | COPY --from=deps /app/node_modules ./node_modules 13 | COPY . . 14 | 15 | RUN npm run build 16 | 17 | # Production image, copy all the files and run next 18 | FROM node:20.2.0-alpine AS runner 19 | WORKDIR /app 20 | 21 | ENV NODE_ENV production 22 | 23 | RUN addgroup --system --gid 1001 nodejs 24 | RUN adduser --system --uid 1001 nextjs 25 | 26 | # You only need to copy next.config.js if you are NOT using the default configuration 27 | COPY --from=builder /app/next.config.js ./ 28 | COPY --from=builder /app/public ./public 29 | COPY --from=builder /app/package.json ./package.json 30 | 31 | # Automatically leverage output traces to reduce image size 32 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 33 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 34 | 35 | USER nextjs 36 | 37 | EXPOSE 3000 38 | 39 | ENV PORT 3000 40 | 41 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /next/Dockerfile.development: -------------------------------------------------------------------------------- 1 | # pulling official base image 2 | FROM node:20.2.0-alpine 3 | 4 | # Setting working directory 5 | WORKDIR /next 6 | 7 | # exposing ports 8 | EXPOSE 3001 9 | 10 | # npm start 11 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /next/env.d.ts: -------------------------------------------------------------------------------- 1 | namespace NodeJS { 2 | interface ProcessEnv { 3 | NEXT_PUBLIC_BACKEND_URI: string; 4 | BACKEND_URI: string; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /next/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 | -------------------------------------------------------------------------------- /next/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | reactStrictMode: true, 5 | compiler: { 6 | styledComponents: true, 7 | }, 8 | output: "standalone" 9 | }; 10 | 11 | module.exports = nextConfig; 12 | -------------------------------------------------------------------------------- /next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://backend:8080", 6 | "scripts": { 7 | "dev": "next dev -p 3001", 8 | "build": "next build", 9 | "start": "next start -p 3001", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "11.10.4", 14 | "@emotion/styled": "11.10.4", 15 | "@mui/material": "5.10.6", 16 | "@types/prismjs": "^1.26.0", 17 | "@types/styled-components": "5.1.25", 18 | "framer-motion": "7.3.6", 19 | "next": "12.3.1", 20 | "prismjs": "^1.29.0", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "react-is": "18.2.0", 24 | "redux-persist": "^6.0.0", 25 | "sharp": "0.31.0", 26 | "styled-components": "5.3.5" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "7.19.1", 30 | "@types/node": "17.0.21", 31 | "@types/react": "17.0.44", 32 | "eslint": "8.11.0", 33 | "eslint-config-next": "12.1.0", 34 | "typescript": "4.8.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /next/public/assets/CSESocEventsCP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/CSESocEventsCP.png -------------------------------------------------------------------------------- /next/public/assets/WebsitesIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/WebsitesIcon.png -------------------------------------------------------------------------------- /next/public/assets/close_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next/public/assets/csesoc_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/csesoc_discord.png -------------------------------------------------------------------------------- /next/public/assets/csesoc_facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/csesoc_facebook.png -------------------------------------------------------------------------------- /next/public/assets/csesoc_instagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/csesoc_instagram.png -------------------------------------------------------------------------------- /next/public/assets/csesoc_spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/csesoc_spotify.png -------------------------------------------------------------------------------- /next/public/assets/csesoc_youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/csesoc_youtube.png -------------------------------------------------------------------------------- /next/public/assets/execs/2007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2007.png -------------------------------------------------------------------------------- /next/public/assets/execs/2008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2008.png -------------------------------------------------------------------------------- /next/public/assets/execs/2009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2009.png -------------------------------------------------------------------------------- /next/public/assets/execs/2010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2010.png -------------------------------------------------------------------------------- /next/public/assets/execs/2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2011.png -------------------------------------------------------------------------------- /next/public/assets/execs/2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2012.png -------------------------------------------------------------------------------- /next/public/assets/execs/2013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2013.png -------------------------------------------------------------------------------- /next/public/assets/execs/2014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2014.png -------------------------------------------------------------------------------- /next/public/assets/execs/2015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2015.png -------------------------------------------------------------------------------- /next/public/assets/execs/2016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2016.png -------------------------------------------------------------------------------- /next/public/assets/execs/2017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2017.png -------------------------------------------------------------------------------- /next/public/assets/execs/2018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2018.png -------------------------------------------------------------------------------- /next/public/assets/execs/2019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2019.png -------------------------------------------------------------------------------- /next/public/assets/execs/2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2020.png -------------------------------------------------------------------------------- /next/public/assets/execs/2021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/execs/2021.png -------------------------------------------------------------------------------- /next/public/assets/menu_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next/public/assets/socials/discord_coloured.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next/public/assets/socials/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /next/public/assets/socials/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /next/public/assets/socials/spotify_coloured.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /next/public/assets/socials/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /next/public/assets/sponsors/ak.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/ak.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/amz.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/amz.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/app.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/app.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/arc.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/arc.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/aris.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/aris.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/atl.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/atl.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/aw.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/aw.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/bkk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/bkk.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/canva.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/canva.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/cbx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/cbx.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/cog.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/cog.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/ctd.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/ctd.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/del.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/del.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/disp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/disp.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/dlb.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/dlb.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/fl.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/fl.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/goog.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/goog.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/imc.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/imc.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/jds.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/jds.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/jst.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/jst.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/knapp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/knapp.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/mqb.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/mqb.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/msft.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/msft.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/mtl.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/mtl.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/mvc.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/mvc.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/nine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/nine.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/nm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/nm.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/opt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/opt.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/pal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/pal.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/pear.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/pear.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/prsp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/prsp.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/quant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/quant.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/qube.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/qube.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/rokt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/rokt.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/rpt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/rpt.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/rs.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/rs.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/sig.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/sig.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/unsw.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/unsw.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/wgg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/wgg.webp -------------------------------------------------------------------------------- /next/public/assets/sponsors/zip.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/assets/sponsors/zip.webp -------------------------------------------------------------------------------- /next/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/public/favicon.ico -------------------------------------------------------------------------------- /next/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /next/src/assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/assets/example.png -------------------------------------------------------------------------------- /next/src/assets/execs.js: -------------------------------------------------------------------------------- 1 | export const content = [ 2 | { 3 | alt_text: "2021", 4 | execs: "2021.png", 5 | }, 6 | { 7 | alt_text: "2020", 8 | execs: "2020.png", 9 | }, 10 | { 11 | alt_text: "2019", 12 | execs: "2019.png", 13 | }, 14 | { 15 | alt_text: "2018", 16 | execs: "2018.png", 17 | }, 18 | { 19 | alt_text: "2017", 20 | execs: "2017.png", 21 | }, 22 | { 23 | alt_text: "2016", 24 | execs: "2016.png", 25 | }, 26 | { 27 | alt_text: "2015", 28 | execs: "2015.png", 29 | }, 30 | { 31 | alt_text: "2014", 32 | execs: "2014.png", 33 | }, 34 | { 35 | alt_text: "2013", 36 | execs: "2013.png", 37 | }, 38 | { 39 | alt_text: "2012", 40 | execs: "2012.png", 41 | }, 42 | { 43 | alt_text: "2011", 44 | execs: "2011.png", 45 | }, 46 | { 47 | alt_text: "2010", 48 | execs: "2010.png", 49 | }, 50 | { 51 | alt_text: "2009", 52 | execs: "2009.png", 53 | }, 54 | { 55 | alt_text: "2008", 56 | execs: "2008.png", 57 | }, 58 | { 59 | alt_text: "2007", 60 | execs: "2007.png", 61 | }, 62 | ] 63 | -------------------------------------------------------------------------------- /next/src/assets/firstday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/assets/firstday.png -------------------------------------------------------------------------------- /next/src/assets/makingfriends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/assets/makingfriends.png -------------------------------------------------------------------------------- /next/src/assets/studytips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/assets/studytips.png -------------------------------------------------------------------------------- /next/src/assets/wishiknew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/assets/wishiknew.png -------------------------------------------------------------------------------- /next/src/components/aboutus/ReusableSpheres.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledSphere, sphereProps } from './Sphere-Styled'; 3 | 4 | type Props = sphereProps; 5 | 6 | export default function Sphere({ ...styleProps }: Props) { 7 | return ( 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /next/src/components/blog/types.ts: -------------------------------------------------------------------------------- 1 | export interface Document { 2 | document_name: string; 3 | document_id: string; 4 | content: Block[]; 5 | } 6 | 7 | export type Block = Element[]; 8 | 9 | export type Element = Paragraph | Image | Code; 10 | 11 | interface Paragraph { 12 | type: "paragraph"; 13 | children: Text[]; 14 | } 15 | 16 | interface Image { 17 | type: "image"; 18 | url: string; 19 | } 20 | 21 | interface Code { 22 | type: "code"; 23 | language: String; 24 | children: CodeLine[] 25 | } 26 | 27 | interface Text extends TextStyle { 28 | text: string; 29 | link?: string; 30 | } 31 | 32 | interface CodeLine extends TextStyle { 33 | text: string; 34 | language: string; 35 | } 36 | 37 | export interface TextStyle { 38 | bold?: boolean; 39 | italic?: boolean; 40 | underline?: boolean; 41 | code?: boolean; 42 | quote?: boolean; 43 | align?: "left" | "right" | "center"; 44 | textSize: number; 45 | } 46 | -------------------------------------------------------------------------------- /next/src/components/eventspage/ClearLayeredGlassContainer-Styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { device } from "../../styles/device"; 3 | 4 | export type positionProps = { 5 | position?: string; 6 | top?: number; 7 | left?: number; 8 | dark?: boolean; 9 | center?: boolean; 10 | }; 11 | 12 | export const GlassContainer = styled.div` 13 | position: ${(props) => props.position}; 14 | 15 | display: ${(props) => props.center ? "flex": ""}; 16 | justify-content: ${(props) => props.center ? "center": ""}; 17 | align-items: ${(props) => props.center ? "center": ""}; 18 | 19 | top: ${(props) => props.top}vw; 20 | left: ${(props) => props.left}vw; 21 | border-radius: 1vw; 22 | background-color: ${(props) => props.dark ? '#00000030' : '#FFFFFF30'}; 23 | border-width: 0.15vw; 24 | border-style: solid; 25 | border-color: #FAFCFF; 26 | width: 80vw; 27 | height: 50vw; 28 | @media ${device.laptop} { 29 | width: 36.7vw; 30 | height: 20vw; 31 | } 32 | `; 33 | 34 | export const ImgContainer = styled.div` 35 | position: relative; 36 | width: 77vw; 37 | height: 60vw; 38 | top: -4.15vw; 39 | left: 1.5vw; 40 | @media ${device.laptop} { 41 | width: 36vw; 42 | height: 17.8vw; 43 | top: 2.15vw; 44 | left: 1.5vw; 45 | } 46 | ` -------------------------------------------------------------------------------- /next/src/components/eventspage/ClearLayeredGlassContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GlassContainer, ImgContainer } from './ClearLayeredGlassContainer-Styled'; 3 | import Image from 'next/image'; 4 | 5 | export default function ClearLayeredGlass() { 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /next/src/components/navbar/HamburgerMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Image from "next/image"; 4 | import CloseIcon from "../../../public/assets/close_icon.svg"; 5 | import LogoImg from "../../../public/assets/logo.svg"; 6 | 7 | import { HamburgerMenuProps } from "./types"; 8 | import { 9 | MenuOverlay, MenuContainer, 10 | MenuHeader, MenuItemWrapper, 11 | MenuItem, LogoContainer, CloseButton 12 | } from "./HamburgerMenu-styled"; 13 | 14 | const HamburgerMenu = (props: HamburgerMenuProps) => { 15 | return ( 16 | 17 | 18 | {props.open ? ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) : <>} 26 | 27 | Future Students 28 | About Us 29 | Contact 30 | Events 31 | Resources 32 | Sponsors 33 | 34 | 35 | 36 | ) 37 | }; 38 | 39 | export default HamburgerMenu; -------------------------------------------------------------------------------- /next/src/components/navbar/types.ts: -------------------------------------------------------------------------------- 1 | export type NavbarOpenHandler = () => void; 2 | export enum NavbarType { 3 | HOMEPAGE, 4 | MINIPAGE 5 | } 6 | 7 | export type NavbarOpenProps = { 8 | open: boolean, 9 | setNavbarOpen: NavbarOpenHandler, 10 | variant: NavbarType 11 | }; 12 | 13 | export type HamburgerMenuProps = { 14 | open: boolean, 15 | setNavbarOpen: NavbarOpenHandler, 16 | } -------------------------------------------------------------------------------- /next/src/components/resources/image-data.tsx: -------------------------------------------------------------------------------- 1 | import degree_planner from "../../svgs/degree_planner.svg" 2 | import jobs_board from "../../svgs/jobs_board.svg" 3 | import notangles from "../../svgs/notangles.svg" 4 | 5 | const images = [ 6 | { 7 | url: degree_planner.src, 8 | link: "https://circles.csesoc.app/degree-wizard" 9 | }, 10 | { 11 | url: jobs_board.src, 12 | link: "https://jobsboard.csesoc.unsw.edu.au/" 13 | }, 14 | { 15 | url: notangles.src, 16 | link: "https://notangles.csesoc.app/" 17 | } 18 | 19 | ]; 20 | 21 | export default images; -------------------------------------------------------------------------------- /next/src/components/start/assets/enrolment-guide-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/components/start/assets/enrolment-guide-banner.png -------------------------------------------------------------------------------- /next/src/components/start/assets/peer-mentoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/components/start/assets/peer-mentoring.png -------------------------------------------------------------------------------- /next/src/components/start/events/EventTab.tsx: -------------------------------------------------------------------------------- 1 | import { MouseEventHandler } from "react"; 2 | import styled from "styled-components"; 3 | import { device } from "../../../styles/device"; 4 | 5 | const EventTabContainer = styled.button` 6 | display: flex; 7 | border-radius: 5px; 8 | border: 5px solid #A683F8; 9 | width: 90%; 10 | max-width: 500px; 11 | // height: 70px; 12 | text-align: center; 13 | align-items: center; 14 | justify-content: center; 15 | color: #A683F8; 16 | padding: 10px; 17 | font-size: 1em; 18 | background: white; 19 | `; 20 | 21 | 22 | type Props = { 23 | title: string; 24 | date: string; 25 | onClick: MouseEventHandler; 26 | }; 27 | 28 | 29 | export default function EventTab({ title, date, onClick }: Props) { 30 | return ( 31 | 32 | {date} | {title} 33 | 34 | ) 35 | } -------------------------------------------------------------------------------- /next/src/components/start/view/types.ts: -------------------------------------------------------------------------------- 1 | export type ViewProps = { 2 | idx: number; 3 | focusedView: number; 4 | }; 5 | -------------------------------------------------------------------------------- /next/src/hooks/TimelineScroll.tsx: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, useCallback, useRef, useState } from "react"; 2 | 3 | const useTimelineScroll = ( 4 | views: number, 5 | throttle: number, 6 | predicate?: () => boolean, 7 | ): [ 8 | MutableRefObject, 9 | (_direction: number) => void, 10 | number, 11 | (_focusedView: number) => void, 12 | ] => { 13 | const [focusedView, _setFocusedView] = useState(0); 14 | const scrolling = useRef(false); 15 | 16 | const setFocusedView = useCallback( 17 | (focusedView: number) => { 18 | if (focusedView < 0 || focusedView >= views) { 19 | return; 20 | } 21 | 22 | _setFocusedView(focusedView); 23 | scrolling.current = true; 24 | setTimeout(() => { 25 | scrolling.current = false; 26 | }, throttle); 27 | }, 28 | [throttle, views], 29 | ); 30 | 31 | const handleScroll = useCallback( 32 | (direction: number) => { 33 | if (predicate?.()) { 34 | return; 35 | } 36 | 37 | if (direction < 0) { 38 | setFocusedView(focusedView - 1); 39 | } else if (direction > 0) { 40 | setFocusedView(focusedView + 1); 41 | } 42 | }, 43 | [focusedView, predicate, setFocusedView], 44 | ); 45 | 46 | return [scrolling, handleScroll, focusedView, setFocusedView]; 47 | }; 48 | 49 | export default useTimelineScroll; 50 | -------------------------------------------------------------------------------- /next/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from "next/app"; 2 | import { ThemeProvider } from "styled-components"; 3 | 4 | import { theme } from "../styles/theme"; 5 | import { GlobalStyles } from "../styles/globalStyles"; 6 | 7 | function MyApp({ Component, pageProps }: AppProps) { 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default MyApp; 17 | -------------------------------------------------------------------------------- /next/src/styles/device.ts: -------------------------------------------------------------------------------- 1 | export const size = { 2 | mobileS: '320px', 3 | mobileM: '375px', 4 | mobileL: '425px', 5 | tablet: '768px', 6 | laptop: '1024px', 7 | laptopL: '1440px', 8 | desktop: '1920px', 9 | desktopL: '2560px' 10 | } 11 | 12 | export const device = { 13 | mobileS: `(min-width: ${size.mobileS})`, 14 | mobileM: `(min-width: ${size.mobileM})`, 15 | mobileL: `(min-width: ${size.mobileL})`, 16 | tablet: `(min-width: ${size.tablet})`, 17 | laptop: `(min-width: ${size.laptop})`, 18 | laptopL: `(min-width: ${size.laptopL})`, 19 | desktop: `(min-width: ${size.desktop})`, 20 | desktopL: `(min-width: ${size.desktop})` 21 | }; 22 | -------------------------------------------------------------------------------- /next/src/styles/globalStyles.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | 3 | export const GlobalStyles = createGlobalStyle` 4 | html, 5 | body { 6 | overflow-x: hidden; 7 | padding: 0; 8 | margin: 0; 9 | font-family: 'Raleway', sans-serif; 10 | } 11 | 12 | :root { 13 | /* global css color variables */ 14 | --primary-purple: #9291DE; 15 | --primary-blue: #3977F8; 16 | --accent-darker-purple: #5E5D8D; 17 | } 18 | 19 | a { 20 | color: inherit; 21 | text-decoration: none; 22 | } 23 | 24 | * { 25 | box-sizing: border-box; 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /next/src/styles/theme.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTheme } from "styled-components"; 2 | 3 | export const theme: DefaultTheme = { 4 | colors: { 5 | main: "#3977F8", 6 | purple: "#969DC7", 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /next/src/svgs/2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/svgs/2022.png -------------------------------------------------------------------------------- /next/src/svgs/BottomRect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /next/src/svgs/HPCurve.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type Props = {} 4 | 5 | export default function HPCurve({}: Props) { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /next/src/svgs/RCurve.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /next/src/svgs/RectangleCurve.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type Props = { 4 | width?: number; 5 | height?: number; 6 | dontPreserveAspectRatio?: boolean; 7 | } 8 | 9 | export default function RectangleCurve({width, height, dontPreserveAspectRatio}: Props) { 10 | // 964 1405 11 | return ( 12 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } -------------------------------------------------------------------------------- /next/src/svgs/TopRect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /next/src/svgs/otter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csesoc/website/85cc526e3cb1ce404458cd86bc089175c19717a3/next/src/svgs/otter.png -------------------------------------------------------------------------------- /next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 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 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cms.csesoc.unsw.edu.au", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:alpine 2 | 3 | COPY requirements.txt . 4 | RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev 5 | RUN pip install -r requirements.txt 6 | 7 | COPY . . 8 | 9 | ENTRYPOINT [ "python", "migrate.py" ] -------------------------------------------------------------------------------- /postgres/dbver.txt: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /postgres/down/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS frontend CASCADE; 2 | DROP TABLE IF EXISTS groups CASCADE; 3 | DROP TABLE IF EXISTS person CASCADE; 4 | DROP TABLE IF EXISTS filesystem CASCADE; 5 | 6 | DROP TYPE IF EXISTS permissions_enum; -------------------------------------------------------------------------------- /postgres/requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg2-binary==2.9.3 -------------------------------------------------------------------------------- /postgres/up/01-create_migrations_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS migrations ( 2 | MigrationID SERIAL PRIMARY KEY, 3 | VersionID INTEGER default 0 4 | ); 5 | 6 | DO LANGUAGE plpgsql $$ 7 | BEGIN 8 | IF NOT EXISTS (SELECT FROM migrations WHERE MigrationID = 1) THEN 9 | INSERT INTO migrations (MigrationID, VersionID) VALUES (1, 0); 10 | END IF; 11 | END $$; -------------------------------------------------------------------------------- /postgres/up/02-create_groups_table.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS hstore; 2 | SET timezone = 'Australia/Sydney'; 3 | 4 | /* A group provides users with specific access permissions for certain entities */ 5 | DROP TABLE IF EXISTS groups; 6 | CREATE TABLE groups ( 7 | GroupID SERIAL PRIMARY KEY, 8 | Name VARCHAR(50) NOT NULL 9 | ); 10 | -------------------------------------------------------------------------------- /postgres/up/03-create_person_table.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS person; 2 | CREATE TABLE person ( 3 | UID SERIAL PRIMARY KEY, 4 | Email VARCHAR(50) UNIQUE NOT NULL, 5 | First_name VARCHAR(50) NOT NULL, 6 | Password CHAR(64) NOT NULL, 7 | 8 | /* non duplicate email and password constraints */ 9 | CONSTRAINT no_dupes UNIQUE (Email, Password) 10 | ); 11 | 12 | /* create user function plpgsql */ 13 | DROP FUNCTION IF EXISTS create_normal_user; 14 | CREATE OR REPLACE FUNCTION create_normal_user (email VARCHAR, name VARCHAR, password VARCHAR) RETURNS INT 15 | LANGUAGE plpgsql 16 | AS $$ 17 | DECLARE 18 | userID INT; 19 | BEGIN 20 | INSERT INTO person (Email, First_name, Password) 21 | VALUES (email, name, encode(sha256(password::BYTEA), 'hex')) 22 | RETURNING UID INTO userID; 23 | RETURN userID; 24 | END $$; 25 | 26 | /* Manages the membership of users to groups */ 27 | DROP TABLE IF EXISTS group_membership; 28 | CREATE TABLE group_membership ( 29 | GroupID INT NOT NULL, 30 | UID INT NOT NULL, 31 | 32 | CONSTRAINT fk_AccessUser FOREIGN KEY (UID) 33 | REFERENCES person(UID), 34 | 35 | CONSTRAINT fk_AccessGroupID FOREIGN KEY (GroupID) 36 | REFERENCES groups(GroupID) 37 | ); -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", ":semanticCommits"], 3 | "lockFileMaintenance": { "enabled": true, "automerge": true }, 4 | "prHourlyLimit": 2, 5 | "labels": ["dependencies"], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": false, 10 | "automergeType": "branch" 11 | }, 12 | { 13 | "groupName": "docker-github-actions", 14 | "matchPackagePatterns": ["docker/*"], 15 | "automerge": true, 16 | "automergeType": "branch", 17 | "addLabels": ["deps: docker-actions"] 18 | }, 19 | { 20 | "matchUpdateTypes": ["minor", "patch"], 21 | "groupName": "weekly minor & patch updates", 22 | "schedule": ["before 5am every monday"] 23 | }, 24 | { 25 | "managers": ["npm"], 26 | "addLabels": ["deps: javascript"] 27 | }, 28 | { 29 | "managers": ["gomod"], 30 | "addLabels": ["deps: go"] 31 | }, 32 | { 33 | "matchPackageNames": ["go", "golang"], 34 | "groupName": "go" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /services/events-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19-alpine as app-builder 2 | WORKDIR /go/src/app 3 | COPY . . 4 | RUN apk add git 5 | # Static build required so that we can safely copy the binary over. 6 | # `-tags timetzdata` embeds zone info from the "time/tzdata" package. 7 | RUN CGO_ENABLED=0 go install -ldflags '-extldflags "-static"' -tags timetzdata 8 | 9 | FROM scratch 10 | # the test program: 11 | COPY --from=app-builder /go/bin/events-service /events-service 12 | # the tls certificates: 13 | # NB: this pulls directly from the upstream image, which already has ca-certificates: 14 | COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 15 | ENTRYPOINT ["/events-service"] 16 | -------------------------------------------------------------------------------- /services/events-service/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | events-service: 3 | container_name: events-service 4 | build: 5 | context: . 6 | dockerfile: ./Dockerfile 7 | ports: 8 | - 8080:8080 9 | environment: 10 | - FB_TOKEN=please_replace_me 11 | -------------------------------------------------------------------------------- /services/events-service/go.mod: -------------------------------------------------------------------------------- 1 | module events-service 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /services/events-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | eventService := EventsService{ 13 | cache: responseCache{ 14 | cacheLock: sync.RWMutex{}, 15 | expiryTime: time.Now(), 16 | }, 17 | } 18 | 19 | http.HandleFunc("/events-api/v1/get-all", func(w http.ResponseWriter, r *http.Request) { 20 | fmt.Fprint(w, string(eventService.FetchEvents())) 21 | }) 22 | 23 | // listen to port 24 | err := http.ListenAndServe(":8080", nil) 25 | if err != nil { 26 | log.Fatal( 27 | fmt.Errorf("failed to start server: %w", err)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/events-service/service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | type EventsService struct { 10 | cache responseCache 11 | } 12 | 13 | // FetchEvents reads from the FB API and process the data into a nice clean format 14 | // to be returned to the user :) 15 | func (service *EventsService) FetchEvents() []byte { 16 | if service.cache.HasExpired() { 17 | events, err := GetFBEvents() 18 | // if theres an error log it and renew the cache 19 | // keeping the old stale data 20 | if err != nil { 21 | log.Printf("Cache refresh service failed, error: %v", err) 22 | service.cache.Renew() 23 | } else { 24 | parsedJson, _ := json.Marshal(events) 25 | service.cache.RenewWith(parsedJson) 26 | } 27 | 28 | } 29 | 30 | resp, err := service.cache.Read() 31 | if err != nil { 32 | panic(fmt.Errorf( 33 | "fatal error, cache should not be empty at this point: %w", err, 34 | )) 35 | } 36 | 37 | return resp 38 | } 39 | -------------------------------------------------------------------------------- /utilities/createUsers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1 = filename 3 | 4 | echo "Welcome to the create user command line for CMS" 5 | echo "please input your name: " 6 | read name 7 | echo "please input your email: " 8 | echo "accepted formats are: gmail/ ad.unsw.edu.au / student.unsw.edu.au" 9 | read email 10 | echo "please input your password: " 11 | read password 12 | echo 'please input the frontendid: ' 13 | read frontendid 14 | 15 | # not sure if this is vulnerable to command injection? 16 | docker exec pg_container psql -U postgres -d test_db -c "select create_normal_user('$email', '$name', '$password', '$frontendid');" 17 | -------------------------------------------------------------------------------- /utilities/ghost-exporter/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "7.1.5", 7 | "commands": [ 8 | "paket" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /utilities/ghost-exporter/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | open GhostSyntax 4 | open CMSSyntax 5 | open Newtonsoft.Json 6 | 7 | open FSharpPlus 8 | open Fleece.Newtonsoft 9 | open System.IO 10 | open Converters 11 | 12 | [] 13 | let main args = 14 | let ghostDocument = ofJsonText (File.ReadAllText args[0]) 15 | 16 | match Option.ofResult ghostDocument with 17 | | Some document -> 18 | // Write the result to disk 19 | let compiledResult = toJsonText (GhostToCms document) 20 | let fileName = Path.GetFileNameWithoutExtension (args[0]) 21 | File.WriteAllText ($"{Path.GetFileNameWithoutExtension (args[0])}__exported.json", compiledResult) 22 | 23 | | _ -> printfn "%s" "failed to parse ghost document" 24 | 25 | 0 -------------------------------------------------------------------------------- /utilities/ghost-exporter/ghost-exporter.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | ghost_exporter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /utilities/ghost-exporter/paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://api.nuget.org/v3/index.json 2 | 3 | storage: none 4 | framework: net5.0, netstandard2.0, netstandard2.1 5 | nuget Fleece 0.10.0 6 | nuget Fleece.NewtonsoftJson 0.10.0 7 | nuget FSharp.Core content: none 8 | nuget FSharpPlus 1.2.4 -------------------------------------------------------------------------------- /utilities/ghost-exporter/paket.lock: -------------------------------------------------------------------------------- 1 | STORAGE: NONE 2 | RESTRICTION: || (== net5.0) (== netstandard2.0) (== netstandard2.1) 3 | NUGET 4 | remote: https://api.nuget.org/v3/index.json 5 | Fleece (0.10) 6 | FSharp.Core (>= 5.0.2) 7 | FSharpPlus (>= 1.2.4) 8 | Fleece.NewtonsoftJson (0.10) 9 | Fleece (>= 0.10) 10 | FSharp.Core (>= 5.0.2) 11 | Newtonsoft.Json (>= 10.0.2) 12 | FSharp.Core (6.0.5) - content: none 13 | FSharpPlus (1.2.4) 14 | FSharp.Core (>= 4.6.2) 15 | Newtonsoft.Json (13.0.1) 16 | -------------------------------------------------------------------------------- /utilities/ghost-exporter/paket.references: -------------------------------------------------------------------------------- 1 | Fleece 2 | Fleece.NewtonsoftJson 3 | FSharpPlus 4 | FSharp.Core --------------------------------------------------------------------------------