From bec3d67171f2ace38271ccc315cf5a3400c8c59f Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 22 Aug 2022 14:43:21 +0300 Subject: [PATCH] initial --- .deploy/deploy-dev.yaml | 41 ++++++++ .deploy/deploy-prod.yaml | 41 ++++++++ .gitignore | 119 +++++++++++++++++++++++ .gitlab-ci.yml | 45 +++++++++ Dockerfile | 13 +++ battleship/__init__.py | 0 battleship/admin.py | 3 + battleship/apps.py | 6 ++ battleship/migrations/0001_initial.py | 44 +++++++++ battleship/migrations/__init__.py | 0 battleship/models.py | 30 ++++++ battleship/tests.py | 3 + battleship/views.py | 99 +++++++++++++++++++ battleship_back/__init__.py | 0 battleship_back/asgi.py | 16 ++++ battleship_back/settings.py | 131 ++++++++++++++++++++++++++ battleship_back/urls.py | 29 ++++++ battleship_back/wsgi.py | 16 ++++ manage.py | 22 +++++ nginx/Dockerfile | 2 + nginx/nginx.conf | 15 +++ requirements.txt | 6 ++ 22 files changed, 681 insertions(+) create mode 100644 .deploy/deploy-dev.yaml create mode 100644 .deploy/deploy-prod.yaml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 Dockerfile create mode 100644 battleship/__init__.py create mode 100644 battleship/admin.py create mode 100644 battleship/apps.py create mode 100644 battleship/migrations/0001_initial.py create mode 100644 battleship/migrations/__init__.py create mode 100644 battleship/models.py create mode 100644 battleship/tests.py create mode 100644 battleship/views.py create mode 100644 battleship_back/__init__.py create mode 100644 battleship_back/asgi.py create mode 100644 battleship_back/settings.py create mode 100644 battleship_back/urls.py create mode 100644 battleship_back/wsgi.py create mode 100755 manage.py create mode 100644 nginx/Dockerfile create mode 100644 nginx/nginx.conf create mode 100644 requirements.txt diff --git a/.deploy/deploy-dev.yaml b/.deploy/deploy-dev.yaml new file mode 100644 index 0000000..c8414a9 --- /dev/null +++ b/.deploy/deploy-dev.yaml @@ -0,0 +1,41 @@ +version: "3.4" + + +services: + + nginx: + image: mathwave/sprint-repo:battleship-nginx + networks: + - net + ports: + - "1236:80" + deploy: + mode: replicated + restart_policy: + condition: any + placement: + constraints: [node.role == manager] + update_config: + parallelism: 1 + order: start-first + + backend: + image: mathwave/sprint-repo:battleship-back + networks: + - net + environment: + DB_HOST: "pg.develop.sprinthub.ru" + DB_PASSWORD: $DB_PASSWORD_DEV + DEBUG: "true" + command: ./manage.py runserver 0.0.0.0:8000 + deploy: + mode: replicated + restart_policy: + condition: any + update_config: + parallelism: 1 + order: start-first + +networks: + net: + driver: overlay diff --git a/.deploy/deploy-prod.yaml b/.deploy/deploy-prod.yaml new file mode 100644 index 0000000..c8414a9 --- /dev/null +++ b/.deploy/deploy-prod.yaml @@ -0,0 +1,41 @@ +version: "3.4" + + +services: + + nginx: + image: mathwave/sprint-repo:battleship-nginx + networks: + - net + ports: + - "1236:80" + deploy: + mode: replicated + restart_policy: + condition: any + placement: + constraints: [node.role == manager] + update_config: + parallelism: 1 + order: start-first + + backend: + image: mathwave/sprint-repo:battleship-back + networks: + - net + environment: + DB_HOST: "pg.develop.sprinthub.ru" + DB_PASSWORD: $DB_PASSWORD_DEV + DEBUG: "true" + command: ./manage.py runserver 0.0.0.0:8000 + deploy: + mode: replicated + restart_policy: + condition: any + update_config: + parallelism: 1 + order: start-first + +networks: + net: + driver: overlay diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92785dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,119 @@ +# Django # +*.log +*.pot +*.pyc +__pycache__ +db.sqlite3 +media +data +*/__pycache__ + +# Backup files # +*.bak + +# If you are using PyCharm # +.idea +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/gradle.xml +.idea/**/libraries +*.iws /out/ + +# Python # +*.py[cod] +*$py.class + +# Distribution / packaging +.Python build/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +postgres-data + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +ENV/ +venv/ +env.bak/ +venv.bak/ + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Sublime Text # +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +*.sublime-workspace +*.sublime-project + +# sftp configuration file +sftp-config.json + +# Package control specific files Package +Control.last-run +Control.ca-list +Control.ca-bundle +Control.system-ca-bundle +GitHub.sublime-settings + +# Visual Studio Code # +.vscode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..44ab3a8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,45 @@ +stages: + - build + - deploy-dev + - deploy-prod + +build: + stage: build + tags: + - dev + before_script: + - docker login -u mathwave -p $DOCKERHUB_PASSWORD + script: + - docker build -t mathwave/sprint-repo:battleship-back . + - docker push mathwave/sprint-repo:battleship-back + - docker build -t mathwave/sprint-repo:battleship-nginx nginx + - docker push mathwave/sprint-repo:battleship-nginx + +.deploy: + before_script: + - docker login -u mathwave -p $DOCKERHUB_PASSWORD + +deploy-dev: + extends: + - .deploy + stage: deploy-dev + tags: + - dev + rules: + - if: '$CI_COMMIT_BRANCH == "master"' + when: on_success + - when: manual + script: + - docker stack deploy -c ./.deploy/deploy-dev.yaml sprint + +deploy-prod: + extends: + - .deploy + stage: deploy-prod + tags: + - prod + only: + - master + when: manual + script: + - docker stack deploy -c ./.deploy/deploy-prod.yaml sprint diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b3c7286 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.8 + +ENV PYTHONUNBUFFERED 1 +ENV DJANGO_SETTINGS_MODULE battleship_back.settings +RUN mkdir -p /usr/src/app/ + +COPY requirements.txt /usr/src/app/requirements.txt + +WORKDIR /usr/src/app/ + +RUN pip3 install -r requirements.txt + +COPY . /usr/src/app/ \ No newline at end of file diff --git a/battleship/__init__.py b/battleship/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battleship/admin.py b/battleship/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/battleship/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/battleship/apps.py b/battleship/apps.py new file mode 100644 index 0000000..15e8315 --- /dev/null +++ b/battleship/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BattleshipConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'battleship' diff --git a/battleship/migrations/0001_initial.py b/battleship/migrations/0001_initial.py new file mode 100644 index 0000000..7b74b55 --- /dev/null +++ b/battleship/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.15 on 2022-08-22 11:35 + +import battleship.models +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Game', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('last_move_ts', models.DateTimeField(default=django.utils.timezone.now)), + ('turn', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='Player', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('field', models.CharField(default=' ', max_length=100)), + ('number', models.IntegerField()), + ('attend_token', models.CharField(default=battleship.models.generate_token, max_length=30)), + ('token', models.CharField(blank=True, max_length=30, null=True)), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='battleship.game')), + ], + ), + migrations.AddConstraint( + model_name='player', + constraint=models.UniqueConstraint(fields=('token',), name='unique_player_token'), + ), + migrations.AddConstraint( + model_name='player', + constraint=models.UniqueConstraint(fields=('attend_token',), name='unique_player_attend_token'), + ), + ] diff --git a/battleship/migrations/__init__.py b/battleship/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battleship/models.py b/battleship/models.py new file mode 100644 index 0000000..8361aab --- /dev/null +++ b/battleship/models.py @@ -0,0 +1,30 @@ +import random + +from django.db import models + +# Create your models here. +from django.utils import timezone + + +def generate_token(): + letters = 'qwertyuioppasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890' + return ''.join([random.choice(letters) for _ in range(30)]) + + +class Game(models.Model): + last_move_ts = models.DateTimeField(default=timezone.now) + turn = models.IntegerField(default=0) + + +class Player(models.Model): + game = models.ForeignKey(Game, on_delete=models.CASCADE) + field = models.CharField(max_length=100, default=' ' * 100) + number = models.IntegerField() + attend_token = models.CharField(max_length=30, default=generate_token) + token = models.CharField(max_length=30, null=True, blank=True) + + class Meta: + constraints = [ + models.UniqueConstraint(fields=['token'], name='unique_player_token'), + models.UniqueConstraint(fields=['attend_token'], name='unique_player_attend_token') + ] diff --git a/battleship/tests.py b/battleship/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/battleship/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/battleship/views.py b/battleship/views.py new file mode 100644 index 0000000..06aa928 --- /dev/null +++ b/battleship/views.py @@ -0,0 +1,99 @@ +from django.http import JsonResponse +from django.shortcuts import render + +# Create your views here. +from battleship.models import Game, Player, generate_token + + +def new_game(request): + game = Game.objects.create() + player1 = Player.objects.create( + game=game, + number=0, + token=generate_token() + ) + player2 = Player.objects.create( + game=game, + number=1 + ) + return JsonResponse({ + 'game_id': game.id, + 'player_token': player2.attend_token, + 'my_token': player1.token + }) + + +def attend_game(request): + game_id = request.POST['game_id'] + attend_token = request.POST['attend_token'] + player = Player.objects.get(game_id=game_id, attend_token=attend_token) + if player.token is not None: + return JsonResponse({}, status=403) + player.token = generate_token() + player.save() + return JsonResponse({ + 'token': player.token + }) + + +def place_ships(request): + game_id = request.POST['game_id'] + token = request.POST['token'] + player = Player.objects.get(game_id=game_id, token=token) + if player.field != ' ' * 100: + return JsonResponse({}, status=403) + player.field = request.POST['field'] + player.save() + return JsonResponse({}) + + +def check_opponent(request): + game_id = request.POST['game_id'] + token = request.POST['token'] + player = Player.objects.get(game_id=game_id, token=token) + player2 = Player.objects.filter(game_id=game_id, number=(1 - player.number)).first() + if player2 is None: + return JsonResponse({"attend": False, "ready": False}) + return JsonResponse({"attend": True, "ready": player2.field != ' ' * 100}) + + +def shoot(request): + game_id = request.POST['game_id'] + token = request.POST['token'] + player = Player.objects.get(game_id=game_id, token=token) + if player.game.turn != player.number: + return JsonResponse({}, status=403) + player2 = Player.objects.get(game_id=game_id, number=(1 - player.number)) + h = request.POST['h'] + v = request.POST['v'] + pos = h * 10 + v + if player2.field[pos] == 'x' or player2.field[pos] == '.': + return JsonResponse({}, status=403) + if player2.field[pos] == 'o': + new_symb = 'x' + else: + new_symb = '.' + player.game.turn = (1 - player.game.turn) + player.game.save() + if pos == 0: + player2.field = new_symb + player2.field[1:] + elif pos == 99: + player2.field[:99] + new_symb + else: + player2.field = player2.field[:pos] + new_symb + player2.field[pos + 1:] + player2.save() + return JsonResponse({ + 'shot': new_symb == 'x', + 'game_finish': 'o' not in player2.field + }) + + +def check_status(request): + game_id = request.POST['game_id'] + token = request.POST['token'] + player = Player.objects.get(game_id=game_id, token=token) + player2 = Player.objects.get(game_id=game_id, number=1 - player.number) + return JsonResponse({ + 'my_turn': player.game.turn == player.number, + 'game_finished': 'o' not in player.field or 'o' not in player2.field + }) diff --git a/battleship_back/__init__.py b/battleship_back/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battleship_back/asgi.py b/battleship_back/asgi.py new file mode 100644 index 0000000..6e3524f --- /dev/null +++ b/battleship_back/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for battleship_back project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'battleship_back.settings') + +application = get_asgi_application() diff --git a/battleship_back/settings.py b/battleship_back/settings.py new file mode 100644 index 0000000..eeb888b --- /dev/null +++ b/battleship_back/settings.py @@ -0,0 +1,131 @@ +""" +Django settings for battleship_back project. + +Generated by 'django-admin startproject' using Django 3.2.15. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-$x=irhk3ts1ae%b+kpr+g%mk3v$eb++^0eh(eg0)+-hb+ane82' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'battleship.apps.BattleshipConfig', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'battleship_back.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')] + , + '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 = 'battleship_back.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "battleship", + "USER": "postgres", + "PASSWORD": os.getenv("DB_PASSWORD", "password"), + "HOST": os.getenv("DB_HOST", "127.0.0.1"), + "PORT": 5432, + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/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.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/battleship_back/urls.py b/battleship_back/urls.py new file mode 100644 index 0000000..d8ca9e0 --- /dev/null +++ b/battleship_back/urls.py @@ -0,0 +1,29 @@ +"""battleship_back URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +from battleship import views + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/new_game', views.new_game), + path('api/check_status', views.check_status), + path('api/check_opponent', views.check_opponent), + path('api/attend_game', views.attend_game), + path('api/place_ships', views.place_ships), + path('api/shoot', views.shoot) +] diff --git a/battleship_back/wsgi.py b/battleship_back/wsgi.py new file mode 100644 index 0000000..50f591c --- /dev/null +++ b/battleship_back/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for battleship_back project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'battleship_back.settings') + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..a2bf14c --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'battleship_back.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..0d947da --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx +COPY nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..3ccd82a --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,15 @@ +events {} + +http { + server { + listen 80; + + location /api/ { + proxy_pass http://backend:8000/api/; + } + +; location / { +; proxy_pass http://frontend:3000/; +; } + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..08cdb6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +asgiref==3.5.2 +Django==3.2.15 +psycopg2==2.9.3 +pytz==2022.2.1 +sqlparse==0.4.2 +typing_extensions==4.3.0