diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d64571e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.git +.idea +.claude +.forgejo +.gitlab-ci.yml +.env +.env.* +*.env +delme*.py +venv +.venv +__pycache__ +*.pyc +.pytest_cache +log +logs +media +advertising_db diff --git a/.gitignore b/.gitignore index 926e870..6140c58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ .idea .env +local.env +staging.env media /clients/ /delme.py +/delme_not_commit.py /log/accounts.log /log/errors.log **/__pycache__ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..bf6056b --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,104 @@ +stages: + - build + - test + - deploy + +# The container registry (port 5050) is not reachable from the runners, so +# nothing is pushed. Once it is, add a job that pushes $CI_REGISTRY_IMAGE. + +# Checks that the Dockerfile builds. Uses the runner host's Docker daemon, so +# the runner needs /var/run/docker.sock in [runners.docker] volumes (no dind). +build: + stage: build + image: docker:27 + tags: + - local + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + script: + - docker build --pull -t chat-ci:$CI_JOB_ID . + after_script: + - docker rmi chat-ci:$CI_JOB_ID || true + +test: + stage: test + image: debian:13 + tags: + - local + services: + # no PostGIS needed: the settings use the plain postgresql backend + - name: postgres:17 + alias: chat_db + - name: redis:7 + alias: redis + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + cache: + key: pip + paths: + - .cache/pip + variables: + PIP_CACHE_DIR: $CI_PROJECT_DIR/.cache/pip + # postgres service + POSTGRES_USER: ci + POSTGRES_PASSWORD: ci-password + POSTGRES_DB: chat_db + # app settings; dummy values only, real secrets never belong in a test job + DEBUG: "true" + DB_NAME: chat_db + DB_USER: ci + DB_PASSWORD: ci-password + DB_HOST: chat_db + DB_PORT: "5432" + REDIS_BASE_URL: redis://redis:6379/1 + ACCOUNTS_BASE_PUBLIC_URL: https://accounts.invalid + OAUTH2_PROVIDER_BASE_PUBLIC_URL: https://accounts.invalid/oauth2 + OAUTH2_PROVIDER_BASE_PRIVATE_URL: https://accounts.invalid/oauth2 + OAUTH2_PROVIDER_CLIENT_ID: ci + OAUTH2_PROVIDER_CLIENT_SECRET: ci + MINIO_ENDPOINT: minio.invalid + MINIO_ACCESS_KEY: ci + MINIO_SECRET_KEY: ci + before_script: + # keep in sync with the Dockerfile + - apt-get update + - apt-get install -y python3 python3-pip binutils libproj-dev gdal-bin + - pip3 install --break-system-packages --ignore-installed -r requirements.txt + - pip3 install --break-system-packages setuptools + script: + - python3 manage.py check + - python3 manage.py makemigrations --check --dry-run + # the suite is pytest-style; `manage.py test` would silently run 0 tests + - python3 -m pytest -q --junitxml=report.xml + artifacts: + when: always + reports: + junit: report.xml + +# Runs on the staging host through a shell runner tagged `staging` that can use +# the docker CLI. The compose stack keeps a git checkout of this repo (mounted +# at /app), so this updates that checkout and restarts the service. +# Set DEPLOY_DIR in Settings > CI/CD > Variables (the directory that holds +# docker-compose.yml). DEPLOY_CHECKOUT and DEPLOY_SERVICE default to `chat`; +# override them there if the checkout directory or compose service is named +# differently. +deploy_staging: + stage: deploy + tags: + - staging + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + when: manual + allow_failure: true # otherwise the pipeline shows "blocked" until someone deploys + resource_group: staging # never run two deploys at once + environment: + name: staging + variables: + DEPLOY_CHECKOUT: chat + DEPLOY_SERVICE: chat + script: + - cd "${DEPLOY_DIR:?set DEPLOY_DIR in Settings > CI/CD > Variables}" + - git -C "$DEPLOY_CHECKOUT" pull --ff-only origin "$CI_DEFAULT_BRANCH" + - docker compose up -d --build "$DEPLOY_SERVICE" diff --git a/Dockerfile b/Dockerfile index df21b9b..36a898e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,15 @@ -FROM base:debian_13 -ENV PYTHONUNBUFFERED 1 +FROM debian:13 +ENV PYTHONUNBUFFERED=1 WORKDIR /app -#RUN date -RUN apt update -#RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y -#RUN apt install binutils libproj-dev gdal-bin -y -RUN apt install nano htop iputils-ping -y -#COPY requirements.txt /app/requirements.txt -#RUN pip3 install --break-system-packages -r requirements.txt + +# netcat-openbsd: run.sh waits for the DB with `nc -z` +RUN apt-get update && \ + apt-get install -y python3 python3-pip binutils libproj-dev gdal-bin netcat-openbsd && \ + rm -rf /var/lib/apt/lists/* +COPY requirements.txt /app/requirements.txt +RUN pip3 install --break-system-packages --ignore-installed -r requirements.txt +RUN pip3 install --break-system-packages setuptools +# In docker compose the checkout mounted at /app overrides this copy; +# in CI the image has to carry the code itself. +COPY . /app #ENTRYPOINT ["./run.sh"] diff --git a/conftest.py b/conftest.py index a1dd7ea..ba7d5a0 100644 --- a/conftest.py +++ b/conftest.py @@ -1,3 +1,5 @@ +import os + import pytest @@ -6,6 +8,8 @@ def django_db_setup(django_test_environment, django_db_blocker): """ Override pytest-django's default DB setup to: - Use the local unix-socket superuser instead of the .env credentials + (skipped in CI, where the DB service credentials come from the + environment) - Remove psycopg3 connection pooling (pool=True conflicts with test transaction wrapping and causes PoolTimeout during test-DB creation) """ @@ -13,9 +17,10 @@ def django_db_setup(django_test_environment, django_db_blocker): from django.test.utils import setup_databases db = settings.DATABASES["default"] - db["USER"] = "hashdal" - db["PASSWORD"] = "" - db["HOST"] = "" + if not os.environ.get("CI"): + db["USER"] = "hashdal" + db["PASSWORD"] = "" + db["HOST"] = "" db["OPTIONS"] = {} # clear pool=True; psycopg3 pool conflicts with pytest-django with django_db_blocker.unblock(): diff --git a/delme.py b/delme.py deleted file mode 100644 index 272a767..0000000 --- a/delme.py +++ /dev/null @@ -1,81 +0,0 @@ -from mattermostdriver import Driver -from django.conf import settings -from pprint import pprint - -mattermost = Driver({ - 'url': 'chat.addwin.ir', - # 'login_id': 'sahama', - # 'password': settings.MATTERMOST_BASE_PUBLICE_URL, - # 'token': settings.MATTERMOST_TOKEN, - 'token': 'x36shfdd5fnz3qm9ut7crb6xoh', - - 'scheme': 'https', - 'port': 443, - 'basepath': '/api/v4', - 'verify': True, # Or /path/to/file.pem - # 'mfa_token': 'YourMFAToken', - - 'auth': None, - 'timeout': 30, - - 'request_timeout': None, - 'keepalive': False, - 'keepalive_delay': 5, - - 'websocket_kw_args': None, - - 'debug': True -}) - -# mattermost.login() -# mattermost.users.create_user(options={ -# "email": "user2@example.com", -# "username": "newuser2", -# "first_name": "John", -# "last_name": "Doe", -# "password": "SecurePassword123!",} -# ) - - -user_client = Driver({ - 'url': 'chat.addwin.ir', - 'login_id': 'newuser', - 'password': 'SecurePassword123!', - # 'token': settings.MATTERMOST_TOKEN, - - 'scheme': 'https', - 'port': 443, - 'basepath': '/api/v4', - 'verify': True, # Or /path/to/file.pem - # 'mfa_token': 'YourMFAToken', - - 'auth': None, - 'timeout': 30, - - 'request_timeout': None, - 'keepalive': False, - 'keepalive_delay': 5, - - 'websocket_kw_args': None, - - 'debug': True -}) - -user_client.login() -# print(user_client.users.get_user_by_username(username='newuser')) # jrrryzhq4bfftchoe1anji6a5c -# print(user_client.users.get_user_by_username(username='newuser2')) # yy6ew7jy63dnb85ox6skgi969r -# print(user_client.users.get_user_by_username(username='sahama')) # hqst9xib4typ7fynq3wn4qnzye -# print(user_client.users.get_user_by_username(username='ghasemi')) # w6ou8xyaqiyw5b8e3bhyae17xw -print(user_client.channels.create_direct_message_channel(options=["jrrryzhq4bfftchoe1anji6a5c", "w6ou8xyaqiyw5b8e3bhyae17xw"])) -# exit() - -print(user_client.posts.create_post(options={ - "channel_id": "76xjniwh3jgdtmitk66nigtktr", # anth9sme4bn9tdantqxx1g4kky - "message": "پیام از سمت پایتون" - })) - -pprint(user_client.posts.get_unread_posts_for_channel(**{ - "channel_id": "anth9sme4bn9tdantqxx1g4kky", - "user_id": "jrrryzhq4bfftchoe1anji6a5c" - })) -