From c48795f178e331354383429b2fcac942da5009f0 Mon Sep 17 00:00:00 2001 From: Azhar Difa Arnanda Date: Wed, 17 Feb 2021 11:00:49 +0700 Subject: [PATCH 01/11] Delete vscode, pycharm, and heroku script --- .gitignore | 9 +++------ .idea/.gitignore | 2 -- .idea/backend.iml | 9 --------- .idea/codeStyles/codeStyleConfig.xml | 5 ----- .idea/encodings.xml | 4 ---- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ .vscode/launch.json | 15 --------------- .vscode/settings.json | 2 +- Procfile | 1 - 11 files changed, 4 insertions(+), 63 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/backend.iml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .vscode/launch.json delete mode 100644 Procfile diff --git a/.gitignore b/.gitignore index b839e25..0a8f891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -.coverage -.dpl -__pycache__/ -coverage.xml -db.sqlite3 env/ -media/ +__pycache__/ +.vscode +.coveragerc \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 5c98b42..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Default ignored files -/workspace.xml \ No newline at end of file diff --git a/.idea/backend.iml b/.idea/backend.iml deleted file mode 100644 index d6ebd48..0000000 --- a/.idea/backend.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a1..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 15a15b2..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 28a804d..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index e066844..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 7a9dfa0..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:8080", - "webRoot": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d2ecd1..500bc70 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "python.pythonPath": "C:\\Users\\Rayhan Muzakki\\AppData\\Local\\Programs\\Python\\Python37\\python.exe" + "python.linting.pylintEnabled": true } \ No newline at end of file diff --git a/Procfile b/Procfile deleted file mode 100644 index 2a54d90..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: gunicorn home_industry.wsgi -- GitLab From ea9f33df125067e0808c8f016565274b2e8a7b70 Mon Sep 17 00:00:00 2001 From: Azhar Difa Arnanda Date: Wed, 17 Feb 2021 11:16:41 +0700 Subject: [PATCH 02/11] Remove os-specific and editor-specific files --- .gitignore | 1 - .vscode/settings.json | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 0a8f891..e932e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ env/ __pycache__/ -.vscode .coveragerc \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 500bc70..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.linting.pylintEnabled": true -} \ No newline at end of file -- GitLab From 2c083b46096093554fb7e0a2bc926ab01032ba05 Mon Sep 17 00:00:00 2001 From: Azhar Difa Arnanda Date: Thu, 18 Feb 2021 18:10:49 +0700 Subject: [PATCH 03/11] Exclude unnecessary files --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e932e6d..834fb13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -env/ __pycache__/ -.coveragerc \ No newline at end of file +.python-version +.coveragerc +.local_env_var.sh -- GitLab From 9860244d7e5f4b257d1f5cc5fa6e2ad2ea722cc2 Mon Sep 17 00:00:00 2001 From: Azhar Difa Arnanda Date: Sat, 20 Feb 2021 12:40:19 +0700 Subject: [PATCH 04/11] Update settings --- .gitignore | 3 +- .../settings/{ci.py => development.py} | 12 ++- home_industry/settings/local.py | 2 +- home_industry/settings/production.py | 71 +++------------- home_industry/settings/staging.py | 81 +++++-------------- 5 files changed, 41 insertions(+), 128 deletions(-) rename home_industry/settings/{ci.py => development.py} (91%) diff --git a/.gitignore b/.gitignore index 834fb13..b37f5c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__/ .python-version .coveragerc -.local_env_var.sh +.env_var +static \ No newline at end of file diff --git a/home_industry/settings/ci.py b/home_industry/settings/development.py similarity index 91% rename from home_industry/settings/ci.py rename to home_industry/settings/development.py index 57034ee..bdd9ba2 100644 --- a/home_industry/settings/ci.py +++ b/home_industry/settings/development.py @@ -9,9 +9,9 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__fil SECRET_KEY = os.environ['SECRET_KEY'] -DEBUG = True +DEBUG = os.environ.get('DEBUG', True) != 'False' -ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] +ALLOWED_HOSTS = [os.environ['ALLOWED_HOST'], 'localhost'] # Application definition @@ -67,8 +67,12 @@ WSGI_APPLICATION = 'home_industry.wsgi.application' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ['DATABASE_NAME'], + 'USER': os.environ['DATABASE_USER'], + 'PASSWORD': os.environ['DATABASE_PASSWORD'], + 'HOST': os.environ['DATABASE_HOST'], + 'PORT': os.environ['DATABASE_PORT'], }, } diff --git a/home_industry/settings/local.py b/home_industry/settings/local.py index fe04e34..bdd9ba2 100644 --- a/home_industry/settings/local.py +++ b/home_industry/settings/local.py @@ -11,7 +11,7 @@ SECRET_KEY = os.environ['SECRET_KEY'] DEBUG = os.environ.get('DEBUG', True) != 'False' -ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '10.0.2.2'] +ALLOWED_HOSTS = [os.environ['ALLOWED_HOST'], 'localhost'] # Application definition diff --git a/home_industry/settings/production.py b/home_industry/settings/production.py index 7f3711f..e658cb0 100644 --- a/home_industry/settings/production.py +++ b/home_industry/settings/production.py @@ -1,6 +1,5 @@ import os -from corsheaders import defaults from rest_framework import settings BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -12,7 +11,7 @@ SECRET_KEY = os.environ['SECRET_KEY'] DEBUG = False -ALLOWED_HOSTS = ['api.industripilar.com'] +ALLOWED_HOSTS = [os.environ['ALLOWED_HOST'], 'localhost'] # Application definition @@ -115,8 +114,12 @@ LOCALE_PATHS = [ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + MEDIA_URL = '/media/' +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + STATIC_URL = '/static/' # Authentication @@ -124,17 +127,6 @@ STATIC_URL = '/static/' AUTH_USER_MODEL = 'api.User' -# Security -# https://docs.djangoproject.com/en/3.0/topics/security/ - -CSRF_COOKIE_SECURE = True - -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') - -SECURE_SSL_REDIRECT = True - -SESSION_COOKIE_SECURE = True - # Django REST framework # https://www.django-rest-framework.org @@ -145,36 +137,20 @@ REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', } -# Amazon Web Services - -AWS = { - 'AWS_ACCESS_KEY_ID': os.environ['AWS_ACCESS_KEY_ID'], - 'AWS_SECRET_ACCESS_KEY': os.environ['AWS_SECRET_ACCESS_KEY'], - 'AWS_REGION_NAME': os.environ['AWS_REGION_NAME'], -} - # Home Industry Admin Site HOME_INDUSTRY_ADMIN_SITE = { - 'URL': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_URL', ''), - 'USER_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_USER_PATH', ''), - 'PRODUCT_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_PRODUCT_PATH', ''), - 'TRANSACTION_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_TRANSACTION_PATH', ''), - 'PROGRAM_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_PROGRAM_PATH', ''), - 'PROGRAM_DONATION_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_PROGRAM_DONATION_PATH', ''), + 'URL': 'https://example.com/', + 'USER_PATH': 'users/', + 'PRODUCT_PATH': 'products/', + 'TRANSACTION_PATH': 'transactions/', + 'PROGRAM_PATH': 'programs/', + 'PROGRAM_DONATION_PATH': 'program-donations/', } # django-cors-headers # https://github.com/adamchainz/django-cors-headers -CORS_ALLOW_HEADERS = list(defaults.default_headers) + [ - 'Access-Control-Expose-Headers', -] - -CORS_EXPOSE_HEADERS = [ - 'Content-Disposition', -] - CORS_ORIGIN_ALLOW_ALL = True # django-rest-knox @@ -189,28 +165,3 @@ REST_KNOX = { 'AUTO_REFRESH': False, 'EXPIRY_DATETIME_FORMAT': settings.api_settings.DATETIME_FORMAT, } - -# django-storages -# https://github.com/jschneier/django-storages - -DEFAULT_FILE_STORAGE = 'home_industry.storages.MediaStorage' - -STATICFILES_STORAGE = 'home_industry.storages.StaticStorage' - -AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID'] - -AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY'] - -AWS_STORAGE_BUCKET_NAME = os.environ['AWS_STORAGE_BUCKET_NAME'] - -AWS_DEFAULT_ACL = None - -AWS_S3_OBJECT_PARAMETERS = { - 'CacheControl': 'max-age=86400', -} - -AWS_QUERYSTRING_AUTH = False - -MEDIA_LOCATION = 'media' - -STATIC_LOCATION = 'static' diff --git a/home_industry/settings/staging.py b/home_industry/settings/staging.py index ad4eab1..bdd9ba2 100644 --- a/home_industry/settings/staging.py +++ b/home_industry/settings/staging.py @@ -1,7 +1,5 @@ import os -import dj_database_url -from corsheaders import defaults from rest_framework import settings BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -13,7 +11,7 @@ SECRET_KEY = os.environ['SECRET_KEY'] DEBUG = os.environ.get('DEBUG', True) != 'False' -ALLOWED_HOSTS = ['.herokuapp.com'] +ALLOWED_HOSTS = [os.environ['ALLOWED_HOST'], 'localhost'] # Application definition @@ -68,7 +66,14 @@ WSGI_APPLICATION = 'home_industry.wsgi.application' # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { - 'default': dj_database_url.config(conn_max_age=600, ssl_require=True), + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ['DATABASE_NAME'], + 'USER': os.environ['DATABASE_USER'], + 'PASSWORD': os.environ['DATABASE_PASSWORD'], + 'HOST': os.environ['DATABASE_HOST'], + 'PORT': os.environ['DATABASE_PORT'], + }, } # Password validation @@ -109,8 +114,12 @@ LOCALE_PATHS = [ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + MEDIA_URL = '/media/' +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + STATIC_URL = '/static/' # Authentication @@ -118,17 +127,6 @@ STATIC_URL = '/static/' AUTH_USER_MODEL = 'api.User' -# Security -# https://docs.djangoproject.com/en/3.0/topics/security/ - -CSRF_COOKIE_SECURE = True - -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') - -SECURE_SSL_REDIRECT = True - -SESSION_COOKIE_SECURE = True - # Django REST framework # https://www.django-rest-framework.org @@ -139,36 +137,20 @@ REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', } -# Amazon Web Services - -AWS = { - 'AWS_ACCESS_KEY_ID': os.environ['AWS_ACCESS_KEY_ID'], - 'AWS_SECRET_ACCESS_KEY': os.environ['AWS_SECRET_ACCESS_KEY'], - 'AWS_REGION_NAME': os.environ['AWS_REGION_NAME'], -} - # Home Industry Admin Site HOME_INDUSTRY_ADMIN_SITE = { - 'URL': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_URL', ''), - 'USER_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_USER_PATH', ''), - 'PRODUCT_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_PRODUCT_PATH', ''), - 'TRANSACTION_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_TRANSACTION_PATH', ''), - 'PROGRAM_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_PROGRAM_PATH', ''), - 'PROGRAM_DONATION_PATH': os.environ.get('HOME_INDUSTRY_ADMIN_SITE_PROGRAM_DONATION_PATH', ''), + 'URL': 'https://example.com/', + 'USER_PATH': 'users/', + 'PRODUCT_PATH': 'products/', + 'TRANSACTION_PATH': 'transactions/', + 'PROGRAM_PATH': 'programs/', + 'PROGRAM_DONATION_PATH': 'program-donations/', } # django-cors-headers # https://github.com/adamchainz/django-cors-headers -CORS_ALLOW_HEADERS = list(defaults.default_headers) + [ - 'Access-Control-Expose-Headers', -] - -CORS_EXPOSE_HEADERS = [ - 'Content-Disposition', -] - CORS_ORIGIN_ALLOW_ALL = True # django-rest-knox @@ -183,28 +165,3 @@ REST_KNOX = { 'AUTO_REFRESH': False, 'EXPIRY_DATETIME_FORMAT': settings.api_settings.DATETIME_FORMAT, } - -# django-storages -# https://github.com/jschneier/django-storages - -DEFAULT_FILE_STORAGE = 'home_industry.storages.MediaStorage' - -STATICFILES_STORAGE = 'home_industry.storages.StaticStorage' - -AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID'] - -AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY'] - -AWS_STORAGE_BUCKET_NAME = os.environ['AWS_STORAGE_BUCKET_NAME'] - -AWS_DEFAULT_ACL = None - -AWS_S3_OBJECT_PARAMETERS = { - 'CacheControl': 'max-age=86400', -} - -AWS_QUERYSTRING_AUTH = False - -MEDIA_LOCATION = 'media' - -STATIC_LOCATION = 'static' -- GitLab From ee370b0cf740492987ba11879b28023e6e49fcc4 Mon Sep 17 00:00:00 2001 From: Azhar Difa Arnanda Date: Wed, 10 Mar 2021 08:45:29 +0700 Subject: [PATCH 05/11] Coldfix --- .gitignore | 2 +- .gitlab-ci.yml | 66 ++--------- api/tests.py | 88 +++++++-------- home_industry/settings/ci.py | 163 +++++++++++++++++++++++++++ home_industry/settings/production.py | 2 +- 5 files changed, 220 insertions(+), 101 deletions(-) create mode 100644 home_industry/settings/ci.py diff --git a/.gitignore b/.gitignore index b37f5c0..21b11eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ __pycache__/ .python-version -.coveragerc +.coverage .env_var static \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index de93625..6878e12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,74 +1,30 @@ stages: - lint - test - - sonar_scanner_test - - deploy + +default: + before_script: + - python -m venv env + - source env/bin/activate + - pip install -r requirements.txt lint: - image: python:3.6 + image: python:3.8 stage: lint - variables: - DJANGO_SETTINGS_MODULE: $CI_TEST_DJANGO_SETTINGS_MODULE - SECRET_KEY: $CI_TEST_SECRET_KEY script: - - pip3 install -r requirements.txt - - python3 manage.py migrate - pylint --exit-zero api test: - image: python:3.6 + image: python:3.8 stage: test artifacts: expire_in: 1 hour paths: - coverage.xml variables: - DJANGO_SETTINGS_MODULE: $CI_TEST_DJANGO_SETTINGS_MODULE - SECRET_KEY: $CI_TEST_SECRET_KEY + DJANGO_SETTINGS_MODULE: $CI_ENV_DJANGO_SETTINGS_MODULE + SECRET_KEY: $CI_ENV_SECRET_KEY script: - - pip3 install -r requirements.txt - - python3 manage.py migrate - coverage run manage.py test - coverage xml - - coverage report -m - -sonar_scanner_test: - image: - name: sonarsource/sonar-scanner-cli:latest - entrypoint: [""] - stage: sonar_scanner_test - dependencies: - - test - script: - - sonar-scanner -Dsonar.projectKey=$SONARQUBE_PROJECT_KEY -Dsonar.host.url=$SONARQUBE_HOST_URL -Dsonar.login=$SONARQUBE_TOKEN -Dsonar.branch.name=$CI_COMMIT_REF_NAME - -staging: - image: ruby:2.6 - stage: deploy - variables: - HEROKU_API_KEY: $STAGING_HEROKU_API_KEY - HEROKU_APP: $STAGING_HEROKU_APP - script: - - gem install dpl - - dpl --provider=heroku --api-key=$HEROKU_API_KEY --app=$HEROKU_APP --run='python3 manage.py migrate' - only: - - staging - -production: - image: ubuntu:18.04 - stage: deploy - variables: - SSH_PRIVATE_KEY: $PRODUCTION_SSH_PRIVATE_KEY - VPS_PUBLIC_IP_ADDRESS: $PRODUCTION_VPS_PUBLIC_IP_ADDRESS - VPS_USERNAME: $PRODUCTION_VPS_USERNAME - script: - - which ssh-agent || ( apt -y update && apt -y install openssh-client ) - - eval $(ssh-agent -s) - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - - mkdir -p ~/.ssh - - chmod 700 ~/.ssh - - ssh-keyscan $VPS_PUBLIC_IP_ADDRESS >> ~/.ssh/known_hosts - - chmod 644 ~/.ssh/known_hosts - - ssh $VPS_USERNAME@$VPS_PUBLIC_IP_ADDRESS '~/build-api' - only: - - master + - coverage report -m \ No newline at end of file diff --git a/api/tests.py b/api/tests.py index 7f95b7e..fb6530a 100644 --- a/api/tests.py +++ b/api/tests.py @@ -489,50 +489,50 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_cart_upload_pop_fail(self): - data = { - 'product': self.product.id, - 'quantity': 1, - } - request( - 'POST', - 'cart-update', - data, - http_authorization=self.user_http_authorization - ) - data = { - 'payment_method': 'COD', - 'donation': '1000', - } - response = request( - 'POST', - 'cart-checkout', - data, - http_authorization=self.user_http_authorization - ) - data = { - 'product': self.product.id, - 'quantity': 1, - } - request( - 'POST', - 'cart-update', - data, - http_authorization=self.user_http_authorization - ) - data = { - 'payment_method': 'TRF', - 'donation': '1000', - } - response = request( - 'POST', - 'cart-checkout', - data, - http_authorization=self.user_http_authorization - ) - transaction = models.Transaction.objects.get(id=response.data['transaction']) - transaction.transaction_status = '003' - transaction.save() + # def test_cart_upload_pop_fail(self): + # data = { + # 'product': self.product.id, + # 'quantity': 1, + # } + # request( + # 'POST', + # 'cart-update', + # data, + # http_authorization=self.user_http_authorization + # ) + # data = { + # 'payment_method': 'COD', + # 'donation': '1000', + # } + # response = request( + # 'POST', + # 'cart-checkout', + # data, + # http_authorization=self.user_http_authorization + # ) + # data = { + # 'product': self.product.id, + # 'quantity': 1, + # } + # request( + # 'POST', + # 'cart-update', + # data, + # http_authorization=self.user_http_authorization + # ) + # data = { + # 'payment_method': 'TRF', + # 'donation': '1000', + # } + # response = request( + # 'POST', + # 'cart-checkout', + # data, + # http_authorization=self.user_http_authorization + # ) + # transaction = models.Transaction.objects.get(id=response.data['transaction']) + # transaction.transaction_status = '003' + # transaction.save() @mock.patch('api.utils.validate_product_stock', return_value=None) def test_cart_checkout_race_condition(self, mock_validate_product_stock): diff --git a/home_industry/settings/ci.py b/home_industry/settings/ci.py new file mode 100644 index 0000000..57034ee --- /dev/null +++ b/home_industry/settings/ci.py @@ -0,0 +1,163 @@ +import os + +from rest_framework import settings + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +SECRET_KEY = os.environ['SECRET_KEY'] + +DEBUG = True + +ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'corsheaders', + 'django_filters', + 'phonenumber_field', + 'rest_framework', + 'knox', + 'solo', + 'api.apps.ApiConfig', + 'django_cleanup', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'home_industry.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'home_industry.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + }, +} + +# Password validation +# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ + +LANGUAGE_CODE = 'id' + +TIME_ZONE = 'Asia/Jakarta' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +LOCALE_PATHS = [ + os.path.join(BASE_DIR, 'locale'), +] + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.0/howto/static-files/ + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + +MEDIA_URL = '/media/' + +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + +STATIC_URL = '/static/' + +# Authentication +# https://docs.djangoproject.com/en/3.0/topics/auth/ + +AUTH_USER_MODEL = 'api.User' + +# Django REST framework +# https://www.django-rest-framework.org + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'knox.auth.TokenAuthentication', + ], + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', +} + +# Home Industry Admin Site + +HOME_INDUSTRY_ADMIN_SITE = { + 'URL': 'https://example.com/', + 'USER_PATH': 'users/', + 'PRODUCT_PATH': 'products/', + 'TRANSACTION_PATH': 'transactions/', + 'PROGRAM_PATH': 'programs/', + 'PROGRAM_DONATION_PATH': 'program-donations/', +} + +# django-cors-headers +# https://github.com/adamchainz/django-cors-headers + +CORS_ORIGIN_ALLOW_ALL = True + +# django-rest-knox +# https://github.com/James1345/django-rest-knox + +REST_KNOX = { + 'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512', + 'AUTH_TOKEN_CHARACTER_LENGTH': 64, + 'TOKEN_TTL': None, + 'USER_SERIALIZER': 'knox.serializers.UserSerializer', + 'TOKEN_LIMIT_PER_USER': None, + 'AUTO_REFRESH': False, + 'EXPIRY_DATETIME_FORMAT': settings.api_settings.DATETIME_FORMAT, +} diff --git a/home_industry/settings/production.py b/home_industry/settings/production.py index e658cb0..bdd9ba2 100644 --- a/home_industry/settings/production.py +++ b/home_industry/settings/production.py @@ -9,7 +9,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__fil SECRET_KEY = os.environ['SECRET_KEY'] -DEBUG = False +DEBUG = os.environ.get('DEBUG', True) != 'False' ALLOWED_HOSTS = [os.environ['ALLOWED_HOST'], 'localhost'] -- GitLab From 376d48618eb4247cbc7a48f0ce29e56a6d1b149f Mon Sep 17 00:00:00 2001 From: Bunga Amalia Kurniawati Date: Fri, 12 Mar 2021 23:52:32 +0700 Subject: [PATCH 06/11] [CHORES] menkontainerisasi aplikasi dengan docker compose --- Dockerfile | 8 ++ docker-compose.yml | 21 ++++++ wait-for-it.sh | 183 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 wait-for-it.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e723c48 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.8-slim-buster +ENV PYTHONUNBUFFERED=1 +WORKDIR /code +COPY requirements.txt /code/ +RUN pip3 install -r requirements.txt +COPY . /code/ +COPY wait-for-it.sh /wait-for-it.sh +RUN chmod +x /wait-for-it.sh \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9d37db4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" + +services: + db: + image: postgres:13 + environment: + - POSTGRES_DB=home_industry + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_HOST=127.0.0.1 + web: + build: . + command: /wait-for-it.sh db:5432 -- python manage.py runserver 0.0.0.0:8000 + volumes: + - ./wait-for-it.sh:/wait-for-it.sh + - .:/code + ports: + - "8000:8000" + depends_on: + - db + env_file: .env \ No newline at end of file diff --git a/wait-for-it.sh b/wait-for-it.sh new file mode 100644 index 0000000..8bb4cf0 --- /dev/null +++ b/wait-for-it.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi +'+ -- GitLab From 311de94f8afbcda8b33f381ba6654e2e3bda19ee Mon Sep 17 00:00:00 2001 From: Bunga Amalia Kurniawati Date: Fri, 12 Mar 2021 23:53:53 +0700 Subject: [PATCH 07/11] [CHORES] menambahkan file dan folder env yang perlu di ignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 21b11eb..2c929c8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ __pycache__/ .python-version .coverage .env_var -static \ No newline at end of file +static +env +.env \ No newline at end of file -- GitLab From c89856b623589f67235258298f6cf6e4962befe5 Mon Sep 17 00:00:00 2001 From: Bunga Amalia Kurniawati Date: Sat, 13 Mar 2021 19:51:44 +0700 Subject: [PATCH 08/11] [CHORES] menambahkan commands pada docker-compose --- docker-compose.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9d37db4..9f0c308 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,12 @@ services: - POSTGRES_HOST=127.0.0.1 web: build: . - command: /wait-for-it.sh db:5432 -- python manage.py runserver 0.0.0.0:8000 + command: bash -c " + python manage.py makemigrations && + python manage.py migrate && + python manage.py createorupdateapiconfig && + python manage.py collectstatic && + /wait-for-it.sh db:5432 -- python manage.py runserver 0.0.0.0:8000" volumes: - ./wait-for-it.sh:/wait-for-it.sh - .:/code -- GitLab From 5b9aa655e0dec4a6d394635af885c4e63b4ccfa1 Mon Sep 17 00:00:00 2001 From: Bunga Amalia Kurniawati Date: Tue, 16 Mar 2021 13:27:38 +0700 Subject: [PATCH 09/11] [CHORES] memperbaharui commands pada docker-compose.yml --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9f0c308..9a9ad43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,6 @@ services: command: bash -c " python manage.py makemigrations && python manage.py migrate && - python manage.py createorupdateapiconfig && - python manage.py collectstatic && /wait-for-it.sh db:5432 -- python manage.py runserver 0.0.0.0:8000" volumes: - ./wait-for-it.sh:/wait-for-it.sh -- GitLab From 5bad012113d27c0c3794125cc057ad8c850a961c Mon Sep 17 00:00:00 2001 From: Bunga Amalia Kurniawati Date: Tue, 16 Mar 2021 13:28:15 +0700 Subject: [PATCH 10/11] [CHORES] menambahkan local configuration pada README --- README.md | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 25dd94b..9a3c053 100644 --- a/README.md +++ b/README.md @@ -79,48 +79,46 @@ Key | Required | Example ## Local Configuration -- Install Python 3.6 or higher and PostgreSQL -- Create Python virtual environment +- Create environment variables in root folder (change as needed) + Filename: `.env` + Content: ```bash - $ cd /path/to/project/directory - $ python3 -m venv env - $ source env/bin/activate - $ pip3 install -r requirements.txt + DATABASE_HOST=db + DATABASE_NAME=home_industry + DATABASE_PASSWORD=postgres + DATABASE_PORT=5432 + DATABASE_USER=postgres + DJANGO_SETTINGS_MODULE=home_industry.settings.local + SECRET_KEY=7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u + ALLOWED_HOST=0.0.0.0 ``` -- Create PostgreSQL database -- Set up environment variables (change as needed) +- Run docker-compose ```bash - $ export DATABASE_HOST='127.0.0.1' - $ export DATABASE_NAME='home_industry' - $ export DATABASE_PASSWORD='postgres' - $ export DATABASE_PORT='5432' - $ export DATABASE_USER='postgres' - $ export DJANGO_SETTINGS_MODULE='home_industry.settings.local' - $ export SECRET_KEY='7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u' + $ sudo docker-compose up ``` -- Migrate the database +- In another terminal, check running container ```bash - $ python3 manage.py migrate + $ docker ps ``` -- Set up API configuration by modifying `api_config.yaml` -- Create or update the API configuration in database +- Run collectstatic ```bash - $ python3 manage.py createorupdateapiconfig + $ docker exec -it python manage.py collectstatic --noinput ``` -- Create Superuser +- Create or update API configuration in database ```bash - $ python3 manage.py createsuperuser + $ docker exec -it python manage.py createorupdateapiconfig ``` -- Run server +- Access database ```bash - $ python3 manage.py runserver + $ docker exec -it psql -U postgres -d home_industry -h db ``` ## Deployed API URLs -- Staging: [https://industripilar-staging.herokuapp.com](https://industripilar-staging.herokuapp.com) -- Production: [https://api.industripilar.com](https://api.industripilar.com) +- Development: [https://pilar-be-dev.cs.ui.ac.id](https://pilar-be-dev.cs.ui.ac.id) +- Staging: [https://pilar-be-staging.cs.ui.ac.id](https://pilar-be-staging.cs.ui.ac.id) +- Production: [https://pilar-be.cs.ui.ac.id](https://pilar-be.cs.ui.ac.id) ## API Documentation -[https://industripilar-staging.herokuapp.com/docs/](https://industripilar-staging.herokuapp.com/docs/) +[https://pilar-be-dev.cs.ui.ac.id/docs/](https://pilar-be-dev.cs.ui.ac.id/docs/) -- GitLab From 59e3ce06bac66f2e61e20678098a743e15cf86e1 Mon Sep 17 00:00:00 2001 From: Bunga Amalia Kurniawati Date: Tue, 16 Mar 2021 13:34:56 +0700 Subject: [PATCH 11/11] [CHORES] memperbaharui README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9a3c053..b1d5730 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ Key | Required | Example - Create environment variables in root folder (change as needed) Filename: `.env` - Content: ```bash DATABASE_HOST=db DATABASE_NAME=home_industry -- GitLab