├── backend ├── boards │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_apps.py │ │ ├── test_models.py │ │ ├── test_utils.py │ │ ├── test_factories.py │ │ ├── factories.py │ │ └── test_comment_viewset.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_auto_20200322_1633.py │ │ ├── 0011_auto_20200517_1011.py │ │ ├── 0005_auto_20200406_1433.py │ │ ├── 0009_task_labels.py │ │ ├── 0008_auto_20200510_1646.py │ │ ├── 0006_auto_20200410_1542.py │ │ ├── 0010_auto_20200517_0826.py │ │ ├── 0002_auto_20200321_0631.py │ │ ├── 0004_auto_20200329_0919.py │ │ ├── 0007_label.py │ │ ├── 0012_comment.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── viewsets.py │ ├── admin.py │ ├── utils.py │ ├── permissions.py │ ├── management │ │ └── commands │ │ │ └── load_benchmark.py │ └── models.py ├── config │ ├── __init__.py │ ├── settings │ │ ├── __init__.py │ │ ├── local.py │ │ └── production.py │ ├── env_utils.py │ ├── wsgi.py │ └── urls.py ├── accounts │ ├── tests │ │ ├── __init__.py │ │ └── test_models.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0004_user_is_guest.py │ │ ├── 0005_auto_20201006_1436.py │ │ ├── 0003_auto_20200505_1756.py │ │ └── 0002_auto_20200327_1817.py │ ├── __init__.py │ ├── apps.py │ ├── permissions.py │ ├── admin.py │ ├── management │ │ └── commands │ │ │ ├── db.py │ │ │ └── load.py │ ├── models.py │ └── serializers.py ├── requirements │ ├── production.txt │ ├── local.txt │ └── base.txt ├── media │ └── avatars │ │ ├── bat.png │ │ ├── bear.png │ │ ├── bee.png │ │ ├── bird.png │ │ ├── bug.png │ │ ├── camel.png │ │ ├── cat.png │ │ ├── cobra.png │ │ ├── cow.png │ │ ├── dog.png │ │ ├── dove.png │ │ ├── duck.png │ │ ├── eagle.png │ │ ├── fish.png │ │ ├── fox.png │ │ ├── frog.png │ │ ├── hen.png │ │ ├── horse.png │ │ ├── koala.png │ │ ├── lion.png │ │ ├── mouse.png │ │ ├── panda.png │ │ ├── shark.png │ │ ├── sheep.png │ │ ├── tiger.png │ │ ├── zebra.png │ │ ├── cheetah.png │ │ ├── dolphin.png │ │ ├── giraffe.png │ │ ├── gorilla.png │ │ ├── leopard.png │ │ ├── monkey.png │ │ ├── parrot.png │ │ ├── penguin.png │ │ ├── spider.png │ │ ├── turtle.png │ │ ├── butterfly.png │ │ ├── crocodile.png │ │ ├── dinosaur.png │ │ ├── elephant.png │ │ ├── flamingo.png │ │ ├── kangaroo.png │ │ ├── squirrel.png │ │ └── starfish.png ├── .pre-commit-config.yaml ├── pytest.ini ├── manage.py ├── fixtures │ ├── users.yaml │ └── tasks.yaml └── conftest.py ├── frontend ├── .env ├── src │ ├── features │ │ ├── board │ │ │ ├── index.ts │ │ │ ├── NewBoardDialog.test.tsx │ │ │ └── BoardList.test.tsx │ │ ├── column │ │ │ ├── index.ts │ │ │ └── Column.tsx │ │ ├── home │ │ │ ├── Home.test.tsx │ │ │ └── Home.tsx │ │ ├── auth │ │ │ ├── __snapshots__ │ │ │ │ └── Footer.test.tsx.snap │ │ │ ├── Footer.test.tsx │ │ │ ├── Auth.tsx │ │ │ ├── EnterAsGuest.tsx │ │ │ └── Footer.tsx │ │ ├── toast │ │ │ ├── __snapshots__ │ │ │ │ └── Toast.test.tsx.snap │ │ │ ├── ToastSlice.tsx │ │ │ ├── Toast.tsx │ │ │ └── Toast.test.tsx │ │ ├── responsive │ │ │ └── ResponsiveSlice.tsx │ │ ├── task │ │ │ ├── TaskLabels.tsx │ │ │ └── AddTask.tsx │ │ ├── comment │ │ │ ├── CommentTextarea.tsx │ │ │ ├── CommentItem.test.tsx │ │ │ ├── CommentSection.test.tsx │ │ │ └── CommentItem.tsx │ │ ├── member │ │ │ ├── MemberDetail.tsx │ │ │ ├── MemberSlice.tsx │ │ │ └── MemberListDialog.test.tsx │ │ ├── label │ │ │ ├── LabelCreate.tsx │ │ │ ├── LabelSlice.tsx │ │ │ └── LabelRow.tsx │ │ └── profile │ │ │ └── Profile.test.tsx │ ├── react-app-env.d.ts │ ├── static │ │ ├── font │ │ │ ├── Inter-Black.woff │ │ │ ├── Inter-Bold.woff │ │ │ ├── Inter-Bold.woff2 │ │ │ ├── Inter-Light.woff │ │ │ ├── Inter-Thin.woff │ │ │ ├── Inter-Thin.woff2 │ │ │ ├── Inter.var.woff2 │ │ │ ├── Inter-Black.woff2 │ │ │ ├── Inter-Italic.woff │ │ │ ├── Inter-Italic.woff2 │ │ │ ├── Inter-Light.woff2 │ │ │ ├── Inter-Medium.woff │ │ │ ├── Inter-Medium.woff2 │ │ │ ├── Inter-Regular.woff │ │ │ ├── Inter-BoldItalic.woff │ │ │ ├── Inter-ExtraBold.woff │ │ │ ├── Inter-ExtraBold.woff2 │ │ │ ├── Inter-ExtraLight.woff │ │ │ ├── Inter-Regular.woff2 │ │ │ ├── Inter-SemiBold.woff │ │ │ ├── Inter-SemiBold.woff2 │ │ │ ├── Inter-ThinItalic.woff │ │ │ ├── Inter-roman.var.woff2 │ │ │ ├── Inter-BlackItalic.woff │ │ │ ├── Inter-BlackItalic.woff2 │ │ │ ├── Inter-BoldItalic.woff2 │ │ │ ├── Inter-ExtraLight.woff2 │ │ │ ├── Inter-LightItalic.woff │ │ │ ├── Inter-LightItalic.woff2 │ │ │ ├── Inter-MediumItalic.woff │ │ │ ├── Inter-ThinItalic.woff2 │ │ │ ├── Inter-italic.var.woff2 │ │ │ ├── Inter-ExtraBoldItalic.woff │ │ │ ├── Inter-MediumItalic.woff2 │ │ │ ├── Inter-SemiBoldItalic.woff │ │ │ ├── Inter-SemiBoldItalic.woff2 │ │ │ ├── Inter-ExtraBoldItalic.woff2 │ │ │ ├── Inter-ExtraLightItalic.woff │ │ │ └── Inter-ExtraLightItalic.woff2 │ │ └── svg │ │ │ ├── times.svg │ │ │ └── github.svg │ ├── routing.tsx │ ├── utils │ │ ├── shortcuts.ts │ │ ├── localStorage.tsx │ │ ├── utils.test.tsx │ │ ├── reorder.tsx │ │ └── testHelpers.tsx │ ├── components │ │ ├── Flex.tsx │ │ ├── SEO.tsx │ │ ├── FullPageSpinner.tsx │ │ ├── PriorityOption.test.tsx │ │ ├── MemberAvatar.tsx │ │ ├── Spinner.tsx │ │ ├── PageError.tsx │ │ ├── AvatarTag.tsx │ │ ├── PriorityOption.tsx │ │ ├── AvatarOption.tsx │ │ ├── Close.tsx │ │ ├── LabelChip.tsx │ │ ├── AssigneeAutoComplete.tsx │ │ ├── Navbar.tsx │ │ ├── BoardName.tsx │ │ └── UserMenu.tsx │ ├── index.css │ ├── index.tsx │ ├── setupTests.ts │ ├── api.tsx │ ├── AuthenticatedApp.tsx │ ├── store.ts │ ├── App.tsx │ ├── types.ts │ ├── styles.tsx │ └── const.ts ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── cypress.json ├── cypress │ ├── support │ │ ├── index.js │ │ └── commands.js │ ├── fixtures │ │ ├── board_create.json │ │ ├── dog_avatar.json │ │ ├── board_list.json │ │ ├── testuser.json │ │ ├── testuser_update.json │ │ ├── users.json │ │ └── os_board.json │ ├── util.js │ ├── plugins │ │ └── index.js │ └── integration │ │ ├── e2e │ │ └── auth.spec.js │ │ └── stubbed │ │ ├── auth.spec.js │ │ └── profile.spec.js ├── .vscode │ ├── extensions.json │ └── settings.json ├── craco.config.js ├── tsconfig.json ├── .eslintrc └── README.md ├── .dockerignore ├── .pyup.yml ├── ansible ├── ansible.cfg ├── hosts ├── setup.yml ├── README.md ├── deploy.yml └── roles │ ├── nginx │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── proxy.conf │ ├── certbot │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── letsencrypt.sh │ ├── common │ └── tasks │ │ └── checkout.yml │ ├── docker-compose │ └── tasks │ │ └── main.yml │ └── security │ └── tasks │ └── main.yml ├── .codeclimate.yml ├── services.yml ├── .production.env.example ├── nginx.Dockerfile ├── django.Dockerfile ├── LICENSE ├── docker-compose.yml ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore └── CODE_OF_CONDUCT.md /backend/boards/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/boards/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | EXTEND_ESLINT=true -------------------------------------------------------------------------------- /backend/accounts/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/boards/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/config/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/node_modules 3 | -------------------------------------------------------------------------------- /frontend/src/features/board/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Board"; 2 | -------------------------------------------------------------------------------- /frontend/src/features/column/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Column"; 2 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /backend/requirements/production.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | gunicorn==20.0.4 4 | -------------------------------------------------------------------------------- /backend/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "accounts.apps.AccountsConfig" 2 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /frontend/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "video": false 4 | } 5 | -------------------------------------------------------------------------------- /frontend/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | import "@cypress/code-coverage/support"; 2 | import "./commands"; 3 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /backend/media/avatars/bat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/bat.png -------------------------------------------------------------------------------- /backend/media/avatars/bear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/bear.png -------------------------------------------------------------------------------- /backend/media/avatars/bee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/bee.png -------------------------------------------------------------------------------- /backend/media/avatars/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/bird.png -------------------------------------------------------------------------------- /backend/media/avatars/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/bug.png -------------------------------------------------------------------------------- /backend/media/avatars/camel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/camel.png -------------------------------------------------------------------------------- /backend/media/avatars/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/cat.png -------------------------------------------------------------------------------- /backend/media/avatars/cobra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/cobra.png -------------------------------------------------------------------------------- /backend/media/avatars/cow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/cow.png -------------------------------------------------------------------------------- /backend/media/avatars/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/dog.png -------------------------------------------------------------------------------- /backend/media/avatars/dove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/dove.png -------------------------------------------------------------------------------- /backend/media/avatars/duck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/duck.png -------------------------------------------------------------------------------- /backend/media/avatars/eagle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/eagle.png -------------------------------------------------------------------------------- /backend/media/avatars/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/fish.png -------------------------------------------------------------------------------- /backend/media/avatars/fox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/fox.png -------------------------------------------------------------------------------- /backend/media/avatars/frog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/frog.png -------------------------------------------------------------------------------- /backend/media/avatars/hen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/hen.png -------------------------------------------------------------------------------- /backend/media/avatars/horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/horse.png -------------------------------------------------------------------------------- /backend/media/avatars/koala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/koala.png -------------------------------------------------------------------------------- /backend/media/avatars/lion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/lion.png -------------------------------------------------------------------------------- /backend/media/avatars/mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/mouse.png -------------------------------------------------------------------------------- /backend/media/avatars/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/panda.png -------------------------------------------------------------------------------- /backend/media/avatars/shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/shark.png -------------------------------------------------------------------------------- /backend/media/avatars/sheep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/sheep.png -------------------------------------------------------------------------------- /backend/media/avatars/tiger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/tiger.png -------------------------------------------------------------------------------- /backend/media/avatars/zebra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/zebra.png -------------------------------------------------------------------------------- /frontend/cypress/fixtures/board_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 5, 3 | "name": "Physics", 4 | "owner": 1 5 | } 6 | -------------------------------------------------------------------------------- /backend/media/avatars/cheetah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/cheetah.png -------------------------------------------------------------------------------- /backend/media/avatars/dolphin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/dolphin.png -------------------------------------------------------------------------------- /backend/media/avatars/giraffe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/giraffe.png -------------------------------------------------------------------------------- /backend/media/avatars/gorilla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/gorilla.png -------------------------------------------------------------------------------- /backend/media/avatars/leopard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/leopard.png -------------------------------------------------------------------------------- /backend/media/avatars/monkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/monkey.png -------------------------------------------------------------------------------- /backend/media/avatars/parrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/parrot.png -------------------------------------------------------------------------------- /backend/media/avatars/penguin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/penguin.png -------------------------------------------------------------------------------- /backend/media/avatars/spider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/spider.png -------------------------------------------------------------------------------- /backend/media/avatars/turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/turtle.png -------------------------------------------------------------------------------- /backend/media/avatars/butterfly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/butterfly.png -------------------------------------------------------------------------------- /backend/media/avatars/crocodile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/crocodile.png -------------------------------------------------------------------------------- /backend/media/avatars/dinosaur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/dinosaur.png -------------------------------------------------------------------------------- /backend/media/avatars/elephant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/elephant.png -------------------------------------------------------------------------------- /backend/media/avatars/flamingo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/flamingo.png -------------------------------------------------------------------------------- /backend/media/avatars/kangaroo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/kangaroo.png -------------------------------------------------------------------------------- /backend/media/avatars/squirrel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/squirrel.png -------------------------------------------------------------------------------- /backend/media/avatars/starfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/backend/media/avatars/starfish.png -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /backend/boards/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BoardsConfig(AppConfig): 5 | name = "boards" 6 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/dog_avatar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 3, 3 | "photo": "/media/avatars/dog.png", 4 | "name": "dog" 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Black.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Bold.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Bold.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Light.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Thin.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Thin.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter.var.woff2 -------------------------------------------------------------------------------- /backend/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = "accounts" 6 | -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Black.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Italic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Italic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Light.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Medium.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Medium.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Regular.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-BoldItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraBold.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraBold.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraLight.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-Regular.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-SemiBold.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-SemiBold.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ThinItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-roman.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-roman.var.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-BlackItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-BlackItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-BoldItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraLight.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-LightItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-LightItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-MediumItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ThinItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-italic.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-italic.var.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraBoldItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-MediumItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-SemiBoldItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/routing.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from "history"; 2 | 3 | const history = createBrowserHistory(); 4 | 5 | export default history; 6 | -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraLightItalic.woff -------------------------------------------------------------------------------- /frontend/src/static/font/Inter-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebase/knboard/HEAD/frontend/src/static/font/Inter-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /frontend/src/utils/shortcuts.ts: -------------------------------------------------------------------------------- 1 | const getMetaKey = () => 2 | navigator.platform.indexOf("Mac") > -1 ? "⌘" : "ctrl"; 3 | 4 | export default getMetaKey; 5 | -------------------------------------------------------------------------------- /backend/accounts/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def test_custom_user_model(): 5 | assert settings.AUTH_USER_MODEL == "accounts.User" 6 | -------------------------------------------------------------------------------- /backend/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: 19.10b0 4 | hooks: 5 | - id: black 6 | language_version: python3.8 7 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # PyUp is used to keep Python dependencies secure & up-to-date 2 | # See https://pyup.io/docs/configuration/ for all available options 3 | 4 | update: all 5 | schedule: "every week" 6 | -------------------------------------------------------------------------------- /frontend/src/components/Flex.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | 3 | const Flex = styled.div` 4 | display: flex; 5 | justify-content: space-between; 6 | `; 7 | 8 | export default Flex; 9 | -------------------------------------------------------------------------------- /backend/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = config.settings.local 3 | python_files = test_*.py 4 | norecursedirs = .venv .git 5 | addopts = -p no:warnings --strict-markers 6 | junit_family = xunit1 7 | -------------------------------------------------------------------------------- /backend/requirements/local.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | black==19.10b0 4 | django-debug-toolbar==2.2 5 | django-debug-toolbar-request-history==0.1.3 6 | pytest-django==3.9.0 7 | pytest-factoryboy==2.0.3 8 | pytest-cov==2.9.0 9 | -------------------------------------------------------------------------------- /backend/accounts/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsSelf(permissions.BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | return obj == request.user 7 | -------------------------------------------------------------------------------- /ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | # ansible.cfg 2 | 3 | [defaults] 4 | transport = ssh 5 | inventory = ./hosts 6 | interpreter_python = python3 7 | 8 | [ssh_connection] 9 | pipelining = True 10 | control_path = /tmp/ansible-ssh-%%h-%%p-%%r 11 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | checks: 4 | similar-code: 5 | enabled: false 6 | 7 | exclude_patterns: 8 | - "**/node_modules/" 9 | - "**/migrations/" 10 | - "**/tests/" 11 | - "**/*.test.*" 12 | - "**/*.spec.*" 13 | -------------------------------------------------------------------------------- /backend/boards/tests/test_apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import apps 2 | 3 | from boards.apps import BoardsConfig 4 | 5 | 6 | def test_config(): 7 | assert BoardsConfig.name == "boards" 8 | assert apps.get_app_config("boards").name == "boards" 9 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import url("static/font/inter.css"); 2 | 3 | html { 4 | font-family: "Inter", sans-serif; 5 | } 6 | @supports (font-variation-settings: normal) { 7 | html { 8 | font-family: "Inter var", sans-serif; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/static/svg/times.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /services.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: postgres:11 6 | environment: 7 | - POSTGRES_DB=knboard 8 | - POSTGRES_USER=knboard 9 | - POSTGRES_PASSWORD=knboard 10 | ports: 11 | - "5432:5432" 12 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/board_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "id": 1, "name": "Internals", "owner": 1 }, 3 | { "id": 2, "name": "Operating Systems", "owner": 2 }, 4 | { "id": 3, "name": "Fundamentals of Computation", "owner": 2 }, 5 | { "id": 4, "name": "Data Science", "owner": 3 } 6 | ] 7 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/testuser.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "testuser", 4 | "first_name": "Ragnar", 5 | "last_name": "Rebase", 6 | "email": "t@t.com", 7 | "avatar": null, 8 | "date_joined": "2020-04-01T04:06:21.792404Z", 9 | "is_guest": false 10 | } 11 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/testuser_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "newname", 4 | "first_name": "Ragnar", 5 | "last_name": "Rebase", 6 | "email": "t@t.com", 7 | "avatar": null, 8 | "date_joined": "2020-04-01T04:25:04.833718Z", 9 | "is_guest": false 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/SEO.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Helmet, HelmetProps } from "react-helmet"; 3 | 4 | const SEO = ({ title }: Pick) => ( 5 | 6 | ); 7 | 8 | export default SEO; 9 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | import "react-markdown-editor-lite/lib/index.css"; 5 | import "./index.css"; 6 | import App from "./App"; 7 | 8 | ReactDOM.render(, document.getElementById("root")); 9 | -------------------------------------------------------------------------------- /frontend/craco.config.js: -------------------------------------------------------------------------------- 1 | const emotionPresetOptions = {}; 2 | 3 | const emotionBabelPreset = require("@emotion/babel-preset-css-prop").default( 4 | undefined, 5 | emotionPresetOptions 6 | ); 7 | 8 | module.exports = { 9 | babel: { 10 | plugins: emotionBabelPreset.plugins, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /ansible/hosts: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | ansible_become=true 3 | ansible_become_user=root 4 | env_file=../.env 5 | 6 | [knboard] 7 | knboard.com 8 | 9 | [knboard:vars] 10 | repo_folder=/srv/knboard/ 11 | repo_name=rrebase/knboard 12 | repo_branch=live 13 | letsencrypt_email=info@knboard.com 14 | domain_name=knboard.com 15 | create_user=rareba 16 | -------------------------------------------------------------------------------- /frontend/src/components/FullPageSpinner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CircularProgress, Fade } from "@material-ui/core"; 3 | 4 | const FullPageSpinner = () => ( 5 | 6 | 7 | 8 | ); 9 | 10 | export default FullPageSpinner; 11 | -------------------------------------------------------------------------------- /backend/config/env_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.exceptions import ImproperlyConfigured 4 | 5 | 6 | def get_env(env_variable): 7 | try: 8 | return os.environ[env_variable] 9 | except KeyError: 10 | error_msg = f"Set the {env_variable} environment variable" 11 | raise ImproperlyConfigured(error_msg) 12 | -------------------------------------------------------------------------------- /frontend/src/features/home/Home.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { screen } from "@testing-library/react"; 3 | import Home from "./Home"; 4 | import { renderWithProviders } from "utils/testHelpers"; 5 | 6 | it("should have visit boards", () => { 7 | renderWithProviders(); 8 | expect(screen.getByText(/View Boards/i)).toBeVisible(); 9 | }); 10 | -------------------------------------------------------------------------------- /ansible/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: knboard 4 | vars: 5 | docker_compose_version: 1.25.5 6 | sys_packages: ["curl", "vim", "git", "ufw", "haveged"] 7 | copy_local_key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}" 8 | 9 | roles: 10 | - role: security 11 | - role: docker-compose 12 | - role: nginx 13 | - role: certbot 14 | -------------------------------------------------------------------------------- /ansible/README.md: -------------------------------------------------------------------------------- 1 | ## Ansible playbooks for server setup and deployment 2 | 3 | Setup the server: 4 | ```sh 5 | ansible-playbook setup.yml --verbose 6 | ``` 7 | 8 | Initial request for SSL certs: 9 | ```sh 10 | ssh knboard.com 11 | sudo su - 12 | cd /srv/knboard/ 13 | ./init-letsencrypt.sh 14 | ``` 15 | 16 | Deploy: 17 | ```sh 18 | ansible-playbook deploy.yml --verbose 19 | ``` 20 | -------------------------------------------------------------------------------- /backend/requirements/base.txt: -------------------------------------------------------------------------------- 1 | Django>=3.1 2 | djangorestframework==3.12.1 3 | django-filter==2.4.0 4 | django-extensions==3.0.3 5 | django-allauth==0.42.0 6 | django-model-utils==4.0.0 7 | django-admin-sortable==2.2.3 8 | dj-rest-auth==1.0.7 9 | markdown==3.2.2 10 | PyYAML==5.3.1 11 | Pillow==7.1.2 12 | pre-commit==2.4.0 13 | shortuuid==1.0.1 14 | psycopg2==2.8.5 --no-binary psycopg2 15 | -------------------------------------------------------------------------------- /frontend/src/features/auth/__snapshots__/Footer.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should render github link correctly 1`] = ` 4 | 10 | GitHub 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /frontend/cypress/util.js: -------------------------------------------------------------------------------- 1 | // keycodes 2 | export const tab = 9; 3 | export const enter = 13; 4 | export const escape = 27; 5 | export const space = 32; 6 | export const pageUp = 33; 7 | export const pageDown = 34; 8 | export const end = 35; 9 | export const home = 36; 10 | export const arrowLeft = 37; 11 | export const arrowUp = 38; 12 | export const arrowRight = 39; 13 | export const arrowDown = 40; 14 | -------------------------------------------------------------------------------- /ansible/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: knboard 4 | gather_facts: false 5 | 6 | tasks: 7 | - include: roles/common/tasks/checkout.yml 8 | 9 | - name: Run `docker-compose up --build --detach` 10 | command: 11 | docker-compose up --build --detach 12 | args: 13 | chdir: "{{ repo_folder }}" 14 | register: output 15 | 16 | - debug: 17 | var: output 18 | -------------------------------------------------------------------------------- /ansible/roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Ensures /etc/nginx dir exists 4 | file: path=/etc/nginx state=directory 5 | 6 | - name: Create nginx.config from template 7 | template: 8 | src: templates/nginx.conf 9 | dest: /etc/nginx/nginx.conf 10 | 11 | - name: Create proxy.config from template 12 | template: 13 | src: templates/proxy.conf 14 | dest: /etc/nginx/proxy.conf 15 | -------------------------------------------------------------------------------- /backend/boards/migrations/0003_auto_20200322_1633.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-03-22 16:33 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("boards", "0002_auto_20200321_0631"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions(name="board", options={"ordering": ["id"]},), 14 | ] 15 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 2, 4 | "username": "steveapple1", 5 | "avatar": null 6 | }, 7 | { 8 | "id": 3, 9 | "username": "daveice", 10 | "avatar": null 11 | }, 12 | { 13 | "id": 4, 14 | "username": "timwoodstock", 15 | "avatar": null 16 | }, 17 | { 18 | "id": 5, 19 | "username": "stenwood55", 20 | "avatar": null 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /ansible/roles/certbot/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - include: roles/common/tasks/checkout.yml 4 | 5 | - name: Build containers (may take a few minutes) 6 | command: 7 | docker-compose build 8 | args: 9 | chdir: "{{ repo_folder }}" 10 | 11 | - name: Create init-letsencrypt from template 12 | template: 13 | src: templates/letsencrypt.sh 14 | dest: "{{ repo_folder }}init-letsencrypt.sh" 15 | mode: a+x 16 | -------------------------------------------------------------------------------- /frontend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /// 3 | 4 | /** 5 | * @type {Cypress.PluginConfig} 6 | */ 7 | module.exports = (on, config) => { 8 | require("@cypress/code-coverage/task")(on, config); 9 | on( 10 | "file:preprocessor", 11 | require("@cypress/code-coverage/use-browserify-istanbul") 12 | ); 13 | return config; 14 | }; 15 | -------------------------------------------------------------------------------- /.production.env.example: -------------------------------------------------------------------------------- 1 | POSTGRES_HOST=postgres 2 | POSTGRES_PORT=5432 3 | POSTGRES_DB=knboard 4 | POSTGRES_USER=knboard 5 | POSTGRES_PASSWORD=examplepassword 6 | 7 | DJANGO_SECRET_KEY=01tHOatUVZDm508jsRlEdtwbNuDFnsPFkrBcz2PUGhDWz5H7xO 8 | DJANGO_STATIC_URL=/django-static/ 9 | DJANGO_STATIC_ROOT=/app/django-static/ 10 | DJANGO_SETTINGS_MODULE=config.settings.production 11 | DJANGO_ALLOWED_HOSTS=example.com 12 | DJANGO_ALLOW_GUEST_ACCESS= 13 | -------------------------------------------------------------------------------- /frontend/src/features/toast/__snapshots__/Toast.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should show toast and auto hide 1`] = ` 4 | Array [ 5 | Object { 6 | "payload": undefined, 7 | "type": "toast/clearToast", 8 | }, 9 | ] 10 | `; 11 | 12 | exports[`should show toast and auto hide 2`] = ` 13 | Array [ 14 | Object { 15 | "payload": undefined, 16 | "type": "toast/clearToast", 17 | }, 18 | ] 19 | `; 20 | -------------------------------------------------------------------------------- /ansible/roles/common/tasks/checkout.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Checkout repo 4 | git: 5 | repo: https://github.com/{{ repo_name }}.git 6 | accept_hostkey: yes 7 | dest: "{{ repo_folder }}" 8 | version: "{{ repo_branch }}" 9 | key_file: /root/.ssh/id_rsa 10 | 11 | - name: Copy .env file to remote 12 | copy: 13 | src: "{{ env_file }}" 14 | dest: "{{ repo_folder }}" 15 | owner: root 16 | group: root 17 | mode: u=rw,g=r,o=r 18 | -------------------------------------------------------------------------------- /backend/boards/viewsets.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets, mixins 2 | 3 | 4 | class ModelDetailViewSet( 5 | mixins.CreateModelMixin, 6 | mixins.RetrieveModelMixin, 7 | mixins.UpdateModelMixin, 8 | mixins.DestroyModelMixin, 9 | viewsets.GenericViewSet, 10 | ): 11 | """ 12 | A viewset that provides default `create()`, `retrieve()`, `update()`, 13 | `partial_update()` and `destroy()`` actions. 14 | """ 15 | 16 | pass 17 | -------------------------------------------------------------------------------- /backend/boards/migrations/0011_auto_20200517_1011.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-17 10:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("boards", "0010_auto_20200517_0826"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="board", name="name", field=models.CharField(max_length=50), 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /ansible/roles/nginx/templates/proxy.conf: -------------------------------------------------------------------------------- 1 | proxy_http_version 1.1; 2 | proxy_cache_bypass $http_upgrade; 3 | 4 | proxy_set_header Upgrade $http_upgrade; 5 | proxy_set_header Connection "upgrade"; 6 | proxy_set_header Host $host; 7 | proxy_set_header X-Real-IP $remote_addr; 8 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 9 | proxy_set_header X-Forwarded-Proto $scheme; 10 | proxy_set_header X-Forwarded-Host $host; 11 | proxy_set_header X-Forwarded-Port $server_port; 12 | -------------------------------------------------------------------------------- /backend/boards/migrations/0005_auto_20200406_1433.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-04-06 14:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("boards", "0004_auto_20200329_0919"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="task", name="description", field=models.TextField(blank=True), 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/boards/tests/test_models.py: -------------------------------------------------------------------------------- 1 | def test_board_owner_added_to_members(board_factory, steve, leo): 2 | board = board_factory(owner=steve) 3 | members_ids = [member.id for member in board.members.all()] 4 | assert steve.id in members_ids 5 | assert leo.id not in members_ids 6 | 7 | 8 | def test_str_methods(board, column, task): 9 | assert str(board) == board.name 10 | assert str(column) == column.title 11 | assert str(task) == f"{task.id} - {task.title}" 12 | -------------------------------------------------------------------------------- /backend/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for knboard project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /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/extend-expect"; 6 | import "mutationobserver-shim"; 7 | import "jest-localstorage-mock"; 8 | 9 | import { axiosMock } from "utils/testHelpers"; 10 | 11 | beforeEach(() => { 12 | axiosMock.reset(); 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/src/components/PriorityOption.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, screen } from "@testing-library/react"; 3 | import PriorityOption from "./PriorityOption"; 4 | import { PRIO1 } from "utils/colors"; 5 | 6 | it("should render without errors", () => { 7 | render(); 8 | expect(screen.getByText("High")).toBeVisible(); 9 | expect(screen.getByTestId("priority-icon")).toHaveAttribute("color", PRIO1); 10 | }); 11 | -------------------------------------------------------------------------------- /backend/accounts/migrations/0004_user_is_guest.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-17 16:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("accounts", "0003_auto_20200505_1756"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="user", 15 | name="is_guest", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/boards/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from boards.utils import reverse_querystring 4 | 5 | 6 | def test_reverse_querystring_multiple(): 7 | assert ( 8 | reverse_querystring( 9 | "user-search", query_kwargs={"board": "1", "search": "steve"} 10 | ) 11 | == "/api/u/search/?board=1&search=steve" 12 | ) 13 | 14 | 15 | def test_reverse_querystring_exception_when_no_qparams(): 16 | with pytest.raises(ValueError): 17 | reverse_querystring("user-search") 18 | -------------------------------------------------------------------------------- /backend/boards/migrations/0009_task_labels.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-05-13 15:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("boards", "0008_auto_20200510_1646"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="task", 15 | name="labels", 16 | field=models.ManyToManyField(related_name="tasks", to="boards.Label"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /frontend/src/components/MemberAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from "@material-ui/core"; 2 | import React from "react"; 3 | import { avatarStyles } from "styles"; 4 | import { BoardMember } from "types"; 5 | 6 | interface Props { 7 | member?: BoardMember; 8 | } 9 | 10 | const MemberAvatar = ({ member }: Props) => { 11 | return ( 12 | 13 | {member?.username?.charAt(0) || "-"} 14 | 15 | ); 16 | }; 17 | 18 | export default MemberAvatar; 19 | -------------------------------------------------------------------------------- /backend/boards/admin.py: -------------------------------------------------------------------------------- 1 | from adminsortable.admin import SortableAdmin 2 | from django.contrib import admin 3 | 4 | from .models import Board, Label, Column, Task 5 | 6 | 7 | class LabelAdmin(admin.ModelAdmin): 8 | list_display = ["name", "board"] 9 | list_filter = ["board"] 10 | search_fields = ["name"] 11 | 12 | class Meta: 13 | model = Label 14 | 15 | 16 | admin.site.register(Label, LabelAdmin) 17 | admin.site.register(Board) 18 | admin.site.register(Column, SortableAdmin) 19 | admin.site.register(Task, SortableAdmin) 20 | -------------------------------------------------------------------------------- /backend/boards/migrations/0008_auto_20200510_1646.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-05-10 16:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("boards", "0007_label"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddConstraint( 14 | model_name="label", 15 | constraint=models.UniqueConstraint( 16 | fields=("name", "board"), name="unique_name_board" 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .models import Avatar, User 5 | 6 | 7 | class CustomUserAdmin(UserAdmin): 8 | list_display = ( 9 | "username", 10 | "email", 11 | "first_name", 12 | "last_name", 13 | "is_staff", 14 | "is_guest", 15 | ) 16 | list_filter = ("is_staff", "is_superuser", "is_active", "groups", "is_guest") 17 | 18 | 19 | admin.site.register(Avatar) 20 | admin.site.register(User, CustomUserAdmin) 21 | -------------------------------------------------------------------------------- /backend/accounts/management/commands/db.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core import management 3 | from django.core.management.base import BaseCommand, CommandError 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Helpful command to run both makemigrations and migrate in one go" 8 | 9 | def handle(self, *args, **options): 10 | if not settings.DEBUG: 11 | raise CommandError("Command not meant for production") 12 | 13 | management.call_command("makemigrations") 14 | management.call_command("migrate") 15 | -------------------------------------------------------------------------------- /frontend/src/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Fade, CircularProgress } from "@material-ui/core"; 3 | import { css } from "@emotion/core"; 4 | 5 | interface Props { 6 | loading: boolean; 7 | } 8 | 9 | const Spinner = ({ loading }: Props) => ( 10 | 20 | 21 | 22 | ); 23 | 24 | export default Spinner; 25 | -------------------------------------------------------------------------------- /backend/accounts/migrations/0005_auto_20201006_1436.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1 on 2020-10-06 14:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("accounts", "0004_user_is_guest"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="first_name", 16 | field=models.CharField( 17 | blank=True, max_length=150, verbose_name="first name" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "baseUrl": "src" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Knboard", 3 | "name": "Knboard", 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": "192x177" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x471" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /backend/accounts/management/commands/load.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core import management 3 | from django.core.management.base import BaseCommand, CommandError 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Helpful command to load all fixtures" 8 | 9 | def handle(self, *args, **options): 10 | if not settings.DEBUG: 11 | raise CommandError("Command not meant for production") 12 | 13 | management.call_command("loaddata", "users") 14 | management.call_command("loaddata", "tasks") 15 | management.call_command("loaddata", "avatars") 16 | -------------------------------------------------------------------------------- /frontend/src/utils/localStorage.tsx: -------------------------------------------------------------------------------- 1 | import { LOCAL_STORAGE_KEY } from "const"; 2 | 3 | export const loadState = () => { 4 | try { 5 | const serializedState = localStorage.getItem(LOCAL_STORAGE_KEY); 6 | if (serializedState === null) { 7 | return undefined; 8 | } 9 | return JSON.parse(serializedState); 10 | } catch (err) { 11 | return undefined; 12 | } 13 | }; 14 | 15 | export const saveState = (state: any) => { 16 | const serializedState = JSON.stringify(state); 17 | localStorage.setItem(LOCAL_STORAGE_KEY, serializedState); 18 | }; 19 | 20 | export default { loadState, saveState }; 21 | -------------------------------------------------------------------------------- /frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.settings.editor": "json", 3 | "editor.formatOnSave": true, 4 | "editor.tabSize": 2, 5 | "javascript.format.enable": false, 6 | "typescript.tsdk": "node_modules/typescript/lib", 7 | "eslint.packageManager": "yarn", 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "typescript", 12 | "typescriptreact" 13 | ], 14 | "eslint.workingDirectories": [{ "mode": "auto" }], 15 | "[javascript]": { "editor.formatOnSave": false }, 16 | "[typescript]": { "editor.formatOnSave": false }, 17 | "editor.codeActionsOnSave": { "source.fixAll": true } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/PageError.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Alert } from "@material-ui/lab"; 3 | import styled from "@emotion/styled"; 4 | 5 | const Container = styled.div` 6 | margin-top: 10rem; 7 | display: flex; 8 | justify-content: center; 9 | margin-left: auto; 10 | margin-right: auto; 11 | font-size: 1.25rem; 12 | `; 13 | 14 | interface Props { 15 | children: React.ReactNode; 16 | } 17 | 18 | const PageError = ({ children }: Props) => ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | 26 | export default PageError; 27 | -------------------------------------------------------------------------------- /backend/boards/utils.py: -------------------------------------------------------------------------------- 1 | from django.utils.http import urlencode 2 | from rest_framework.reverse import reverse 3 | 4 | 5 | def reverse_querystring( 6 | view, urlconf=None, args=None, kwargs=None, current_app=None, query_kwargs=None 7 | ): 8 | """ 9 | Custom reverse to handle query strings. 10 | 11 | Usage: 12 | reverse('app.views.my_view', kwargs={'pk': 123}, query_kwargs={'search', 'Steve'}) 13 | """ 14 | base_url = reverse( 15 | view, urlconf=urlconf, args=args, kwargs=kwargs, current_app=current_app 16 | ) 17 | if query_kwargs is None: 18 | raise ValueError("Use reverse()") 19 | 20 | return f"{base_url}?{urlencode(query_kwargs)}" 21 | -------------------------------------------------------------------------------- /backend/boards/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsOwner(permissions.BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | return obj.owner == request.user 7 | 8 | 9 | class IsOwnerForDangerousMethods(permissions.BasePermission): 10 | """ 11 | Object-level permission to only allow owners of an object to delete it. 12 | Assumes the model instance has an `owner` attribute. 13 | """ 14 | 15 | def has_object_permission(self, request, view, obj): 16 | if request.method in ["POST", "PATCH", "DELETE"]: 17 | return obj.owner == request.user 18 | 19 | return request.method in permissions.SAFE_METHODS 20 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == "__main__": 21 | main() 22 | -------------------------------------------------------------------------------- /frontend/src/components/AvatarTag.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Avatar as UserAvatar } from "types"; 3 | import { Chip, Avatar, ChipProps } from "@material-ui/core"; 4 | 5 | interface Option { 6 | id: number; 7 | username: string; 8 | avatar: UserAvatar | null; 9 | } 10 | 11 | interface Props extends ChipProps { 12 | option: Option; 13 | } 14 | 15 | const AvatarTag = ({ option, ...rest }: Props) => { 16 | return ( 17 | } 20 | variant="outlined" 21 | label={option.username} 22 | size="small" 23 | {...rest} 24 | /> 25 | ); 26 | }; 27 | 28 | export default AvatarTag; 29 | -------------------------------------------------------------------------------- /frontend/src/features/auth/Footer.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { fireEvent, render, screen, waitFor } from "@testing-library/react"; 3 | import Footer from "./Footer"; 4 | 5 | it("should open popover and have text visible", () => { 6 | render(