commit 24aa9bcf828db35f8d7855e0f623bd9e9df4d9ec Author: WiRight Date: Tue Apr 28 22:59:35 2026 +0300 Initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e724d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# Operating system files +.DS_Store +Thumbs.db + +# Dependency directories +node_modules/ +venv/ +.env +*.env + +# Build and output folders +dist/ +build/ +out/ +coverage/ +coverage-final.json + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +*.log + +# Temporary files +tmp/ +temp/ +*.tmp +*.swp +*.swo + +# IDE/editor directories +.vscode/ +.idea/ +*.sublime-workspace +*.sublime-project + +# API/Web project specific +.env.local +.env.*.local +/.next/ +/.nuxt/ +/.vercel/ +/.cache/ + +# Compiled files +*.class +*.dll +*.exe +*.o +*.pyc +*.pyo +*.pyd +__pycache__/ + +# Misc +.DS_Store +*.bak +*.backup +*.orig +*.rej + +# Ignore secrets and credentials +secret* +credentials* + +# Generated documentation +doc/api/ +docs/ + +# Docker +docker-compose.override.yml +*.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c711a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: help + +dc = docker compose +devDC = $(dc) -f compose.yaml -f compose.dev.yaml +project = dc + +help: + @echo "Usage: \n" + @echo "- prod" + @echo "- dev" + @echo "- down" + @echo "- log" + @echo "- config" + @echo "- devConfig" + +prod: + $(dc) up -d + +dev: + $(devDC) up -d + +down: + $(devDC) down + +logs: + $(devDC) logs -f --tail=20 + +config: + $(devDC) config + +devConfig: + $(devDC) config diff --git a/README.md b/README.md new file mode 100644 index 0000000..6683207 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# ProjectName + +ProjectName — это проект для организации задач команды и управления рабочими процессами. + +Этот репозиторий `deploy` служит оркестратором проекта. +Он содержит инфраструктуру и конфигурации для развёртывания ProjectName, управления окружениями и запуска служб. + +Основные задачи репозитория `deploy`: +- подготовка инфраструктуры для приложения; +- настройка CI/CD и автоматического деплоя; +- управление конфигурациями окружений; +- запуск и мониторинг сервисов. + +Использование: +1. Настройте переменные окружения и секреты. +2. Запустите процедуры развёртывания. +3. Следите за состоянием сервиса и логами. + +Этот репозиторий не содержит основную логику приложения ProjectName, а отвечает за его развёртывание и эксплуатацию. diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..878080b --- /dev/null +++ b/cliff.toml @@ -0,0 +1,91 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing s +trim = true +# postprocessors +postprocessors = [ + { pattern = '(IDA-\d+)', replace = "[${1}](https://ru.yougile.com/team/a0c003776084/Identity#${1})" } + # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL +] + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # Replace issue numbers + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit with https://github.com/crate-ci/typos + # If the spelling is incorrect, it will be automatically fixed. + #{ pattern = '.*', replace_command = 'typos --write-changes -' }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "🚀 Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^fixes", group = "🐛 Fixes" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor", group = "🚜 Refactor" }, + { message = "^style", group = "🎨 Styling" }, + { message = "^test", group = "🧪 Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, + { body = ".*security", group = "🛡️ Security" }, + { message = "^revert", group = "◀️ Revert" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# regex for matching git tags +# tag_pattern = "v[0-9].*" +# regex for skipping tags +# skip_tags = "" +# regex for ignoring tags +# ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" +# limit the number of commits included in the changelog. +# limit_commits = 42 diff --git a/compose.dev.yaml b/compose.dev.yaml new file mode 100644 index 0000000..b99d7f1 --- /dev/null +++ b/compose.dev.yaml @@ -0,0 +1,22 @@ +services: + + adminer: + container_name: ${APP_PREFIX}-${APP_ENV}-adminer + image: adminer:${DEPLOY_ADMINER_TAG:-5.4.2-standalone} + restart: always + environment: + ADMINER_PLUGINS: > + tables-filter tinymce dump-json table-structure pretty-json-column + dark-switcher idsmk-edit-foreign + ADMINER_DESIGN: dracula + ADMINER_DEFAULT_SERVER: db + links: + - "db" + depends_on: + db: + condition: service_healthy + volumes: + - ./services/adminer/idsmk-edit-foreign.php:/var/www/html/plugins/idsmk-edit-foreign.php:ro + - /etc/localtime:/etc/localtime:ro + networks: + net: diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..a634fcc --- /dev/null +++ b/compose.yaml @@ -0,0 +1,52 @@ +services: + nginx: + container_name: ${APP_PREFIX}-${APP_ENV}-nginx + image: nginx:${DEPLOY_NGINX_TAG:-1.29.8-alpine-slim} + restart: always + working_dir: /etc/nginx/conf.d + env_file: + - .env + ports: + - "${APP_PORT}:80" + # depends_on: + # api: + # condition: service_started + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost || exit 1 + interval: 5m + timeout: 3s + retries: 3 + start_period: 2m + volumes: + - ./services/nginx/hosts/${NGINX_HOSTS}:/etc/nginx/templates + - ./services/nginx/.htpasswd:/etc/nginx/.htpasswd + - /etc/localtime:/etc/localtime:ro + networks: + net: + + db: + container_name: ${APP_PREFIX}-${APP_ENV}-db + image: postgres:${DEPLOY_POSTGRES_TAG:-18.3-alpine3.23} + restart: always + environment: + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DATABASE: ${DB_DATABASE} + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}" ] + interval: 5s + timeout: 2s + retries: 5 + volumes: + - db:/var/lib/postgresql + - /etc/localtime:/etc/localtime:ro + networks: + net: + +volumes: + db: + name: ${APP_PREFIX}-${APP_ENV}-db + +networks: + net: + name: ${APP_PREFIX}-${APP_ENV}-net diff --git a/services/adminer/idsmk-edit-foreign.php b/services/adminer/idsmk-edit-foreign.php new file mode 100644 index 0000000..c43273e --- /dev/null +++ b/services/adminer/idsmk-edit-foreign.php @@ -0,0 +1,92 @@ +limit = $limit; + } + + function editInput($table, $field, $attrs, $value) { + static $foreignTables = []; + static $values = []; + + $foreignKeys = &$foreignTables[$table]; + + if ($foreignKeys === null) { + $foreignKeys = Adminer\column_foreign_keys($table); + } + + $titleFields = [ + 'name', + 'title', + 'nickname', + 'email', + ]; + + $titleFieldName = null; + + foreach ((array) $foreignKeys[$field["field"]] as $foreignKey) { + if (count($foreignKey["source"]) == 1) { + $target = $foreignKey["table"]; + $id = $foreignKey["target"][0]; + $options = &$values[$target][$id]; + + if (!$options) { + $column = Adminer\idf_escape($id); + + if (preg_match('~binary~', $field["type"])) { + $column = "HEX($column)"; + } + + $selectFields = [ + $column, + ]; + + $targetFields = array_keys(Adminer\fields($target)); + + foreach ($targetFields as $targetField) { + if (in_array($targetField, $titleFields)) { + $selectFields[] = Adminer\idf_escape($targetField); + $titleFieldName = $targetField; + + break; + } + } + + $rows = Adminer\get_rows('SELECT ' . implode(',', $selectFields) . ' FROM ' . Adminer\table($target) . ' ORDER BY 1'); + + $options = [ + '', + ]; + + foreach ($rows as $row) { + $options[$row[$id]] = $row[$titleFieldName ?? $id]; + } + + if ($this->limit && count($options) - 1 > $this->limit) { + return; + } + } + + return "" . Adminer\optionlist($options, $value, true) . ""; + } + } + } + + protected $translations = [ + 'cs' => ['' => 'Výběr cizího klíče v editačním formuláři'], + 'de' => ['' => 'Wählen Sie im Bearbeitungsformular den Fremdschlüssel aus'], + 'pl' => ['' => 'Wybierz klucz obcy w formularzu edycji'], + 'ro' => ['' => 'Selectați cheia străină în formularul de editare'], + 'ja' => ['' => '外部キーを編集フォームで選択'], + 'ru' => ['' => 'Выбор внешнего ключа при редактировании таблицы (с подстановкой Title)'], + ]; +} diff --git a/services/nginx/.htpasswd b/services/nginx/.htpasswd new file mode 100644 index 0000000..186f629 --- /dev/null +++ b/services/nginx/.htpasswd @@ -0,0 +1 @@ +asmkuser:$apr1$95jzw9DR$qCcDTA8jp2tges/mk8Hyg1 diff --git a/services/nginx/hosts/local/adminer.conf.template b/services/nginx/hosts/local/adminer.conf.template new file mode 100644 index 0000000..89bab62 --- /dev/null +++ b/services/nginx/hosts/local/adminer.conf.template @@ -0,0 +1,15 @@ +server { + listen 80; + + server_name adminer.${APP_HOST}; + + client_max_body_size 50M; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + proxy_pass http://adminer:8080; + } +} diff --git a/services/nginx/hosts/local/api.conf.template b/services/nginx/hosts/local/api.conf.template new file mode 100644 index 0000000..86f75a1 --- /dev/null +++ b/services/nginx/hosts/local/api.conf.template @@ -0,0 +1,21 @@ +server { + listen 80; + + server_name api.${APP_HOST}; + + client_max_body_size 500m; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + proxy_http_version 1.1; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400s; + chunked_transfer_encoding on; + + proxy_pass http://api:8080; + } +} diff --git a/services/nginx/hosts/local/web.conf.template b/services/nginx/hosts/local/web.conf.template new file mode 100644 index 0000000..c3ae39d --- /dev/null +++ b/services/nginx/hosts/local/web.conf.template @@ -0,0 +1,27 @@ +server { + listen 80 default_server; + + server_name ${APP_HOST}; + + client_max_body_size 500m; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + proxy_pass http://web:3000; + } + + location /_nuxt/ { + proxy_http_version 1.1; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + + proxy_cache_bypass $http_upgrade; + + proxy_pass http://web:3000; + } +} diff --git a/services/nginx/hosts/prod/api.conf.template b/services/nginx/hosts/prod/api.conf.template new file mode 100644 index 0000000..d2ad9c5 --- /dev/null +++ b/services/nginx/hosts/prod/api.conf.template @@ -0,0 +1,15 @@ +server { + listen 80; + + server_name api.${APP_HOST}; + + client_max_body_size 500m; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + proxy_pass http://api:8080; + } +} diff --git a/services/nginx/hosts/prod/web.conf.template b/services/nginx/hosts/prod/web.conf.template new file mode 100644 index 0000000..74a4d73 --- /dev/null +++ b/services/nginx/hosts/prod/web.conf.template @@ -0,0 +1,15 @@ +server { + listen 80 default_server; + + server_name ${APP_HOST}; + + client_max_body_size 500m; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + proxy_pass http://web:3000; + } +} diff --git a/sources/.gitignore b/sources/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/sources/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore