├── .coveragerc ├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .env.template ├── .github └── workflows │ ├── cd_app_update.yml │ ├── ci_backend_lint_and_unit_tests.yml │ ├── docs_publish_vuepress.yml │ ├── ecr_backend.yml │ ├── ecr_backend_nginx.yml │ ├── ecr_frontend.yml │ ├── iac_cdk_actions.yml │ ├── iac_pulumi_actions.yml │ ├── iac_terraform_actions.yml │ ├── k6_load_test.yml │ └── release-please.yml ├── .gitignore ├── .vscode ├── .gitignore └── extension.json ├── CHANGELOG.md ├── Makefile ├── README.md ├── backend ├── .dockerignore ├── .gitignore ├── Dockerfile ├── __init__.py ├── apps │ ├── accounts │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── authentication.py │ │ ├── forms.py │ │ ├── managers.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_customuser_profile_setup_complete_and_more.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── schema.py │ │ ├── serializers.py │ │ ├── tasks.py │ │ ├── templates │ │ │ ├── emails │ │ │ │ ├── account_activation_email.html │ │ │ │ └── email_admins.html │ │ │ ├── profile.html │ │ │ ├── register.html │ │ │ └── registration │ │ │ │ └── login.html │ │ ├── tests │ │ │ ├── test_accounts.py │ │ │ ├── test_accounts_gql.py │ │ │ ├── test_auth.py │ │ │ └── test_drf_fbv.py │ │ ├── tokens.py │ │ ├── urls │ │ │ ├── __init__.py │ │ │ ├── auth │ │ │ │ ├── api_urls.py │ │ │ │ └── mtv_urls.py │ │ │ ├── auth_urls.py │ │ │ └── drf_fbv_urls.py │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── drf_auth_views.py │ │ │ ├── drf_fbv_views.py │ │ │ └── mtv_auth_views.py │ ├── blog │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── factory.py │ │ ├── forms.py │ │ ├── management │ │ │ └── commands │ │ │ │ └── generate_posts.py │ │ ├── managers.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto_20210703_2332.py │ │ │ ├── 0003_auto_20210918_1404.py │ │ │ ├── 0004_alter_post_image.py │ │ │ ├── 0005_alter_post_created_by.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── permissions.py │ │ ├── schema.py │ │ ├── serializers.py │ │ ├── templates │ │ │ ├── blog │ │ │ │ ├── post_confirm_delete.html │ │ │ │ ├── post_create.html │ │ │ │ ├── post_detail.html │ │ │ │ ├── post_edit.html │ │ │ │ └── post_list.html │ │ │ ├── edit_post.html │ │ │ ├── new_post.html │ │ │ ├── post.html │ │ │ ├── post_likes.html │ │ │ ├── post_pagination.html │ │ │ └── posts.html │ │ ├── tests │ │ │ ├── test_blog.py │ │ │ ├── test_blog_cbv.py │ │ │ ├── test_blog_drf_fbvs.py │ │ │ └── test_blog_gql.py │ │ ├── urls │ │ │ ├── cbv_urls.py │ │ │ ├── drf_cbv_urls.py │ │ │ ├── drf_fbv_urls.py │ │ │ └── fbv_urls.py │ │ └── views │ │ │ ├── class_based_views.py │ │ │ ├── drf_cbv_views.py │ │ │ ├── drf_fbv_views.py │ │ │ └── views.py │ ├── chat │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ └── core │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── constants.py │ │ ├── db.py │ │ ├── management │ │ └── commands │ │ │ ├── create_database.py │ │ │ ├── pre_update.py │ │ │ ├── start_celery_beat.py │ │ │ └── start_celery_worker.py │ │ ├── middleware.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── tasks.py │ │ ├── templates │ │ ├── index.html │ │ └── nav.html │ │ ├── tests.py │ │ ├── urls │ │ ├── api_urls.py │ │ └── mtv_urls.py │ │ ├── utils.py │ │ └── views.py ├── assets │ ├── postgres.png │ └── redis.png ├── backend │ ├── __init__.py │ ├── asgi.py │ ├── celery_app.py │ ├── context_processors.py │ ├── schema.py │ ├── settings │ │ ├── __init__.py │ │ ├── aws.py │ │ ├── base.py │ │ ├── development.py │ │ ├── ec2.py │ │ ├── production.py │ │ └── swarm_ec2.py │ ├── storage_backends.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── media │ └── .gitignore ├── notebooks │ ├── ORM_example.ipynb │ ├── README.md │ ├── celery_example.ipynb │ ├── email_debug.ipynb │ ├── m2m_examples.ipynb │ ├── media │ │ └── images │ │ │ └── postgres.png │ └── post_serializer_example.ipynb ├── poetry.lock ├── pyproject.toml ├── pytest.ini ├── requirements.txt ├── requirements_dev.txt ├── scripts │ ├── aws │ │ ├── ecs_exec.sh │ │ ├── ssm_port_forward.sh │ │ └── ssm_start_session.sh │ └── start_prod.sh ├── static │ ├── favicon.ico │ ├── img │ │ ├── django.jpeg │ │ ├── drf_square.png │ │ ├── quasar.png │ │ └── vue3.png │ ├── main.css │ └── openapi │ │ └── schema.yml ├── staticfiles │ └── .gitkeep └── templates │ ├── 403.html │ ├── 404.html │ ├── base.html │ └── swagger-ui.html ├── cli └── main.py ├── cypress ├── cypress.json ├── cypress │ ├── fixtures │ │ ├── example.json │ │ └── redis.png │ ├── integration │ │ ├── createPost.spec.js │ │ ├── graphql.spec.js │ │ ├── quasar │ │ │ ├── createPost.spec.js │ │ │ ├── login.spec.js │ │ │ └── register.spec.js │ │ └── registration.spec.js │ ├── plugins │ │ └── index.js │ ├── screenshots │ │ └── .gitignore │ ├── support │ │ ├── commands.js │ │ ├── index.js │ │ └── utils │ │ │ └── index.js │ └── videos │ │ └── .gitignore └── package.json ├── docker-compose.devcontainer.yml ├── docker-compose.ec2.yml ├── docker-compose.yml ├── iac ├── README.md ├── cdk │ ├── .eslintrc.json │ ├── .gitattributes │ ├── .github │ │ ├── pull_request_template.md │ │ └── workflows │ │ │ ├── build.yml │ │ │ ├── pull-request-lint.yml │ │ │ └── upgrade.yml │ ├── .gitignore │ ├── .mergify.yml │ ├── .npmignore │ ├── .projen │ │ ├── deps.json │ │ ├── files.json │ │ └── tasks.json │ ├── .projenrc.js │ ├── LICENSE │ ├── README.md │ ├── cdk.json │ ├── package.json │ ├── src │ │ ├── ecs │ │ │ ├── configs │ │ │ │ ├── app │ │ │ │ │ └── alpha.json │ │ │ │ └── base │ │ │ │ │ └── dev.json │ │ │ └── index.ts │ │ ├── index.ts │ │ └── main.ts │ ├── test │ │ └── .gitignore │ ├── tsconfig.dev.json │ ├── tsconfig.json │ └── yarn.lock ├── pulumi │ └── live │ │ └── ecs │ │ ├── app │ │ ├── .gitignore │ │ ├── Pulumi.alpha.yaml │ │ ├── Pulumi.gamma.yaml │ │ ├── Pulumi.yaml │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── yarn.lock │ │ └── base │ │ ├── .gitignore │ │ ├── Pulumi.dev.yaml │ │ ├── Pulumi.yaml │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── yarn.lock └── terraform │ ├── .gitignore │ ├── bootstrap │ ├── .gitignore │ ├── README.md │ ├── bootstrap.tfvars.template │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf │ └── live │ └── ecs │ ├── app │ ├── .gitignore │ ├── README.md │ ├── backend.config.example │ ├── envs │ │ ├── .gitignore │ │ ├── alpha.tfvars │ │ ├── beta.tfvars │ │ ├── brian.tfvars │ │ ├── default.tf │ │ ├── sample.tfvars.template │ │ └── test.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf │ └── base │ ├── .gitignore │ ├── envs │ ├── .gitignore │ ├── dev.tfvars │ └── dev.tfvars.example │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf ├── k6 └── script.js ├── nginx ├── backend │ └── prod │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── nginx.conf │ │ ├── script.sh │ │ ├── server.crt │ │ ├── server.csr │ │ ├── server.key │ │ └── v3.ext ├── dev │ ├── Dockerfile │ ├── dev.conf │ └── local.conf ├── devcontainer │ ├── Dockerfile │ └── nginx.conf ├── ec2 │ ├── docker-compose.ec2.init.yml │ ├── entrypoint.sh │ ├── index.html │ ├── init.conf │ ├── nginx.conf │ └── templates │ │ ├── app.conf.template │ │ └── init.conf.template ├── ecs │ ├── Dockerfile │ └── ecs.conf └── prod │ ├── Dockerfile │ └── prod.conf ├── nuxt-app ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.vue ├── assets │ └── css │ │ └── tailwind.css ├── components.json ├── components │ ├── DarkMode.vue │ └── ui │ │ ├── button │ │ ├── Button.vue │ │ └── index.ts │ │ ├── card │ │ ├── Card.vue │ │ ├── CardContent.vue │ │ ├── CardDescription.vue │ │ ├── CardFooter.vue │ │ ├── CardHeader.vue │ │ ├── CardTitle.vue │ │ └── index.ts │ │ ├── dropdown-menu │ │ ├── DropdownMenu.vue │ │ ├── DropdownMenuCheckboxItem.vue │ │ ├── DropdownMenuContent.vue │ │ ├── DropdownMenuGroup.vue │ │ ├── DropdownMenuItem.vue │ │ ├── DropdownMenuLabel.vue │ │ ├── DropdownMenuRadioGroup.vue │ │ ├── DropdownMenuRadioItem.vue │ │ ├── DropdownMenuSeparator.vue │ │ ├── DropdownMenuShortcut.vue │ │ ├── DropdownMenuSub.vue │ │ ├── DropdownMenuSubContent.vue │ │ ├── DropdownMenuSubTrigger.vue │ │ ├── DropdownMenuTrigger.vue │ │ └── index.ts │ │ ├── form │ │ ├── FormControl.vue │ │ ├── FormDescription.vue │ │ ├── FormItem.vue │ │ ├── FormLabel.vue │ │ ├── FormMessage.vue │ │ ├── index.ts │ │ ├── injectionKeys.ts │ │ └── useFormField.ts │ │ ├── input │ │ ├── Input.vue │ │ └── index.ts │ │ ├── label │ │ ├── Label.vue │ │ └── index.ts │ │ ├── navigation-menu │ │ ├── NavigationMenu.vue │ │ ├── NavigationMenuContent.vue │ │ ├── NavigationMenuIndicator.vue │ │ ├── NavigationMenuItem.vue │ │ ├── NavigationMenuLink.vue │ │ ├── NavigationMenuList.vue │ │ ├── NavigationMenuTrigger.vue │ │ ├── NavigationMenuViewport.vue │ │ └── index.ts │ │ ├── popover │ │ ├── Popover.vue │ │ ├── PopoverContent.vue │ │ ├── PopoverTrigger.vue │ │ └── index.ts │ │ ├── table │ │ ├── Table.vue │ │ ├── TableBody.vue │ │ ├── TableCaption.vue │ │ ├── TableCell.vue │ │ ├── TableEmpty.vue │ │ ├── TableFooter.vue │ │ ├── TableHead.vue │ │ ├── TableHeader.vue │ │ ├── TableRow.vue │ │ └── index.ts │ │ ├── tabs │ │ ├── Tabs.vue │ │ ├── TabsContent.vue │ │ ├── TabsList.vue │ │ ├── TabsTrigger.vue │ │ └── index.ts │ │ ├── textarea │ │ ├── Textarea.vue │ │ └── index.ts │ │ └── toast │ │ ├── Toast.vue │ │ ├── ToastAction.vue │ │ ├── ToastClose.vue │ │ ├── ToastDescription.vue │ │ ├── ToastProvider.vue │ │ ├── ToastTitle.vue │ │ ├── ToastViewport.vue │ │ ├── Toaster.vue │ │ ├── index.ts │ │ └── use-toast.ts ├── composables │ ├── useAuth.ts │ ├── useChat.ts │ └── useProfile.ts ├── layouts │ ├── basic.vue │ └── default.vue ├── nuxt.config.ts ├── package-lock.json ├── package.json ├── pages │ ├── Chat │ │ ├── [id].vue │ │ └── index.vue │ ├── Login │ │ └── index.vue │ ├── Profile │ │ └── index.vue │ ├── Signup │ │ └── index.vue │ ├── Verify │ │ └── [uid] │ │ │ └── [token].vue │ ├── Version │ │ └── index.vue │ ├── index.vue │ └── reset-password │ │ └── index.vue ├── plugins │ └── apiBase.ts ├── public │ ├── favicon.ico │ └── robots.txt ├── server │ └── tsconfig.json ├── stores │ ├── authStore.ts │ ├── chatStore.ts │ └── profileStore.ts ├── tailwind.config.js └── tsconfig.json ├── packages ├── app-update │ └── action.yml ├── ecr-run-task │ └── action.yml ├── run-task │ └── action.yml └── test │ └── action.yml ├── quasar-app ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .prettierrc ├── .vscode │ ├── extensions.json │ └── settings.json ├── Dockerfile ├── README.md ├── babel.config.js ├── package.json ├── public │ ├── _redirects │ ├── bigsur.png │ ├── black.png │ ├── bootstrap.png │ ├── cdk.png │ ├── celery.png │ ├── circleci.jpeg │ ├── cloud-init.png │ ├── conventionalcommits.png │ ├── copilot.png │ ├── cypress.png │ ├── diagrams.png │ ├── django.jpeg │ ├── do.png │ ├── docker.png │ ├── drf_square.png │ ├── ecs.png │ ├── elephant.png │ ├── favicon.ico │ ├── focalfossa.png │ ├── ghactions.png │ ├── gitlab.png │ ├── gnu.png │ ├── gql.png │ ├── graphene.png │ ├── graphql.png │ ├── icons │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-167x167.png │ │ ├── apple-icon-180x180.png │ │ ├── favicon-128x128.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── ms-icon-144x144.png │ │ └── safari-pinned-tab.svg │ ├── jazzband.png │ ├── jsii.png │ ├── jupyter.png │ ├── jwt.svg │ ├── k6.png │ ├── k8s.png │ ├── m1.jpeg │ ├── mailhog.png │ ├── minikube.png │ ├── netlify.png │ ├── nginx.png │ ├── openapi.png │ ├── pgadmin.svg │ ├── poetry.png │ ├── portainer.svg │ ├── postgres-alt.svg │ ├── projen.png │ ├── projen.svg │ ├── pulumi.png │ ├── pytest.png │ ├── python.png │ ├── quasar.png │ ├── raspberrypi.png │ ├── redis.png │ ├── sentry.png │ ├── swarm.png │ ├── systemd.png │ ├── terraform.webp │ ├── traefik.png │ ├── ts.png │ ├── ublog.png │ ├── vscode.png │ ├── vue3.png │ ├── vuepress.png │ ├── windows10.png │ └── wsl.png ├── quasar.conf.js ├── src-pwa │ ├── custom-service-worker.js │ ├── pwa-flag.d.ts │ └── register-service-worker.js ├── src-ssr │ └── ssr-flag.d.ts ├── src │ ├── App.vue │ ├── assets │ │ └── quasar-logo-vertical.svg │ ├── boot │ │ ├── .gitkeep │ │ └── axios.ts │ ├── classes │ │ └── index.ts │ ├── components │ │ ├── EssentialLink.vue │ │ ├── LoginForm │ │ │ └── index.vue │ │ ├── NavLink │ │ │ └── index.vue │ │ ├── Post │ │ │ └── index.vue │ │ ├── PostForm │ │ │ └── index.vue │ │ ├── RegistrationForm │ │ │ └── index.vue │ │ ├── TechCard │ │ │ └── index.vue │ │ └── models.ts │ ├── css │ │ ├── app.scss │ │ └── quasar.variables.scss │ ├── data │ │ └── technologies.ts │ ├── env.d.ts │ ├── index.template.html │ ├── layouts │ │ └── MainLayout.vue │ ├── modules │ │ ├── activate.ts │ │ ├── auth.ts │ │ ├── chat.ts │ │ ├── createPost.ts │ │ ├── darkMode.ts │ │ ├── debug.ts │ │ ├── pagination.ts │ │ ├── posts.ts │ │ ├── profile.ts │ │ └── registration.ts │ ├── pages │ │ ├── About │ │ │ └── index.vue │ │ ├── ActivateAccount │ │ │ └── index.vue │ │ ├── Chat │ │ │ └── index.vue │ │ ├── Chats │ │ │ └── index.vue │ │ ├── CreatePost │ │ │ └── index.vue │ │ ├── Debug │ │ │ └── index.vue │ │ ├── Error404.vue │ │ ├── Index.vue │ │ ├── Login │ │ │ └── index.vue │ │ ├── PostDetail │ │ │ └── index.vue │ │ ├── Posts │ │ │ └── index.vue │ │ ├── Profile │ │ │ └── index.vue │ │ ├── Register │ │ │ └── index.vue │ │ └── Technologies │ │ │ └── index.vue │ ├── router │ │ ├── index.ts │ │ └── routes.ts │ ├── shims-vue.d.ts │ ├── store │ │ ├── index.ts │ │ ├── module-example │ │ │ ├── actions.ts │ │ │ ├── getters.ts │ │ │ ├── index.ts │ │ │ ├── mutations.ts │ │ │ └── state.ts │ │ └── store-flag.d.ts │ ├── types │ │ └── index.ts │ └── utils │ │ └── index.ts ├── start_dev.sh ├── tsconfig.json └── yarn.lock └── vuepress-docs ├── .gitignore ├── docs ├── .vuepress │ ├── .gitignore │ ├── config.ts │ ├── configs │ │ ├── index.ts │ │ └── navbar │ │ │ ├── en.ts │ │ │ ├── index.ts │ │ │ └── zh.ts │ └── public │ │ ├── diagrams │ │ ├── django-cdk.png │ │ ├── django-ecs.png │ │ ├── docker-compose.png │ │ ├── docker-swarm-ec2.png │ │ └── jwt-authentication.png │ │ ├── images │ │ ├── docker-swarm-ec2-hero.png │ │ ├── screenshots │ │ │ ├── gh-pages-settings.png │ │ │ └── ublog-screenshot.png │ │ └── ublog.png │ │ └── manifest.webmanifest ├── README.md ├── deploy │ ├── app.md │ ├── aws.md │ ├── aws │ │ ├── cdk.md │ │ ├── pulumi.md │ │ └── terraform.md │ └── github-actions.md ├── intro.md ├── topics │ ├── README.md │ ├── django.md │ ├── docker-compose.md │ ├── nuxt.md │ └── vuepress.md └── zh │ ├── README.md │ └── intro.md ├── package.json └── yarn.lock /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | *__init__* 4 | *asgi.py 5 | *wsgi.py 6 | *manage.py -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerComposeFile": "../docker-compose.devcontainer.yaml", 3 | "service": "backend", 4 | "shutdownAction": "stopCompose", 5 | "features": { 6 | "ghcr.io/devcontainers/features/common-utils:2": { 7 | "version": "latest" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | media 2 | .local-env 3 | .github 4 | .pytest_cache 5 | .vscode 6 | cdk 7 | cdk.out 8 | cypress 9 | docs 10 | k8s 11 | notes 12 | pulumi 13 | vuepress-docs 14 | quasar-app/node_modules 15 | quasar-app/dist 16 | raspberrypi 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.py] 14 | indent_size = 4 15 | 16 | # 4 space indentation 17 | [*.ts] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=sk-proj-ABCD 2 | NVIDIA_API_KEY=nvapi-ABC-123 3 | -------------------------------------------------------------------------------- /.github/workflows/docs_publish_vuepress.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for deploying documentation site to GitHub Pages 2 | name: '[DOCS] github pages' 3 | run-name: '[DOCS] github pages' 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Setup Node 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: "20" 18 | 19 | - name: Cache dependencies 20 | uses: actions/cache@v4 21 | with: 22 | path: ~/.npm 23 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 24 | restore-keys: | 25 | ${{ runner.os }}-node- 26 | 27 | - run: cd vuepress-docs && yarn 28 | - run: cd vuepress-docs && yarn docs:build 29 | 30 | - name: deploy 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./vuepress-docs/docs/.vuepress/dist 35 | publish_branch: master 36 | -------------------------------------------------------------------------------- /.github/workflows/k6_load_test.yml: -------------------------------------------------------------------------------- 1 | # github action to create or update an ad hoc environment 2 | name: '[k6] load test' 3 | run-name: '[k6] load tests for ${{ inputs.url }}' 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | URL: 9 | description: 'URL to use for load test' 10 | required: true 11 | type: string 12 | 13 | jobs: 14 | k6_load_test: 15 | name: k6 Load Test 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Run local k6 test 23 | uses: grafana/k6-action@v0.3.1 24 | env: 25 | BASE_URL: ${{ inputs.URL }} 26 | with: 27 | filename: k6/script.js 28 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: google-github-actions/release-please-action@v3 11 | with: 12 | token: ${{ secrets.GH_PAT }} 13 | release-type: simple 14 | package-name: django-step-by-step 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .local-env/ 2 | .coverage 3 | htmlcov 4 | node_modules 5 | notes 6 | .env 7 | .venv 8 | cdk.out 9 | .raspberrypi.env 10 | cdk.context.json 11 | backend/celerybeat-schedule-* 12 | 13 | # Redis .rdb files 14 | *.rdb 15 | backend/.bash_history 16 | .terraform 17 | -------------------------------------------------------------------------------- /.vscode/.gitignore: -------------------------------------------------------------------------------- 1 | settings.json 2 | dryrun.log 3 | configurationCache.log 4 | targets.log 5 | extension.json 6 | -------------------------------------------------------------------------------- /.vscode/extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "octref.vetur" 6 | ], 7 | "unwantedRecommendations": [ 8 | "hookyqr.beautify", 9 | "dbaeumer.jshint", 10 | "ms-vscode.vscode-typescript-tslint-plugin" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | media -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.3-slim AS base 2 | ENV PYTHONUNBUFFERED=1 \ 3 | PYTHONDONTWRITEBYTECODE=1 \ 4 | PIP_DISABLE_PIP_VERSION_CHECK=1 \ 5 | POETRY_VERSION='2.0.1' 6 | 7 | ARG SOURCE_TAG 8 | ENV SOURCE_TAG=$SOURCE_TAG 9 | 10 | RUN apt-get update && \ 11 | apt-get install -y --no-install-recommends \ 12 | build-essential \ 13 | curl && \ 14 | rm -rf /var/lib/apt/lists/* 15 | 16 | RUN useradd --create-home --home-dir /code --shell /bin/bash app 17 | 18 | WORKDIR /code 19 | RUN pip install "poetry==$POETRY_VERSION" 20 | COPY poetry.lock pyproject.toml /code/ 21 | 22 | FROM base AS prod 23 | RUN curl https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o /usr/local/share/global-bundle.pem 24 | RUN POETRY_VIRTUALENVS_CREATE=false poetry install --only main 25 | COPY . /code 26 | RUN chown -R app:app /code 27 | USER app 28 | 29 | FROM base AS local 30 | RUN apt-get update && apt-get install procps -y # for pkill 31 | RUN POETRY_VIRTUALENVS_CREATE=false poetry install --with test,dev 32 | USER app 33 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-step-by-step/85bc5121c0df47b80c83a96813cb9be7fc9843ba/backend/__init__.py -------------------------------------------------------------------------------- /backend/apps/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-step-by-step/85bc5121c0df47b80c83a96813cb9be7fc9843ba/backend/apps/accounts/__init__.py -------------------------------------------------------------------------------- /backend/apps/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = "apps.accounts" 6 | -------------------------------------------------------------------------------- /backend/apps/accounts/authentication.py: -------------------------------------------------------------------------------- 1 | from rest_framework.authentication import BaseAuthentication 2 | from rest_framework.exceptions import AuthenticationFailed 3 | from django.conf import settings 4 | from rest_framework_simplejwt.tokens import AccessToken 5 | from django.contrib.auth import get_user_model 6 | 7 | User = get_user_model() 8 | 9 | 10 | class HttpOnlyJWTAuthentication(BaseAuthentication): 11 | def authenticate(self, request): 12 | access_token = request.COOKIES.get("access") 13 | if not access_token: 14 | return None # No token, return None to continue with other auth methods 15 | 16 | try: 17 | decoded_token = AccessToken(access_token) 18 | user = User.objects.get(id=decoded_token["user_id"]) 19 | except (User.DoesNotExist, Exception) as e: 20 | raise AuthenticationFailed("Invalid or expired token") 21 | 22 | return (user, None) 23 | -------------------------------------------------------------------------------- /backend/apps/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-step-by-step/85bc5121c0df47b80c83a96813cb9be7fc9843ba/backend/apps/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /backend/apps/accounts/templates/emails/account_activation_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | 4 |
5 | 11 | 12 | 13 |14 | Your email address ({{ user.email }}) was just used to register an account 15 | on {{ domain }}. 16 |
17 | 18 |Please click on the link below to confirm your registration:
19 | 20 | 25 | Confirm Email 26 | 27 | 28 | 29 | 30 | {% endautoescape %} 31 | -------------------------------------------------------------------------------- /backend/apps/accounts/templates/emails/email_admins.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | 3 | 4 | 5 | 6 | 7 | 8 |{{ message }}
9 | 10 | 11 | 12 | 13 | {% endautoescape %} 14 | -------------------------------------------------------------------------------- /backend/apps/accounts/templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block title %}Profile{% endblock %} 5 | 6 | 7 | 8 | {% block content %} 9 |{{ request.user.email }}
14 | 15 |If you see this page, the nginx web server is successfully installed and 14 | working. Further configuration is required.
15 | 16 |For online documentation and support please refer to
17 | nginx.org.
18 | Commercial support is available at
19 | nginx.com.
Thank you for using nginx.
22 | 23 | 24 | -------------------------------------------------------------------------------- /nginx/ec2/init.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-step-by-step/85bc5121c0df47b80c83a96813cb9be7fc9843ba/nginx/ec2/init.conf -------------------------------------------------------------------------------- /nginx/ec2/nginx.conf: -------------------------------------------------------------------------------- 1 | http { 2 | include /etc/nginx/mime.types; 3 | client_max_body_size 100m; 4 | 5 | upstream backend { 6 | server backend:8000; 7 | } 8 | 9 | upstream frontend { 10 | server nuxt-app:3000; 11 | } 12 | 13 | include /etc/nginx/conf.d/*.conf; # Ensure this line is present 14 | } -------------------------------------------------------------------------------- /nginx/ec2/templates/init.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name ${DOMAIN_NAME}; 4 | 5 | location /.well-known/acme-challenge/ { 6 | root /var/www/certbot; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nginx/ecs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 as build-stage 2 | ARG VERSION 3 | ENV VERSION=$VERSION 4 | WORKDIR /app/ 5 | COPY quasar-app/package.json /app/ 6 | RUN npm cache verify 7 | RUN npm rebuild node-sass 8 | RUN npm install -g @quasar/cli 9 | RUN npm install --progress=false 10 | COPY quasar-app /app/ 11 | RUN quasar build -m spa 12 | 13 | FROM --platform=linux/amd64 nginx:1.13.12-alpine as production 14 | COPY nginx/ecs/ecs.conf /etc/nginx/nginx.conf 15 | COPY --from=build-stage /app/dist/spa /dist/ 16 | EXPOSE 80 17 | CMD ["nginx", "-g", "daemon off;"] 18 | -------------------------------------------------------------------------------- /nginx/ecs/ecs.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include /etc/nginx/mime.types; 10 | client_max_body_size 100m; 11 | 12 | server { 13 | listen 80; 14 | charset utf-8; 15 | 16 | root /dist/; 17 | index index.html; 18 | 19 | # frontend 20 | location / { 21 | try_files $uri $uri/ @rewrites; 22 | } 23 | 24 | # frontend rewrites 25 | location @rewrites { 26 | rewrite ^(.+)$ /index.html last; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /nginx/prod/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 as build-stage 2 | ARG VERSION 3 | ENV VERSION=$VERSION 4 | WORKDIR /app/ 5 | COPY quasar-app/package.json /app/ 6 | RUN npm cache verify 7 | RUN npm rebuild node-sass 8 | RUN npm install -g @quasar/cli 9 | RUN npm install --progress=false 10 | COPY quasar-app /app/ 11 | RUN quasar build -m spa 12 | 13 | # ci stage 14 | FROM --platform=linux/amd64 nginx:1.13.12-alpine as production 15 | COPY nginx/prod/prod.conf /etc/nginx/nginx.conf 16 | COPY --from=build-stage /app/dist/spa /dist/ 17 | EXPOSE 80 18 | CMD ["nginx", "-g", "daemon off;"] 19 | -------------------------------------------------------------------------------- /nuxt-app/.dockerignore: -------------------------------------------------------------------------------- 1 | .nuxt 2 | .output 3 | node_modules 4 | 5 | .gitignore 6 | README.md 7 | -------------------------------------------------------------------------------- /nuxt-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /nuxt-app/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base stage 2 | ARG NODE_VERSION=20.18.0 3 | FROM node:${NODE_VERSION}-slim AS base 4 | 5 | ARG SOURCE_TAG 6 | ENV SOURCE_TAG=$SOURCE_TAG 7 | ENV NUXT_PUBLIC_APP_VERSION=$SOURCE_TAG 8 | 9 | ARG PORT=3000 10 | ENV PORT=$PORT 11 | 12 | WORKDIR /src 13 | 14 | 15 | FROM base AS build-prod 16 | 17 | COPY --link package.json package-lock.json ./ 18 | RUN npm install 19 | 20 | COPY --link . . 21 | RUN npm run build 22 | 23 | 24 | FROM base AS prod 25 | 26 | ENV NODE_ENV=production 27 | 28 | COPY --from=build-prod /src/.output /src/.output 29 | 30 | CMD [ "node", ".output/server/index.mjs" ] 31 | 32 | 33 | FROM base AS build-dev 34 | 35 | ENV NODE_ENV=development 36 | 37 | COPY --link package.json package-lock.json ./ 38 | RUN npm install 39 | 40 | 41 | FROM base AS dev 42 | 43 | COPY --from=build-dev /src/node_modules /src/node_modules 44 | 45 | CMD [ "npm", "run", "dev" ] 46 | -------------------------------------------------------------------------------- /nuxt-app/app.vue: -------------------------------------------------------------------------------- 1 | 2 |
12 |
18 |