├── .dockerignore ├── .env.template ├── .gitignore ├── .gitlab-ci.yml ├── .python-version ├── LICENSE ├── README.md ├── architecture.png ├── awscdk ├── .gitignore ├── README.md ├── app.py ├── awscdk │ ├── __init__.py │ ├── alb.py │ ├── app_stack.py │ ├── awscdk.egg-info │ │ ├── PKG-INFO │ │ ├── SOURCES.txt │ │ ├── dependency_links.txt │ │ ├── requires.txt │ │ └── top_level.txt │ ├── backend.py │ ├── backend_assets.py │ ├── backend_tasks.py │ ├── bastion_host.py │ ├── celery_autoscaling.py │ ├── celery_default.py │ ├── cert.py │ ├── cloudfront.py │ ├── ecr.py │ ├── ecs.py │ ├── elasticache.py │ ├── env_vars.py │ ├── flower.py │ ├── hosted_zone.py │ ├── rds.py │ ├── static_site_bucket.py │ └── vpc.py ├── cdk.json ├── manifest.json ├── requirements.txt ├── setup.py ├── source.bat ├── stack.yml └── tree.json ├── awslambda └── publish_celery_metrics.py ├── backend ├── .dockerignore ├── .flake8 ├── .gitignore ├── .isort.cfg ├── apps │ ├── __init__.py │ ├── accounts │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── managers.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto_20201117_0735.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── schema.py │ │ ├── serializers.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils │ │ │ └── social │ │ │ │ ├── __init__.py │ │ │ │ └── oauth.py │ │ └── views.py │ ├── banking │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── fixtures │ │ │ └── test.csv │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto_20200126_1558.py │ │ │ ├── 0003_auto_20200126_1618.py │ │ │ ├── 0004_transaction_source_file.py │ │ │ ├── 0005_auto_20200606_1325.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── tasks.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── core │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── constants.py │ │ ├── consumers.py │ │ ├── management │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ ├── create_default_user.py │ │ │ │ └── watch_celery.py │ │ ├── middleware │ │ │ ├── __init__.py │ │ │ └── healthchecks.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── routing.py │ │ ├── tasks.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── celery_utils.py │ │ │ └── testing_utils.py │ │ └── views.py │ └── hn │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_link_posted_by.py │ │ ├── 0003_vote.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── schema.py │ │ ├── tests.py │ │ └── views.py ├── backend │ ├── __init__.py │ ├── asgi.py │ ├── celery_app.py │ ├── routing.py │ ├── schema.py │ ├── settings │ │ ├── __init__.py │ │ ├── base.py │ │ ├── development.py │ │ ├── gitlab-ci.py │ │ ├── minikube.py │ │ └── production.py │ ├── storage_backends.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── media │ └── .gitignore ├── notebooks │ └── .gitignore ├── pytest.ini ├── requirements │ ├── base.txt │ ├── dev.txt │ └── test.txt ├── scripts │ ├── ci │ │ └── Dockerfile │ ├── dev │ │ ├── Dockerfile │ │ ├── start_asgi.sh │ │ ├── start_beat.sh │ │ ├── start_celery.sh │ │ ├── start_ci.sh │ │ └── start_dev.sh │ └── prod │ │ ├── Dockerfile │ │ └── start_prod.sh ├── static │ └── .gitignore └── templates │ └── .gitignore ├── compose ├── docs.yml ├── minikube.yml └── test.yml ├── cypress.json ├── cypress.yml ├── cypress ├── .gitignore ├── fixtures │ └── example.json ├── integration │ ├── test_api.js │ ├── test_homepage.js │ ├── test_redis_connection.js │ └── test_ws_connection.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── delete_pipelines.sh ├── docker-compose.ci.yml ├── docker-compose.yml ├── documentation ├── .gitignore ├── Dockerfile ├── build_documentation.sh ├── docs │ ├── .vuepress │ │ ├── components │ │ │ └── technology.vue │ │ ├── config.js │ │ ├── public │ │ │ ├── architecture.png │ │ │ ├── cdk.png │ │ │ ├── celery.png │ │ │ ├── django-channels-transparent-bg.png │ │ │ ├── django-channels-white-bg-square.jpg │ │ │ ├── django-channels-white-bg.png │ │ │ ├── django.jpg │ │ │ ├── django1.jpg │ │ │ ├── docker.png │ │ │ ├── ecs.png │ │ │ ├── gitlab.svg │ │ │ ├── postgres.png │ │ │ ├── quasar.png │ │ │ └── vue.png │ │ └── styles │ │ │ └── palette.styl │ ├── README.md │ ├── devops │ │ └── aws-cdk │ │ │ └── README.md │ ├── guide │ │ ├── project-setup │ │ │ └── README.md │ │ └── testing │ │ │ └── README.md │ ├── start │ │ ├── overview │ │ │ └── README.md │ │ └── tools │ │ │ └── README.md │ └── topics │ │ ├── graphql │ │ └── README.md │ │ └── minikube │ │ └── README.md ├── package.json └── start_dev.sh ├── gitlab-ci ├── README.md ├── aws │ ├── app.yml │ ├── cdk.yml │ └── dev.yml ├── documentation.yml └── renovate.yml ├── gitlab-runner ├── .gitignore └── config.toml.template ├── integration-tests.sh ├── kubernetes ├── beat │ └── deployment.yml ├── celery │ └── deployment.yml ├── channels │ ├── deployment.yml │ └── service.yml ├── django │ ├── deployment.yml │ ├── migration.yml │ └── service.yml ├── flower │ ├── deployment.yml │ └── service.yml ├── frontend │ ├── deployment.yml │ └── service.yml ├── ingress.yml ├── postgres │ ├── deployment.yml │ ├── secrets.yml │ ├── service.yml │ ├── volume.yml │ └── volume_claim.yml └── redis │ ├── deployment.yml │ └── service.yml ├── nginx ├── ci │ ├── Dockerfile │ └── ci.conf ├── dev │ ├── Dockerfile │ └── dev.conf ├── flowerproxy │ ├── Dockerfile │ └── proxy.conf └── minikube │ ├── Dockerfile │ └── minikube.conf ├── quasar ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .stylintrc ├── Dockerfile ├── README.md ├── babel.config.js ├── package.json ├── quasar.conf.js ├── src-electron │ └── electron-flag.d.ts ├── src-pwa │ ├── custom-service-worker.js │ ├── pwa-flag.d.ts │ └── register-service-worker.js ├── src │ ├── App.vue │ ├── assets │ │ ├── quasar-logo-full.svg │ │ └── sad.svg │ ├── boot │ │ ├── .gitkeep │ │ ├── apollo.js │ │ ├── axios.js │ │ ├── components.js │ │ ├── i18n.js │ │ └── websockets.js │ ├── components │ │ ├── .gitkeep │ │ ├── LeftMenuLink.vue │ │ ├── MainCarousel.vue │ │ ├── auth │ │ │ ├── LoginForm.vue │ │ │ └── SignUpForm.vue │ │ ├── services │ │ │ └── ServiceLink.vue │ │ └── ui │ │ │ ├── BaseBtn.vue │ │ │ ├── BaseCard.vue │ │ │ ├── BaseDate.vue │ │ │ ├── BaseInput.vue │ │ │ ├── BasePage.vue │ │ │ ├── DarkMode.vue │ │ │ ├── PageHeader.vue │ │ │ ├── PageSubHeader.vue │ │ │ └── PageText.vue │ ├── css │ │ ├── app.styl │ │ └── quasar.variables.styl │ ├── i18n │ │ ├── cn-cn │ │ │ ├── index.js │ │ │ └── leftDrawer │ │ │ │ └── index.js │ │ ├── en-us │ │ │ ├── index.js │ │ │ └── leftDrawer │ │ │ │ └── index.js │ │ └── index.js │ ├── index.template.html │ ├── layouts │ │ ├── MainLayout.vue │ │ └── primary │ │ │ ├── MainHeader.vue │ │ │ └── MainLeftDrawer.vue │ ├── pages │ │ ├── About.vue │ │ ├── Auth │ │ │ ├── Callback.vue │ │ │ ├── GitHub.vue │ │ │ ├── Google.vue │ │ │ ├── GqlLoginForm.vue │ │ │ ├── Login.vue │ │ │ ├── LoginGQL.vue │ │ │ └── SignUp.vue │ │ ├── Banking │ │ │ ├── StatementFileTable.vue │ │ │ ├── StatementUploadForm.vue │ │ │ ├── StatementUploadWidget.vue │ │ │ └── index.vue │ │ ├── Celery │ │ │ └── index.vue │ │ ├── Environment.vue │ │ ├── Error404.vue │ │ ├── Examples │ │ │ ├── Redis.vue │ │ │ ├── Websockets.vue │ │ │ └── index.vue │ │ ├── HackerNewsClone │ │ │ ├── HackerNewsLink.vue │ │ │ ├── UploadForm.vue │ │ │ └── index.vue │ │ ├── Index.vue │ │ ├── Logout.vue │ │ ├── Services │ │ │ ├── index.vue │ │ │ └── services.js │ │ ├── Transactions │ │ │ ├── TransactionsTable.vue │ │ │ └── index.vue │ │ └── todos.js │ ├── router │ │ ├── index.js │ │ └── routes.js │ ├── statics │ │ ├── app-logo-128x128.png │ │ └── icons │ │ │ ├── apple-icon-120x120.png │ │ │ ├── apple-icon-152x152.png │ │ │ ├── apple-icon-167x167.png │ │ │ ├── apple-icon-180x180.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── favicon.ico │ │ │ ├── icon-128x128.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-256x256.png │ │ │ ├── icon-384x384.png │ │ │ ├── icon-512x512.png │ │ │ ├── ms-icon-144x144.png │ │ │ └── safari-pinned-tab.svg │ ├── store │ │ ├── auth │ │ │ ├── gqljwt.js │ │ │ └── index.js │ │ ├── banking │ │ │ ├── index.js │ │ │ ├── statements │ │ │ │ └── index.js │ │ │ ├── transactions │ │ │ │ └── index.js │ │ │ └── upload │ │ │ │ └── index.js │ │ ├── hn │ │ │ ├── index.js │ │ │ └── upload.js │ │ ├── index.js │ │ ├── module-example │ │ │ ├── actions.js │ │ │ ├── getters.js │ │ │ ├── index.js │ │ │ ├── mutations.js │ │ │ └── state.js │ │ ├── social │ │ │ └── index.js │ │ ├── store-flag.d.ts │ │ ├── ui │ │ │ └── index.js │ │ └── user │ │ │ └── index.js │ └── utils │ │ └── oauth.js ├── start_dev.sh └── yarn.lock ├── renovate.json ├── renovate └── config.js └── stack.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | backend/media/* 2 | documentation/ 3 | cloudformation/ 4 | .vscode 5 | cypress/videos/ 6 | gitlab-runner 7 | kubernetes 8 | node_modules 9 | **/node_modules 10 | notes -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | SECRET_KEY=secret 2 | DEBUG=True 3 | 4 | POSTGRES_NAME=postgres 5 | POSTGRES_USERNAME=postgres 6 | POSTGRES_PASSWORD=postgres 7 | POSTGRES_SERVICE_HOST=postgres 8 | POSTGRES_SERVICE_PORT=5432 9 | 10 | 11 | # social auth 12 | GITHUB_KEY=yourkey 13 | GITHUB_SECRET=yoursecret 14 | 15 | GOOGLE_OAUTH2_KEY=12345-abcde.apps.googleusercontent.com 16 | GOOGLE_OAUTH2_SECRET=abcde-fghij 17 | 18 | SOCIAL_AUTH_FACEBOOK_KEY = '' 19 | SOCIAL_AUTH_FACEBOOK_SECRET = '' 20 | 21 | CELERY_BROKER_URL=redis://redis:6379/0 22 | CELERY_RESULT_BACKEND=redis://redis:6379/1 23 | 24 | DJANGO_EMAIL_HOST=mailhog 25 | DJANGO_EMAIL_PORT=1025 26 | 27 | # for local development, change ``localhost`` to your 28 | # local IP, such as ``196.168.1.16`` 29 | DOMAIN_NAME=localhost 30 | 31 | # Environment Variables for VuePress 32 | CI_PROJECT_URL=https://gitlab.com/verbose-equals-true/django-postgres-vue-gitlab-ecs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | scratchpad 3 | .vscode 4 | .python-version 5 | *.code-workspace 6 | .coverage 7 | htmlcov/ 8 | node_modules/ 9 | package-lock.json 10 | *.ipynb 11 | .ipynb_checkpoints/ 12 | wiki/ 13 | backend/.coverage.* 14 | backend/celerybeat.pid 15 | backend/celerybeat-schedule 16 | .env 17 | .pytest_cache 18 | .~lock.* 19 | notes/ 20 | .vscode 21 | cdk.out -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - renovate 3 | - documentation 4 | - test 5 | - build 6 | - integration 7 | - release 8 | - deploy 9 | 10 | include: 11 | - local: /gitlab-ci/documentation.yml 12 | - local: /gitlab-ci/renovate.yml 13 | - local: /gitlab-ci/aws/cdk.yml 14 | - local: /gitlab-ci/aws/dev.yml 15 | - local: /gitlab-ci/aws/app.yml 16 | 17 | .Pytest: 18 | image: python:3.8 19 | stage: test 20 | services: 21 | - postgres:11.5 22 | - redis:5.0 23 | variables: 24 | DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/postgres" 25 | DJANGO_SETTINGS_MODULE: "backend.settings.gitlab-ci" 26 | SECRET_KEY: "secret" 27 | STACK_NAME: "placeholder" 28 | DEBUG: "True" 29 | before_script: 30 | - cd backend 31 | - pip install -r requirements/test.txt 32 | - pip install -r requirements/base.txt 33 | script: 34 | - flake8 35 | - black -l 79 -S --check . 36 | - pytest --cov 37 | after_script: 38 | - echo "Pytest tests complete" 39 | coverage: "/TOTAL.+ ([0-9]{1,3}%)/" 40 | 41 | .Jest: 42 | image: node:12.19.0 43 | stage: test 44 | before_script: 45 | - cd quasar 46 | - npm install --progress=false 47 | script: 48 | - npm run lint 49 | - npm run test 50 | after_script: 51 | - echo "Jest tests complete" 52 | coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/' 53 | 54 | # gitlab-runner exec docker "e2e cypress tests without docker-compose" 55 | .e2e: 56 | stage: integration 57 | image: docker:19.03.13 58 | only: 59 | - master 60 | variables: 61 | DOCKER_HOST: tcp://docker:2375 62 | DOCKER_DRIVER: overlay2 63 | services: 64 | - docker:19.03.13-dind 65 | before_script: 66 | - apk add --update py-pip 67 | - pip install docker-compose~=1.23.0 68 | script: 69 | - sh integration-tests.sh 70 | artifacts: 71 | paths: 72 | - cypress/videos/ 73 | - tests/screenshots/ 74 | expire_in: 2 days 75 | 76 | 77 | # # use this test with gitlab-runner locally 78 | # .e2e-local: 79 | # <<: *cypress 80 | # image: localhost:5000/backend:latest 81 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | briancaffey -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Brian Caffey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/architecture.png -------------------------------------------------------------------------------- /awscdk/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Welcome to your CDK Python project! 3 | 4 | This is a blank project for Python development with CDK. 5 | 6 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 7 | 8 | This project is set up like a standard Python project. The initialization 9 | process also creates a virtualenv within this project, stored under the .env 10 | directory. To create the virtualenv it assumes that there is a `python3` 11 | (or `python` for Windows) executable in your path with access to the `venv` 12 | package. If for any reason the automatic creation of the virtualenv fails, 13 | you can create the virtualenv manually. 14 | 15 | To manually create a virtualenv on MacOS and Linux: 16 | 17 | ``` 18 | $ python3 -m venv .env 19 | ``` 20 | 21 | After the init process completes and the virtualenv is created, you can use the following 22 | step to activate your virtualenv. 23 | 24 | ``` 25 | $ source .env/bin/activate 26 | ``` 27 | 28 | If you are a Windows platform, you would activate the virtualenv like this: 29 | 30 | ``` 31 | % .env\Scripts\activate.bat 32 | ``` 33 | 34 | Once the virtualenv is activated, you can install the required dependencies. 35 | 36 | ``` 37 | $ pip install -r requirements.txt 38 | ``` 39 | 40 | At this point you can now synthesize the CloudFormation template for this code. 41 | 42 | ``` 43 | $ cdk synth 44 | ``` 45 | 46 | To add additional dependencies, for example other CDK libraries, just add 47 | them to your `setup.py` file and rerun the `pip install -r requirements.txt` 48 | command. 49 | 50 | ## Useful commands 51 | 52 | * `cdk ls` list all stacks in the app 53 | * `cdk synth` emits the synthesized CloudFormation template 54 | * `cdk deploy` deploy this stack to your default AWS account/region 55 | * `cdk diff` compare deployed stack with current state 56 | * `cdk docs` open CDK documentation 57 | 58 | Enjoy! 59 | -------------------------------------------------------------------------------- /awscdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | from aws_cdk import core 5 | 6 | from awscdk.app_stack import ApplicationStack 7 | 8 | # naming conventions, also used for ACM certs, DNS Records, resource naming 9 | # Dynamically generated resource names created in CDK are used in GitLab CI 10 | # such as cluster name, task definitions, etc. 11 | environment_name = f"{os.environ.get('ENVIRONMENT', 'dev')}" 12 | base_domain_name = os.environ.get("DOMAIN_NAME", "mysite.com") 13 | # if the the production environent subdomain should nott be included in the URL 14 | # redefine `full_domain_name` to `base_domain_name` for that environment 15 | full_domain_name = f"{environment_name}.{base_domain_name}" # dev.mysite.com 16 | if environment_name == "app": 17 | full_domain_name = base_domain_name 18 | base_app_name = os.environ.get("APP_NAME", "mysite-com") 19 | full_app_name = f"{environment_name}-{base_app_name}" # dev-mysite-com 20 | aws_region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1") 21 | 22 | 23 | app = core.App() 24 | stack = ApplicationStack( 25 | app, 26 | f"{full_app_name}-stack", 27 | environment_name=environment_name, 28 | base_domain_name=base_domain_name, 29 | full_domain_name=full_domain_name, 30 | base_app_name=base_app_name, 31 | full_app_name=full_app_name, 32 | env={"region": aws_region}, 33 | ) 34 | 35 | # in order to be able to tag ECS resources, you need to go to 36 | # the ECS Console > Account Settings > Amazon ECS ARN and resource ID settings 37 | # and enable at least Service and Task. Optionally enable 38 | # CloudWatch Container Insights 39 | stack.node.apply_aspect(core.Tag("StackName", full_app_name)) 40 | 41 | app.synth() 42 | -------------------------------------------------------------------------------- /awscdk/awscdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/awscdk/awscdk/__init__.py -------------------------------------------------------------------------------- /awscdk/awscdk/alb.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_ec2 as ec2, 3 | aws_elasticloadbalancingv2 as elbv2, 4 | aws_cloudformation as cloudformation, 5 | core, 6 | ) 7 | 8 | 9 | class AlbStack(cloudformation.NestedStack): 10 | def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | self.alb = elbv2.ApplicationLoadBalancer( 14 | self, "ALB", internet_facing=True, vpc=scope.vpc 15 | ) 16 | 17 | self.alb.connections.allow_from_any_ipv4( 18 | ec2.Port.tcp(80), "Internet access ALB 80" 19 | ) 20 | 21 | self.alb.connections.allow_from_any_ipv4( 22 | ec2.Port.tcp(443), "Internet access ALB 443" 23 | ) 24 | 25 | self.listener = self.alb.add_listener( 26 | "ALBListener", port=80, open=True 27 | ) 28 | 29 | self.https_listener = self.alb.add_listener( 30 | "HTTPSListener", 31 | port=443, 32 | certificates=[scope.certificate], 33 | open=True, 34 | ) 35 | 36 | # self.listener.add_redirect_response( 37 | # 'RedirectNonHttpsTraffic', status_code="HTTP_301", port="443" 38 | # ) 39 | 40 | self.default_target_group = elbv2.ApplicationTargetGroup( 41 | self, 42 | "DefaultTargetGroup", 43 | port=80, 44 | protocol=elbv2.ApplicationProtocol.HTTP, 45 | vpc=scope.vpc, 46 | target_type=elbv2.TargetType.IP, 47 | ) 48 | 49 | self.listener.add_target_groups( 50 | "DefaultTargetGroup", target_groups=[self.default_target_group] 51 | ) 52 | 53 | self.https_listener.add_target_groups( 54 | "HTTPSDefaultTargetGroup", 55 | target_groups=[self.default_target_group], 56 | ) 57 | -------------------------------------------------------------------------------- /awscdk/awscdk/awscdk.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | awscdk/awscdk.egg-info/PKG-INFO 4 | awscdk/awscdk.egg-info/SOURCES.txt 5 | awscdk/awscdk.egg-info/dependency_links.txt 6 | awscdk/awscdk.egg-info/requires.txt 7 | awscdk/awscdk.egg-info/top_level.txt -------------------------------------------------------------------------------- /awscdk/awscdk/awscdk.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /awscdk/awscdk/awscdk.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | aws-cdk.core==1.42.0 2 | aws-cdk.aws_cloudformation==1.42.0 3 | aws-cdk.aws_autoscaling==1.42.0 4 | aws-cdk.aws_applicationautoscaling==1.42.0 5 | aws-cdk.aws_certificatemanager==1.42.0 6 | aws-cdk.aws_cloudwatch==1.42.0 7 | aws-cdk.aws_logs==1.42.0 8 | aws-cdk.aws_lambda==1.42.0 9 | aws-cdk.aws_events==1.42.0 10 | aws-cdk.aws_events_targets==1.42.0 11 | aws-cdk.aws_secretsmanager==1.42.0 12 | aws-cdk.aws_route53==1.42.0 13 | aws-cdk.aws_s3==1.42.0 14 | aws_cdk.aws_s3_deployment==1.42.0 15 | aws-cdk.aws_cloudfront==1.42.0 16 | aws-cdk.aws_route53_targets==1.42.0 17 | aws-cdk.aws_ecr==1.42.0 18 | aws-cdk.aws_ec2==1.42.0 19 | aws-cdk.aws_rds==1.42.0 20 | aws-cdk.aws_ssm==1.42.0 21 | aws-cdk.aws_elasticache==1.42.0 22 | aws-cdk.aws_elasticloadbalancingv2==1.42.0 23 | aws-cdk.aws_ecs==1.42.0 24 | aws-cdk.aws_ecs_patterns==1.42.0 25 | aws-cdk.aws_autoscaling==1.42.0 26 | aws-cdk.aws_sqs==1.42.0 27 | -------------------------------------------------------------------------------- /awscdk/awscdk/awscdk.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /awscdk/awscdk/backend_assets.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_iam as iam, 3 | aws_s3 as s3, 4 | core, 5 | aws_cloudformation as cloudformation, 6 | ) 7 | 8 | 9 | class BackendAssetsStack(cloudformation.NestedStack): 10 | def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | self.assets_bucket = s3.Bucket( 14 | self, "AssetsBucket", bucket_name=f"{scope.full_app_name}-assets" 15 | ) 16 | 17 | self.policy_statement = iam.PolicyStatement( 18 | actions=["s3:GetObject"], 19 | resources=[f"{self.assets_bucket.bucket_arn}/static/*"], 20 | ) 21 | 22 | self.policy_statement.add_any_principal() 23 | 24 | self.static_site_policy_document = iam.PolicyDocument( 25 | statements=[self.policy_statement] 26 | ) 27 | 28 | self.assets_bucket.add_to_resource_policy(self.policy_statement) 29 | -------------------------------------------------------------------------------- /awscdk/awscdk/celery_autoscaling.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | core, 3 | aws_ec2 as ec2, 4 | aws_ecs as ecs, 5 | aws_events as events, 6 | aws_lambda, 7 | aws_events_targets as events_targets, 8 | aws_logs as logs, 9 | aws_cloudformation as cloudformation, 10 | ) 11 | 12 | 13 | class CeleryAutoscalingStack(cloudformation.NestedStack): 14 | def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: 15 | super().__init__( 16 | scope, id, **kwargs, 17 | ) 18 | 19 | self.lambda_function = aws_lambda.Function( 20 | self, 21 | "CeleryMetricsLambdaFunction", 22 | code=aws_lambda.Code.asset("awslambda"), 23 | handler="publish_celery_metrics.lambda_handler", 24 | runtime=aws_lambda.Runtime.PYTHON_3_7, 25 | environment=scope.variables.regular_variables, 26 | ) 27 | 28 | self.celery_default_cw_metric_schedule = events.Rule( 29 | self, 30 | "CeleryDefaultCWMetricSchedule", 31 | schedule=events.Schedule.rate(core.Duration.minutes(5)), 32 | targets=[ 33 | events_targets.LambdaFunction(handler=self.lambda_function) 34 | ], 35 | ) 36 | 37 | # TODO: refactor this to loop through CloudWatch metrics multiple celery queues 38 | scope.celery_default_service.default_celery_queue_cw_metric.grant_put_metric_data( 39 | scope.backend_service.backend_task.task_role 40 | ) 41 | -------------------------------------------------------------------------------- /awscdk/awscdk/cert.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import ( 4 | core, 5 | aws_certificatemanager as acm, 6 | ) 7 | 8 | 9 | class SiteCertificate(acm.Certificate): 10 | def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: 11 | super().__init__( 12 | scope, 13 | id, 14 | domain_name=scope.full_domain_name, 15 | validation_method=acm.ValidationMethod.DNS, 16 | **kwargs, 17 | ) 18 | -------------------------------------------------------------------------------- /awscdk/awscdk/ecr.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import core, aws_ecr as ecr 2 | 3 | 4 | class ElasticContainerRepo(ecr.Repository): 5 | def __init__( 6 | self, scope: core.Construct, id: str, domain_name: str, **kwargs 7 | ) -> None: 8 | super().__init__( 9 | scope, id, repository_name=f"{domain_name}/backend", 10 | ) 11 | -------------------------------------------------------------------------------- /awscdk/awscdk/ecs.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | aws_ecs as ecs, 3 | aws_cloudformation as cloudformation, 4 | aws_ec2 as ec2, 5 | core, 6 | ) 7 | 8 | 9 | class EcsStack(cloudformation.NestedStack): 10 | def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | self.cluster = ecs.Cluster( 14 | self, 15 | "EcsCluster", 16 | vpc=scope.vpc, 17 | cluster_name=f"{scope.full_app_name}-cluster", 18 | ) 19 | -------------------------------------------------------------------------------- /awscdk/awscdk/elasticache.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import ( 4 | core, 5 | aws_ec2 as ec2, 6 | aws_cloudformation as cloudformation, 7 | aws_elasticache as elasticache, 8 | ) 9 | 10 | 11 | class ElastiCacheStack(cloudformation.NestedStack): 12 | def __init__(self, scope: core.Construct, id: str, **kwargs): 13 | super().__init__(scope, id, **kwargs) 14 | 15 | self.elasticache_security_group = ec2.CfnSecurityGroup( 16 | self, 17 | "ElastiCacheSecurityGroup", 18 | vpc_id=scope.vpc.vpc_id, 19 | group_description="ElastiCacheSecurityGroup", 20 | security_group_ingress=[ 21 | ec2.CfnSecurityGroup.IngressProperty( 22 | ip_protocol="tcp", 23 | to_port=6379, 24 | from_port=6379, 25 | source_security_group_id=scope.vpc.vpc_default_security_group, 26 | ) 27 | ], 28 | ) 29 | 30 | self.elasticache_subnet_group = elasticache.CfnSubnetGroup( 31 | self, 32 | "CfnSubnetGroup", 33 | subnet_ids=scope.vpc.select_subnets( 34 | subnet_type=ec2.SubnetType.ISOLATED 35 | ).subnet_ids, 36 | description="The subnet group for ElastiCache", 37 | ) 38 | 39 | self.elasticache = elasticache.CfnCacheCluster( 40 | self, 41 | "ElastiCacheClusterRedis", 42 | cache_node_type="cache.t2.micro", 43 | engine="redis", 44 | num_cache_nodes=1, 45 | vpc_security_group_ids=[ 46 | self.elasticache_security_group.get_att("GroupId").to_string() 47 | ], 48 | cache_subnet_group_name=self.elasticache_subnet_group.ref, # noqa 49 | ) 50 | -------------------------------------------------------------------------------- /awscdk/awscdk/env_vars.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import core, aws_secretsmanager as secrets, aws_ecs as ecs 4 | 5 | 6 | class Variables(core.Construct): 7 | def __init__( 8 | self, 9 | scope: core.Construct, 10 | id: str, 11 | bucket_name: str, 12 | postgres_host: str, 13 | redis_host: str, 14 | db_secret: secrets.ISecret, 15 | full_domain_name: str, 16 | **kwargs, 17 | ) -> None: 18 | super().__init__( 19 | scope, id, **kwargs, 20 | ) 21 | 22 | self.django_secret_key = secrets.Secret( 23 | self, 24 | "DjangoSecretKey", 25 | generate_secret_string=secrets.SecretStringGenerator( 26 | exclude_punctuation=True, include_space=False, 27 | ), 28 | ) 29 | 30 | self.regular_variables = { 31 | "DJANGO_SETTINGS_MODULE": "backend.settings.production", 32 | "DEBUG": "", 33 | "FULL_DOMAIN_NAME": full_domain_name, 34 | "FULL_APP_NAME": scope.full_app_name, 35 | "CELERY_METRICS_TOKEN": "my-secret-token", 36 | "AWS_STORAGE_BUCKET_NAME": bucket_name, 37 | "POSTGRES_SERVICE_HOST": postgres_host, 38 | "POSTGRES_PASSWORD": db_secret.secret_value_from_json( 39 | "password" 40 | ).to_string(), 41 | "SECRET_KEY": os.environ.get( 42 | "SECRET_KEY", "mysecretkey123" 43 | ), # self.django_secret_key.to_string(), 44 | "REDIS_SERVICE_HOST": redis_host, 45 | } 46 | 47 | self.secret_variables = { 48 | "DJANGO_SECRET_KEY": ecs.Secret.from_secrets_manager( 49 | self.django_secret_key 50 | ), 51 | } 52 | -------------------------------------------------------------------------------- /awscdk/awscdk/hosted_zone.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import core, aws_route53 as route53 4 | 5 | 6 | class HostedZone(core.Construct): 7 | def __init__(self, scope: core.Construct, id: str, **kwargs): 8 | super().__init__(scope, id, **kwargs) 9 | 10 | self.hosted_zone = route53.HostedZone.from_hosted_zone_attributes( 11 | self, 12 | "hosted_zone", 13 | hosted_zone_id=os.environ.get("HOSTED_ZONE_ID", "ABC123"), 14 | zone_name=os.environ.get("DOMAIN_NAME", "mysite.com"), 15 | ) 16 | -------------------------------------------------------------------------------- /awscdk/awscdk/static_site_bucket.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import ( 4 | aws_s3 as s3, 5 | aws_s3_deployment as s3_deployment, 6 | aws_iam as iam, 7 | aws_cloudformation as cloudformation, 8 | core, 9 | ) 10 | 11 | 12 | class StaticSiteStack(cloudformation.NestedStack): 13 | def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None: 14 | super().__init__(scope, id, **kwargs) 15 | 16 | self.static_site_bucket = s3.Bucket( 17 | self, 18 | "StaticSiteBucket", 19 | access_control=s3.BucketAccessControl.PUBLIC_READ, 20 | bucket_name=f"{scope.full_app_name}-frontend", 21 | removal_policy=core.RemovalPolicy.DESTROY, 22 | website_index_document="index.html", 23 | website_error_document="index.html", 24 | ) 25 | 26 | self.policy_statement = iam.PolicyStatement( 27 | actions=["s3:GetObject"], 28 | resources=[f"{self.static_site_bucket.bucket_arn}/*"], 29 | ) 30 | 31 | self.policy_statement.add_any_principal() 32 | 33 | self.static_site_policy_document = iam.PolicyDocument( 34 | statements=[self.policy_statement] 35 | ) 36 | 37 | self.static_site_bucket.add_to_resource_policy(self.policy_statement) 38 | -------------------------------------------------------------------------------- /awscdk/awscdk/vpc.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import core 2 | 3 | from aws_cdk import aws_ec2 as ec2, aws_cloudformation as cloudformation 4 | 5 | 6 | class VpcStack(cloudformation.NestedStack): 7 | def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: 8 | super().__init__(scope, id, **kwargs) 9 | 10 | self.vpc = ec2.Vpc( 11 | self, 12 | "Vpc", 13 | max_azs=2, 14 | cidr="10.0.0.0/16", 15 | nat_gateways=0, 16 | subnet_configuration=[ 17 | ec2.SubnetConfiguration( 18 | subnet_type=ec2.SubnetType.PUBLIC, 19 | name="Public", 20 | cidr_mask=24, 21 | ), 22 | ec2.SubnetConfiguration( 23 | subnet_type=ec2.SubnetType.ISOLATED, 24 | name="Isolated", 25 | cidr_mask=24, 26 | ), 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /awscdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py" 3 | } 4 | -------------------------------------------------------------------------------- /awscdk/requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | -------------------------------------------------------------------------------- /awscdk/source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .env/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .env\Scripts\activate.bat for you 13 | .env\Scripts\activate.bat 14 | -------------------------------------------------------------------------------- /awslambda/publish_celery_metrics.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import urllib.request 4 | 5 | FULL_DOMAIN_NAME = os.environ.get("FULL_DOMAIN_NAME") 6 | CELERY_METRICS_PATH = "api/celery-metrics/" 7 | 8 | CELERY_METRICS_URL = f"https://{FULL_DOMAIN_NAME}/{CELERY_METRICS_PATH}" 9 | CELERY_METRICS_TOKEN = os.environ.get("CELERY_METRICS_TOKEN") 10 | 11 | 12 | def lambda_handler(event, context): 13 | data = {"celery_metrics_token": CELERY_METRICS_TOKEN} 14 | params = json.dumps(data).encode('utf8') 15 | req = urllib.request.Request( 16 | CELERY_METRICS_URL, 17 | data=params, 18 | headers={'content-type': 'application/json'}, 19 | ) 20 | response = urllib.request.urlopen(req) 21 | return response.read() 22 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | data/* 2 | static/* 3 | backup.json 4 | media/* 5 | notebooks/* 6 | .pytest_cache/ -------------------------------------------------------------------------------- /backend/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | */migrations/* 4 | notebooks 5 | ignore = C812, E226, C819, W503, C815, E203, E266, E501 6 | select = B,C,E,F,W,T4,B9 7 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .coverage 3 | 4 | celerybeat-schedule 5 | celerybeat.pid -------------------------------------------------------------------------------- /backend/.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | multi_line_output=3 3 | line_length=60 4 | known_third_party=rest_framework_simplejwt 5 | -------------------------------------------------------------------------------- /backend/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/__init__.py -------------------------------------------------------------------------------- /backend/apps/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/accounts/__init__.py -------------------------------------------------------------------------------- /backend/apps/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .forms import CustomUserChangeForm, CustomUserCreationForm 5 | from .models import CustomUser 6 | 7 | 8 | class CustomUserAdmin(UserAdmin): 9 | add_form = CustomUserCreationForm 10 | form = CustomUserChangeForm 11 | model = CustomUser 12 | list_display = ( 13 | "email", 14 | "is_staff", 15 | "is_active", 16 | ) 17 | list_filter = ( 18 | "email", 19 | "is_staff", 20 | "is_active", 21 | ) 22 | fieldsets = ( 23 | (None, {"fields": ("email", "password")}), 24 | ("Permissions", {"fields": ("is_staff", "is_active")},), 25 | ) 26 | add_fieldsets = ( 27 | ( 28 | None, 29 | { 30 | "classes": ("wide",), 31 | "fields": ("email", "password1", "password2", "is_staff", "is_active",), 32 | }, 33 | ), 34 | ) 35 | search_fields = ("email",) 36 | ordering = ("email",) 37 | 38 | 39 | admin.site.register(CustomUser, CustomUserAdmin) 40 | -------------------------------------------------------------------------------- /backend/apps/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = "accounts" 6 | -------------------------------------------------------------------------------- /backend/apps/accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 2 | 3 | from .models import CustomUser 4 | 5 | 6 | class CustomUserCreationForm(UserCreationForm): 7 | class Meta(UserCreationForm): 8 | model = CustomUser 9 | fields = ("email",) 10 | 11 | 12 | class CustomUserChangeForm(UserChangeForm): 13 | class Meta: 14 | model = CustomUser 15 | fields = ("email",) 16 | -------------------------------------------------------------------------------- /backend/apps/accounts/managers.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | from django.contrib.auth.base_user import BaseUserManager 5 | from django.utils.translation import ugettext_lazy as _ 6 | 7 | 8 | def make_password(size=10, chars=string.ascii_uppercase + string.digits): 9 | return "".join(random.choice(chars) for _ in range(size)) 10 | 11 | 12 | class CustomUserManager(BaseUserManager): 13 | """ 14 | Custom user model manager where email is the unique identifiers 15 | for authentication instead of usernames. 16 | """ 17 | 18 | def create_user(self, email, password=None, **extra_fields): 19 | """ 20 | Create and save a User with the given email and password. 21 | """ 22 | if not email: 23 | raise ValueError(_("The Email must be set")) 24 | if not password: 25 | # if user is created with social auth 26 | # generate random password 27 | password = make_password() 28 | email = self.normalize_email(email) 29 | user = self.model(email=email, **extra_fields) 30 | user.set_password(password) 31 | user.save() 32 | return user 33 | 34 | def create_superuser(self, email, password, **extra_fields): 35 | """ 36 | Create and save a SuperUser with the given email and password. 37 | """ 38 | extra_fields.setdefault("is_staff", True) 39 | extra_fields.setdefault("is_superuser", True) 40 | extra_fields.setdefault("is_active", True) 41 | 42 | if extra_fields.get("is_staff") is not True: 43 | raise ValueError(_("Superuser must have is_staff=True.")) 44 | if extra_fields.get("is_superuser") is not True: 45 | raise ValueError(_("Superuser must have is_superuser=True.")) 46 | return self.create_user(email, password, **extra_fields) 47 | -------------------------------------------------------------------------------- /backend/apps/accounts/migrations/0002_auto_20201117_0735.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.2 on 2020-11-17 07:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("accounts", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="customuser", 15 | name="first_name", 16 | field=models.CharField( 17 | blank=True, max_length=150, verbose_name="first name" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/apps/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /backend/apps/accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | from .managers import CustomUserManager 6 | 7 | 8 | class CustomUser(AbstractUser): 9 | class Meta: 10 | verbose_name = _("user") 11 | verbose_name_plural = _("users") 12 | 13 | username = None 14 | email = models.EmailField(_("email address"), unique=True) 15 | 16 | USERNAME_FIELD = "email" 17 | REQUIRED_FIELDS = [] 18 | 19 | objects = CustomUserManager() 20 | 21 | def __str__(self): 22 | return self.email 23 | -------------------------------------------------------------------------------- /backend/apps/accounts/schema.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | from django.contrib.auth import get_user_model 3 | from graphene_django import DjangoObjectType 4 | 5 | 6 | class UserType(DjangoObjectType): 7 | class Meta: 8 | model = get_user_model() 9 | 10 | 11 | class Query(graphene.ObjectType): 12 | current_user = graphene.Field(UserType) 13 | users = graphene.List(UserType) 14 | 15 | def resolve_users(self, info): 16 | return get_user_model().objects.all() 17 | 18 | def resolve_current_user(self, info): 19 | user = info.context.user 20 | print(dir(info.context)) 21 | if user.is_anonymous: 22 | raise Exception("Not logged in...") 23 | 24 | return user 25 | 26 | 27 | class CreateUser(graphene.Mutation): 28 | user = graphene.Field(UserType) 29 | 30 | class Arguments: 31 | password = graphene.String(required=True) 32 | email = graphene.String(required=True) 33 | 34 | def mutate(self, info, password, email): 35 | user = get_user_model()(email=email) 36 | user.set_password(password) 37 | user.save() 38 | 39 | return CreateUser(user=user) 40 | 41 | 42 | class Mutation(graphene.ObjectType): 43 | create_user = CreateUser.Field() 44 | -------------------------------------------------------------------------------- /backend/apps/accounts/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from rest_framework import serializers 3 | 4 | User = get_user_model() 5 | 6 | 7 | class UserSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = User 10 | fields = ("id", "email", "is_staff", "is_superuser") 11 | -------------------------------------------------------------------------------- /backend/apps/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | from . import views 4 | 5 | 6 | urlpatterns = [ 7 | path("login-set-cookie/", views.login_set_cookie, name="login-view"), 8 | path("login/", views.login_view, name="login-view"), 9 | path("logout/", views.logout_view, name="logout-view"), 10 | path("users/profile/", views.Profile.as_view(), name="user-profile",), 11 | # Social Auth Callbacks 12 | path("social//", views.exchange_token, name="social-auth",), 13 | ] 14 | 15 | urlpatterns += [ 16 | path("api-auth/", include("rest_framework.urls")), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/apps/accounts/utils/social/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/accounts/utils/social/__init__.py -------------------------------------------------------------------------------- /backend/apps/banking/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/banking/__init__.py -------------------------------------------------------------------------------- /backend/apps/banking/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import StatementFile, Transaction 4 | 5 | # Register your models here. 6 | 7 | 8 | admin.site.register(StatementFile) 9 | admin.site.register(Transaction) 10 | -------------------------------------------------------------------------------- /backend/apps/banking/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BankingConfig(AppConfig): 5 | name = "banking" 6 | -------------------------------------------------------------------------------- /backend/apps/banking/fixtures/test.csv: -------------------------------------------------------------------------------- 1 | Posted Date,Reference Number,Payee,Address,Amount 2 | 12/12/2019,2.48031793457268E+022,HONEY GROW # 100 PHILADELPHIA PA,PHILADELPHIA PA ,-11.99 3 | -------------------------------------------------------------------------------- /backend/apps/banking/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-01-26 06:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="StatementFile", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("statement_file", models.FileField(upload_to=""),), 26 | ("month", models.DateField()), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name="Transaction", 31 | fields=[ 32 | ( 33 | "id", 34 | models.AutoField( 35 | auto_created=True, 36 | primary_key=True, 37 | serialize=False, 38 | verbose_name="ID", 39 | ), 40 | ), 41 | ("date", models.DateField()), 42 | ("description", models.CharField(max_length=1000),), 43 | ("location", models.CharField(max_length=1000),), 44 | ("amount", models.FloatField()), 45 | ], 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /backend/apps/banking/migrations/0002_auto_20200126_1558.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-01-26 15:58 2 | 3 | import django.core.files.storage 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("banking", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="statementfile", 16 | name="statement_file", 17 | field=models.FileField( 18 | storage=django.core.files.storage.FileSystemStorage(location="/code/"), 19 | upload_to="", 20 | ), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/apps/banking/migrations/0003_auto_20200126_1618.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-01-26 16:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("banking", "0002_auto_20200126_1558"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="statementfile", 15 | name="statement_file", 16 | field=models.FileField(upload_to=""), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/apps/banking/migrations/0004_transaction_source_file.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-01-26 16:49 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("banking", "0003_auto_20200126_1618"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="transaction", 16 | name="source_file", 17 | field=models.ForeignKey( 18 | default=11, 19 | on_delete=django.db.models.deletion.CASCADE, 20 | to="banking.StatementFile", 21 | ), 22 | preserve_default=False, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/apps/banking/migrations/0005_auto_20200606_1325.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-06 13:25 2 | 3 | import backend.storage_backends 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("banking", "0004_transaction_source_file"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="statementfile", 16 | name="statement_file", 17 | field=models.FileField( 18 | storage=backend.storage_backends.PrivateMediaStorage, 19 | upload_to="banking", 20 | ), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/apps/banking/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/banking/migrations/__init__.py -------------------------------------------------------------------------------- /backend/apps/banking/models.py: -------------------------------------------------------------------------------- 1 | from backend.storage_backends import PrivateMediaStorage 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | 7 | 8 | class StatementFile(models.Model): 9 | statement_file = models.FileField(storage=PrivateMediaStorage, upload_to="banking") 10 | month = models.DateField(null=False, blank=False) 11 | 12 | def __str__(self): 13 | return self.statement_file.name 14 | 15 | 16 | class Transaction(models.Model): 17 | source_file = models.ForeignKey(StatementFile, on_delete=models.CASCADE,) 18 | date = models.DateField() 19 | description = models.CharField(max_length=1000) 20 | location = models.CharField(max_length=1000) 21 | amount = models.FloatField(null=False, blank=False) 22 | -------------------------------------------------------------------------------- /backend/apps/banking/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from .models import StatementFile, Transaction 4 | 5 | 6 | class TransactionSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Transaction 9 | fields = "__all__" 10 | 11 | 12 | class StatementFileSerializer(serializers.ModelSerializer): 13 | 14 | id = serializers.IntegerField(required=False) 15 | 16 | class Meta: 17 | model = StatementFile 18 | fields = ("month", "statement_file", "id") 19 | -------------------------------------------------------------------------------- /backend/apps/banking/tasks.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import datetime 3 | from io import StringIO 4 | 5 | import celery 6 | 7 | from apps.banking.models import StatementFile, Transaction 8 | 9 | from backend.celery_app import app 10 | 11 | 12 | class BaseTask(celery.Task): 13 | pass 14 | 15 | 16 | @app.task(bind=True, base=BaseTask) 17 | def process_statement_file(self, statement_file_id): 18 | 19 | """ 20 | This functions accepts one argument: the ID of a statement file 21 | It then opens the file, creates transactions and then uploads all 22 | of the transactions with one bulk create database operation 23 | """ 24 | 25 | statement_file = StatementFile.objects.get(id=statement_file_id).statement_file 26 | file_data = statement_file.read().decode("utf-8") 27 | csv_data = csv.DictReader(StringIO(file_data), delimiter=",") 28 | 29 | transactions = [] 30 | for row in csv_data: 31 | date = datetime.datetime.strptime(row["Posted Date"], "%m/%d/%Y") 32 | description = row["Payee"] 33 | address = row["Address"] 34 | amount = row["Amount"] 35 | transaction = Transaction( 36 | date=date, 37 | description=description, 38 | location=address, 39 | amount=amount, 40 | source_file_id=statement_file_id, 41 | ) 42 | transactions.append(transaction) 43 | 44 | Transaction.objects.bulk_create(transactions) 45 | return 46 | -------------------------------------------------------------------------------- /backend/apps/banking/tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | 5 | from django.core.files.uploadedfile import SimpleUploadedFile 6 | from django.test import override_settings 7 | from django.urls import reverse 8 | 9 | from apps.core import constants as c 10 | from apps.core.utils.testing_utils import login 11 | 12 | from .models import StatementFile, Transaction 13 | 14 | 15 | @override_settings(CELERY_TASK_ALWAYS_EAGER=True) 16 | @pytest.mark.django_db(transaction=True) 17 | def test_upload_statement_file(): 18 | 19 | client = login() 20 | 21 | filename = "test.csv" 22 | file_path = "apps/banking/fixtures/" + filename 23 | 24 | form = {"month": "2019-01-19"} 25 | 26 | with open(file_path, "rb") as fp: 27 | tmp_file = SimpleUploadedFile( 28 | filename, fp.read(), content_type="multipart/form-data", 29 | ) 30 | data = {"form": json.dumps(form), "file": tmp_file} 31 | 32 | url = reverse("statements") 33 | client.post( 34 | f"{c.TEST_BASE_URL}{url}", data, format="multipart", 35 | ) 36 | 37 | assert StatementFile.objects.all().count() == 1 38 | assert Transaction.objects.all().count() == 1 39 | -------------------------------------------------------------------------------- /backend/apps/banking/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path( 7 | "transactions/", 8 | views.TransactionViewSet.as_view({"get": "get"}), 9 | name="transactions", 10 | ), 11 | path( 12 | "statements/", 13 | views.StatementViewSet.as_view({"get": "get", "post": "post"}), 14 | name="statements", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /backend/apps/core/__init__.py: -------------------------------------------------------------------------------- 1 | from backend.celery_app import app as celery_app 2 | 3 | __all__ = ("celery_app",) 4 | -------------------------------------------------------------------------------- /backend/apps/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin # noqa 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /backend/apps/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = "core" 6 | -------------------------------------------------------------------------------- /backend/apps/core/constants.py: -------------------------------------------------------------------------------- 1 | # https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#web-application-flow 2 | # https://developers.google.com/identity/protocols/OpenIDConnect 3 | 4 | OAUTH = { 5 | "github": {"name": "github", "url": "https://github.com/login/oauth/access_token"}, 6 | "google-oauth2": { 7 | "name": "google-oauth2", 8 | "url": "https://oauth2.googleapis.com/token", 9 | }, 10 | "facebook": { 11 | "name": "facebook", 12 | "url": "https://graph.facebook.com/v5.0/oauth/access_token", 13 | }, 14 | } 15 | 16 | TEST_BASE_URL = "http://localhost" 17 | -------------------------------------------------------------------------------- /backend/apps/core/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/core/management/commands/__init__.py -------------------------------------------------------------------------------- /backend/apps/core/management/commands/create_default_user.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.contrib.auth import get_user_model 4 | from django.core.management.base import BaseCommand 5 | 6 | 7 | class Command(BaseCommand): 8 | help = "Creates a default superuser for local development" 9 | 10 | def handle(self, *args, **options): 11 | User = get_user_model() 12 | password = os.environ.get("SUPERUSER_PASSWORD", "password") 13 | if not User.objects.all(): 14 | print("Creating default user") 15 | User.objects.create_superuser( 16 | email="admin@company.com", password=password, 17 | ) 18 | print( 19 | """ 20 | Default user created: 21 | email: 'admin@company.com' 22 | """ 23 | ) 24 | else: 25 | print("Not creating default user") 26 | -------------------------------------------------------------------------------- /backend/apps/core/management/commands/watch_celery.py: -------------------------------------------------------------------------------- 1 | """ 2 | This command allows for celery to be reloaded when project 3 | code is saved. This command is called in 4 | `docker-compose.dev.yml` and is only for use in development 5 | 6 | https://avilpage.com/2017/05/how-to-auto-reload-celery-workers-in-development.html 7 | """ 8 | 9 | import os 10 | import shlex 11 | import subprocess 12 | 13 | from django.core.management.base import BaseCommand 14 | from django.utils import autoreload 15 | 16 | 17 | def restart_celery(queue=None, concurrency=None): 18 | cmd = "pkill -9 celery" 19 | subprocess.call(shlex.split(cmd)) 20 | cmd = f"celery worker --app=backend.celery_app:app --loglevel=info -Q {queue} -n worker-{queue}@%h --concurrency={os.environ.get('CONCURRENT_WORKERS', 2)} --max-memory-per-child=150000" # noqa 21 | subprocess.call(shlex.split(cmd)) 22 | 23 | 24 | class Command(BaseCommand): 25 | def add_arguments(self, parser): 26 | parser.add_argument("-q", "--queue", nargs=1, default="celery", type=str) 27 | parser.add_argument("-c", "--concurrency", type=str) 28 | 29 | def handle(self, *args, **options): 30 | queue = options["queue"][0] 31 | concurrency = options["concurrency"] or 1 32 | print("Starting celery worker with autoreload...") 33 | autoreload.run_with_reloader( 34 | restart_celery, queue=queue, concurrency=concurrency 35 | ) 36 | -------------------------------------------------------------------------------- /backend/apps/core/middleware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/core/middleware/__init__.py -------------------------------------------------------------------------------- /backend/apps/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/core/migrations/__init__.py -------------------------------------------------------------------------------- /backend/apps/core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models # noqa 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /backend/apps/core/routing.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import consumers 4 | 5 | websocket_urlpatterns = [ 6 | url(r"^ws/ping-pong/$", consumers.CoreConsumer.as_asgi(),), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/apps/core/tasks.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import celery 4 | from django.core.mail import send_mail 5 | 6 | from backend.celery_app import app 7 | 8 | 9 | # http://docs.celeryproject.org/en/latest/userguide/tasks.html#task-inheritance 10 | class BaseTask(celery.Task): 11 | pass 12 | 13 | 14 | @app.task(bind=True, base=BaseTask) 15 | def debug_task(self): 16 | time.sleep(10) 17 | 18 | 19 | @app.task(bind=True, base=BaseTask) 20 | def debug_periodic_task(self): 21 | print("Periodic task complete") 22 | 23 | 24 | @app.task(bind=True, base=BaseTask) 25 | def send_test_email_task(self): 26 | send_mail( 27 | "Email subject", 28 | "Email message.", 29 | "from@example.com", 30 | ["to@example.com"], 31 | fail_silently=False, 32 | ) 33 | 34 | 35 | @app.task(bind=True, base=BaseTask) 36 | def sleep_task(self, seconds): 37 | time.sleep(int(seconds)) 38 | return f"Slept {seconds} seconds" 39 | -------------------------------------------------------------------------------- /backend/apps/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("health-check/", views.health_check, name="health-check"), 7 | path("celery-metrics/", views.celery_metrics, name="celery-metrics"), 8 | path("celery/sleep-task/", views.sleep_task_view, name="sleep-task"), 9 | path("debug/send-test-email/", views.send_test_email, name="send-test-email",), 10 | path( 11 | "debug/redis/", 12 | views.DebugRedis.as_view({"get": "get", "post": "post", "delete": "delete"}), 13 | name="debug-redis", 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /backend/apps/core/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/core/utils/__init__.py -------------------------------------------------------------------------------- /backend/apps/core/utils/testing_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for tests 3 | """ 4 | 5 | from channels.db import database_sync_to_async 6 | from django.contrib.auth import get_user_model 7 | from rest_framework.test import APIClient 8 | from rest_framework_simplejwt.tokens import AccessToken 9 | 10 | User = get_user_model() 11 | 12 | EMAIL = "admin@company.com" 13 | PASSWORD = "5Mr6IUPOFjuL" 14 | 15 | 16 | def token_for_new_user(): 17 | email, password = EMAIL, PASSWORD 18 | user = User.objects.create_user(email=email, password=password) 19 | token = AccessToken.for_user(user) 20 | return token 21 | 22 | 23 | def login(): 24 | client = APIClient() 25 | token = token_for_new_user() 26 | client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}") 27 | return client 28 | 29 | 30 | @database_sync_to_async 31 | def channels_login(): 32 | return token_for_new_user() 33 | -------------------------------------------------------------------------------- /backend/apps/core/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.http import JsonResponse 5 | from rest_framework import viewsets 6 | from rest_framework.decorators import ( 7 | api_view, 8 | permission_classes, 9 | authentication_classes, 10 | ) 11 | 12 | 13 | from .utils.celery_utils import publish_celery_metrics 14 | from apps.core.tasks import send_test_email_task, sleep_task 15 | 16 | r = settings.REDIS 17 | 18 | 19 | class DebugRedis(viewsets.ViewSet): 20 | def get(self, request): 21 | count = None 22 | 23 | value = r.get("cached_value") 24 | 25 | if value: 26 | count = value 27 | return JsonResponse({"count": count}) 28 | 29 | def post(self, request): 30 | new_count = int(request.data["count"]) 31 | r.set("cached_value", new_count) 32 | new_count = r.get("cached_value") 33 | return JsonResponse({"count": new_count}) 34 | 35 | def delete(self, request): 36 | r.delete("cached_value") 37 | return JsonResponse({"count": r.get("cached_value")}) 38 | 39 | 40 | def health_check(request): 41 | response = JsonResponse({"message": "OK"}) 42 | return response 43 | 44 | 45 | @api_view(["POST"]) 46 | def sleep_task_view(request): 47 | sleep_seconds = request.data.get("seconds") 48 | sleep_task.apply_async([sleep_seconds], queue=settings.CELERY_QUEUE_DEFAULT) 49 | return JsonResponse({"message": f"Sleep task submitted ({sleep_seconds} seconds)"}) 50 | 51 | 52 | @api_view(["POST"]) 53 | @permission_classes([]) 54 | @authentication_classes([]) 55 | def celery_metrics(request): 56 | if request.data.get("celery_metrics_token") == os.environ.get( 57 | "CELERY_METRICS_TOKEN" 58 | ): 59 | published_celery_metrics = publish_celery_metrics() 60 | return JsonResponse(published_celery_metrics) 61 | else: 62 | return JsonResponse({"message": "Unauthorized"}, status=401) 63 | 64 | 65 | def send_test_email(request): 66 | send_test_email_task.delay() 67 | return JsonResponse({"message": "Success"}) 68 | -------------------------------------------------------------------------------- /backend/apps/hn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/hn/__init__.py -------------------------------------------------------------------------------- /backend/apps/hn/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Link 5 | 6 | admin.site.register(Link) 7 | -------------------------------------------------------------------------------- /backend/apps/hn/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HnConfig(AppConfig): 5 | name = "hn" 6 | -------------------------------------------------------------------------------- /backend/apps/hn/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-02-11 04:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Link", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("url", models.URLField()), 26 | ("description", models.TextField(blank=True),), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/apps/hn/migrations/0002_link_posted_by.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-02-11 17:06 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("hn", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="link", 18 | name="posted_by", 19 | field=models.ForeignKey( 20 | null=True, 21 | on_delete=django.db.models.deletion.CASCADE, 22 | to=settings.AUTH_USER_MODEL, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/apps/hn/migrations/0003_vote.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-02-11 18:51 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("hn", "0002_link_posted_by"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Vote", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ( 29 | "link", 30 | models.ForeignKey( 31 | on_delete=django.db.models.deletion.CASCADE, 32 | related_name="votes", 33 | to="hn.Link", 34 | ), 35 | ), 36 | ( 37 | "user", 38 | models.ForeignKey( 39 | on_delete=django.db.models.deletion.CASCADE, 40 | to=settings.AUTH_USER_MODEL, 41 | ), 42 | ), 43 | ], 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /backend/apps/hn/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/apps/hn/migrations/__init__.py -------------------------------------------------------------------------------- /backend/apps/hn/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | # Create your models here. 5 | 6 | 7 | class Link(models.Model): 8 | url = models.URLField() 9 | description = models.TextField(blank=True) 10 | posted_by = models.ForeignKey( 11 | settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE, 12 | ) 13 | 14 | 15 | class Vote(models.Model): 16 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 17 | link = models.ForeignKey(Link, related_name="votes", on_delete=models.CASCADE) 18 | -------------------------------------------------------------------------------- /backend/apps/hn/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/apps/hn/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /backend/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/backend/__init__.py -------------------------------------------------------------------------------- /backend/backend/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI entrypoint. Configures Django and then runs the application 3 | defined in the ASGI_APPLICATION setting. 4 | """ 5 | 6 | import os 7 | 8 | import django 9 | from channels.routing import get_default_application 10 | 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") 12 | django.setup() 13 | application = get_default_application() 14 | -------------------------------------------------------------------------------- /backend/backend/celery_app.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | from django.conf import settings # noqa | needs to be after os env 3 | 4 | app = Celery("backend") 5 | app.config_from_object("django.conf:settings", namespace="CELERY") 6 | 7 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 8 | -------------------------------------------------------------------------------- /backend/backend/routing.py: -------------------------------------------------------------------------------- 1 | from channels.auth import AuthMiddlewareStack 2 | from channels.routing import ProtocolTypeRouter, URLRouter 3 | from django.core.asgi import get_asgi_application 4 | 5 | import apps.core.routing 6 | 7 | application = ProtocolTypeRouter( 8 | { 9 | # Empty for now (http->django views is added by default) 10 | "http": get_asgi_application(), 11 | "websocket": AuthMiddlewareStack( 12 | URLRouter(apps.core.routing.websocket_urlpatterns) 13 | ), 14 | } 15 | ) 16 | -------------------------------------------------------------------------------- /backend/backend/schema.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | import graphql_jwt 3 | 4 | import apps.accounts.schema 5 | import apps.hn.schema 6 | 7 | 8 | class Query( 9 | apps.accounts.schema.Query, apps.hn.schema.Query, graphene.ObjectType, 10 | ): 11 | pass 12 | 13 | 14 | class Mutation( 15 | apps.accounts.schema.Mutation, apps.hn.schema.Mutation, graphene.ObjectType, 16 | ): 17 | token_auth = graphql_jwt.ObtainJSONWebToken.Field() 18 | verify_token = graphql_jwt.Verify.Field() 19 | refresh_token = graphql_jwt.Refresh.Field() 20 | 21 | 22 | schema = graphene.Schema(query=Query, mutation=Mutation) 23 | -------------------------------------------------------------------------------- /backend/backend/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/backend/backend/settings/__init__.py -------------------------------------------------------------------------------- /backend/backend/settings/development.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | 3 | SECRET_KEY = "my-secret-key" 4 | 5 | DEBUG_APPS = ["django_extensions", "debug_toolbar"] 6 | 7 | INSTALLED_APPS += DEBUG_APPS # noqa 8 | 9 | MIDDLEWARE = [ 10 | "apps.core.middleware.healthchecks.HealthCheckMiddleware", 11 | "debug_toolbar.middleware.DebugToolbarMiddleware", 12 | ] + MIDDLEWARE # noqa 13 | 14 | 15 | def show_toolbar(request): 16 | return True 17 | 18 | 19 | DEBUG_TOOLBAR_CONFIG = { 20 | "SHOW_TOOLBAR_CALLBACK": show_toolbar, 21 | } 22 | 23 | log_level = "DEBUG" 24 | 25 | LOGGING = { 26 | "version": 1, 27 | "disable_existing_loggers": False, 28 | "handlers": {"console": {"class": "logging.StreamHandler",},}, # noqa 29 | "loggers": { 30 | "django": { 31 | "handlers": ["console"], 32 | "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"), # noqa 33 | }, 34 | "portal": { 35 | "handlers": ["console"], 36 | "level": os.getenv("PORTAL_LOG_LEVEL", log_level), # noqa 37 | }, 38 | }, 39 | } 40 | 41 | EMAIL_USE_TLS = False 42 | 43 | NOTEBOOK_ARGUMENTS = [ 44 | "--ip", 45 | "0.0.0.0", 46 | "--allow-root", 47 | "--no-browser", 48 | ] 49 | -------------------------------------------------------------------------------- /backend/backend/settings/gitlab-ci.py: -------------------------------------------------------------------------------- 1 | """ 2 | Settings file for GitLab CI 3 | 4 | This file inherits from `backend/backend/settings.py` 5 | """ 6 | 7 | from .base import * # noqa 8 | 9 | ASGI_APPLICATION = "backend.routing.application" 10 | 11 | INSTALLED_APPS = DJANGO_APPS + PROJECT_APPS + THIRD_PARTY_APPS # noqa # noqa 12 | 13 | DATABASES = { 14 | "default": { 15 | "ENGINE": "django.db.backends.postgresql_psycopg2", 16 | "NAME": "postgres", 17 | "USER": "postgres", 18 | "PASSWORD": "postgres", 19 | "HOST": "postgres", 20 | "PORT": "5432", 21 | }, 22 | } 23 | 24 | MIDDLEWARE = [ 25 | "corsheaders.middleware.CorsMiddleware", 26 | "django.middleware.security.SecurityMiddleware", 27 | "django.contrib.sessions.middleware.SessionMiddleware", 28 | "social_django.middleware.SocialAuthExceptionMiddleware", 29 | "django.middleware.common.CommonMiddleware", 30 | # disabled to simplify testing 31 | # 'django.middleware.csrf.CsrfViewMiddleware', 32 | "django.contrib.auth.middleware.AuthenticationMiddleware", 33 | "django.contrib.messages.middleware.MessageMiddleware", 34 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 35 | ] 36 | 37 | 38 | STATIC_URL = "/" 39 | 40 | STATIC_ROOT = "/static/" 41 | -------------------------------------------------------------------------------- /backend/backend/settings/minikube.py: -------------------------------------------------------------------------------- 1 | from .development import * # noqa 2 | 3 | print("loading minikube settings...") 4 | 5 | DATABASES = { 6 | "default": { 7 | "ENGINE": "django.db.backends.postgresql_psycopg2", 8 | "NAME": os.environ.get("POSTGRES_NAME", "kubernetes_django"), # noqa 9 | "USER": os.environ.get("POSTGRES_USER", "postgres"), # noqa 10 | "PASSWORD": os.environ.get("POSTGRES_PASSWORD", "postgres"), # noqa 11 | "HOST": os.environ.get("POSTGRES_SERVICE_HOST", "postgres"), # noqa 12 | "PORT": os.environ.get("POSTGRES_SERVICE_PORT", 5432), # noqa 13 | } 14 | } 15 | 16 | # Django Channels 17 | 18 | CHANNEL_LAYERS = { 19 | "default": { 20 | "BACKEND": "channels_redis.core.RedisChannelLayer", 21 | "CONFIG": {"hosts": [(os.environ.get("REDIS_SERVICE_HOST"), 6379,)],}, # noqa 22 | }, 23 | } 24 | 25 | # Celery Configuration 26 | 27 | CELERY_BROKER_URL = f"redis://{os.environ.get('REDIS_SERVICE_HOST')}/1" # noqa 28 | CELERY_RESULT_BACKEND = f"redis://{os.environ.get('REDIS_SERVICE_HOST')}/1" # noqa 29 | -------------------------------------------------------------------------------- /backend/backend/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | 3 | # import requests 4 | 5 | ALLOWED_HOSTS = ["*"] # noqa 6 | 7 | # https://stackoverflow.com/questions/49828259/when-deploying-django-into-aws-fargate-how-do-you-add-the-local-ip-into-allowed 8 | # METADATA_URI = os.environ['ECS_CONTAINER_METADATA_URI'] # noqa 9 | # container_metadata = requests.get(METADATA_URI).json() 10 | # ALLOWED_HOSTS.append(container_metadata['Networks'][0]['IPv4Addresses'][0]) 11 | 12 | # Email 13 | 14 | EMAIL_USE_TLS = True 15 | EMAIL_HOST_USER = os.environ.get( # noqa 16 | "DJANGO_EMAIL_HOST_USER", "user@gmail.com" 17 | ) # noqa 18 | EMAIL_HOST_PASSWORD = os.environ.get( # noqa 19 | "DJANGO_EMAIL_HOST_PASSWORD", "emailpassword" 20 | ) # noqa 21 | 22 | # AWS S3 Static Files 23 | 24 | STATICFILES_STORAGE = "backend.storage_backends.StaticStorage" 25 | STATIC_URL = "/static/" 26 | MEDIA_URL = "/media/" 27 | FULL_DOMAIN_NAME = os.environ.get("FULL_DOMAIN_NAME", "dev.mysite.com") # noqa 28 | AWS_S3_CUSTOM_DOMAIN = FULL_DOMAIN_NAME 29 | 30 | STATIC_ROOT = f"//{FULL_DOMAIN_NAME}/{STATIC_URL}/" 31 | MEDIA_ROOT = f"//{FULL_DOMAIN_NAME}/{MEDIA_URL}/" 32 | 33 | 34 | # Logging 35 | 36 | log_level = "INFO" 37 | 38 | LOGGING = { 39 | "version": 1, 40 | "disable_existing_loggers": False, 41 | "handlers": {"console": {"class": "logging.StreamHandler",},}, # noqa 42 | "loggers": { 43 | "django": { 44 | "handlers": ["console"], 45 | "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"), # noqa # noqa 46 | }, 47 | "portal": { 48 | "handlers": ["console"], 49 | "level": os.getenv("PORTAL_LOG_LEVEL", log_level), # noqa # noqa 50 | }, 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /backend/backend/storage_backends.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.files.storage import FileSystemStorage 3 | from storages.backends.s3boto3 import S3Boto3Storage 4 | 5 | 6 | class StaticStorage(S3Boto3Storage): 7 | location = settings.AWS_STATIC_LOCATION 8 | 9 | 10 | if settings.DEBUG: 11 | 12 | class PrivateMediaStorage(FileSystemStorage): 13 | location = settings.AWS_PRIVATE_MEDIA_LOCATION 14 | 15 | 16 | else: 17 | 18 | class PrivateMediaStorage(S3Boto3Storage): 19 | location = settings.AWS_PRIVATE_MEDIA_LOCATION 20 | default_acl = "private" 21 | file_overwrite = False 22 | custom_domain = False 23 | -------------------------------------------------------------------------------- /backend/backend/urls.py: -------------------------------------------------------------------------------- 1 | """backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls.static import static 18 | from django.contrib import admin 19 | from django.urls import include, path 20 | from django.views.decorators.csrf import csrf_exempt 21 | from graphene_django.views import GraphQLView 22 | 23 | urlpatterns = [ 24 | path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=settings.DEBUG)),), 25 | path("admin/", admin.site.urls), 26 | path("api/", include("apps.accounts.urls")), 27 | path("api/", include("apps.core.urls")), 28 | path("api/", include("apps.banking.urls")), 29 | ] 30 | 31 | 32 | if settings.DEBUG: 33 | import debug_toolbar # noqa 34 | 35 | urlpatterns = ( 36 | urlpatterns 37 | + [path("admin/__debug__/", include(debug_toolbar.urls),)] 38 | + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT,) 39 | + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT,) 40 | ) 41 | -------------------------------------------------------------------------------- /backend/backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend 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.1/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", "backend.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault( 7 | "DJANGO_SETTINGS_MODULE", "backend.settings.development", 8 | ) 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 | -------------------------------------------------------------------------------- /backend/media/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /backend/notebooks/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /backend/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = backend.settings 3 | python_files = tests.py test_*.py *_tests.py 4 | 5 | -------------------------------------------------------------------------------- /backend/requirements/base.txt: -------------------------------------------------------------------------------- 1 | boto3==1.16.23 2 | 3 | celery==5.0.2 4 | kombu==5.0.2 5 | 6 | channels==3.0.2 7 | channels-redis==3.2.0 8 | 9 | daphne==3.0.1 10 | 11 | graphene-django==2.13.0 12 | django-graphql-jwt==0.3.1 13 | 14 | Django==3.1.3 15 | django-filter==2.2.0 16 | djangorestframework==3.12.2 17 | djangorestframework_simplejwt==4.4.0 18 | django-debug-toolbar==2.2 19 | 20 | gevent==20.9.0 21 | 22 | # Python Social Auth 23 | 24 | social-auth-app-django==3.4.0 25 | social-auth-core==3.3.3 26 | 27 | # Django page caching is not working with this version of Redis 28 | # but this version is required by Celery 29 | django-redis==4.12.1 30 | 31 | django-storages==1.10.1 32 | 33 | # TODO: figure out why pandas is taking a long time to download 34 | # in cdk deploy step 35 | # pandas==0.24.2 36 | 37 | gunicorn==20.0.4 38 | 39 | psycopg2-binary==2.8.6 40 | 41 | redis==3.5.3 42 | 43 | requests==2.24.0 -------------------------------------------------------------------------------- /backend/requirements/dev.txt: -------------------------------------------------------------------------------- 1 | watchdog==0.10.3 2 | pyyaml==5.3.1 3 | argh==0.26.2 4 | django-extensions==2.2.9 # NOTE: in installed_apps 5 | Werkzeug==1.0.1 # used for runserver_plus exception console 6 | ipython==7.18.1 7 | jupyter==1.0.0 8 | gql==2.0.0 -------------------------------------------------------------------------------- /backend/requirements/test.txt: -------------------------------------------------------------------------------- 1 | black==19.10b0 2 | pytest==6.1.1 3 | pytest-cov==2.10.1 4 | pytest-django==4.1.0 5 | factory_boy==3.1.0 6 | flake8==3.8.4 7 | flake8-isort==4.0.0 8 | coverage==5.3 9 | pytest-profiling==1.7.0 -------------------------------------------------------------------------------- /backend/scripts/ci/Dockerfile: -------------------------------------------------------------------------------- 1 | # this image is tagged and pushed to the production registry (such as ECR) 2 | FROM python:3.8 as production 3 | ENV PYTHONUNBUFFERED 1 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | RUN mkdir /code 6 | WORKDIR /code 7 | COPY backend/requirements/base.txt /code/requirements/ 8 | RUN python3 -m pip install --upgrade pip 9 | RUN pip install -r requirements/base.txt 10 | COPY backend/scripts/prod/start_prod.sh \ 11 | backend/scripts/dev/start_ci.sh \ 12 | backend/scripts/dev/start_asgi.sh \ 13 | / 14 | ADD backend/ /code/ 15 | 16 | # build stage that generates quasar assets 17 | FROM node:10-alpine as build-stage 18 | ENV FULL_DOMAIN_NAME localhost:9000 19 | WORKDIR /app/ 20 | COPY quasar/package.json /app/ 21 | RUN npm cache verify 22 | RUN npm install -g @quasar/cli 23 | RUN npm install --progress=false 24 | COPY quasar /app/ 25 | RUN quasar build -m pwa 26 | 27 | # this stage is used for integration testing 28 | FROM production as gitlab-ci 29 | # update and install nodejs 30 | COPY --from=build-stage /app/dist/pwa/index.html /code/templates/ 31 | COPY --from=build-stage /app/dist/pwa /static 32 | 33 | COPY cypress.json /code 34 | 35 | RUN mkdir /code/cypress 36 | COPY cypress/ /code/cypress/ 37 | 38 | RUN apt-get -qq update && apt-get -y install nodejs npm 39 | RUN node -v 40 | RUN npm -v 41 | 42 | # cypress dependencies 43 | RUN apt-get -qq install -y xvfb \ 44 | libgtk-3-dev \ 45 | libnotify-dev \ 46 | libgconf-2-4 \ 47 | libnss3 \ 48 | libxss1 \ 49 | libasound2 50 | -------------------------------------------------------------------------------- /backend/scripts/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | ENV PYTHONUNBUFFERED 1 3 | ENV PYTHONDONTWRITEBYTECODE 1 4 | RUN mkdir /code 5 | RUN useradd -m app 6 | USER app 7 | WORKDIR /code 8 | COPY --chown=app:app requirements/base.txt requirements/dev.txt requirements/test.txt /code/requirements/ 9 | ENV PATH="/home/app/.local/bin:${PATH}" 10 | RUN python3 -m pip install --upgrade pip 11 | RUN pip install -r requirements/base.txt \ 12 | && pip install -r requirements/dev.txt \ 13 | && pip install -r requirements/test.txt 14 | ADD --chown=app:app . /code/ 15 | -------------------------------------------------------------------------------- /backend/scripts/dev/start_asgi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | # until cd /app/backend/server 5 | # do 6 | # echo "Waiting for server volume..." 7 | # done 8 | 9 | # until ./manage.py migrate 10 | # do 11 | # echo "Waiting for postgres ready..." 12 | # sleep 2 13 | # done 14 | 15 | # cd backend 16 | 17 | if $CI_PIPELINE_TRIGGERED ; then cd backend ; fi 18 | 19 | daphne backend.asgi:application --bind 0.0.0.0 --port 9000 -------------------------------------------------------------------------------- /backend/scripts/dev/start_beat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o nounset 4 | set -o errexit 5 | 6 | PIDFILE='/code/celerybeat.pid' 7 | SCHEDULE_FILE='/code/celerybeat-schedule' 8 | 9 | trap "rm ${PIDFILE} ; exit 130" SIGINT 10 | trap "rm ${PIDFILE} ; exit 137" SIGKILL 11 | trap "rm ${PIDFILE} ; exit 143" SIGTERM 12 | 13 | if [[ -f $PIDFILE ]] 14 | then 15 | rm $PIDFILE 16 | fi 17 | 18 | if [[ -f $SCHEDULE_FILE ]] 19 | then 20 | rm $SCHEDULE_FILE 21 | fi 22 | 23 | exec python3 manage.py watch_celery_beat 24 | -------------------------------------------------------------------------------- /backend/scripts/dev/start_celery.sh: -------------------------------------------------------------------------------- 1 | celery worker --app=backend.celery_app:app --loglevel=info -------------------------------------------------------------------------------- /backend/scripts/dev/start_ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # python3 manage.py collectstatic --no-input 4 | python3 manage.py makemigrations 5 | python3 manage.py migrate --no-input 6 | python3 manage.py create_default_user 7 | gunicorn -t 300 -b 0.0.0.0:8000 backend.wsgi 8 | -------------------------------------------------------------------------------- /backend/scripts/dev/start_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 manage.py collectstatic --no-input 4 | python3 manage.py makemigrations 5 | python3 manage.py migrate --no-input 6 | 7 | # this creates a default superuser if there are no users 8 | # see backend/accounts/management/commands/create_default_user.py 9 | python3 manage.py create_default_user 10 | while true; do 11 | python3 manage.py runserver_plus 0.0.0.0:8000 12 | sleep 5s 13 | done 14 | -------------------------------------------------------------------------------- /backend/scripts/prod/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 as production 2 | ENV PYTHONUNBUFFERED 1 3 | ENV PYTHONDONTWRITEBYTECODE 1 4 | RUN mkdir /code 5 | RUN useradd -m app 6 | USER app 7 | WORKDIR /code 8 | COPY --chown=app:app requirements/base.txt /code/requirements/ 9 | ENV PATH="/home/app/.local/bin:${PATH}" 10 | RUN python3 -m pip install --upgrade pip 11 | RUN pip install -r requirements/base.txt 12 | ADD --chown=app:app . /code/ 13 | -------------------------------------------------------------------------------- /backend/scripts/prod/start_prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gunicorn -t 300 -k gevent -w 4 -b 0.0.0.0:8000 backend.wsgi 4 | -------------------------------------------------------------------------------- /backend/static/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /backend/templates/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /compose/docs.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | vuepress: 5 | container_name: vuepress 6 | build: 7 | context: ../documentation 8 | command: ["sh", "/app/start_dev.sh"] 9 | ports: 10 | - "8082:8080" 11 | - "8081:8081" 12 | volumes: 13 | - ../documentation:/app/ 14 | environment: 15 | CI_PROJECT_URL: ${CI_PROJECT_URL} 16 | -------------------------------------------------------------------------------- /compose/minikube.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | frontend: 5 | image: frontend:2 6 | build: 7 | context: ../ 8 | dockerfile: nginx/minikube/Dockerfile 9 | args: 10 | - FULL_DOMAIN_NAME=minikube.local 11 | - GOOGLE_OAUTH2_KEY=google123 12 | - GITHUB_KEY=github123 13 | 14 | backend: 15 | image: backend:1 16 | build: 17 | context: ../backend/ 18 | dockerfile: scripts/dev/Dockerfile 19 | -------------------------------------------------------------------------------- /compose/test.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | postgres: 5 | container_name: postgres 6 | image: postgres 7 | networks: 8 | - main 9 | ports: 10 | - "5432:5432" 11 | volumes: 12 | - pg-data:/var/lib/postgresql/data 13 | environment: 14 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 15 | - POSTGRES_USER=${POSTGRES_USERNAME} 16 | - POSTGRES_DB=${POSTGRES_NAME} 17 | 18 | redis: 19 | image: redis:alpine 20 | volumes: 21 | - redis-data:/data 22 | container_name: redis 23 | networks: 24 | - main 25 | 26 | 27 | backend: &backend 28 | container_name: backend 29 | build: 30 | context: ../ 31 | dockerfile: backend/scripts/prod/Dockerfile 32 | ports: 33 | - "9000:9000" 34 | command: /start_asgi.sh 35 | volumes: 36 | - django-static:/code/static 37 | networks: 38 | - main 39 | environment: 40 | - CI_PIPELINE_TRIGGERED=True 41 | - SECRET_KEY=${SECRET_KEY} 42 | - DEBUG=True 43 | - DJANGO_EMAIL_HOST=${DJANGO_EMAIL_HOST} 44 | - DJANGO_EMAIL_PORT=${DJANGO_EMAIL_PORT} 45 | - POSTGRES_NAME=${POSTGRES_NAME} 46 | - POSTGRES_USERNAME=${POSTGRES_USERNAME} 47 | - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} 48 | - POSTGRES_SERVICE_HOST=${POSTGRES_SERVICE_HOST} 49 | - POSTGRES_SERVICE_PORT=${POSTGRES_SERVICE_PORT} 50 | - CELERY_BROKER_URL=${CELERY_BROKER_URL} 51 | - CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND} 52 | - DJANGO_SETTINGS_MODULE=backend.settings.gitlab-ci 53 | - GITHUB_KEY=${GITHUB_KEY} 54 | - GITHUB_SECRET=${GITHUB_SECRET} 55 | - GOOGLE_OAUTH2_KEY=${GOOGLE_OAUTH2_KEY} 56 | - GOOGLE_OAUTH2_SECRET=${GOOGLE_OAUTH2_SECRET} 57 | depends_on: 58 | - postgres 59 | - redis 60 | 61 | 62 | volumes: 63 | pg-data: 64 | django-static: 65 | redis-data: 66 | 67 | networks: 68 | main: 69 | driver: bridge 70 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:9000" 3 | } 4 | -------------------------------------------------------------------------------- /cypress.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | cypress: 6 | image: "cypress/included:3.4.0" 7 | container_name: cypress 8 | networks: 9 | - main 10 | depends_on: 11 | - nginx 12 | environment: 13 | - CYPRESS_baseUrl=http://nginx 14 | working_dir: /e2e 15 | volumes: 16 | - ./:/e2e 17 | -------------------------------------------------------------------------------- /cypress/.gitignore: -------------------------------------------------------------------------------- 1 | *.mp4 2 | *.jpep 3 | *.png 4 | *.jpg -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /cypress/integration/test_api.js: -------------------------------------------------------------------------------- 1 | describe('Test API', function () { 2 | it("Can access a public API route", function () { 3 | cy.request("/api/") 4 | .its('body') 5 | .then((body) => { 6 | expect(body).to.deep.equal({"message": "Root"}); 7 | }); 8 | }); 9 | }); -------------------------------------------------------------------------------- /cypress/integration/test_homepage.js: -------------------------------------------------------------------------------- 1 | describe('Test visit homepage', function() { 2 | it("can visit the homepage", function() { 3 | cy.visit("/"); 4 | cy.contains("Verbose Equals True"); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /cypress/integration/test_redis_connection.js: -------------------------------------------------------------------------------- 1 | describe("Test redis connection", function () { 2 | it("can set value to redis key", function () { 3 | cy.login(); 4 | cy.visit("/examples/redis"); 5 | cy.get("#clear").click(); 6 | cy.get("#val").should("be.empty"); 7 | 8 | cy.get("#input").type(8); 9 | cy.get("#set").click(); 10 | cy.wait(500); 11 | cy.get(".redis-debug") 12 | .find("#val") 13 | .then(($identifier) => { 14 | const value = $identifier.text(); 15 | expect(value).to.equal("8"); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /cypress/integration/test_ws_connection.js: -------------------------------------------------------------------------------- 1 | describe('Test websockets', function() { 2 | it("User sees PONG message from websocket PING message", function() { 3 | cy.login(); 4 | cy.visit("/examples/websockets"); 5 | cy.wait(500); 6 | cy.get("#ping").click(); 7 | cy.wait(500); 8 | cy.get('.pong').should('have.length', 1); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | Cypress.Commands.add("login", () => { 13 | cy.request({ 14 | url: "/api/auth/obtain_token/", 15 | method: "POST", 16 | body: { 17 | email: "admin@company.com", 18 | password: "password", 19 | }, 20 | }) 21 | .its("body") 22 | .then(() => {}); 23 | }); 24 | 25 | // 26 | // 27 | // -- This is a child command -- 28 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 29 | // 30 | // 31 | // -- This is a dual command -- 32 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 33 | // 34 | // 35 | // -- This is will overwrite an existing command -- 36 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 37 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /delete_pipelines.sh: -------------------------------------------------------------------------------- 1 | # this script removes old GitLab CI pipelines 2 | # there is no option to bulk delete pipelines 3 | 4 | if [[ -z "${PROJECT_ID}" ]]; then 5 | echo "Please set a PROJECT_ID environment variable." 6 | exit 1; 7 | fi 8 | 9 | if [[ -z "${GITLAB_TOKEN}" ]]; then 10 | echo "Please set a GITLAB_TOKEN environment variable." 11 | exit 1; 12 | fi 13 | 14 | 15 | 16 | BASE_URL="https://gitlab.com/api/v4/projects/$PROJECT_ID/" 17 | PIPELINES_URL="${BASE_URL}pipelines?private_token=$GITLAB_TOKEN&per_page=10000" 18 | 19 | 20 | echo $PIPELINES_URL 21 | echo "Fetching pipeline ids" 22 | curl $PIPELINES_URL | jq -r '.[].id' > /tmp/ids.txt 23 | 24 | echo "Deleting pipelines..." 25 | 26 | while read l; do 27 | echo "Deleting $l"; 28 | echo $l; 29 | sleep 1; 30 | output=$(curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" --request "DELETE" "${BASE_URL}pipelines/$l" | echo $1); 31 | echo $output 32 | echo "Deleted pipeline $l" 33 | done < /tmp/ids.txt 34 | -------------------------------------------------------------------------------- /docker-compose.ci.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | services: 6 | postgres: 7 | container_name: postgres 8 | image: postgres:11.5 9 | networks: 10 | - main 11 | ports: 12 | - "5434:5432" 13 | volumes: 14 | - pg-data:/var/lib/postgresql/data 15 | environment: 16 | - POSTGRES_PASSWORD=postgres123 17 | 18 | backend: &backend 19 | container_name: backend 20 | build: 21 | context: ./backend 22 | dockerfile: scripts/prod/Dockerfile 23 | command: > 24 | bash -c " 25 | python3 manage.py makemigrations 26 | python3 manage.py migrate --no-input 27 | python3 manage.py create_default_user 28 | gunicorn -t 300 -b 0.0.0.0:8000 backend.wsgi 29 | " 30 | networks: 31 | - main 32 | volumes: 33 | - ./backend:/code 34 | - django-static:/code/static 35 | depends_on: 36 | - postgres 37 | environment: 38 | - SECRET_KEY='secret' 39 | - DEBUG=True 40 | - DJANGO_SETTINGS_MODULE=backend.settings.gitlab-ci 41 | - POSTGRES_PASSWORD=postgres123 42 | 43 | asgiserver: 44 | <<: *backend 45 | container_name: asgiserver 46 | command: 47 | - "daphne" 48 | - "backend.asgi:application" 49 | - "--bind" 50 | - "0.0.0.0" 51 | - "--port" 52 | - "9000" 53 | volumes: 54 | - ./backend:/code 55 | 56 | nginx: 57 | container_name: nginx 58 | build: 59 | context: . 60 | dockerfile: nginx/ci/Dockerfile 61 | ports: 62 | - 80:80 63 | networks: 64 | - main 65 | volumes: 66 | - django-static:/usr/src/app/static 67 | depends_on: 68 | - backend 69 | 70 | redis: 71 | image: redis:alpine 72 | container_name: redis 73 | volumes: 74 | - redis-data:/data 75 | networks: 76 | - main 77 | 78 | volumes: 79 | django-static: 80 | portainer-data: 81 | pg-data: 82 | redis-data: 83 | 84 | networks: 85 | main: 86 | driver: bridge 87 | -------------------------------------------------------------------------------- /documentation/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docs/.vuepress/dist/ -------------------------------------------------------------------------------- /documentation/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | WORKDIR /app/ 3 | COPY . . 4 | -------------------------------------------------------------------------------- /documentation/build_documentation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install project dependencies 4 | cd documentation 5 | 6 | npm install 7 | 8 | # build vuepress files 9 | npm run docs:build -------------------------------------------------------------------------------- /documentation/docs/.vuepress/components/technology.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | -------------------------------------------------------------------------------- /documentation/docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | title: "Verbose Equals True", 5 | base: "/django-postgres-vue-gitlab-ecs/", 6 | port: 8080, 7 | configureWebpack: { 8 | resolve: { 9 | alias: { 10 | "@assets": path.resolve(__dirname, "../assets"), 11 | }, 12 | }, 13 | }, 14 | dest: "../public", 15 | plugins: { 16 | "@vuepress/google-analytics": { 17 | ga: process.env.GOOGLE_ANALYTICS_CODE, 18 | }, 19 | }, 20 | serviceWorker: false, 21 | themeConfig: { 22 | lastUpdated: "Last Updated: ", 23 | sidebar: "auto", 24 | repo: process.env.CI_PROJECT_URL, 25 | docsBranch: "develop", 26 | repoLabel: "View on GitLab", 27 | docsDir: "documentation/docs", 28 | editLinks: true, 29 | editLinkText: "Edit this page on GitLab", 30 | nav: [ 31 | { text: "Home", link: "/" }, 32 | { 33 | text: "Start Here", 34 | items: [ 35 | { text: "Overview", link: "/start/overview/" }, 36 | { text: "Tools Used", link: "/start/tools/" }, 37 | ], 38 | }, 39 | { 40 | text: "Guide", 41 | items: [{ text: "Testing", link: "/guide/testing/" }], 42 | }, 43 | { 44 | text: "DevOps", 45 | items: [{ text: "AWS CDK", link: "/devops/aws-cdk/" }], 46 | }, 47 | { 48 | text: "Topics", 49 | items: [ 50 | { text: "GraphQL", link: "/topics/graphql/" }, 51 | { text: "Minikube", link: "/topics/minikube/" }, 52 | ], 53 | }, 54 | ], 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/architecture.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/cdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/cdk.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/celery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/celery.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/django-channels-transparent-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/django-channels-transparent-bg.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/django-channels-white-bg-square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/django-channels-white-bg-square.jpg -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/django-channels-white-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/django-channels-white-bg.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/django.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/django.jpg -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/django1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/django1.jpg -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/docker.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/ecs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/ecs.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/gitlab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/postgres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/postgres.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/quasar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/quasar.png -------------------------------------------------------------------------------- /documentation/docs/.vuepress/public/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/documentation/docs/.vuepress/public/vue.png -------------------------------------------------------------------------------- /documentation/docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | 4 | tagline: A web application built with Django, Vue.js, GitLab and AWS 5 | actionText: Project Overview → 6 | actionLink: start/overview/ 7 | features: 8 | - title: Free and Open Source 9 | details: Everything in this project is and always will be free and open source. There is no premium content, paywall or credit card required. 10 | - title: Detailed Explanations 11 | details: This project aims to have verbose explanations for the code, design decisions and best practices. 12 | - title: Beginner Friendly 13 | details: This project aims to be accessible to people with all levels of skill. 14 | - title: Best Practices 15 | details: Covers best practices for local development, CI/CD, staging and production environments 16 | footer: MIT Licensed | Copyright © 2020 Brian Caffey 17 | --- 18 | -------------------------------------------------------------------------------- /documentation/docs/devops/aws-cdk/README.md: -------------------------------------------------------------------------------- 1 | # AWS CDK 2 | -------------------------------------------------------------------------------- /documentation/docs/topics/graphql/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL 2 | 3 | This topic covers implementign a Hacker News clone with GraphQL using Graphene and Apollo. 4 | 5 | For Graphene, I followed along with this tutorial: [https://www.howtographql.com/graphql-python/0-introduction/](https://www.howtographql.com/graphql-python/0-introduction/) 6 | 7 | To setup the Apollo client with Vue Apollo, I followed along with the following resources: 8 | 9 | 1. [https://github.com/hasura/graphql-engine/blob/master/community/sample-apps/quasar-framework-vue-graphql/src/plugins/apollo.js](https://github.com/hasura/graphql-engine/blob/master/community/sample-apps/quasar-framework-vue-graphql/src/plugins/apollo.js) 10 | 11 | 2. [https://lmiller1990.github.io/electic/posts/integrating_apollo_with_vue_and_vuex.html](https://lmiller1990.github.io/electic/posts/integrating_apollo_with_vue_and_vuex.html) 12 | 13 | 3. [https://apollo.vuejs.org/](https://apollo.vuejs.org/) 14 | 15 | 4. [https://www.apollographql.com/docs/react/networking/authentication/#header](https://www.apollographql.com/docs/react/networking/authentication/#header) 16 | -------------------------------------------------------------------------------- /documentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vuepress dev docs", 4 | "docs:build": "vuepress build docs", 5 | "start": "http-server /app/docs/.vuepress/dist -p 8002", 6 | "local": "nodemon --ext md,vue --watch .vuepress --watch . --exec vuepress dev docs" 7 | }, 8 | "dependencies": { 9 | "vuepress": "1.7.1", 10 | "@vuepress/plugin-google-analytics": "1.7.1" 11 | }, 12 | "devDependencies": { 13 | "nodemon": "2.0.6" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /documentation/start_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # https://docs.npmjs.com/cli/cache 4 | npm cache verify 5 | 6 | # install project dependencies 7 | npm install 8 | 9 | # build vuepress files 10 | npm run local -------------------------------------------------------------------------------- /gitlab-ci/README.md: -------------------------------------------------------------------------------- 1 | # `/gitlab-ci` 2 | 3 | This folder contains YAML files that are referenced by `.gitlab-ci.yml`. 4 | 5 | Currently there are two main folders: `aws` and `gcp`. 6 | 7 | `aws` contains two templates: `cloudformation.yml` and `cdk.yml`. `cdk.yml` is the preferred way to deploy infrastucture and make updates to the application. 8 | -------------------------------------------------------------------------------- /gitlab-ci/aws/app.yml: -------------------------------------------------------------------------------- 1 | .base_app_env: &base_app_env 2 | variables: 3 | ENVIRONMENT: app 4 | # DB_SNAPSHOT_ID: 5 | 6 | .workflow_app: &app_settings 7 | <<: *base_app_env 8 | rules: 9 | - if: "$CI_COMMIT_TAG =~ /^v/" 10 | when: always 11 | 12 | .manual_job_app: &manual_app_settings 13 | <<: *base_app_env 14 | rules: 15 | - if: "$CI_COMMIT_TAG =~ /^v/" 16 | when: manual 17 | 18 | quasar_build_pwa (app): 19 | <<: *app_settings 20 | extends: .quasar_build_pwa 21 | 22 | cdk_deploy (app): 23 | <<: *app_settings 24 | extends: .cdk_deploy 25 | 26 | collectstatic (app): 27 | <<: *manual_app_settings 28 | extends: .collectstatic 29 | 30 | migrate (app): 31 | <<: *manual_app_settings 32 | extends: .migrate 33 | 34 | createsuperuser (app): 35 | <<: *manual_app_settings 36 | extends: .createsuperuser 37 | 38 | cdk_destroy (app): 39 | <<: *manual_app_settings 40 | extends: .cdk_destroy 41 | -------------------------------------------------------------------------------- /gitlab-ci/aws/dev.yml: -------------------------------------------------------------------------------- 1 | .base_dev_env: &base_dev_env 2 | variables: 3 | ENVIRONMENT: dev 4 | # RDS_INSTANCE_SIZE: 5 | # DB_SNAPSHOT_ID: 6 | 7 | .workflow_dev: &dev_settings 8 | <<: *base_dev_env 9 | rules: 10 | - if: "$CI_COMMIT_TAG =~ /^rc/" 11 | when: always 12 | 13 | .manual_job_dev: &manual_dev_settings 14 | <<: *base_dev_env 15 | rules: 16 | - if: "$CI_COMMIT_TAG =~ /^rc/" 17 | when: manual 18 | 19 | quasar_build_pwa (dev): 20 | <<: *dev_settings 21 | extends: .quasar_build_pwa 22 | 23 | cdk_deploy (dev): 24 | <<: *dev_settings 25 | extends: .cdk_deploy 26 | 27 | collectstatic (dev): 28 | <<: *manual_dev_settings 29 | extends: .collectstatic 30 | 31 | migrate (dev): 32 | <<: *manual_dev_settings 33 | extends: .migrate 34 | 35 | createsuperuser (dev): 36 | <<: *manual_dev_settings 37 | extends: .createsuperuser 38 | 39 | cdk_destroy (dev): 40 | <<: *manual_dev_settings 41 | extends: .cdk_destroy 42 | -------------------------------------------------------------------------------- /gitlab-ci/documentation.yml: -------------------------------------------------------------------------------- 1 | .pages: 2 | image: node:latest 3 | stage: documentation 4 | script: 5 | - ./documentation/build_documentation.sh 6 | artifacts: 7 | paths: 8 | - public 9 | only: 10 | changes: 11 | - documentation/**/* 12 | -------------------------------------------------------------------------------- /gitlab-ci/renovate.yml: -------------------------------------------------------------------------------- 1 | .renovate: 2 | image: docker:19.03.8 3 | services: 4 | - docker:19.03.8-dind 5 | stage: renovate 6 | script: 7 | - | 8 | docker run \ 9 | -e GITLAB_TOKEN="$GITLAB_TOKEN" \ 10 | -e GITHUB_TOKEN="$GITHUB_TOKEN" \ 11 | -v $PWD/renovate/config.js:/usr/src/app/config.js \ 12 | renovate/renovate:19 13 | only: 14 | - renovate -------------------------------------------------------------------------------- /gitlab-runner/.gitignore: -------------------------------------------------------------------------------- 1 | config.toml -------------------------------------------------------------------------------- /gitlab-runner/config.toml.template: -------------------------------------------------------------------------------- 1 | concurrent = 1 2 | check_interval = 0 3 | 4 | [session_server] 5 | session_timeout = 1800 6 | 7 | [[runners]] 8 | name = "Local Gitlab Runner" 9 | url = "https://gitlab.com" 10 | token = "addyourtokenhere" 11 | executor = "docker" 12 | [runners.custom_build_dir] 13 | [runners.docker] 14 | tls_verify = false 15 | image = "docker:stable" 16 | privileged = true 17 | disable_entrypoint_overwrite = false 18 | oom_kill_disable = false 19 | disable_cache = false 20 | volumes = ["/cache"] 21 | shm_size = 0 22 | [runners.cache] 23 | [runners.cache.s3] 24 | [runners.cache.gcs] 25 | [runners.custom] 26 | run_exec = "" -------------------------------------------------------------------------------- /integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set -e 4 | 5 | echo "Starting services" 6 | docker-compose -f docker-compose.ci.yml up -d --build 7 | 8 | sleep 20 9 | echo "Services are up and ready" 10 | 11 | echo "Seeding database with user" 12 | # docker-compose -f docker-compose.ci.yml exec backend python manage.py create_default_user 13 | 14 | docker-compose -f docker-compose.ci.yml -f cypress.yml up --exit-code-from cypress 15 | 16 | echo "Cypress tests passed successfully." 17 | 18 | echo "Stopping docker compose..." 19 | docker-compose -f docker-compose.ci.yml -f cypress.yml down -------------------------------------------------------------------------------- /kubernetes/beat/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: celery-beat 5 | labels: 6 | deployment: celery-beat 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | pod: celery-beat 12 | template: 13 | metadata: 14 | labels: 15 | pod: celery-beat 16 | spec: 17 | containers: 18 | - name: celery-beat 19 | image: backend:14 20 | command: ["celery", "beat", "--app=backend.celery_app:app", "--loglevel=info"] 21 | env: 22 | - name: DJANGO_SETTINGS_MODULE 23 | value: 'backend.settings.minikube' 24 | 25 | - name: SECRET_KEY 26 | value: "my-secret-key" 27 | 28 | - name: POSTGRES_NAME 29 | value: postgres 30 | 31 | - name: POSTGRES_USER 32 | valueFrom: 33 | secretKeyRef: 34 | name: postgres-credentials 35 | key: user 36 | 37 | - name: POSTGRES_PASSWORD 38 | valueFrom: 39 | secretKeyRef: 40 | name: postgres-credentials 41 | key: password -------------------------------------------------------------------------------- /kubernetes/celery/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: celery-worker 5 | labels: 6 | deployment: celery-worker 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | pod: celery-worker 12 | template: 13 | metadata: 14 | labels: 15 | pod: celery-worker 16 | spec: 17 | containers: 18 | - name: celery-worker 19 | image: backend:14 20 | command: ["celery", "worker", "--app=backend.celery_app:app", "--loglevel=info"] 21 | env: 22 | - name: DJANGO_SETTINGS_MODULE 23 | value: 'backend.settings.minikube' 24 | 25 | - name: SECRET_KEY 26 | value: "my-secret-key" 27 | 28 | - name: POSTGRES_NAME 29 | value: postgres 30 | 31 | - name: POSTGRES_USER 32 | valueFrom: 33 | secretKeyRef: 34 | name: postgres-credentials 35 | key: user 36 | 37 | - name: POSTGRES_PASSWORD 38 | valueFrom: 39 | secretKeyRef: 40 | name: postgres-credentials 41 | key: password -------------------------------------------------------------------------------- /kubernetes/channels/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: django-channels 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: django-channels-container 10 | template: 11 | metadata: 12 | labels: 13 | app: django-channels-container 14 | spec: 15 | containers: 16 | - name: backend 17 | imagePullPolicy: IfNotPresent 18 | image: backend:14 19 | command: ["daphne", "backend.asgi:application", "--bind", "0.0.0.0", "--port", "9000"] 20 | livenessProbe: 21 | httpGet: 22 | path: /healthz 23 | port: 9000 24 | readinessProbe: 25 | # an http probe 26 | httpGet: 27 | path: /readiness 28 | port: 9000 29 | initialDelaySeconds: 20 30 | timeoutSeconds: 5 31 | ports: 32 | - containerPort: 9000 33 | env: 34 | - name: DJANGO_SETTINGS_MODULE 35 | value: 'backend.settings.minikube' 36 | 37 | - name: SECRET_KEY 38 | value: "my-secret-key" 39 | 40 | - name: POSTGRES_NAME 41 | value: postgres 42 | 43 | - name: POSTGRES_USER 44 | valueFrom: 45 | secretKeyRef: 46 | name: postgres-credentials 47 | key: user 48 | 49 | - name: POSTGRES_PASSWORD 50 | valueFrom: 51 | secretKeyRef: 52 | name: postgres-credentials 53 | key: password 54 | 55 | -------------------------------------------------------------------------------- /kubernetes/channels/service.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: kubernetes-django-channels-service 5 | spec: 6 | selector: 7 | app: django-channels-container 8 | ports: 9 | - protocol: TCP 10 | port: 9000 11 | targetPort: 9000 12 | type: NodePort -------------------------------------------------------------------------------- /kubernetes/django/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: django 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: django-container 10 | template: 11 | metadata: 12 | labels: 13 | app: django-container 14 | spec: 15 | containers: 16 | - name: backend 17 | imagePullPolicy: IfNotPresent 18 | image: backend:13 19 | command: ["./manage.py", "runserver", "0.0.0.0:8000"] 20 | livenessProbe: 21 | httpGet: 22 | path: /healthz 23 | port: 8000 24 | readinessProbe: 25 | # an http probe 26 | httpGet: 27 | path: /readiness 28 | port: 8000 29 | initialDelaySeconds: 10 30 | timeoutSeconds: 5 31 | ports: 32 | - containerPort: 8000 33 | env: 34 | - name: DJANGO_SETTINGS_MODULE 35 | value: 'backend.settings.minikube' 36 | 37 | - name: SECRET_KEY 38 | value: "my-secret-key" 39 | 40 | - name: POSTGRES_NAME 41 | value: postgres 42 | 43 | - name: POSTGRES_USER 44 | valueFrom: 45 | secretKeyRef: 46 | name: postgres-credentials 47 | key: user 48 | 49 | - name: POSTGRES_PASSWORD 50 | valueFrom: 51 | secretKeyRef: 52 | name: postgres-credentials 53 | key: password 54 | 55 | volumeMounts: 56 | - name: postgres-volume-mount 57 | mountPath: /var/lib/busybox 58 | 59 | volumes: 60 | - name: postgres-volume-mount 61 | persistentVolumeClaim: 62 | claimName: postgres-pvc 63 | 64 | -------------------------------------------------------------------------------- /kubernetes/django/migration.yml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: django-migrations 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: django 10 | image: backend:11 11 | command: ['python', 'manage.py', 'migrate'] 12 | env: 13 | - name: POSTGRES_USER 14 | valueFrom: 15 | secretKeyRef: 16 | name: postgres-credentials 17 | key: user 18 | 19 | - name: POSTGRES_PASSWORD 20 | valueFrom: 21 | secretKeyRef: 22 | name: postgres-credentials 23 | key: password 24 | 25 | - name: POSTGRES_NAME 26 | value: postgres 27 | 28 | - name: DJANGO_SETTINGS_MODULE 29 | value: 'backend.settings.minikube' 30 | 31 | restartPolicy: Never 32 | backoffLimit: 5 -------------------------------------------------------------------------------- /kubernetes/django/service.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: kubernetes-django-service 5 | spec: 6 | selector: 7 | app: django-container 8 | ports: 9 | - protocol: TCP 10 | port: 8000 11 | targetPort: 8000 12 | type: NodePort -------------------------------------------------------------------------------- /kubernetes/flower/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: flower 5 | labels: 6 | deployment: flower 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | pod: celery-flower 12 | template: 13 | metadata: 14 | labels: 15 | pod: celery-flower 16 | spec: 17 | containers: 18 | - name: flower 19 | image: mher/flower 20 | ports: 21 | - containerPort: 5555 22 | env: 23 | - name: CELERY_BROKER_URL 24 | value: redis://$(REDIS_SERVICE_HOST)/1 25 | resources: 26 | limits: 27 | cpu: 100m 28 | memory: 100Mi -------------------------------------------------------------------------------- /kubernetes/flower/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: flower-service 5 | spec: 6 | selector: 7 | pod: celery-flower 8 | ports: 9 | - port: 5555 10 | type: NodePort -------------------------------------------------------------------------------- /kubernetes/frontend/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: frontend-deployment 5 | labels: 6 | app: frontend 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: frontend-container 12 | template: 13 | metadata: 14 | labels: 15 | app: frontend-container 16 | spec: 17 | containers: 18 | - name: frontend 19 | imagePullPolicy: IfNotPresent 20 | image: frontend:2 21 | -------------------------------------------------------------------------------- /kubernetes/frontend/service.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: kubernetes-frontend-service 5 | spec: 6 | selector: 7 | app: frontend-container 8 | ports: 9 | - nodePort: 30002 10 | protocol: TCP 11 | port: 80 12 | targetPort: 80 13 | type: NodePort -------------------------------------------------------------------------------- /kubernetes/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: minikube-ingress 5 | spec: 6 | rules: 7 | - host: minikube.local 8 | http: 9 | paths: 10 | - path: /api/ 11 | backend: 12 | serviceName: kubernetes-django-service 13 | servicePort: 8000 14 | - path: /admin/ 15 | backend: 16 | serviceName: kubernetes-django-service 17 | servicePort: 8000 18 | - path: /static/ 19 | backend: 20 | serviceName: kubernetes-django-service 21 | servicePort: 8000 22 | - path: /ws/ 23 | backend: 24 | serviceName: kubernetes-django-channels-service 25 | servicePort: 9000 26 | - path: / 27 | backend: 28 | serviceName: kubernetes-frontend-service 29 | servicePort: 80 -------------------------------------------------------------------------------- /kubernetes/postgres/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: postgres-deployment 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: postgres-container 10 | template: 11 | metadata: 12 | labels: 13 | app: postgres-container 14 | tier: backend 15 | spec: 16 | containers: 17 | - name: postgres-container 18 | image: postgres:9.6.6 19 | env: 20 | - name: POSTGRES_USER 21 | valueFrom: 22 | secretKeyRef: 23 | name: postgres-credentials 24 | key: user 25 | 26 | - name: POSTGRES_PASSWORD 27 | valueFrom: 28 | secretKeyRef: 29 | name: postgres-credentials 30 | key: password 31 | 32 | ports: 33 | - containerPort: 5432 34 | volumeMounts: 35 | - name: postgres-volume-mount 36 | mountPath: /var/lib/postgresql/data 37 | 38 | volumes: 39 | - name: postgres-volume-mount 40 | persistentVolumeClaim: 41 | claimName: postgres-pvc 42 | -------------------------------------------------------------------------------- /kubernetes/postgres/secrets.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: postgres-credentials 5 | type: Opaque 6 | data: 7 | user: YnJpYW4= 8 | password: cGFzc3dvcmQx -------------------------------------------------------------------------------- /kubernetes/postgres/service.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: postgres 5 | spec: 6 | selector: 7 | app: postgres-container 8 | ports: 9 | - protocol: TCP 10 | port: 5432 11 | targetPort: 5432 12 | -------------------------------------------------------------------------------- /kubernetes/postgres/volume.yml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolume 2 | apiVersion: v1 3 | metadata: 4 | name: postgres-pv 5 | labels: 6 | type: local 7 | spec: 8 | storageClassName: manual 9 | capacity: 10 | storage: 2Gi 11 | accessModes: 12 | - ReadWriteOnce 13 | hostPath: 14 | path: /data/postgres-pv1 -------------------------------------------------------------------------------- /kubernetes/postgres/volume_claim.yml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: postgres-pvc 5 | labels: 6 | type: local 7 | spec: 8 | storageClassName: manual 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 2Gi 14 | volumeName: postgres-pv -------------------------------------------------------------------------------- /kubernetes/redis/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: redis 5 | labels: 6 | deployment: redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | pod: redis 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | pod: redis 16 | spec: 17 | containers: 18 | - name: master 19 | image: redis 20 | resources: 21 | requests: 22 | cpu: 100m 23 | memory: 100Mi 24 | ports: 25 | - containerPort: 6379 26 | -------------------------------------------------------------------------------- /kubernetes/redis/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: redis 5 | spec: 6 | selector: 7 | pod: redis 8 | ports: 9 | - protocol: TCP 10 | port: 6379 11 | targetPort: 6379 -------------------------------------------------------------------------------- /nginx/ci/Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM node:10-alpine as build-stage 3 | WORKDIR /app/ 4 | COPY quasar/package.json /app/ 5 | RUN npm cache verify 6 | RUN npm install -g @quasar/cli 7 | RUN npm install --progress=false 8 | COPY quasar /app/ 9 | RUN quasar build -m pwa 10 | 11 | # ci stage 12 | FROM nginx:1.19.3-alpine as ci-stage 13 | COPY nginx/ci/ci.conf /etc/nginx/nginx.conf 14 | COPY --from=build-stage /app/dist/pwa /dist/ 15 | EXPOSE 80 16 | CMD ["nginx", "-g", "daemon off;"] 17 | -------------------------------------------------------------------------------- /nginx/ci/ci.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 | upstream backend { 13 | server backend:8000; 14 | } 15 | 16 | upstream asgiserver { 17 | server asgiserver:9000; 18 | } 19 | 20 | server { 21 | listen 80; 22 | charset utf-8; 23 | 24 | root /dist/; 25 | index index.html; 26 | 27 | # frontend 28 | location / { 29 | try_files $uri $uri/ @rewrites; 30 | } 31 | 32 | location @rewrites { 33 | rewrite ^(.+)$ /index.html last; 34 | } 35 | 36 | location /ws/ { 37 | proxy_pass http://asgiserver; 38 | proxy_http_version 1.1; 39 | proxy_set_header Upgrade $http_upgrade; 40 | proxy_set_header Connection "upgrade"; 41 | # proxy_redirect off; 42 | } 43 | 44 | # backend urls 45 | location ~ ^/(admin|api|media|static) { 46 | proxy_redirect off; 47 | proxy_pass http://backend; 48 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 49 | proxy_set_header Host $http_host; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nginx/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.19.4-alpine 2 | COPY dev/dev.conf /etc/nginx/nginx.conf 3 | EXPOSE 80 4 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /nginx/flowerproxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.19.3-alpine 2 | COPY proxy.conf /etc/nginx/nginx.conf 3 | EXPOSE 80 4 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /nginx/flowerproxy/proxy.conf: -------------------------------------------------------------------------------- 1 | # server { 2 | # listen 80; 3 | # charset utf-8; 4 | 5 | # location /flower/ { 6 | # rewrite ^/flower/(.*)$ /$1 break; 7 | # proxy_pass http://localhost:5555; 8 | # proxy_set_header Host $host; 9 | # proxy_redirect off; 10 | # proxy_http_version 1.1; 11 | # proxy_set_header Upgrade $http_upgrade; 12 | # proxy_set_header Connection "upgrade"; 13 | # } 14 | # } 15 | 16 | 17 | # server { 18 | # listen 80; 19 | # charset utf-8; 20 | 21 | # location /flower/ { 22 | # return 200 'Test'; 23 | # add_header Content-Type text/plain; 24 | # } 25 | 26 | # location /flower/test { 27 | # return 200 'Testing'; 28 | # add_header Content-Type text/plain; 29 | # } 30 | # } 31 | 32 | 33 | user nginx; 34 | worker_processes 1; 35 | 36 | events { 37 | worker_connections 1024; 38 | } 39 | 40 | http { 41 | include /etc/nginx/mime.types; 42 | client_max_body_size 100m; 43 | 44 | server { 45 | listen 80; 46 | charset utf-8; 47 | 48 | # flower 49 | location /flower/ { 50 | rewrite ^/flower/(.*)$ /$1 break; 51 | proxy_pass http://localhost:5555; 52 | proxy_set_header Host $host; 53 | proxy_redirect off; 54 | proxy_http_version 1.1; 55 | proxy_set_header Upgrade $http_upgrade; 56 | proxy_set_header Connection "upgrade"; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /nginx/minikube/Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM node:10-alpine as build-stage 3 | ARG FULL_DOMAIN_NAME 4 | ARG GOOGLE_OAUTH2_KEY 5 | ARG GITHUB_KEY 6 | 7 | ENV FULL_DOMAIN_NAME=${DOMAIN_NAME} 8 | ENV GOOGLE_OAUTH2_KEY=${GOOGLE_OAUTH2_KEY} 9 | ENV GITHUB_KEY=${GITHUB_KEY} 10 | 11 | WORKDIR /app/ 12 | COPY quasar/package.json /app/ 13 | RUN npm cache verify 14 | RUN npm install -g @quasar/cli 15 | RUN npm install --progress=false 16 | COPY quasar /app/ 17 | RUN quasar build -m pwa 18 | 19 | # ci stage 20 | FROM nginx:1.19.3-alpine as ci-stage 21 | COPY nginx/minikube/minikube.conf /etc/nginx/nginx.conf 22 | COPY --from=build-stage /app/dist/pwa /dist/ 23 | EXPOSE 80 24 | CMD ["nginx", "-g", "daemon off;"] 25 | -------------------------------------------------------------------------------- /nginx/minikube/minikube.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 | # upstream backend { 13 | # server backend:8000; 14 | # } 15 | 16 | # upstream asgiserver { 17 | # server asgiserver:9000; 18 | # } 19 | 20 | server { 21 | listen 80; 22 | charset utf-8; 23 | 24 | root /dist/; 25 | index index.html; 26 | 27 | # frontend 28 | location / { 29 | try_files $uri $uri/ @rewrites; 30 | } 31 | 32 | location @rewrites { 33 | rewrite ^(.+)$ /index.html last; 34 | } 35 | 36 | # location /ws/ { 37 | # proxy_pass http://asgiserver; 38 | # proxy_http_version 1.1; 39 | # proxy_set_header Upgrade $http_upgrade; 40 | # proxy_set_header Connection "upgrade"; 41 | # # proxy_redirect off; 42 | # } 43 | 44 | # # backend urls 45 | # location ~ ^/(admin|api|media|static) { 46 | # proxy_redirect off; 47 | # proxy_pass http://backend; 48 | # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 49 | # proxy_set_header Host $http_host; 50 | # } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /quasar/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .quasar 3 | dist -------------------------------------------------------------------------------- /quasar/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /quasar/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /quasar/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | parserOptions: { 5 | parser: "babel-eslint", 6 | sourceType: "module" 7 | }, 8 | 9 | env: { 10 | browser: true 11 | }, 12 | 13 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 14 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 15 | extends: ["plugin:vue/essential", "@vue/prettier"], 16 | 17 | // required to lint *.vue files 18 | plugins: ["vue"], 19 | 20 | globals: { 21 | ga: true, // Google Analytics 22 | cordova: true, 23 | __statics: true, 24 | process: true 25 | }, 26 | 27 | // add your custom rules here 28 | rules: { 29 | "prefer-promise-reject-errors": "off", 30 | 31 | // allow console.log during development only 32 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 33 | // allow debugger during development only 34 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /quasar/.gitignore: -------------------------------------------------------------------------------- 1 | .quasar 2 | .DS_Store 3 | .thumbs.db 4 | node_modules 5 | /dist 6 | /src-cordova/node_modules 7 | /src-cordova/platforms 8 | /src-cordova/plugins 9 | /src-cordova/www 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | -------------------------------------------------------------------------------- /quasar/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /quasar/.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": "never", 3 | "brackets": "never", 4 | "colons": "never", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "depthLimit": false, 10 | "duplicates": true, 11 | "efficient": "always", 12 | "extendPref": false, 13 | "globalDupe": true, 14 | "indentPref": 2, 15 | "leadingZero": "never", 16 | "maxErrors": false, 17 | "maxWarnings": false, 18 | "mixed": false, 19 | "namingConvention": false, 20 | "namingConventionStrict": false, 21 | "none": "never", 22 | "noImportant": false, 23 | "parenSpace": "never", 24 | "placeholder": false, 25 | "prefixVarsWithDollar": "always", 26 | "quotePref": "single", 27 | "semicolons": "never", 28 | "sortOrder": false, 29 | "stackedProperties": "never", 30 | "trailingWhitespace": "never", 31 | "universal": "never", 32 | "valid": true, 33 | "zeroUnits": "never", 34 | "zIndexNormalize": false 35 | } 36 | -------------------------------------------------------------------------------- /quasar/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.19.0 2 | 3 | WORKDIR /app/ 4 | 5 | COPY . . 6 | 7 | RUN npm install -g @quasar/cli 8 | 9 | COPY start_dev.sh . 10 | 11 | CMD ["/app/start_dev.sh"] -------------------------------------------------------------------------------- /quasar/README.md: -------------------------------------------------------------------------------- 1 | # Verbose Equals True (quasarfrontend) 2 | 3 | Quasar frontend for Verbose Equals True 4 | 5 | ## Install the dependencies 6 | ```bash 7 | yarn 8 | ``` 9 | 10 | ### Start the app in development mode (hot-code reloading, error reporting, etc.) 11 | ```bash 12 | quasar dev 13 | ``` 14 | 15 | ### Lint the files 16 | ```bash 17 | yarn run lint 18 | ``` 19 | 20 | ### Build the app for production 21 | ```bash 22 | quasar build 23 | ``` 24 | 25 | ### Customize the configuration 26 | See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js). 27 | -------------------------------------------------------------------------------- /quasar/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@quasar/babel-preset-app"], 3 | }; 4 | -------------------------------------------------------------------------------- /quasar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasarfrontend", 3 | "version": "0.0.1", 4 | "description": "Quasar frontend for Verbose Equals True", 5 | "productName": "Verbose Equals True", 6 | "cordovaId": "org.cordova.quasar.app", 7 | "author": "Brian Caffey", 8 | "private": true, 9 | "scripts": { 10 | "lint": "eslint --ext .js,.vue src", 11 | "test": "echo \"No test specified\" && exit 0" 12 | }, 13 | "dependencies": { 14 | "@quasar/extras": "1.9.10", 15 | "apollo-cache-inmemory": "1.6.6", 16 | "apollo-client": "2.6.10", 17 | "apollo-link": "1.2.14", 18 | "apollo-link-context": "1.0.20", 19 | "apollo-link-http": "1.5.17", 20 | "axios": "0.21.0", 21 | "emoji-mart-vue": "2.6.6", 22 | "graphql": "15.4.0", 23 | "graphql-tag": "2.11.0", 24 | "highcharts": "8.2.2", 25 | "highcharts-vue": "1.3.5", 26 | "js-cookie": "2.2.1", 27 | "prettier": "2.2.0", 28 | "quasar": "1.14.5", 29 | "vue-apollo": "3.0.5", 30 | "vue-i18n": "8.22.2", 31 | "vue-native-websocket": "2.0.14", 32 | "workbox-webpack-plugin": "5.1.4" 33 | }, 34 | "devDependencies": { 35 | "@quasar/app": "2.1.8", 36 | "@vue/eslint-config-prettier": "6.0.0", 37 | "babel-eslint": "10.1.0", 38 | "core-js": "^3.7.0", 39 | "eslint": "7.14.0", 40 | "eslint-loader": "4.0.2", 41 | "eslint-plugin-prettier": "3.1.4", 42 | "eslint-plugin-vue": "7.1.0" 43 | }, 44 | "engines": { 45 | "node": ">= 8.9.0", 46 | "npm": ">= 5.6.0", 47 | "yarn": ">= 1.6.0" 48 | }, 49 | "browserslist": [ 50 | "last 1 version, not dead, ie >= 11" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /quasar/src-electron/electron-flag.d.ts: -------------------------------------------------------------------------------- 1 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 2 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 3 | import "quasar/dist/types/feature-flag"; 4 | 5 | declare module "quasar/dist/types/feature-flag" { 6 | interface QuasarFeatureFlags { 7 | electron: true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /quasar/src-pwa/custom-service-worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file (which will be your service worker) 3 | * is picked up by the build system ONLY if 4 | * quasar.conf > pwa > workboxPluginMode is set to "InjectManifest" 5 | */ 6 | -------------------------------------------------------------------------------- /quasar/src-pwa/pwa-flag.d.ts: -------------------------------------------------------------------------------- 1 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 2 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 3 | import "quasar/dist/types/feature-flag"; 4 | 5 | declare module "quasar/dist/types/feature-flag" { 6 | interface QuasarFeatureFlags { 7 | pwa: true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /quasar/src-pwa/register-service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { register } from "register-service-worker"; 3 | 4 | // The ready(), registered(), cached(), updatefound() and updated() 5 | // events passes a ServiceWorkerRegistration instance in their arguments. 6 | // ServiceWorkerRegistration: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration 7 | 8 | register(process.env.SERVICE_WORKER_FILE, { 9 | // The registrationOptions object will be passed as the second argument 10 | // to ServiceWorkerContainer.register() 11 | // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Parameter 12 | 13 | // registrationOptions: { scope: './' }, 14 | 15 | ready() { 16 | console.log("App is being served from cache by a service worker."); 17 | }, 18 | 19 | registered(/* registration */) { 20 | console.log("Service worker has been registered."); 21 | }, 22 | 23 | cached(/* registration */) { 24 | console.log("Content has been cached for offline use."); 25 | }, 26 | 27 | updatefound(/* registration */) { 28 | console.log("New content is downloading."); 29 | }, 30 | 31 | updated(/* registration */) { 32 | console.log("New content is available; please refresh."); 33 | }, 34 | 35 | offline() { 36 | console.log( 37 | "No internet connection found. App is running in offline mode." 38 | ); 39 | }, 40 | 41 | error(err) { 42 | console.error("Error during service worker registration:", err); 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /quasar/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 30 | -------------------------------------------------------------------------------- /quasar/src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/boot/.gitkeep -------------------------------------------------------------------------------- /quasar/src/boot/apollo.js: -------------------------------------------------------------------------------- 1 | import VueApollo from "vue-apollo"; 2 | 3 | import { ApolloClient } from "apollo-client"; 4 | import { createHttpLink } from "apollo-link-http"; 5 | import { setContext } from "apollo-link-context"; 6 | import { InMemoryCache } from "apollo-cache-inmemory"; 7 | 8 | // HTTP connection to the API 9 | const httpLink = createHttpLink({ 10 | uri: `/graphql/`, 11 | }); 12 | 13 | // Cache implementation 14 | const cache = new InMemoryCache(); 15 | 16 | export default ({ app, Vue, store }) => { 17 | const authLink = setContext((_, { headers }) => { 18 | let authorization = ""; 19 | if (store.getters.isAuthenticated) { 20 | authorization = `JWT ${store.getters["gqljwt/getToken"]}`; 21 | } 22 | return { 23 | headers: { 24 | ...headers, 25 | authorization, 26 | }, 27 | }; 28 | }); 29 | 30 | const apolloClient = new ApolloClient({ 31 | link: authLink.concat(httpLink), 32 | cache, 33 | }); 34 | 35 | const apolloProvider = new VueApollo({ 36 | defaultClient: apolloClient, 37 | }); 38 | 39 | Vue.use(VueApollo); 40 | app.apolloProvider = apolloProvider; 41 | }; 42 | -------------------------------------------------------------------------------- /quasar/src/boot/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default async ({ Vue, store, router }) => { 4 | // https://stackoverflow.com/a/15724300 5 | function getCookie(name) { 6 | const value = `; ${document.cookie}`; 7 | const parts = value.split(`; ${name}=`); 8 | if (parts.length === 2) return parts.pop().split(";").shift(); 9 | } 10 | 11 | const apiCall = axios.create(); 12 | 13 | apiCall.interceptors.request.use( 14 | (config) => { 15 | // Do something before each request is sent 16 | const c = config; 17 | 18 | // this cookie must be sent with each axios request 19 | // in order for POST / PUT /DELETE http methods to work 20 | 21 | const cookie = getCookie("csrftoken") || ""; 22 | 23 | if (cookie) { 24 | c.headers["X-CSRFToken"] = cookie; 25 | } 26 | 27 | return c; 28 | }, 29 | (error) => { 30 | Promise.reject(error); 31 | } 32 | ); 33 | 34 | function handleSuccess(response) { 35 | return { data: response.data }; 36 | } 37 | 38 | function handleError(error) { 39 | switch (error.response.status) { 40 | case 400: 41 | break; 42 | case 401: 43 | // Log out user, remove token, clear state and redirect to login 44 | store.dispatch("AUTH_LOGOUT").then(router.push("/")); 45 | break; 46 | case 404: 47 | // Show 404 page 48 | break; 49 | case 500: 50 | // Serveur Error redirect to 500 51 | break; 52 | default: 53 | // Unknow Error 54 | break; 55 | } 56 | return Promise.reject(error); 57 | } 58 | 59 | apiCall.interceptors.response.use(handleSuccess, handleError); 60 | 61 | Vue.prototype.$axios = apiCall; 62 | }; 63 | -------------------------------------------------------------------------------- /quasar/src/boot/components.js: -------------------------------------------------------------------------------- 1 | import BasePage from "components/ui/BasePage.vue"; 2 | import DarkMode from "components/ui/DarkMode.vue"; 3 | import LeftMenuLink from "components/LeftMenuLink.vue"; 4 | import MainHeader from "layouts/primary/MainHeader.vue"; 5 | import PageHeader from "components/ui/PageHeader.vue"; 6 | import PageSubHeader from "components/ui/PageSubHeader.vue"; 7 | import PageText from "components/ui/PageText.vue"; 8 | import BaseBtn from "components/ui/BaseBtn.vue"; 9 | import BaseCard from "components/ui/BaseCard.vue"; 10 | import MainLeftDrawer from "layouts/primary/MainLeftDrawer.vue"; 11 | import MainCarousel from "components/MainCarousel.vue"; 12 | import BaseInput from "components/ui/BaseInput.vue"; 13 | import BaseDate from "components/ui/BaseDate.vue"; 14 | import { Emoji } from "emoji-mart-vue"; 15 | 16 | // leave the export, even if you don't use it 17 | export default async ({ Vue }) => { 18 | Vue.component("BaseInput", BaseInput); 19 | Vue.component("BaseDate", BaseDate); 20 | Vue.component("Emoji", Emoji); 21 | Vue.component("BasePage", BasePage); 22 | Vue.component("DarkMode", DarkMode); 23 | Vue.component("LeftMenuLink", LeftMenuLink); 24 | Vue.component("MainHeader", MainHeader); 25 | Vue.component("PageHeader", PageHeader); 26 | Vue.component("PageSubHeader", PageSubHeader); 27 | Vue.component("PageText", PageText); 28 | Vue.component("BaseBtn", BaseBtn); 29 | Vue.component("BaseCard", BaseCard); 30 | Vue.component("MainLeftDrawer", MainLeftDrawer); 31 | Vue.component("MainCarousel", MainCarousel); 32 | }; 33 | -------------------------------------------------------------------------------- /quasar/src/boot/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueI18n from "vue-i18n"; 3 | import messages from "src/i18n"; 4 | 5 | Vue.use(VueI18n); 6 | 7 | const i18n = new VueI18n({ 8 | locale: "en-us", 9 | fallbackLocale: "en-us", 10 | messages, 11 | }); 12 | 13 | export default ({ app }) => { 14 | // Set i18n instance on app 15 | app.i18n = i18n; 16 | }; 17 | 18 | export { i18n }; 19 | -------------------------------------------------------------------------------- /quasar/src/boot/websockets.js: -------------------------------------------------------------------------------- 1 | import VueNativeSock from "vue-native-websocket"; 2 | 3 | export default ({ store, Vue }) => { 4 | // something to do 5 | Vue.use(VueNativeSock, process.env.WS_URL, { 6 | store, 7 | format: "json", 8 | connectManually: true, 9 | reconnection: true, // (Boolean) whether to reconnect automatically (false) 10 | reconnectionAttempts: 5, // (Number) number of reconnection attempts before giving up (Infinity), 11 | reconnectionDelay: 3000, // (Number) how long to initially wait before attempting a new (1000) 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /quasar/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/components/.gitkeep -------------------------------------------------------------------------------- /quasar/src/components/LeftMenuLink.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /quasar/src/components/MainCarousel.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 53 | 54 | 62 | -------------------------------------------------------------------------------- /quasar/src/components/auth/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /quasar/src/components/auth/SignUpForm.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /quasar/src/components/services/ServiceLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /quasar/src/components/ui/BaseBtn.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /quasar/src/components/ui/BaseCard.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /quasar/src/components/ui/BaseDate.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /quasar/src/components/ui/BaseInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /quasar/src/components/ui/BasePage.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /quasar/src/components/ui/DarkMode.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /quasar/src/components/ui/PageHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /quasar/src/components/ui/PageSubHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /quasar/src/components/ui/PageText.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /quasar/src/css/app.styl: -------------------------------------------------------------------------------- 1 | // app global css 2 | 3 | .light-mode { 4 | color: black; 5 | } 6 | 7 | .dark-mode { 8 | color: white; 9 | } 10 | 11 | 12 | .dark-mode::selection { 13 | background: teal; /* WebKit/Blink Browsers */ 14 | } 15 | .dark-mode::-moz-selection { 16 | background: teal; /* Gecko Browsers */ 17 | } 18 | 19 | a { 20 | text-decoration: none; 21 | } -------------------------------------------------------------------------------- /quasar/src/css/quasar.variables.styl: -------------------------------------------------------------------------------- 1 | // Quasar Stylus Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Stylus variables found in Quasar's source Stylus files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // It's highly recommended to change the default colors 9 | // to match your app's branding. 10 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 11 | 12 | $primary = #027BE3 13 | $secondary = #26A69A 14 | $accent = #9C27B0 15 | 16 | $positive = #21BA45 17 | $negative = #C10015 18 | $info = #31CCEC 19 | $warning = #F2C037 20 | -------------------------------------------------------------------------------- /quasar/src/i18n/cn-cn/index.js: -------------------------------------------------------------------------------- 1 | import leftDrawer from "./leftDrawer"; 2 | 3 | export default { 4 | ...leftDrawer, 5 | }; 6 | -------------------------------------------------------------------------------- /quasar/src/i18n/cn-cn/leftDrawer/index.js: -------------------------------------------------------------------------------- 1 | const leftDrawer = { 2 | leftDrawer: { 3 | home: { 4 | main: "首页", 5 | sub: "从这里开始", 6 | }, 7 | about: { 8 | main: "关于", 9 | sub: "关于这个APP", 10 | }, 11 | services: { 12 | main: "服务", 13 | sub: "监控,管理,元数据", 14 | }, 15 | examples: { 16 | main: "例子", 17 | sub: "REST, Websockets, 等", 18 | websockets: { 19 | main: "Websockets", 20 | sub: "如何使用Websockets", 21 | }, 22 | }, 23 | tests: { 24 | main: "测试", 25 | sub: "系统测试", 26 | redis: { 27 | main: "Redis", 28 | sub: "redis测试", 29 | }, 30 | }, 31 | environment: { 32 | main: "环境", 33 | sub: "环境变量", 34 | }, 35 | }, 36 | }; 37 | 38 | export default leftDrawer; 39 | -------------------------------------------------------------------------------- /quasar/src/i18n/en-us/index.js: -------------------------------------------------------------------------------- 1 | import leftDrawer from "./leftDrawer"; 2 | 3 | export default { 4 | ...leftDrawer, 5 | failed: "Hello", 6 | success: "Action was successful", 7 | }; 8 | -------------------------------------------------------------------------------- /quasar/src/i18n/en-us/leftDrawer/index.js: -------------------------------------------------------------------------------- 1 | const leftDrawer = { 2 | leftDrawer: { 3 | home: { 4 | main: "Home", 5 | sub: "Start here", 6 | }, 7 | about: { 8 | main: "About", 9 | sub: "About this site", 10 | }, 11 | services: { 12 | main: "Services", 13 | sub: "Monitoring, admin, metadata", 14 | }, 15 | examples: { 16 | main: "Examples", 17 | sub: "REST, Websockets, tasks", 18 | websockets: { 19 | main: "Websockets", 20 | sub: "Websockets example", 21 | }, 22 | }, 23 | tests: { 24 | main: "System Checks", 25 | sub: "System Checks", 26 | redis: { 27 | main: "Redis", 28 | sub: "Test redis connection", 29 | }, 30 | }, 31 | celery: { 32 | main: "Celery", 33 | sub: "Example Celery Tasks", 34 | }, 35 | environment: { 36 | main: "Environment", 37 | sub: "Environment variables", 38 | }, 39 | }, 40 | }; 41 | 42 | export default leftDrawer; 43 | -------------------------------------------------------------------------------- /quasar/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import enUS from "./en-us"; 2 | import cnCN from "./cn-cn"; 3 | 4 | export default { 5 | "en-us": enUS, 6 | "cn-cn": cnCN, 7 | }; 8 | -------------------------------------------------------------------------------- /quasar/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= htmlWebpackPlugin.options.productName %> 5 | 6 | 7 | 11 | 12 | 13 | 17 | 18 | 19 | 25 | 31 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /quasar/src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 36 | -------------------------------------------------------------------------------- /quasar/src/pages/About.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /quasar/src/pages/Auth/Callback.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /quasar/src/pages/Auth/GitHub.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /quasar/src/pages/Auth/Google.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /quasar/src/pages/Auth/Login.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 45 | 46 | 64 | -------------------------------------------------------------------------------- /quasar/src/pages/Auth/LoginGQL.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | 19 | 37 | -------------------------------------------------------------------------------- /quasar/src/pages/Auth/SignUp.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /quasar/src/pages/Banking/StatementFileTable.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /quasar/src/pages/Banking/StatementUploadForm.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /quasar/src/pages/Banking/StatementUploadWidget.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /quasar/src/pages/Banking/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /quasar/src/pages/Celery/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 41 | 42 | 49 | -------------------------------------------------------------------------------- /quasar/src/pages/Environment.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /quasar/src/pages/Error404.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /quasar/src/pages/Examples/Redis.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 64 | 65 | 72 | -------------------------------------------------------------------------------- /quasar/src/pages/Examples/Websockets.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 62 | 63 | 74 | -------------------------------------------------------------------------------- /quasar/src/pages/Examples/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /quasar/src/pages/HackerNewsClone/HackerNewsLink.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | 28 | 37 | -------------------------------------------------------------------------------- /quasar/src/pages/HackerNewsClone/UploadForm.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 57 | 58 | 63 | -------------------------------------------------------------------------------- /quasar/src/pages/HackerNewsClone/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 32 | 33 | 39 | -------------------------------------------------------------------------------- /quasar/src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /quasar/src/pages/Logout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /quasar/src/pages/Services/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /quasar/src/pages/Services/services.js: -------------------------------------------------------------------------------- 1 | let services = [ 2 | { 3 | icon: "verified_user", 4 | name: "Django Admin", 5 | href: "http://localhost/admin", 6 | type: "a", 7 | target: "_blank", 8 | }, 9 | { 10 | icon: "local_florist", 11 | name: "Flower", 12 | href: "http://localhost/flower", 13 | type: "a", 14 | target: "_blank", 15 | }, 16 | { 17 | icon: "email", 18 | name: "Mailhog", 19 | href: "http://localhost:8025", 20 | type: "a", 21 | target: "_blank", 22 | }, 23 | { 24 | icon: "code", 25 | name: "GitLab", 26 | href: 27 | "https://gitlab.com/verbose-equals-true/django-postgres-vue-gitlab-ecs", 28 | type: "a", 29 | target: "_blank", 30 | }, 31 | ]; 32 | 33 | if (process.env.NODE_ENV === "development") { 34 | services = [ 35 | ...services, 36 | { 37 | icon: "storage", 38 | name: "Redis Commander", 39 | href: "http://localhost:8085", 40 | type: "a", 41 | target: "_blank", 42 | }, 43 | ]; 44 | } 45 | export default services; 46 | -------------------------------------------------------------------------------- /quasar/src/pages/Transactions/TransactionsTable.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /quasar/src/pages/Transactions/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /quasar/src/pages/todos.js: -------------------------------------------------------------------------------- 1 | const todos = [ 2 | { 3 | title: "Add ToDo Page", 4 | complete: true, 5 | }, 6 | { 7 | title: "Use Vuex", 8 | complete: true, 9 | }, 10 | { 11 | title: "Implement Dark Mode Option", 12 | complete: false, 13 | }, 14 | { 15 | title: "Complete authentication", 16 | complete: true, 17 | }, 18 | { 19 | title: "Add Carousel to Home Page", 20 | complete: true, 21 | }, 22 | { 23 | title: "Show the menu if on desktop", 24 | complete: false, 25 | }, 26 | { 27 | title: "Add pgadmin4 to services", 28 | complete: false, 29 | }, 30 | ]; 31 | 32 | export default todos; 33 | -------------------------------------------------------------------------------- /quasar/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | 4 | import routes from "./routes"; 5 | 6 | Vue.use(VueRouter); 7 | 8 | export default new VueRouter({ 9 | scrollBehavior: () => ({ x: 0, y: 0 }), 10 | routes, 11 | 12 | // Leave these as is and change from quasar.conf.js instead! 13 | // quasar.conf.js -> build -> vueRouterMode 14 | // quasar.conf.js -> build -> publicPath 15 | mode: process.env.VUE_ROUTER_MODE, 16 | base: process.env.VUE_ROUTER_BASE, 17 | }); 18 | -------------------------------------------------------------------------------- /quasar/src/statics/app-logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/app-logo-128x128.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/apple-icon-120x120.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/apple-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/apple-icon-167x167.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/favicon-16x16.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/favicon-32x32.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/favicon-96x96.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/favicon.ico -------------------------------------------------------------------------------- /quasar/src/statics/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/icon-128x128.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/icon-192x192.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/icon-256x256.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/icon-384x384.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/icon-512x512.png -------------------------------------------------------------------------------- /quasar/src/statics/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briancaffey/django-postgres-vue-gitlab-ecs/206747c230ff8343197ddacc762f289f4083fccd/quasar/src/statics/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /quasar/src/store/auth/gqljwt.js: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | import { Cookies } from "quasar"; 3 | 4 | const state = { 5 | token: Cookies.get("user-token-gql") || "", 6 | }; 7 | 8 | const getters = { 9 | getToken: (s) => s.token, 10 | isAuthenticated: (s) => !!s.token, 11 | }; 12 | 13 | const actions = { 14 | async authRequest({ commit }, { email, password, vm }) { 15 | const resp = await vm.$apollo.mutate({ 16 | mutation: gql` 17 | mutation($email: String!, $password: String!) { 18 | tokenAuth(email: $email, password: $password) { 19 | token 20 | } 21 | } 22 | `, 23 | variables: { 24 | email, 25 | password, 26 | }, 27 | }); 28 | commit("authSuccess", resp.data); 29 | }, 30 | }; 31 | 32 | const mutations = { 33 | authSuccess: (state, payload) => { 34 | Cookies.set("user-token-gql", payload.tokenAuth.token); 35 | state.token = payload.tokenAuth.token; 36 | }, 37 | }; 38 | 39 | export default { 40 | namespaced: true, 41 | state, 42 | getters, 43 | actions, 44 | mutations, 45 | }; 46 | -------------------------------------------------------------------------------- /quasar/src/store/auth/index.js: -------------------------------------------------------------------------------- 1 | export const AUTH_REQUEST = "AUTH_REQUEST"; 2 | export const AUTH_SUCCESS = "AUTH_SUCCESS"; 3 | export const AUTH_ERROR = "AUTH_ERROR"; 4 | export const AUTH_LOGOUT = "AUTH_LOGOUT"; 5 | export const AUTH_REFRESH = "AUTH_REFRESH"; 6 | 7 | // import axios from "axios"; 8 | import Vue from "vue"; 9 | import { Cookies } from "quasar"; 10 | import { USER_REQUEST } from "../user"; 11 | import gqljwt from "./gqljwt"; 12 | 13 | const state = { 14 | token: localStorage.getItem("user-token") || "", 15 | status: "", 16 | hasLoadedOnce: false, 17 | }; 18 | 19 | const getters = { 20 | getToken: (s) => s.token, 21 | isAuthenticated: (s) => !!s.token, 22 | authStatus: (s) => s.status, 23 | }; 24 | 25 | const actions = { 26 | [AUTH_REQUEST]: ({ commit, dispatch }, user) => 27 | new Promise((resolve, reject) => { 28 | commit(AUTH_REQUEST); 29 | Vue.prototype.$axios 30 | .post("/api/login/", user) 31 | .then((resp) => { 32 | localStorage.setItem("user-token", "success"); 33 | commit(AUTH_SUCCESS, resp); 34 | dispatch(USER_REQUEST); 35 | resolve(resp); 36 | }) 37 | .catch((err) => { 38 | commit(AUTH_ERROR, err); 39 | localStorage.removeItem("user-token"); 40 | reject(err); 41 | }); 42 | }), 43 | [AUTH_LOGOUT]: ({ commit, dispatch }) => 44 | new Promise((resolve, reject) => { 45 | commit(AUTH_LOGOUT); 46 | localStorage.removeItem("user-token"); 47 | resolve(); 48 | }), 49 | }; 50 | 51 | const mutations = { 52 | [AUTH_REQUEST]: (requestState) => { 53 | const s = requestState; 54 | s.status = "loading"; 55 | }, 56 | [AUTH_SUCCESS]: (s, resp) => { 57 | s.status = "success"; 58 | s.token = "success"; 59 | s.hasLoadedOnce = true; 60 | }, 61 | [AUTH_ERROR]: (errorState) => { 62 | const s = errorState; 63 | s.status = "error"; 64 | s.hasLoadedOnce = true; 65 | }, 66 | [AUTH_LOGOUT]: (logoutState) => { 67 | const s = logoutState; 68 | s.token = ""; 69 | }, 70 | }; 71 | 72 | const modules = { 73 | gqljwt, 74 | }; 75 | 76 | export default { 77 | state, 78 | getters, 79 | actions, 80 | mutations, 81 | modules, 82 | }; 83 | -------------------------------------------------------------------------------- /quasar/src/store/banking/index.js: -------------------------------------------------------------------------------- 1 | import statements from "./statements"; 2 | import transactions from "./transactions"; 3 | import upload from "./upload"; 4 | 5 | export default { 6 | namespaced: true, 7 | modules: { 8 | statements, 9 | transactions, 10 | upload, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /quasar/src/store/banking/statements/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | 3 | const state = { 4 | loading: true, 5 | files: [], 6 | count: 0, 7 | currentPage: 1, 8 | paginationLimit: 2, 9 | columns: [ 10 | { align: "left", field: "id", label: "ID" }, 11 | { align: "left", field: "month", label: "Month" }, 12 | { align: "left", field: "statement_file", label: "File" }, 13 | ], 14 | }; 15 | 16 | const getters = { 17 | getLoading: (s) => s.loading, 18 | getPagination: (s) => { 19 | return { 20 | pagination: { 21 | rowsPerPage: s.paginationLimit, 22 | pagesNumber: s.count, 23 | }, 24 | }; 25 | }, 26 | getPaginationLimit: (s) => s.paginationLimit, 27 | getFiles: (s) => s.files, 28 | getCount: (s) => s.count, 29 | getTableColumns: (s) => s.columns, 30 | getCurrentPage: (s) => s.currentPage, 31 | queryParams: (s) => { 32 | return { 33 | offset: (s.currentPage - 1) * s.paginationLimit, 34 | limit: s.paginationLimit, 35 | }; 36 | }, 37 | }; 38 | 39 | const actions = { 40 | getFiles: ({ commit, getters }) => { 41 | commit("setLoading", true); 42 | const params = getters.queryParams; 43 | Vue.prototype.$axios.get("/api/statements/", { params }).then((resp) => { 44 | commit("getFiles", resp.data); 45 | commit("setLoading", false); 46 | }); 47 | }, 48 | setCurrentPage: ({ commit, dispatch }, payload) => { 49 | commit("setCurrentPage", payload); 50 | dispatch("getFiles"); 51 | }, 52 | }; 53 | 54 | const mutations = { 55 | setLoading: (state, payload) => { 56 | if (payload) { 57 | state.loading = payload; 58 | } else { 59 | state.loading = !state.loading; 60 | } 61 | }, 62 | getFiles: (state, payload) => { 63 | state.files = payload.results; 64 | state.count = payload.count; 65 | }, 66 | setCurrentPage: (state, payload) => { 67 | state.currentPage = payload; 68 | }, 69 | setPagination: (state, payload) => {}, 70 | }; 71 | 72 | export default { 73 | namespaced: true, 74 | state, 75 | getters, 76 | actions, 77 | mutations, 78 | }; 79 | -------------------------------------------------------------------------------- /quasar/src/store/banking/upload/index.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | date: null, 3 | file: null, 4 | }; 5 | 6 | const getters = { 7 | getDate: (s) => s.date, 8 | getFile: (s) => s.file, 9 | }; 10 | 11 | const actions = { 12 | uploadFile: ({ getters }, payload) => { 13 | const formData = new FormData(); 14 | formData.append("file", getters.getFile); 15 | formData.append("form", JSON.stringify({ month: getters.getDate })); 16 | payload.vm.$axios.post("/api/statements/", formData, { 17 | headers: { "Content-Type": "multipart/form-data" }, 18 | }); 19 | }, 20 | }; 21 | 22 | const mutations = { 23 | setDate: (state, payload) => { 24 | state.date = payload; 25 | }, 26 | setFile: (state, payload) => { 27 | state.file = payload; 28 | }, 29 | }; 30 | 31 | export default { 32 | namespaced: true, 33 | state, 34 | getters, 35 | actions, 36 | mutations, 37 | }; 38 | -------------------------------------------------------------------------------- /quasar/src/store/hn/index.js: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | import upload from "./upload"; 3 | 4 | const state = { 5 | links: null, 6 | searchTerm: "", 7 | }; 8 | 9 | const getters = { 10 | getLinks: (s) => s.links, 11 | getSearchTerm: (s) => s.searchTerm, 12 | }; 13 | 14 | const actions = { 15 | async getLinks({ commit, getters }, { vm }) { 16 | const resp = await vm.$apollo.query({ 17 | query: gql` 18 | query GetLinks($search: String!) { 19 | links(search: $search) { 20 | url 21 | id 22 | description 23 | postedBy { 24 | email 25 | id 26 | } 27 | votes { 28 | id 29 | user { 30 | email 31 | } 32 | } 33 | } 34 | } 35 | `, 36 | variables: { 37 | search: getters.getSearchTerm, 38 | }, 39 | }); 40 | 41 | const links = resp.data.links; 42 | commit("setLinks", { links }); 43 | }, 44 | }; 45 | 46 | const mutations = { 47 | setLinks: (state, { links }) => { 48 | state.links = links; 49 | }, 50 | }; 51 | 52 | const modules = { 53 | upload, 54 | }; 55 | 56 | export default { 57 | namespaced: true, 58 | state, 59 | getters, 60 | actions, 61 | mutations, 62 | modules, 63 | }; 64 | -------------------------------------------------------------------------------- /quasar/src/store/hn/upload.js: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | const state = { 4 | url: "", 5 | description: "", 6 | showUploadForm: false, 7 | }; 8 | 9 | const getters = { 10 | getUrl: (s) => s.url, 11 | getDescription: (s) => s.description, 12 | getShowUploadForm: (s) => s.showUploadForm, 13 | }; 14 | 15 | const actions = { 16 | async submitLink({ getters }, { vm }) { 17 | await vm.$apollo.mutate({ 18 | mutation: gql` 19 | mutation($url: String!, $description: String!) { 20 | createLink(url: $url, description: $description) { 21 | id 22 | url 23 | description 24 | postedBy { 25 | email 26 | id 27 | } 28 | } 29 | } 30 | `, 31 | variables: { 32 | url: getters.getUrl, 33 | description: getters.getDescription, 34 | }, 35 | }); 36 | }, 37 | }; 38 | 39 | const mutations = { 40 | setUrl: (state, payload) => { 41 | state.url = payload; 42 | }, 43 | setDescription: (state, payload) => { 44 | state.description = payload; 45 | }, 46 | toggleUploadForm: (state) => { 47 | state.showUploadForm = !state.showUploadForm; 48 | }, 49 | }; 50 | 51 | export default { 52 | namespaced: true, 53 | state, 54 | getters, 55 | actions, 56 | mutations, 57 | }; 58 | -------------------------------------------------------------------------------- /quasar/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import ui from "./ui"; 4 | import auth from "./auth"; 5 | import user from "./user"; 6 | import social from "./social"; 7 | import banking from "./banking"; 8 | import hn from "./hn"; 9 | 10 | // import example from './module-example' 11 | 12 | Vue.use(Vuex); 13 | 14 | /* 15 | * If not building with SSR mode, you can 16 | * directly export the Store instantiation 17 | */ 18 | export default function (/* { ssrContext } */) { 19 | const Store = new Vuex.Store({ 20 | modules: { 21 | ui, 22 | auth, 23 | user, 24 | social, 25 | banking, 26 | hn, 27 | }, 28 | 29 | // enable strict mode (adds overhead!) 30 | // for dev mode only 31 | strict: process.env.DEV, 32 | }); 33 | return Store; 34 | } 35 | -------------------------------------------------------------------------------- /quasar/src/store/module-example/actions.js: -------------------------------------------------------------------------------- 1 | export function someAction(/* context */) {} 2 | -------------------------------------------------------------------------------- /quasar/src/store/module-example/getters.js: -------------------------------------------------------------------------------- 1 | export function someGetter(/* state */) {} 2 | -------------------------------------------------------------------------------- /quasar/src/store/module-example/index.js: -------------------------------------------------------------------------------- 1 | import state from "./state"; 2 | import * as getters from "./getters"; 3 | import * as mutations from "./mutations"; 4 | import * as actions from "./actions"; 5 | 6 | export default { 7 | namespaced: true, 8 | getters, 9 | mutations, 10 | actions, 11 | state, 12 | }; 13 | -------------------------------------------------------------------------------- /quasar/src/store/module-example/mutations.js: -------------------------------------------------------------------------------- 1 | export function someMutation(/* state */) {} 2 | -------------------------------------------------------------------------------- /quasar/src/store/module-example/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | }; 4 | -------------------------------------------------------------------------------- /quasar/src/store/social/index.js: -------------------------------------------------------------------------------- 1 | import buildURL from "axios/lib/helpers/buildURL"; 2 | import oauth from "../../utils/oauth"; 3 | 4 | const state = { 5 | oauth, 6 | }; 7 | 8 | const getters = { 9 | oauthUrl: () => { 10 | return (provider) => { 11 | const url = state.oauth[provider].url; 12 | const params = state.oauth[provider].params; 13 | 14 | return buildURL(url, params); 15 | }; 16 | }, 17 | }; 18 | 19 | const actions = {}; 20 | 21 | const mutations = {}; 22 | 23 | export default { 24 | state, 25 | getters, 26 | actions, 27 | mutations, 28 | }; 29 | -------------------------------------------------------------------------------- /quasar/src/store/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 2 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 3 | import "quasar/dist/types/feature-flag"; 4 | 5 | declare module "quasar/dist/types/feature-flag" { 6 | interface QuasarFeatureFlags { 7 | store: true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /quasar/src/store/ui/index.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | visible: false, 3 | isDark: true, 4 | leftDrawerOpen: false, 5 | nextLink: null, 6 | authPanel: "login", 7 | }; 8 | 9 | const getters = { 10 | leftDrawerOpen: (s) => s.leftDrawerOpen, 11 | authModalVisible: (s) => s.visible, 12 | getNextLink: (s) => s.nextLink, 13 | getAuthPanel: (s) => s.authPanel, 14 | isDark: (s) => s.isDark, 15 | }; 16 | 17 | const mutations = { 18 | toggleDarkMode: (state) => { 19 | state.isDark = !state.isDark; 20 | }, 21 | setAuthPanel: (state, payload) => { 22 | state.authPanel = payload; 23 | }, 24 | toggleLoginMenu: (state) => { 25 | state.visible = !state.visible; 26 | }, 27 | toggleLeftDrawer: (state, payload) => { 28 | if (payload) { 29 | state.leftDrawerOpen = payload.leftDrawerOpen; 30 | return; 31 | } 32 | state.leftDrawerOpen = !state.leftDrawerOpen; 33 | }, 34 | }; 35 | 36 | export default { 37 | state, 38 | getters, 39 | mutations, 40 | }; 41 | -------------------------------------------------------------------------------- /quasar/src/store/user/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | export const USER_REQUEST = "USER_REQUEST"; 4 | export const USER_SUCCESS = "USER_SUCCESS"; 5 | export const USER_ERROR = "USER_ERROR"; 6 | 7 | import Vue from "vue"; 8 | import { AUTH_LOGOUT } from "../auth"; 9 | 10 | const state = { 11 | status: "", 12 | profile: {}, 13 | }; 14 | 15 | const getters = { 16 | getProfile: (s) => s.profile, 17 | isProfileLoaded: (s) => !!s.profile.name, 18 | }; 19 | 20 | const actions = { 21 | [USER_REQUEST]: ({ dispatch, commit }) => { 22 | Vue.prototype.$axios 23 | .get("/api/users/profile/") 24 | .then((resp) => { 25 | const profile = resp.data; 26 | commit(USER_SUCCESS, { 27 | email: profile.email, 28 | }); 29 | }) 30 | .catch((err) => { 31 | commit(USER_ERROR); 32 | dispatch(AUTH_LOGOUT); 33 | }); 34 | }, 35 | }; 36 | 37 | const mutations = { 38 | [USER_REQUEST]: (s) => { 39 | s.status = "loading"; 40 | }, 41 | [USER_SUCCESS]: (s, resp) => { 42 | // s.status = "success"; 43 | Vue.set(state, "profile", resp); 44 | }, 45 | [USER_ERROR]: (s) => { 46 | s.status = "error"; 47 | }, 48 | [AUTH_LOGOUT]: (s) => { 49 | s.profile = {}; 50 | s.status = ""; 51 | }, 52 | }; 53 | 54 | export default { 55 | state, 56 | getters, 57 | actions, 58 | mutations, 59 | }; 60 | -------------------------------------------------------------------------------- /quasar/src/utils/oauth.js: -------------------------------------------------------------------------------- 1 | const url = process.env.API_URL; 2 | 3 | const oauth = { 4 | github: { 5 | url: "https://github.com/login/oauth/authorize", 6 | params: { 7 | client_id: process.env.GITHUB_KEY, 8 | redirect_uri: `${url}/auth/github/callback`, 9 | login: "", 10 | scope: "user", 11 | state: "eworifjeovivoiej", 12 | }, 13 | }, 14 | "google-oauth2": { 15 | url: "https://accounts.google.com/o/oauth2/v2/auth", 16 | params: { 17 | client_id: process.env.GOOGLE_OAUTH2_KEY, 18 | response_type: "code", 19 | scope: "openid email", 20 | redirect_uri: `${url}/auth/google-oauth2/callback`, 21 | state: "eworifjeovivoiej", // TODO: change these 22 | nonce: "forewijf43oirjoifj", 23 | login_hint: "", 24 | }, 25 | }, 26 | facebook: { 27 | url: "https://www.facebook.com/v5.0/dialog/oauth", 28 | params: { 29 | client_id: process.env.FACEBOOK_KEY, 30 | redirect_uri: `${url}/auth/facebook/callback`, 31 | state: "eworifjeovivoiej", // TODO: change these 32 | scope: "email", 33 | }, 34 | }, 35 | }; 36 | 37 | export default oauth; 38 | -------------------------------------------------------------------------------- /quasar/start_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | 5 | quasar dev -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "ignorePresets": [":prHourlyLimit2"], 5 | "prHourlyLimit": 10, 6 | "pip_requirements": { 7 | "fileMatch": ["requirements/.+.txt$"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /renovate/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endpoint: 'https://gitlab.com/api/v4/', 3 | token: process.env.GITLAB_TOKEN, 4 | platform: 'gitlab', 5 | logFileLevel: 'warn', 6 | logLevel: 'debug', 7 | logFile: 'renovate.log', 8 | onboarding: true, 9 | onboardingConfig: { 10 | extends: ['config:base'], 11 | ignorePresets: [":prHourlyLimit2"], 12 | pip_requirements: { 13 | "fileMatch": ["requirements\/.+\.txt$"] 14 | }, 15 | }, 16 | hostRules: [ 17 | { 18 | "domainName": "github.com", 19 | "encrypted": { 20 | "token": process.env.GITHUB_TOKEN 21 | } 22 | } 23 | ], 24 | repositories: ['verbose-equals-true/django-postgres-vue-gitlab-ecs'], 25 | gitAuthor: "Brian Caffey ", 26 | ignorePresets: [":prHourlyLimit2"], 27 | prHourlyLimit: 10 28 | }; 29 | --------------------------------------------------------------------------------