diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index d78e4ee2..04ede3b2 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -3,16 +3,16 @@ name: "CI/CD" env: REGISTRY: ghcr.io DEVELOPMENT: dev - STAGING: staging + STAGING: stag PRODUCTION: prod MAIN_BRANCH: master DEV_DOMAIN: api.d.sayapp.company - STAGING_DOMAIN: api.s.sayapp.company + STAGE_DOMAIN: api.s.sayapp.company PROD_DOMAIN: api.sayapp.company TARGET_LAYER: prod TEST_TARGET_LAYER: development DEV_STACK_NAME: say-dev-backend - STAGING_STACK_NAME: say-staging-backend + STAGE_STACK_NAME: say-stag-backend PROD_STACK_NAME: say-backend DOCKER_COMPOSE_PATH: docker-compose.yml DOCKER_STACK_PATH: docker-stack.yml @@ -53,7 +53,7 @@ jobs: - name: Set Staging Envars if: ${{ env.REF == 'master' }} run: | - echo "ENVIRONMENT=staging" >> $GITHUB_ENV + echo "ENVIRONMENT=stag" >> $GITHUB_ENV - name: Set Production Envars if: ${{ env.REF == 'release' }} @@ -205,7 +205,7 @@ jobs: environment: name: Staging - url: https://${{ env.STAGING_DOMAIN }} + url: https://${{ env.STAGE_DOMAIN }} steps: - name: Git Checkout @@ -213,32 +213,32 @@ jobs: - name: Set Envars run: | - echo "STACK_FILE=docker-stack-${{ env.STAGING_STACK_NAME }}-${{ github.sha }}.yml" >> $GITHUB_ENV + echo "STACK_FILE=docker-stack-${{ env.STAGE_STACK_NAME }}-${{ github.sha }}.yml" >> $GITHUB_ENV - name: Prepare Stack File run: > - ENVIRONMENT=${{ env.STAGING }} DOMAIN=${{ env.STAGING_DOMAIN }} CONTAINER_IMAGE=${IMAGE_ID} STACK_NAME=${{ env.STAGING_STACK_NAME }} + ENVIRONMENT=${{ env.STAGING }} DOMAIN=${{ env.STAGE_DOMAIN }} CONTAINER_IMAGE=${IMAGE_ID} STACK_NAME=${{ env.STAGE_STACK_NAME }} docker-compose -f ${{ env.DOCKER_COMPOSE_PATH }} -f ${{ env.DOCKER_STACK_PATH }} config > ${{ env.STACK_FILE }} - name: Move Stack File to Server uses: appleboy/scp-action@v0.1.1 with: - host: ${{ secrets.STAGING_SERVER_ADDR }} - username: ${{ secrets.STAGING_SERVER_USER }} - key: ${{ secrets.STAGING_SSH_PRIVATE_KEY }} - port: ${{ secrets.STAGING_SERVER_PORT }} + host: ${{ secrets.STAGE_SERVER_ADDR }} + username: ${{ secrets.STAGE_SERVER_USER }} + key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + port: ${{ secrets.STAGE_SERVER_PORT }} source: ${{ env.STACK_FILE }} target: "/tmp" - name: Deploy Stack uses: appleboy/ssh-action@v0.1.4 with: - host: ${{ secrets.STAGING_SERVER_ADDR }} - username: ${{ secrets.STAGING_SERVER_USER }} - key: ${{ secrets.STAGING_SSH_PRIVATE_KEY }} - port: ${{ secrets.STAGING_SERVER_PORT }} - script: docker stack deploy --prune --resolve-image=changed --with-registry-auth -c /tmp/${{ env.STACK_FILE }} ${{ env.STAGING_STACK_NAME }} + host: ${{ secrets.STAGE_SERVER_ADDR }} + username: ${{ secrets.STAGE_SERVER_USER }} + key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + port: ${{ secrets.STAGE_SERVER_PORT }} + script: docker stack deploy --prune --resolve-image=changed --with-registry-auth -c /tmp/${{ env.STACK_FILE }} ${{ env.STAGE_STACK_NAME }} deploy_production: name: Deploy to Production Server diff --git a/README.md b/README.md index 1debce09..307037b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Backend of SAY App and Panel -[![CI/CD](https://github.com/SAY-DAO/backend/actions/workflows/pipeline.yml/badge.svg?branch=release)](https://github.com/SAY-DAO/backend/actions/workflows/pipeline.yml) +[![Build, Test and Deploy](https://github.com/SAY-DAO/backend/actions/workflows/pipeline.yml/badge.svg)](https://github.com/SAY-DAO/backend/actions/workflows/pipeline.yml) [![codecov](https://codecov.io/gh/SAY-DAO/backend/branch/master/graph/badge.svg?token=RXJ4EXVIR0)](https://codecov.io/gh/SAY-DAO/backend) ### Requirements diff --git a/alembic.ini b/alembic.ini index e6e8fbff..98df9114 100644 --- a/alembic.ini +++ b/alembic.ini @@ -35,7 +35,7 @@ script_location = alembic # are written from script.py.mako # output_encoding = utf-8 -sqlalchemy.url = postgresql://postgres:postgres@localhost/say +sqlalchemy.url = postgresql://postgres:postgres@localhost/say_test [post_write_hooks] diff --git a/alembic/env.py b/alembic/env.py index 0aef1981..3621eb3e 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -2,8 +2,8 @@ import sys from logging.config import fileConfig -from sqlalchemy import engine_from_config -from sqlalchemy import pool +from flask import json +from sqlalchemy import engine_from_config, pool from alembic import context @@ -24,7 +24,6 @@ # target_metadata = mymodel.Base.metadata from say.models import base - target_metadata = base.metadata # other values from the config, defined by the needs of env.py, @@ -34,7 +33,6 @@ from say.config import configs - config.set_main_option('sqlalchemy.url', configs.postgres_url) diff --git a/alembic/versions/23554e6d0034_.py b/alembic/versions/23554e6d0034_.py index 32abc19f..7ca061d7 100644 --- a/alembic/versions/23554e6d0034_.py +++ b/alembic/versions/23554e6d0034_.py @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = '23554e6d0034' -down_revision = '389f9919c78d' +down_revision = 'fa7d127f259b' branch_labels = None depends_on = None diff --git a/alembic/versions/389f9919c78d_.py b/alembic/versions/389f9919c78d_.py deleted file mode 100644 index b63f2271..00000000 --- a/alembic/versions/389f9919c78d_.py +++ /dev/null @@ -1,248 +0,0 @@ -"""empty message - -Revision ID: 389f9919c78d -Revises: e71c915653f6 -Create Date: 2022-11-07 23:24:58.623154 - -""" -import sqlalchemy as sa - -from alembic import op - - -# revision identifiers, used by Alembic. -revision = '389f9919c78d' -down_revision = 'e71c915653f6' -branch_labels = None -depends_on = None - - -def upgrade(): - from say.models import Child - from say.orm import init_model - from say.orm import session - - init_model(op.get_bind()) - - op.alter_column('child', 'birthPlace', nullable=True, new_column_name='_birthPlace') - op.alter_column('child', 'city', nullable=True, new_column_name='_city') - op.alter_column('child', 'country', nullable=True, new_column_name='_country') - op.alter_column('child', 'nationality', nullable=True, new_column_name='_nationality') - - op.add_column('child', sa.Column('birth_place_id', sa.Integer(), nullable=True)) - op.add_column('child', sa.Column('city_id', sa.Integer(), nullable=True)) - op.add_column('child', sa.Column('nationality_id', sa.Integer(), nullable=True)) - - op.create_foreign_key( - op.f('child_birth_place_id_cities_fkey'), - 'child', - 'cities', - ['birth_place_id'], - ['id'], - ) - op.create_foreign_key( - op.f('child_city_id_cities_fkey'), 'child', 'cities', ['city_id'], ['id'] - ) - op.create_foreign_key( - op.f('child_nationality_id_countries_fkey'), - 'child', - 'countries', - ['nationality_id'], - ['id'], - ) - - op.alter_column( - 'child_version', 'birthPlace', nullable=True, new_column_name='_birthPlace' - ) - op.alter_column('child_version', 'city', nullable=True, new_column_name='_city') - op.alter_column('child_version', 'country', nullable=True, new_column_name='_country') - op.alter_column( - 'child_version', 'nationality', nullable=True, new_column_name='_nationality' - ) - op.alter_column( - 'child_version', - 'birthPlace_mod', - nullable=True, - new_column_name='_birthPlace_mod', - ) - op.alter_column( - 'child_version', 'city_mod', nullable=True, new_column_name='_city_mod' - ) - op.alter_column( - 'child_version', 'country_mod', nullable=True, new_column_name='_country_mod' - ) - op.alter_column( - 'child_version', - 'nationality_mod', - nullable=True, - new_column_name='_nationality_mod', - ) - - op.add_column( - 'child_version', - sa.Column('birth_place_id', sa.Integer(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column( - 'birth_place_id_mod', - sa.Boolean(), - server_default=sa.text('false'), - nullable=False, - ), - ) - op.add_column( - 'child_version', - sa.Column('city_id', sa.Integer(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column( - 'city_id_mod', sa.Boolean(), server_default=sa.text('false'), nullable=False - ), - ) - op.add_column( - 'child_version', - sa.Column('nationality_id', sa.Integer(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column( - 'nationality_id_mod', - sa.Boolean(), - server_default=sa.text('false'), - nullable=False, - ), - ) - - for child in session.query(Child): - if child._city == 1: - child.city_id = 135129 # tehran - else: - child.city_id = 134664 # Karaj - - if child._nationality == '93': - child.nationality_id = 1 # Afghanistan - else: - child.nationality_id = 103 # Iran - - if child._birthPlace in ['Afghanistan', 'افغنستان']: - child.birth_place_id = 79 # Kabul - elif child._birthPlace == 'تربت جام': - child.birth_place_id = 135131 # Torbat e jaam - elif child._birthPlace in ['Iran', 'Tehran']: - child.birth_place_id = 135129 # Tehran - else: - child.birth_place_id = 134664 # Karaj - - session.commit() - - op.alter_column('user', '_city', existing_type=sa.INTEGER(), nullable=True) - op.drop_column('user', 'birthPlace') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column( - 'user', sa.Column('birthPlace', sa.VARCHAR(), autoincrement=False, nullable=True) - ) - op.alter_column('user', '_city', existing_type=sa.INTEGER(), nullable=False) - op.add_column( - 'child_version', - sa.Column('birthPlace', sa.TEXT(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column( - 'country_mod', - sa.BOOLEAN(), - server_default=sa.text('false'), - autoincrement=False, - nullable=False, - ), - ) - op.add_column( - 'child_version', - sa.Column('city', sa.INTEGER(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column( - 'city_mod', - sa.BOOLEAN(), - server_default=sa.text('false'), - autoincrement=False, - nullable=False, - ), - ) - op.add_column( - 'child_version', - sa.Column('nationality', sa.INTEGER(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column('country', sa.INTEGER(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child_version', - sa.Column( - 'birthPlace_mod', - sa.BOOLEAN(), - server_default=sa.text('false'), - autoincrement=False, - nullable=False, - ), - ) - op.add_column( - 'child_version', - sa.Column( - 'nationality_mod', - sa.BOOLEAN(), - server_default=sa.text('false'), - autoincrement=False, - nullable=False, - ), - ) - op.drop_column('child_version', 'nationality_id_mod') - op.drop_column('child_version', 'nationality_id') - op.drop_column('child_version', 'city_id_mod') - op.drop_column('child_version', 'city_id') - op.drop_column('child_version', 'birth_place_id_mod') - op.drop_column('child_version', 'birth_place_id') - op.drop_column('child_version', '_nationality_mod') - op.drop_column('child_version', '_nationality') - op.drop_column('child_version', '_country_mod') - op.drop_column('child_version', '_country') - op.drop_column('child_version', '_city_mod') - op.drop_column('child_version', '_city') - op.drop_column('child_version', '_birthPlace_mod') - op.drop_column('child_version', '_birthPlace') - op.add_column( - 'child', - sa.Column('nationality', sa.VARCHAR(), autoincrement=False, nullable=True), - ) - op.add_column( - 'child', sa.Column('city', sa.INTEGER(), autoincrement=False, nullable=False) - ) - op.add_column( - 'child', sa.Column('birthPlace', sa.TEXT(), autoincrement=False, nullable=True) - ) - op.add_column( - 'child', sa.Column('country', sa.INTEGER(), autoincrement=False, nullable=False) - ) - op.drop_constraint( - op.f('child_nationality_id_countries_fkey'), 'child', type_='foreignkey' - ) - op.drop_constraint(op.f('child_city_id_cities_fkey'), 'child', type_='foreignkey') - op.drop_constraint( - op.f('child_birth_place_id_cities_fkey'), 'child', type_='foreignkey' - ) - op.drop_column('child', 'nationality_id') - op.drop_column('child', 'city_id') - op.drop_column('child', 'birth_place_id') - op.drop_column('child', '_nationality') - op.drop_column('child', '_country') - op.drop_column('child', '_city') - op.drop_column('child', '_birthPlace') - # ### end Alembic commands ### diff --git a/alembic/versions/4fbd5bfb97bd_.py b/alembic/versions/4fbd5bfb97bd_.py index 34221525..da547560 100644 --- a/alembic/versions/4fbd5bfb97bd_.py +++ b/alembic/versions/4fbd5bfb97bd_.py @@ -1,7 +1,6 @@ """empty message Revision ID: 4fbd5bfb97bd -Revises: 389f9919c78d Revises: 23554e6d0034 Create Date: 2023-03-16 18:18:05.708589 @@ -19,9 +18,6 @@ def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - import say - import say # ### commands auto generated by Alembic - please adjust! ### diff --git a/alembic/versions/e71c915653f6_.py b/alembic/versions/e71c915653f6_.py deleted file mode 100644 index 94bc2b83..00000000 --- a/alembic/versions/e71c915653f6_.py +++ /dev/null @@ -1,63 +0,0 @@ -"""empty message - -Revision ID: e71c915653f6 -Revises: fa7d127f259b -Create Date: 2022-11-07 21:06:39.691799 - -""" -import sqlalchemy as sa - -from alembic import op - - -# revision identifiers, used by Alembic. -revision = 'e71c915653f6' -down_revision = 'fa7d127f259b' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.execute("""ALTER TABLE "user" RENAME COLUMN city TO _city""") - # op.add_column('user', sa.Column('_city', sa.Integer(), nullable=True)) - - op.execute("""ALTER TABLE "user" RENAME COLUMN country TO _country""") - # op.add_column('user', sa.Column('_country', sqlalchemy_utils.types.country.CountryType(length=2), nullable=True)) - - op.add_column('user', sa.Column('city_id', sa.Integer(), nullable=True)) - op.create_foreign_key(op.f('user_city_id_cities_fkey'), 'user', 'cities', ['city_id'], ['id']) - - op.execute("""ALTER TABLE user_version RENAME COLUMN city TO _city""") - op.execute("""ALTER TABLE user_version RENAME COLUMN city_mod TO _city_mod""") - op.execute("""ALTER TABLE user_version RENAME COLUMN country TO _country""") - op.execute("""ALTER TABLE user_version RENAME COLUMN country_mod TO _country_mod""") - op.add_column('user_version', sa.Column('city_id', sa.Integer(), autoincrement=False, nullable=True)) - op.add_column('user_version', sa.Column('city_id_mod', sa.Boolean(), server_default=sa.text('false'), nullable=False)) - op.drop_column('user_version', 'birthPlace_mod') - op.drop_column('user_version', 'birthPlace') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user_version', sa.Column('city', sa.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('user_version', sa.Column('birthPlace', sa.INTEGER(), autoincrement=False, nullable=True)) - op.add_column('user_version', sa.Column('country', sa.VARCHAR(length=2), autoincrement=False, nullable=True)) - op.add_column('user_version', sa.Column('city_mod', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False)) - op.add_column('user_version', sa.Column('country_mod', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False)) - op.add_column('user_version', sa.Column('birthPlace_mod', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False)) - op.drop_column('user_version', 'city_id_mod') - op.drop_column('user_version', 'city_id') - op.drop_column('user_version', '_country_mod') - op.drop_column('user_version', '_country') - op.drop_column('user_version', '_city_mod') - op.drop_column('user_version', '_city') - op.add_column('user', sa.Column('birthPlace', sa.VARCHAR(), autoincrement=False, nullable=True)) - op.add_column('user', sa.Column('city', sa.INTEGER(), autoincrement=False, nullable=False)) - op.add_column('user', sa.Column('country', sa.VARCHAR(length=8), autoincrement=False, nullable=True)) - op.drop_constraint(op.f('user_city_id_cities_fkey'), 'user', type_='foreignkey') - op.drop_column('user', 'city_id') - op.drop_column('user', '_country') - op.drop_column('user', '_city') - # ### end Alembic commands ### diff --git a/docker-stack.yml b/docker-stack.yml index c61b3e46..6ece3b9a 100644 --- a/docker-stack.yml +++ b/docker-stack.yml @@ -1,4 +1,4 @@ -version: "3.6" +version: '3.6' services: redis: @@ -34,8 +34,8 @@ services: - postgres-password - flower-password networks: - - default - - traefik-public + - default + - traefik-public deploy: placement: constraints: @@ -181,3 +181,4 @@ secrets: flower-password: name: ${STACK_NAME}-flower-password external: true + diff --git a/practice/delete_need_batch.py b/practice/delete_need_batch.py deleted file mode 100644 index 61e3079c..00000000 --- a/practice/delete_need_batch.py +++ /dev/null @@ -1,29 +0,0 @@ -import requests - - -file = '/home/rhonin2/Downloads/delete_needs.csv' -token = 'Bearer ' -url = 'https://api.sayapp.company/api/v2/need/delete/needId=%s' -need_ids = [] -failed_needs = [] - -with open(file) as f: - need_ids = f.read().split('\n')[1:-1] - -headers = {'Authorization': token} - -for id in need_ids: - print(f'deleting {id}') - try: - response = requests.patch(url % id, headers=headers) - if response.status_code == 401: - raise Exception('Bad token') - - if response.status_code != 200: - print(f'unable to delete {id}') - failed_needs.append(id) - except requests.exceptions.HTTPError: - print(f'unable to delete {id}') - failed_needs.append(id).append(id) - -print('failed: ', failed_needs) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1292f245..e2f9c0f8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,4 +4,3 @@ pytest-pudb pytest-cov pytest-mock devtools[pygments] -flake8 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 30fc1525..a4e8fe74 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ SQLAlchemy-Continuum~=1.3.11 alembic~=1.4.3 argon2-cffi~=21.1.0 -Babel~=2.11.0 +Babel~=2.9.0 beautifulsoup4~=4.9.3 cachetools~=4.2.4 -celery~=5.2.7 +celery~=4.4.7 celery-redbeat~=2.0.0 email-validator~=1.1.2 eth-utils~=1.9.5 @@ -17,7 +17,7 @@ Flask-Limiter~=1.4 Flask-Mail~=0.9.1 Flask-RESTful~=0.3.8 Flask-WTF~=0.14.3 -flower~=1.2.0 +flower~=0.9.7 gunicorn~=20.0.4 healthcheck~=1.3.3 humps~=0.2.2 @@ -29,14 +29,13 @@ psycopg2-binary~=2.8.6 pydantic~=1.8.2 python-dotenv~=0.19.0 python-i18n~=0.3.9 -# pytz~=2020.4 +pytz~=2020.4 redis~=3.5.3 requests~=2.25.1 sentry-sdk~=1.16.0 SQLAlchemy~=1.3.22 SQLAlchemy-Utils~=0.38.2 -ujson~=5.5.0 +ujson~=5.2.0 web3~=5.13.1 Werkzeug~=1.0.1 -markupsafe==2.0.1 -jinja2<3.1.0 +markupsafe==2.0.1 \ No newline at end of file diff --git a/say/api/auth_api.py b/say/api/auth_api.py index a818e955..ee163783 100644 --- a/say/api/auth_api.py +++ b/say/api/auth_api.py @@ -9,11 +9,11 @@ from flask import request from flask_jwt_extended import get_raw_jwt from flask_restful import Resource +from sqlalchemy_utils import Country from sqlalchemy_utils import PhoneNumber from sqlalchemy_utils import PhoneNumberParseException from say.exceptions import HTTPException -from say.models import City from say.models import EmailVerification from say.models import PhoneVerification from say.models import ResetPassword @@ -74,10 +74,20 @@ def post(self): if phone_number == '': phone_number = None + if 'countryCode' in request.form.keys() and request.form['countryCode']: + country = request.form['countryCode'] + try: + country = Country(country.upper()) + except ValueError: + return {'message': 'Invalid countryCode'}, 400 + else: + country = None + if 'password' in request.form.keys(): password = request.form['password'] if not validate_password(password): return {'message': 'password must be at least 6 charachters'}, 400 + else: return {'message': 'password is needed'}, 400 @@ -118,11 +128,6 @@ def post(self): except EmailNotValidError: return {'message': 'Invalid email'}, 400 - if data.city_id: - city = session.query(City).get(data.city_id) - if city is None: - return {'message': 'Invalid cityId'}, 400 - code = code.replace('-', '') alreadyExist = ( @@ -176,14 +181,16 @@ def post(self): avatarUrl=None, emailAddress=email, gender=None, + city=0, birthDate=None, + birthPlace=None, lastLogin=last_login, password=password, locale=locale, phone_number=phone_number, + country=country, is_installed=is_installed, is_nakama=False, - city_id=data.city_id, ) if isinstance(verification, EmailVerification): diff --git a/say/api/child_api.py b/say/api/child_api.py index 523669a5..b1f5a23f 100644 --- a/say/api/child_api.py +++ b/say/api/child_api.py @@ -33,8 +33,6 @@ from say.exceptions import HTTP_PERMISION_DENIED from say.models import Child from say.models import ChildNeed -from say.models import City -from say.models import Country from say.models import Family from say.models import Invitation from say.models import Need @@ -488,10 +486,8 @@ def post(self): code = sw.generated_code + format(sw.child_count + 1, '04d') - if 'nationalityId' in request.form.keys(): - nationality = session.query(Country).get(request.form['nationalityId']) - if nationality is None: - return {'message': 'Invalid nationalityId'}, 400 + if 'nationality' in request.form.keys(): + nationality = int(request.form['nationality']) else: nationality = None @@ -514,11 +510,8 @@ def post(self): else: last_name_translations = None - if 'birthPlaceId' in request.form.keys(): - birth_place_id = request.form['birthPlaceId'] - birth_place = session.query(City).get(birth_place_id) - if birth_place is None: - return {'message': 'Invalid birthPlaceId'}, 400 + if 'birthPlace' in request.form.keys(): + birth_place = request.form['birthPlace'] else: birth_place = None @@ -548,12 +541,8 @@ def post(self): birth_date = datetime.strptime(request.form['birthDate'], '%Y-%m-%d') phone_number = request.form['phoneNumber'] - - city_id = request.form['cityId'] - city = session.query(City).get(city_id) - if city is None: - return {'message': 'Invalid cityId'}, 400 - + country = int(request.form['country']) + city = int(request.form['city']) sayname_translations = ujson.loads(request.form['sayname_translations']) bio_translations = ujson.loads(request.form['bio_translations']) bio_summary_translations = ujson.loads(request.form['bio_summary_translations']) @@ -569,7 +558,7 @@ def post(self): lastName_translations=last_name_translations, familyCount=family_count, education=education, - birth_place=birth_place, + birthPlace=birth_place, birthDate=birth_date, address=address, voiceUrl='', @@ -578,6 +567,7 @@ def post(self): sayname_translations=sayname_translations, bio_translations=bio_translations, bio_summary_translations=bio_summary_translations, + country=country, city=city, gender=gender, status=status, @@ -775,12 +765,8 @@ def patch(self, child_id): if 'phoneNumber' in request.form.keys(): primary_child.phoneNumber = request.form['phoneNumber'] - if 'nationalityId' in request.form.keys(): - nationality = session.query(Country).get(request.form['nationalityId']) - if nationality is None: - return {'message': 'Invalid nationalityId'}, 400 - - primary_child.nationality = nationality + if 'nationality' in request.form.keys(): + primary_child.nationality = int(request.form['nationality']) if 'housingStatus' in request.form.keys(): primary_child.housingStatus = int(request.form['housingStatus']) @@ -801,12 +787,11 @@ def patch(self, child_id): if 'familyCount' in request.form.keys(): primary_child.familyCount = int(request.form['familyCount']) - if 'cityId' in request.form.keys(): - city = session.query(City).get(request.form['cityId']) - if city is None: - return {'message': 'Invalid cityId'}, 400 + if 'country' in request.form.keys(): + primary_child.country = int(request.form['country']) - primary_child.city = city + if 'city' in request.form.keys(): + primary_child.city = int(request.form['city']) if 'status' in request.form.keys(): primary_child.status = int(request.form['status']) @@ -814,12 +799,8 @@ def patch(self, child_id): if 'education' in request.form.keys(): primary_child.education = int(request.form['education']) - if 'birthPlaceId' in request.form.keys(): - birth_place = session.query(City).get(request.form['birthPlaceId']) - if birth_place is None: - return {'message': 'Invalid birthPlaceId'}, 400 - - primary_child.birth_place = birth_place + if 'birthPlace' in request.form.keys(): + primary_child.birthPlace = request.form['birthPlace'] if 'birthDate' in request.form.keys(): primary_child.birthDate = datetime.strptime( @@ -946,7 +927,7 @@ def patch(self, child_id): if family: family.isDeleted = True - + child.social_worker.currentChildCount -= 1 child.ngo.currentChildrenCount -= 1 diff --git a/say/api/docs/auth/register.yml b/say/api/docs/auth/register.yml index 461304d7..8c979557 100644 --- a/say/api/docs/auth/register.yml +++ b/say/api/docs/auth/register.yml @@ -1,5 +1,6 @@ Register API --- + tags: - Auth API @@ -14,10 +15,11 @@ parameters: required: true type: string - - name: cityId - in: formData + - in: formData + name: countryCode required: false - type: integer + type: string + default: ir - in: formData name: username @@ -66,10 +68,10 @@ responses: schema: properties: - accessToken: + accessToken: type: string refreshToken: type: string - user: + user: schema: id: user_model diff --git a/say/api/docs/child/update.yml b/say/api/docs/child/update.yml index af965942..0dbd1750 100644 --- a/say/api/docs/child/update.yml +++ b/say/api/docs/child/update.yml @@ -7,6 +7,7 @@ The same rule applied for nationality and birthPlace. For voiceUrl and awakeAvatarUrl you have to upload a file; for avatar in png, jpg or jpeg and for voice in wav, m4a, wma, mp3, aac or ogg format. Social workers can change their NGO by editing ngoId field. --- + tags: - Child API diff --git a/say/api/docs/dashboard/feed.yml b/say/api/docs/dashboard/feed.yml index 53fc3933..a637c7ae 100644 --- a/say/api/docs/dashboard/feed.yml +++ b/say/api/docs/dashboard/feed.yml @@ -3,6 +3,7 @@ Returns required information for dashboard as a json object. It contains user data and user urgent needs. Calling this API affects need, child, child_need, need_family and user tables. --- + tags: - Dashboard API diff --git a/say/api/docs/geo/get-city.yml b/say/api/docs/geo/get-city.yml deleted file mode 100644 index 8f55c42c..00000000 --- a/say/api/docs/geo/get-city.yml +++ /dev/null @@ -1,16 +0,0 @@ -Get a City ---- -tags: - - Geo API -parameters: - - name: id - in: path - description: ID of city - required: true - type: integer - -responses: - 200: - description: Ok - schema: - $ref: "#/definitions/city_model" diff --git a/say/api/docs/geo/get-country.yml b/say/api/docs/geo/get-country.yml deleted file mode 100644 index 94b2de6e..00000000 --- a/say/api/docs/geo/get-country.yml +++ /dev/null @@ -1,15 +0,0 @@ -Get Country ---- -tags: - - Geo API -parameters: - - name: id - in: path - description: ID of Country - required: true - type: integer -responses: - 200: - description: Ok - schema: - $ref: "#/definitions/country_model" diff --git a/say/api/docs/geo/get-state.yml b/say/api/docs/geo/get-state.yml deleted file mode 100644 index 50753dee..00000000 --- a/say/api/docs/geo/get-state.yml +++ /dev/null @@ -1,16 +0,0 @@ -Get State by id ---- -tags: - - Geo API -parameters: - - name: id - in: path - description: ID of State - required: true - type: integer - -responses: - 200: - description: Ok - schema: - $ref: "#/definitions/state_model" diff --git a/say/api/docs/user/add.yml b/say/api/docs/user/add.yml index 88c5862d..cab6abb6 100644 --- a/say/api/docs/user/add.yml +++ b/say/api/docs/user/add.yml @@ -1,8 +1,8 @@ add a user -Adds a user. Adding avatarUrl, emailAddress, gender, birthDate are optional. +Adds a user. Adding avatarUrl, emailAddress, gender, birthDate and birthPlace are optional. For avatarUrl you have to upload a file in png, jpg or jpeg. Be aware that city is integer fields, so you cannot insert their names in those fields. -You have to insert the international codes for countries and cities. +You have to insert the international codes for countries and cities and the same rule is applied for birthPlace. Calling this API only affects on user table. For gender, [true] is male and [false] is female. --- @@ -44,11 +44,20 @@ parameters: - name: gender in: formData type: string - enum: ["female", "male", "other"] + enum: ['female', 'male', 'other'] - - name: cityId + - name: countryCode + in: formData + required: true + type: string + + - name: city + in: formData + required: true + type: integer + + - name: birthPlace in: formData - required: false type: integer - name: birthDate diff --git a/say/api/docs/user/by_id.yml b/say/api/docs/user/by_id.yml index 7cfbc684..0ff06c56 100644 --- a/say/api/docs/user/by_id.yml +++ b/say/api/docs/user/by_id.yml @@ -3,6 +3,7 @@ Returns information of a user with given id as a json object. Calling this API affects child, need, child_need and need_family and user tables. It contains user info, its children and those children's needs. --- + tags: - User API @@ -78,6 +79,10 @@ responses: description: the user's email address in correct format example: "sample@mail.com" + birthPlace: + type: integer + description: the user's birth place international code + birthDate: type: string description: the user's birth date @@ -112,3 +117,4 @@ responses: type: string description: the needs that user has done for their children default: 0 + diff --git a/say/api/docs/user/update.yml b/say/api/docs/user/update.yml index 6ca682c2..ae167dde 100644 --- a/say/api/docs/user/update.yml +++ b/say/api/docs/user/update.yml @@ -3,7 +3,7 @@ Updates a user information. All the fields are optional. For avatarUrl you have to upload a file in png, jpg or jpeg. Be aware that city and country are integer fields, so you cannot insert their names in those fields. -You have to insert the international codes for countries and cities. +You have to insert the international codes for countries and cities and the same rule is applied for birthPlace. Calling this API only affects on user table. For gender, [true] is male and [false] is female. --- @@ -58,9 +58,16 @@ parameters: type: string enum: ["female", "male", "other"] - - name: cityId + - name: countryCode + in: formData + type: string + + - name: city + in: formData + type: integer + + - name: birthPlace in: formData - required: false type: integer - name: birthDate @@ -84,6 +91,46 @@ parameters: in: formData type: boolean + # - in: body + # name: update info + # schema: + # properties: + # userRole: + # type: integer + + # firstName: + # type: string + + # lastName: + # type: string + + # userName: + # type: string + + # avatarUrl: + # type: file + + # emailAddress: + # type: string + + # password: + # type: string + + # gender: + # type: boolean + + # country: + # type: integer + + # city: + # type: integer + + # birthPlace: + # type: integer + + # birthDate: + # type: string + responses: 498: description: invalid gender diff --git a/say/api/ext/__init__.py b/say/api/ext/__init__.py index 607568f5..6c10aa76 100644 --- a/say/api/ext/__init__.py +++ b/say/api/ext/__init__.py @@ -11,7 +11,6 @@ from say.config import configs from say.payment import IDPay -from say.payment import ZIBAL from say.sms import MeliPayamak from .remote_address import get_remote_address @@ -20,7 +19,6 @@ api = Api() jwt = JWTManager() idpay = IDPay(configs.IDPAY_API_KEY, configs.SANDBOX) -zibal = ZIBAL() sms_provider = MeliPayamak( configs.MELI_PAYAMAK_USERNAME, configs.MELI_PAYAMAK_PASSWORD, diff --git a/say/api/geo_api.py b/say/api/geo_api.py index 580ba91c..f1b3ea4a 100644 --- a/say/api/geo_api.py +++ b/say/api/geo_api.py @@ -4,7 +4,6 @@ from say.api.ext import api from say.api.ext import cache from say.decorators import json -from say.exceptions import HTTP_NOT_FOUND from say.models import City from say.models import Country from say.models import State @@ -14,7 +13,7 @@ from say.schema.state import StateSchema -class CountriesAPI(Resource): +class CountryAPI(Resource): @cache.cached(timeout=10 * 60) @json(CountrySchema, use_list=True) @swag_from('./docs/geo/countries.yml') @@ -38,59 +37,8 @@ def get(self, id): return session.query(State).filter(State.country_id == id) -class CityAPI(Resource): - @cache.cached(timeout=10 * 60) - @json(CitySchema) - @swag_from('./docs/geo/get-city.yml') - def get(self, id): - city = session.query(City).get(id) - if city is None: - raise HTTP_NOT_FOUND() - - return city - - -class StateAPI(Resource): - @cache.cached(timeout=10 * 60) - @json(StateSchema) - @swag_from('./docs/geo/get-state.yml') - def get(self, id): - state = session.query(State).get(id) - if state is None: - raise HTTP_NOT_FOUND() - - return state - - -class CountryAPI(Resource): - @cache.cached(timeout=10 * 60) - @json(CountrySchema) - @swag_from('./docs/geo/get-country.yml') - def get(self, id): - country = session.query(Country).get(id) - if country is None: - raise HTTP_NOT_FOUND() - - return country - - -api.add_resource( - CityAPI, - '/api/v2/cities/', -) - -api.add_resource( - StateAPI, - '/api/v2/states/', -) - api.add_resource( CountryAPI, - '/api/v2/countries/', -) - -api.add_resource( - CountriesAPI, '/api/v2/countries', ) api.add_resource( diff --git a/say/api/need_api.py b/say/api/need_api.py index 9089f2e3..b38472b9 100644 --- a/say/api/need_api.py +++ b/say/api/need_api.py @@ -605,9 +605,6 @@ def post(self): image_url = image_path - category = request.form.get('category') - if not category: - return {'message': 'error: category is required!'}, 400 category = request.form.get('category') if not category: diff --git a/say/api/payment_api.py b/say/api/payment_api.py index 0bfa138b..7edd7a39 100644 --- a/say/api/payment_api.py +++ b/say/api/payment_api.py @@ -35,26 +35,25 @@ from ..roles import SUPER_ADMIN from .ext import api from .ext import idpay -from .ext import zibal def validate_amount(need, amount): amount = int(amount) need_unpaid = need.cost - need.paid if int(amount) > need_unpaid: - raise AmountTooHigh(f"Amount can not be greater that {need_unpaid}") + raise AmountTooHigh(f'Amount can not be greater that {need_unpaid}') if int(amount) < configs.MIN_BANK_AMOUNT: - raise AmountTooLow(f"Amount can not be smaller than {configs.MIN_BANK_AMOUNT}") + raise AmountTooLow(f'Amount can not be smaller than {configs.MIN_BANK_AMOUNT}') return amount def generate_order_id(N=configs.PAYMENT_ORDER_ID_LENGTH): - """ + ''' Generate a random string containing lowercase, uppercase and digits - """ + ''' while True: - order_id = "".join( + order_id = ''.join( random.SystemRandom().choice( string.ascii_uppercase + string.ascii_lowercase + string.digits ) @@ -74,12 +73,12 @@ class GetAllPayment(Resource): @authorize(SUPER_ADMIN, SAY_SUPERVISOR, ADMIN) @json - @swag_from("./docs/payment/all.yml") + @swag_from('./docs/payment/all.yml') def get(self): args = request.args - take = args.get("take", 10) - skip = args.get("skip", 0) - need_id = args.get("need_id", None) + take = args.get('take', 10) + skip = args.get('skip', 0) + need_id = args.get('need_id', None) try: if need_id: @@ -89,7 +88,7 @@ def get(self): if take < 1 or skip < 0: raise ValueError() except (ValueError, TypeError): - return {"message": "Invalid skip or take"}, 400 + return {'message': 'Invalid skip or take'}, 400 payments = session.query(Payment).filter(Payment.verified.isnot(None)) @@ -105,7 +104,7 @@ def get(self): payments=list(), ) for payment in payments: - result["payments"].append(obj_to_dict(payment)) + result['payments'].append(obj_to_dict(payment)) session.close() return result @@ -113,7 +112,7 @@ def get(self): class GetPayment(Resource): @authorize(SUPER_ADMIN, SAY_SUPERVISOR, ADMIN) @json - @swag_from("./docs/payment/id.yml") + @swag_from('./docs/payment/id.yml') def get(self, id): payment = session.query(Payment).get(id) session.close() @@ -128,7 +127,7 @@ class AddPayment(Resource): @validate(NewPaymentSchema) @json @commit - @swag_from("./docs/payment/new_payment.yml") + @swag_from('./docs/payment/new_payment.yml') def post(self, data: NewPaymentSchema): user_id = get_user_id() @@ -136,21 +135,20 @@ def post(self, data: NewPaymentSchema): need_id = data.need_id donation = data.donate use_credit = data.use_credit - gateWay = data.gateWay # added for second payment gateway need = session.query(Need).get(need_id) if need is None or need.isDeleted: - return {"message": "Need Not Found"}, 400 + return {'message': 'Need Not Found'}, 400 if need.isDone: - return {"message": "Need is already done"}, 422 + return {'message': 'Need is already done'}, 422 if not need.isConfirmed: - return {"message": "error: need is not confirmed yet!"}, 422 + return {'message': 'error: need is not confirmed yet!'}, 422 user = session.query(User).get(user_id) if user is None: - return {"message": "User Not Found"} + return {'message': 'User Not Found'} family = ( session.query(Family) @@ -167,16 +165,16 @@ def post(self, data: NewPaymentSchema): .first() is None ): - return {"message": "payment must be added by the child's family!"}, 422 + return {'message': 'payment must be added by the child\'s family!'}, 422 try: need_amount = validate_amount(need, need_amount) except ValueError as e: - return {"message": str(e)}, 422 + return {'message': str(e)}, 422 - desc = f"{need.name}-{need.child.sayName}" - name = f"{user.firstName} {user.lastName}" - callback = urljoin(configs.API_URL, "api/v2/payment/verify") + desc = f'{need.name}-{need.child.sayName}' + name = f'{user.firstName} {user.lastName}' + callback = urljoin(configs.API_URL, 'api/v2/payment/verify') credit = 0 if use_credit: @@ -198,51 +196,35 @@ def post(self, data: NewPaymentSchema): payment.verify() success_payment = render_template_i18n( - "successful_payment.html", + 'successful_payment.html', payment=payment, user=user, locale=user.locale, ) - return {"response": success_payment}, 299 + return {'response': success_payment}, 299 # Save some credit for the user if payment.bank_amount < configs.MIN_BANK_AMOUNT: payment.credit_amount -= configs.MIN_BANK_AMOUNT - payment.bank_amount - # idpay gateway - if gateWay == 1: - api_data = { - "order_id": payment.order_id, - "amount": payment.bank_amount, - "name": name, - "desc": desc, - "callback": callback, - } - - transaction = idpay.new_transaction(**api_data) - if "error_code" in transaction: - raise HTTPException( - status_code=422, - message=idpay.ERRORS[transaction["error_code"]], - ) - - payment.gateway_payment_id = transaction["id"] - payment.link = transaction["link"] - - # zibal gateway - if gateWay == 2: - zibal_request = zibal.request(payment.bank_amount, payment.order_id, desc) - if zibal_request["result"] != 100: - raise HTTPException( - status_code=422, - message=zibal.ERRORS[zibal_request["result"]], - ) - if zibal_request["result"] == 100: - trackId = zibal_request["trackId"] - link = urljoin("https://gateway.zibal.ir/start/", str(trackId)) - payment.gateway_payment_id = trackId - payment.link = link - + api_data = { + 'order_id': payment.order_id, + 'amount': payment.bank_amount, + 'name': name, + 'desc': desc, + 'callback': callback, + } + + transaction = idpay.new_transaction(**api_data) + if 'error_code' in transaction: + raise HTTPException( + status_code=422, + message=idpay.ERRORS[transaction['error_code']], + ) + + payment.gateway_payment_id = transaction['id'] + payment.link = transaction['link'] + return payment @@ -250,7 +232,7 @@ class VerifyPayment(Resource): @staticmethod def _verify_payment(payment_id, order_id): unsuccessful_response = render_template_i18n( - "unsuccessful_payment.html", + 'unsuccessful_payment.html', locale=DEFAULT_LOCALE, ) @@ -288,8 +270,8 @@ def _verify_payment(payment_id, order_id): if ( not response - or "error_code" in response - or response["status"] + or 'error_code' in response + or response['status'] not in ( 100, 101, @@ -298,11 +280,11 @@ def _verify_payment(payment_id, order_id): ): return make_response(unsuccessful_response) - transaction_date = datetime.fromtimestamp(int(response["date"])) - gateway_track_id = response["track_id"] - verified = datetime.fromtimestamp(int(response["verify"]["date"])) - card_no = response["payment"]["card_no"] - hashed_card_no = response["payment"]["hashed_card_no"] + transaction_date = datetime.fromtimestamp(int(response['date'])) + gateway_track_id = response['track_id'] + verified = datetime.fromtimestamp(int(response['verify']['date'])) + card_no = response['payment']['card_no'] + hashed_card_no = response['payment']['hashed_card_no'] pending_payment.verify( transaction_date, @@ -315,7 +297,7 @@ def _verify_payment(payment_id, order_id): need.payments.append(pending_payment) return make_response( render_template_i18n( - "successful_payment.html", + 'successful_payment.html', payment=pending_payment, user=user, locale=user.locale, @@ -325,19 +307,19 @@ def _verify_payment(payment_id, order_id): @json @commit def post(self): - payment_id = request.form.get("id") - order_id = request.form.get("order_id") + payment_id = request.form.get('id') + order_id = request.form.get('order_id') return self._verify_payment(payment_id, order_id) @json @commit def get(self): - payment_id = request.args.get("id") - order_id = request.args.get("order_id") + payment_id = request.args.get('id') + order_id = request.args.get('order_id') return self._verify_payment(payment_id, order_id) -api.add_resource(AddPayment, "/api/v2/payment") -api.add_resource(GetPayment, "/api/v2/payment/") -api.add_resource(GetAllPayment, "/api/v2/payment/all") -api.add_resource(VerifyPayment, "/api/v2/payment/verify") +api.add_resource(AddPayment, '/api/v2/payment') +api.add_resource(GetPayment, '/api/v2/payment/') +api.add_resource(GetAllPayment, '/api/v2/payment/all') +api.add_resource(VerifyPayment, '/api/v2/payment/verify') diff --git a/say/api/preneed_api.py b/say/api/preneed_api.py index 45810290..3457b3c3 100644 --- a/say/api/preneed_api.py +++ b/say/api/preneed_api.py @@ -27,7 +27,6 @@ def get(self): Need.title, Need.type, Need.details, - Need.unpayable, ).filter( Need.child_id == DEFAULT_CHILD_ID, Need.isDeleted.is_(False), diff --git a/say/api/user_api.py b/say/api/user_api.py index 56f7d0ff..ebd24870 100644 --- a/say/api/user_api.py +++ b/say/api/user_api.py @@ -9,7 +9,6 @@ from sqlalchemy import func from say.gender import Gender -from say.models import City from say.models import commit from say.models import obj_to_dict from say.models.family_model import Family @@ -183,12 +182,14 @@ def patch(self, user_id, data: UpdateUserSchema): if 'lastName' in request.form.keys(): user.lastName = request.form['lastName'] - if 'cityId' in request.form.keys(): - city = session.query(City).get(request.form.get('cityId')) - if city is None: - return {'message': 'Invalid cityId'}, 400 + if 'country_code' in request.form.keys(): + user.country_code = request.form['country_code'] - user.city_id = request.form.get('cityId') + if 'city' in request.form.keys(): + user.city = int(request.form['city']) + + if 'country' in request.form.keys(): + user.country = request.form['country'] if 'postal_address' in request.form.keys(): user.postal_address = request.form['postal_address'] @@ -205,6 +206,9 @@ def patch(self, user_id, data: UpdateUserSchema): 498, ) + if 'birthPlace' in request.form.keys(): + user.birthPlace = int(request.form['birthPlace']) + if 'locale' in request.form.keys(): user.locale = request.form['locale'].lower() @@ -332,6 +336,11 @@ def post(self): else: birth_date = None + if 'birthPlace' in request.form.keys(): + birth_place = int(request.form['birthPlace']) + else: + birth_place = None + if 'phoneNumber' in request.form.keys(): phone_number = request.form['phoneNumber'] else: @@ -340,7 +349,8 @@ def post(self): password = request.form['password'] first_name = request.form['firstName'] last_name = request.form['lastName'] - city_id = int(request.form['cityId']) + city = int(request.form['city']) + country_code = request.form['countryCode'] username = request.form['userName'] @@ -365,8 +375,10 @@ def post(self): phone_number=phone_number, emailAddress=email_address, gender=gender, - city_id=city_id, + city=city, + country=country_code, birthDate=birth_date, + birthPlace=birth_place, lastLogin=last_login, password=password, ) diff --git a/say/authorization.py b/say/authorization.py index ade65d9f..ff588713 100644 --- a/say/authorization.py +++ b/say/authorization.py @@ -193,7 +193,7 @@ def wrapper(*args, **kwargs): except (PyJWTError, JWTExtendedException) as ex: getLogger().info(ex) - return make_response(jsonify(message='Unauthorized'), 401) + return make_response(jsonify(message=f'Unauthorized, {ex}'), 401) if get_user_role() not in roles: return make_response(jsonify(message='Permission Denied'), 403) diff --git a/say/config.py b/say/config.py index e1232071..843f8547 100644 --- a/say/config.py +++ b/say/config.py @@ -37,7 +37,6 @@ class Config(object): RESET_PASSWORD_EXPIRE_TIME = 2 * 3600 RESET_PASSWORD_TOKEN_LENGTH = 8 IDPAY_API_KEY = 'change-this' - ZIBAL_MERCHANT_ID='zibal' DELIVER_TO_CHILD_DELAY = 4 * 60 * 60 RATELIMIT_DEFAULT = '100 per minutes' PAYMENT_ORDER_ID_LENGTH = 8 diff --git a/say/content/fa.py b/say/content/fa.py index a1d76843..db0e9860 100644 --- a/say/content/fa.py +++ b/say/content/fa.py @@ -1,7 +1,4 @@ class ContentFA: - CONFIRM_PHONE = '%s کد تایید شش رقمی شما در SAY است.لغو11 ' - RESET_PASSWORD = 'برای تغییر گذرواژ روی لینک زیر کلیک کنید.\n%s لغو11 \n%s' - INSTALLION = 'برای استفاده آسان از SAY آن را نصب کنید.\n%s' CONFIRM_PHONE = "%s کد تایید شش رقمی شما در SAY است.لغو11 " RESET_PASSWORD = "برای تغییر گذرواژ روی لینک زیر کلیک کنید. \n%s لغو11" INSTALLION = "برای استفاده آسان از SAY آن را نصب کنید.\n%s" diff --git a/say/models/child_model.py b/say/models/child_model.py index f4f3b42d..f7b145b5 100644 --- a/say/models/child_model.py +++ b/say/models/child_model.py @@ -29,14 +29,6 @@ class Child(base, Timestamp): Integer, ForeignKey('social_worker.id'), nullable=False, index=True ) - city_id = Column(Integer, ForeignKey('cities.id'), nullable=True) - birth_place_id = Column(Integer, ForeignKey('cities.id'), nullable=True) - nationality_id = Column(Integer, ForeignKey('countries.id'), nullable=True) - - cityId = synonym('city_id') - birthPlaceId = synonym('birth_place_id') - nationalityId = synonym('nationality_id') - firstName_translations = Column(HSTORE) firstName = translation_hybrid(firstName_translations) @@ -47,13 +39,12 @@ class Child(base, Timestamp): sayName = translation_hybrid(sayname_translations) phoneNumber = Column(String, nullable=False) - - _nationality = Column(Integer, nullable=True) # 98:iranian | 93:afghan - _country = Column( - Integer, nullable=True + nationality = Column(Integer, nullable=True) # 98:iranian | 93:afghan + country = Column( + Integer, nullable=False ) # 98:iran | 93:afghanistan | ... (real country codes) / [must be change after using real country/city api] - _city = Column( - Integer, nullable=True + city = Column( + Integer, nullable=False ) # 1:tehran | 2:karaj / [must be change after using real country/city api] awakeAvatarUrl = Column(ResourceURL, nullable=False) @@ -68,7 +59,7 @@ class Child(base, Timestamp): bioSummary = translation_hybrid(bio_summary_translations) sayFamilyCount = Column(Integer, nullable=False, default=0) voiceUrl = Column(ResourceURL, nullable=False) - _birthPlace = Column( + birthPlace = Column( Text, nullable=True ) # 1:tehran | 2:karaj / [must be change after using real country/city api] birthDate = Column(Date, nullable=True) @@ -104,34 +95,12 @@ class Child(base, Timestamp): availableNeedsCount = synonym('available_needs_count') totalNeedsCount = synonym('total_needs_count') - city = relationship('City', foreign_keys=city_id, lazy='selectin') - birth_place = relationship('City', foreign_keys=birth_place_id) - nationality = relationship('Country', foreign_keys=nationality_id) - needs = relationship( - 'Need', - back_populates='child', - primaryjoin='and_(Need.child_id==Child.id, ~Need.isDeleted)', - ) - family = relationship('Family', back_populates='child', uselist=False) - - ngo = relationship('Ngo', foreign_keys='Child.id_ngo') - - social_worker = relationship( - 'SocialWorker', - foreign_keys=id_social_worker, - back_populates='children', - ) - migrations = relationship( - 'ChildMigration', - back_populates='child', - ) - @hybrid_property def avatarUrl(self): # TODO: Use right timezone now_time = datetime.utcnow().time() - if self.city.country.phone_code in [98, 93]: + if self.country == 98 or self.country == 93: tz = pytz.timezone('Asia/Tehran') now_time = datetime.now(tz).time() diff --git a/say/models/user_model.py b/say/models/user_model.py index 3e297f45..39e07b8b 100644 --- a/say/models/user_model.py +++ b/say/models/user_model.py @@ -43,9 +43,6 @@ class User(BaseUser, Timestamp): unique=True, ) - city_id = Column(Integer, ForeignKey('cities.id'), nullable=True) - cityId = synonym('city_id') # TODO: For compatibily - firstName = Column(String, nullable=False) lastName = Column(String, nullable=False) userName = Column(String, nullable=False, unique=True) @@ -55,8 +52,8 @@ class User(BaseUser, Timestamp): unique=True, ) phone_number = Column(PhoneNumberType(), unique=True, index=True, nullable=True) - _country = Column(CountryType, nullable=True) - _city = Column(Integer, nullable=True) # 1:tehran | 2:karaj + country = Column(CountryType, nullable=True) + city = Column(Integer, nullable=False) # 1:tehran | 2:karaj postal_address = Column(Text, nullable=True) postal_code = Column(Unicode(10), nullable=True) emailAddress = Column(String, nullable=True, unique=True, index=True) @@ -65,6 +62,7 @@ class User(BaseUser, Timestamp): is_email_verified = Column(Boolean, nullable=False, default=False, index=True) is_phonenumber_verified = Column(Boolean, nullable=False, default=False, index=True) birthDate = Column(Date, nullable=True) + birthPlace = Column(Integer, nullable=True) # 1:tehran | 2:karaj lastLogin = Column(DateTime, nullable=False) _password = Column(String, nullable=False) locale = Column(LocaleType, default=Locale('fa'), nullable=False) @@ -118,8 +116,6 @@ def isVerified(cls): ) ) - city = relationship('City', foreign_keys=city_id) - payments = relationship( 'Payment', back_populates='user', diff --git a/say/payment/__init__.py b/say/payment/__init__.py index 1b883169..31221f94 100644 --- a/say/payment/__init__.py +++ b/say/payment/__init__.py @@ -1,2 +1 @@ from .idpay import IDPay -from .zibal import ZIBAL diff --git a/say/payment/zibal.py b/say/payment/zibal.py deleted file mode 100644 index 1cfa2777..00000000 --- a/say/payment/zibal.py +++ /dev/null @@ -1,42 +0,0 @@ -import requests -from ..config import configs - - -class ZIBAL: - merchant = configs.ZIBAL_MERCHANT_ID - callback_url = "http://nest.saydao.org/dao/api/payments/verify" - - ERRORS = { - 100: "کاربر مسدود شده است.", - 102: "merchant یافت نشد.", - 103: "merchant غیرفعال", - 104: "merchant نامعتبر", - 105: "amount بایستی بزرگتر از 1,000 ریال باشد.", - 106: "callbackUrl نامعتبر می‌باشد. (شروع با http و یا https)", - 113: "amount مبلغ تراکنش از سقف میزان تراکنش بیشتر است.", - } - - def request(self, amount, order_id, description, multiplexingInfos=None): - data = {} - data["merchant"] = self.merchant - data["callbackUrl"] = self.callback_url - data["amount"] = amount - data["orderId"] = order_id - data["description"] = description - data["multiplexingInfos"] = multiplexingInfos - - response = self.postTo("request", data) - return response - - def verify(self, trackId): - data = {} - data["merchant"] = self.merchant - data["trackId"] = trackId - return self.postTo("verify", data) - - def postTo(self, path, parameters): - url = "https://gateway.zibal.ir/v1/" + path - - response = requests.post(url=url, json=parameters) - - return response.json() diff --git a/say/schema/payment.py b/say/schema/payment.py index 6d29ec82..281fd852 100644 --- a/say/schema/payment.py +++ b/say/schema/payment.py @@ -10,7 +10,6 @@ class NewPaymentSchema(CamelModel): amount: conint(gt=0) donate: conint(ge=0) = 0 use_credit: bool = True - gateWay: int = 1 class PaymentSchema(CamelModel): diff --git a/say/schema/user.py b/say/schema/user.py index 57bdc85f..16b3c5b1 100644 --- a/say/schema/user.py +++ b/say/schema/user.py @@ -6,7 +6,6 @@ class UserNameSchema(CamelModel): username: constr(regex=USERNAME_PATTERN) - city_id: int = None class NewUserSchema(UserNameSchema): diff --git a/tests/app/test_register.py b/tests/app/test_register.py index d12dcfa8..b46d1d6b 100644 --- a/tests/app/test_register.py +++ b/tests/app/test_register.py @@ -25,6 +25,7 @@ def mockup(self): 'username': seed, 'password': 'password', 'phoneNumber': self.phone, + 'countryCode': 'ir', 'verifyCode': self.phone_verification._code, 'isInstalled': '0', } @@ -150,6 +151,10 @@ def test_register_user_by_email(self, mocker): ('username', 'a' * 13, 400), ('password', 'a' * 5, 400), ('password', 'a' * 65, 400), + ('countryCode', None, 200), + ('countryCode', '', 200), + ('countryCode', 'axsda', 400), + ('countryCode', 'ca', 200), ], ) def test_register_user_invalid_data(self, field, value, expected): diff --git a/tests/app/test_update_user.py b/tests/app/test_update_user.py index 6ca46f44..0b8fe8d1 100644 --- a/tests/app/test_update_user.py +++ b/tests/app/test_update_user.py @@ -22,8 +22,11 @@ def mockup(self): ('receiveEmail', False, 200), ('firstName', 'arash', 200), ('lastName', 'FZ', 200), + # ('country', 'IR', 200), # FIXME: country is object and should be code + ('city', 1, 200), ('postal_address', '1234567890', 200), ('postal_code', '1234567890', 200), + ('birthPlace', 1, 200), ('locale', 'en', 200), ('birthDate', '1999-09-09', 200), ('birthDate', '1999-29-59', 400), diff --git a/tests/geo/test_cities.py b/tests/geo/test_cities.py index 6b509180..dd7598d7 100644 --- a/tests/geo/test_cities.py +++ b/tests/geo/test_cities.py @@ -1,11 +1,10 @@ from tests.helper import BaseTestClass -CITIES_URL = '/api/v2/cities/%s' LIST_CITIES_URL = '/api/v2/states/%s/cities' -class TestCities(BaseTestClass): +class TestListCities(BaseTestClass): def mockup(self): for _ in range(10): self.state = self._create_state() @@ -24,15 +23,3 @@ def test_list_state_cities(self): assert result[0]['name'] is not None assert result[0]['stateName'] is not None assert result[0]['countryName'] is not None - - def test_get_city(self): - res = self.client.get(CITIES_URL % self.state.cities[0].id) - self.assert_ok(res) - result = res.json - assert result['id'] is not None - assert result['name'] is not None - assert result['stateName'] is not None - assert result['countryName'] is not None - - res = self.client.get(CITIES_URL % -1) - self.assert_code(res, 404) diff --git a/tests/geo/test_countries.py b/tests/geo/test_countries.py index 10dc24b1..844ba994 100644 --- a/tests/geo/test_countries.py +++ b/tests/geo/test_countries.py @@ -2,16 +2,15 @@ LIST_COUNTRIES_URL = '/api/v2/countries' -COUNTRIES_URL = LIST_COUNTRIES_URL + '/%s' class TestListCountries(BaseTestClass): def mockup(self): for _ in range(10): - self.c = self._create_country() + c = self._create_country() for _ in range(5): - self._create_city(country=self.c) - self.session.add(self.c) + self._create_city(country=c) + self.session.add(c) self.session.commit() def test_list_countries(self): @@ -23,13 +22,3 @@ def test_list_countries(self): assert result[0]['id'] is not None assert result[0]['name'] is not None assert result[0]['currencyName'] is not None - - def test_get_country(self): - res = self.client.get(COUNTRIES_URL % self.c.id) - self.assert_ok(res) - result = res.json - assert result['id'] is not None - assert result['name'] is not None - - res = self.client.get(COUNTRIES_URL % -1) - self.assert_code(res, 404) diff --git a/tests/geo/test_states.py b/tests/geo/test_states.py index 494dc314..c60a4d0d 100644 --- a/tests/geo/test_states.py +++ b/tests/geo/test_states.py @@ -1,11 +1,10 @@ from tests.helper import BaseTestClass -STATES_URL = '/api/v2/states/%s' LIST_STATES_URL = '/api/v2/countries/%s/states' -class TestStates(BaseTestClass): +class TestListStates(BaseTestClass): def mockup(self): for _ in range(10): self.country = self._create_country() @@ -23,13 +22,3 @@ def test_list_country_states(self): assert result[0]['id'] is not None assert result[0]['name'] is not None assert result[0]['stateCode'] is not None - - def test_get_state(self): - res = self.client.get(STATES_URL % self.country.states[0].id) - self.assert_ok(res) - result = res.json - assert result['id'] is not None - assert result['name'] is not None - - res = self.client.get(STATES_URL % -1) - self.assert_code(res, 404) diff --git a/tests/helper.py b/tests/helper.py index cc1e1328..6a6a3910 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -125,7 +125,8 @@ def _create_random_user(self, **kwargs): firstName=f'test{seed}', lastName=f'test{seed}', avatarUrl=self.create_test_file('test.png'), - city_id=self._create_city().id, + city=1, + country=1, lastLogin=datetime.utcnow(), cart=Cart(), ) @@ -251,15 +252,14 @@ def _create_random_child(self, sw=None, **kwargs): lastName=randomstr, sayName=randomstr, phoneNumber=randomstr, - city=self._create_city(), + country=seed, + city=seed, awakeAvatarUrl=randomstr, sleptAvatarUrl=randomstr, gender=False, bio=randomstr, bioSummary=randomstr, birthDate=datetime.utcnow(), - nationality=self._create_country(), - birth_place=self._create_city(), voiceUrl=randomstr, generatedCode=sw.generated_code + randomstr, isConfirmed=choice([True, False]), diff --git a/tests/panel/need/test_add_need.py b/tests/panel/need/test_add_need.py index 58f7a19f..4f92b1ca 100644 --- a/tests/panel/need/test_add_need.py +++ b/tests/panel/need/test_add_need.py @@ -2,7 +2,6 @@ import ujson -from say.models import Need from tests.helper import BaseTestClass @@ -36,9 +35,4 @@ def test_add_need(self): data=data, ) self.assert_ok(res) - result = res.json - assert result['category'] == 0 - - need = self.session.query(Need).get(result['id']) - assert need.category == 0 # TODO: Add more tests diff --git a/tests/panel/test_add_child.py b/tests/panel/test_add_child.py index 30f5f871..24277474 100644 --- a/tests/panel/test_add_child.py +++ b/tests/panel/test_add_child.py @@ -23,9 +23,8 @@ def test_add_child(self): gender='true', birthCertificateNumber='1234567890', phoneNumber=f'+98{randint(10000, 1000000)}', - cityId=self._create_city().id, - birthPlaceId=self._create_city().id, - nationality=self._create_country().id, + city='0', + country='0', birthDate='1995-10-10', sayname_translations=ujson.dumps(dict(fa='asd', en='asd')), bio_translations=ujson.dumps(dict(fa='asd', en='asd')),