Compare commits
No commits in common. "8aee3a64ea5213c4279c9caf762fb94a5ad2ed91" and "2fbb9ef2294aea35fe7773b1b4cdfa0bd37b2bdd" have entirely different histories.
8aee3a64ea
...
2fbb9ef229
|
|
@ -1,2 +0,0 @@
|
||||||
Test commit on dev branch.
|
|
||||||
Created to verify dev branch workflow.
|
|
||||||
21
README.md
21
README.md
|
|
@ -5,6 +5,8 @@
|
||||||
- `index.php` — публичная витрина (разделы, тематики, карточки фото, комментарии),
|
- `index.php` — публичная витрина (разделы, тематики, карточки фото, комментарии),
|
||||||
- `admin.php?token=...` — закрытая админка (управление разделами/тематиками/фото/пользователями/комментариями/настройками).
|
- `admin.php?token=...` — закрытая админка (управление разделами/тематиками/фото/пользователями/комментариями/настройками).
|
||||||
|
|
||||||
|
`index-mysql.php` и `admin-mysql.php` оставлены как алиасы для обратной совместимости.
|
||||||
|
|
||||||
## Что умеет проект
|
## Что умеет проект
|
||||||
|
|
||||||
- Иерархия каталога: разделы + тематики (2 уровня).
|
- Иерархия каталога: разделы + тематики (2 уровня).
|
||||||
|
|
@ -38,6 +40,8 @@
|
||||||
photo.andr33v.ru/
|
photo.andr33v.ru/
|
||||||
├─ index.php # публичная витрина
|
├─ index.php # публичная витрина
|
||||||
├─ admin.php # админка по токену
|
├─ admin.php # админка по токену
|
||||||
|
├─ index-mysql.php # alias -> index.php
|
||||||
|
├─ admin-mysql.php # alias -> admin.php
|
||||||
├─ style.css # базовые стили
|
├─ style.css # базовые стили
|
||||||
├─ favicon.svg
|
├─ favicon.svg
|
||||||
├─ config.php.example # шаблон конфига БД и деплоя
|
├─ config.php.example # шаблон конфига БД и деплоя
|
||||||
|
|
@ -83,12 +87,6 @@ cp config.php.example config.php
|
||||||
|
|
||||||
2. Заполни доступы к БД в `config.php`.
|
2. Заполни доступы к БД в `config.php`.
|
||||||
|
|
||||||
Для деплоя из админки в `config.php` можно задать:
|
|
||||||
|
|
||||||
- `deploy.remote_name` (обычно `origin`),
|
|
||||||
- `deploy.remote_url` (по умолчанию `git@github.com:wrkandreev/reframe.git`),
|
|
||||||
- `deploy.branch` (`main` или `dev`).
|
|
||||||
|
|
||||||
3. Создай `secrets.php`:
|
3. Создай `secrets.php`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -161,16 +159,15 @@ php scripts/generate_thumbs.php
|
||||||
|
|
||||||
Деплой запускается из админки (вкладка `Настройки`):
|
Деплой запускается из админки (вкладка `Настройки`):
|
||||||
|
|
||||||
- кнопка `Проверить обновления` делает `git fetch` и сравнивает `HEAD` с `<remote>/<branch>`,
|
- кнопка `Проверить обновления` делает `git fetch` и сравнивает `HEAD` с `origin/<branch>`,
|
||||||
- если локальная ветка отстает и не расходится (`behind > 0`, `ahead = 0`) — показывается кнопка `Обновить проект`.
|
- если локальная ветка отстает и не расходится (`behind > 0`, `ahead = 0`) — показывается кнопка `Обновить проект`.
|
||||||
|
|
||||||
Скрипт `scripts/deploy.sh`:
|
Скрипт `scripts/deploy.sh`:
|
||||||
|
|
||||||
1. настраивает remote из `REMOTE_NAME`/`REMOTE_URL` (если передан `REMOTE_URL`),
|
1. делает `git fetch --all --prune`,
|
||||||
2. делает `git fetch <remote> <branch> --prune`,
|
2. переключает код на `origin/<branch>` через `git reset --hard`,
|
||||||
3. переключает код на `<remote>/<branch>` через `git reset --hard`,
|
3. запускает миграции `php scripts/migrate.php`,
|
||||||
4. запускает миграции `php scripts/migrate.php`,
|
4. сохраняет runtime-папки (`photos`, `thumbs`, `data`).
|
||||||
5. сохраняет runtime-папки (`photos`, `thumbs`, `data`).
|
|
||||||
|
|
||||||
Важно: деплой-скрипт перетирает рабочие изменения в репозитории на сервере.
|
Важно: деплой-скрипт перетирает рабочие изменения в репозитории на сервере.
|
||||||
|
|
||||||
|
|
|
||||||
3
admin-mysql.php
Normal file
3
admin-mysql.php
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
// Backward-compat alias
|
||||||
|
require __DIR__ . '/admin.php';
|
||||||
13
admin.php
13
admin.php
|
|
@ -48,11 +48,6 @@ if ($tokenExpected === '' || !hash_equals($tokenExpected, $tokenIncoming)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployConfig = (array)($config['deploy'] ?? []);
|
$deployConfig = (array)($config['deploy'] ?? []);
|
||||||
$deployRemoteName = trim((string)($deployConfig['remote_name'] ?? 'origin'));
|
|
||||||
if ($deployRemoteName === '') {
|
|
||||||
$deployRemoteName = 'origin';
|
|
||||||
}
|
|
||||||
$deployRemoteUrl = trim((string)($deployConfig['remote_url'] ?? ''));
|
|
||||||
$allowedDeployBranches = ['main', 'dev'];
|
$allowedDeployBranches = ['main', 'dev'];
|
||||||
$defaultDeployBranch = trim((string)($deployConfig['branch'] ?? 'main'));
|
$defaultDeployBranch = trim((string)($deployConfig['branch'] ?? 'main'));
|
||||||
if (!in_array($defaultDeployBranch, $allowedDeployBranches, true)) {
|
if (!in_array($defaultDeployBranch, $allowedDeployBranches, true)) {
|
||||||
|
|
@ -88,8 +83,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = adminHandlePostAction($action, $isAjax, __DIR__, [
|
$result = adminHandlePostAction($action, $isAjax, __DIR__, [
|
||||||
'remote_name' => $deployRemoteName,
|
|
||||||
'remote_url' => $deployRemoteUrl,
|
|
||||||
'branch' => $deployBranch,
|
'branch' => $deployBranch,
|
||||||
'script' => $deployScript,
|
'script' => $deployScript,
|
||||||
'php_bin' => $deployPhpBin,
|
'php_bin' => $deployPhpBin,
|
||||||
|
|
@ -292,12 +285,10 @@ function assetUrl(string $path): string { $f=__DIR__ . '/' . ltrim($path,'/'); $
|
||||||
|
|
||||||
<hr style="border:none;border-top:1px solid #eee;margin:12px 0">
|
<hr style="border:none;border-top:1px solid #eee;margin:12px 0">
|
||||||
<h4 style="margin:0 0 8px">Обновление проекта</h4>
|
<h4 style="margin:0 0 8px">Обновление проекта</h4>
|
||||||
<p class="small" style="margin:0">Remote: <strong><?= h($deployRemoteName) ?></strong><?= $deployRemoteUrl !== '' ? ' (' . h($deployRemoteUrl) . ')' : '' ?></p>
|
<p class="small" style="margin:0">Выбери ветку для проверки и обновления: <strong><?= h($deployBranch) ?></strong></p>
|
||||||
<p class="small" style="margin:4px 0 0">Выбери ветку для проверки и обновления: <strong><?= h($deployBranch) ?></strong></p>
|
|
||||||
|
|
||||||
<?php if (is_array($deployStatus)): ?>
|
<?php if (is_array($deployStatus)): ?>
|
||||||
<?php $deployState = (string)($deployStatus['state'] ?? ''); ?>
|
<?php $deployState = (string)($deployStatus['state'] ?? ''); ?>
|
||||||
<?php $statusRemoteName = (string)($deployStatus['remote_name'] ?? $deployRemoteName); ?>
|
|
||||||
<?php $statusBranch = (string)($deployStatus['branch'] ?? $deployBranch); ?>
|
<?php $statusBranch = (string)($deployStatus['branch'] ?? $deployBranch); ?>
|
||||||
<?php $deployStateMessage = $deployState === 'update_available'
|
<?php $deployStateMessage = $deployState === 'update_available'
|
||||||
? 'Доступна новая версия.'
|
? 'Доступна новая версия.'
|
||||||
|
|
@ -308,7 +299,7 @@ function assetUrl(string $path): string { $f=__DIR__ . '/' . ltrim($path,'/'); $
|
||||||
: 'Ветка расходится с origin. Нужна ручная синхронизация.')); ?>
|
: 'Ветка расходится с origin. Нужна ручная синхронизация.')); ?>
|
||||||
<div class="<?= in_array($deployState, ['local_ahead', 'diverged'], true) ? 'err' : 'ok' ?>" style="margin-top:8px">
|
<div class="<?= in_array($deployState, ['local_ahead', 'diverged'], true) ? 'err' : 'ok' ?>" style="margin-top:8px">
|
||||||
<?= h($deployStateMessage) ?><br>
|
<?= h($deployStateMessage) ?><br>
|
||||||
<span class="small">Локально: <?= h((string)($deployStatus['local_ref'] ?? '-')) ?> · <?= h($statusRemoteName) ?>/<?= h($statusBranch) ?>: <?= h((string)($deployStatus['remote_ref'] ?? '-')) ?> · behind: <?= (int)($deployStatus['behind'] ?? 0) ?> · ahead: <?= (int)($deployStatus['ahead'] ?? 0) ?></span>
|
<span class="small">Локально: <?= h((string)($deployStatus['local_ref'] ?? '-')) ?> · origin/<?= h($statusBranch) ?>: <?= h((string)($deployStatus['remote_ref'] ?? '-')) ?> · behind: <?= (int)($deployStatus['behind'] ?? 0) ?> · ahead: <?= (int)($deployStatus['ahead'] ?? 0) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ return [
|
||||||
'charset' => 'utf8mb4',
|
'charset' => 'utf8mb4',
|
||||||
],
|
],
|
||||||
'deploy' => [
|
'deploy' => [
|
||||||
'remote_name' => 'origin',
|
|
||||||
'remote_url' => 'git@github.com:wrkandreev/reframe.git',
|
|
||||||
'branch' => 'main',
|
'branch' => 'main',
|
||||||
'script' => __DIR__ . '/scripts/deploy.sh',
|
'script' => __DIR__ . '/scripts/deploy.sh',
|
||||||
'php_bin' => 'php',
|
'php_bin' => 'php',
|
||||||
|
|
|
||||||
3
index-mysql.php
Normal file
3
index-mysql.php
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
// Backward-compat alias
|
||||||
|
require __DIR__ . '/index.php';
|
||||||
123
index.php
123
index.php
|
|
@ -27,21 +27,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') ==
|
||||||
|| str_contains((string)($_SERVER['HTTP_ACCEPT'] ?? ''), 'application/json');
|
|| str_contains((string)($_SERVER['HTTP_ACCEPT'] ?? ''), 'application/json');
|
||||||
|
|
||||||
$commentSaved = false;
|
$commentSaved = false;
|
||||||
$savedComment = null;
|
|
||||||
$errorMessage = '';
|
$errorMessage = '';
|
||||||
$errorCode = 422;
|
|
||||||
|
|
||||||
if ($token !== '' && $photoId > 0 && $text !== '') {
|
if ($token !== '' && $photoId > 0 && $text !== '') {
|
||||||
$u = commenterByToken($token);
|
$u = commenterByToken($token);
|
||||||
if ($u) {
|
if ($u) {
|
||||||
try {
|
commentAdd($photoId, (int)$u['id'], limitText($text, 1000));
|
||||||
$savedComment = commentAdd($photoId, (int)$u['id'], limitText($text, 1000));
|
|
||||||
$commentSaved = true;
|
$commentSaved = true;
|
||||||
} catch (Throwable $e) {
|
|
||||||
error_log('Comment add failed: ' . $e->getMessage());
|
|
||||||
$errorMessage = 'Не удалось отправить комментарий.';
|
|
||||||
$errorCode = 500;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$errorMessage = 'Ссылка для комментариев недействительна.';
|
$errorMessage = 'Ссылка для комментариев недействительна.';
|
||||||
}
|
}
|
||||||
|
|
@ -52,20 +44,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') ==
|
||||||
if ($isAjax) {
|
if ($isAjax) {
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
if ($commentSaved) {
|
if ($commentSaved) {
|
||||||
echo json_encode([
|
echo json_encode(['ok' => true, 'message' => 'Ваш комментарий отправлен.'], JSON_UNESCAPED_UNICODE);
|
||||||
'ok' => true,
|
|
||||||
'message' => 'Ваш комментарий отправлен.',
|
|
||||||
'comment' => $savedComment ? [
|
|
||||||
'id' => (int)($savedComment['id'] ?? 0),
|
|
||||||
'comment_text' => (string)($savedComment['comment_text'] ?? ''),
|
|
||||||
'created_at' => (string)($savedComment['created_at'] ?? ''),
|
|
||||||
'display_name' => (string)($savedComment['display_name'] ?? 'Пользователь'),
|
|
||||||
] : null,
|
|
||||||
], JSON_UNESCAPED_UNICODE);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_code($errorCode);
|
http_response_code(422);
|
||||||
echo json_encode(['ok' => false, 'message' => $errorMessage !== '' ? $errorMessage : 'Не удалось отправить комментарий.'], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['ok' => false, 'message' => $errorMessage !== '' ? $errorMessage : 'Не удалось отправить комментарий.'], JSON_UNESCAPED_UNICODE);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
@ -691,7 +674,7 @@ function outputWatermarked(string $path, string $mime): never
|
||||||
|
|
||||||
<h3 class="detail-comments-title">Комментарии</h3>
|
<h3 class="detail-comments-title">Комментарии</h3>
|
||||||
<?php if ($viewer): ?>
|
<?php if ($viewer): ?>
|
||||||
<form class="js-comment-form comment-form" method="post" action="" data-script-path="<?= h((string)($_SERVER['SCRIPT_NAME'] ?? '/index.php')) ?>">
|
<form class="js-comment-form comment-form" method="post" action="?photo_id=<?= (int)$photo['id'] ?><?= $isTopicMode ? '&topic_id=' . $activeTopicId : '§ion_id=' . (int)$detailSectionId ?><?= $viewerToken!=='' ? '&viewer=' . urlencode($viewerToken) : '' ?>">
|
||||||
<input type="hidden" name="action" value="add_comment">
|
<input type="hidden" name="action" value="add_comment">
|
||||||
<input type="hidden" name="photo_id" value="<?= (int)$photo['id'] ?>">
|
<input type="hidden" name="photo_id" value="<?= (int)$photo['id'] ?>">
|
||||||
<input type="hidden" name="section_id" value="<?= $isSectionMode ? (int)$detailSectionId : 0 ?>">
|
<input type="hidden" name="section_id" value="<?= $isSectionMode ? (int)$detailSectionId : 0 ?>">
|
||||||
|
|
@ -705,11 +688,9 @@ function outputWatermarked(string $path, string $mime): never
|
||||||
<p class="muted">Комментарии может оставлять только пользователь с персональной ссылкой.</p>
|
<p class="muted">Комментарии может оставлять только пользователь с персональной ссылкой.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="js-comments-list">
|
|
||||||
<?php foreach($comments as $c): ?>
|
<?php foreach($comments as $c): ?>
|
||||||
<div class="cmt"><strong><?= h((string)($c['display_name'] ?? 'Пользователь')) ?></strong> <span class="muted">· <?= h((string)$c['created_at']) ?></span><br><?= nl2br(h((string)$c['comment_text'])) ?></div>
|
<div class="cmt"><strong><?= h((string)($c['display_name'] ?? 'Пользователь')) ?></strong> <span class="muted">· <?= h((string)$c['created_at']) ?></span><br><?= nl2br(h((string)$c['comment_text'])) ?></div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($detailTotal > 0): ?>
|
<?php if ($detailTotal > 0): ?>
|
||||||
<div class="pager">
|
<div class="pager">
|
||||||
|
|
@ -908,38 +889,6 @@ function outputWatermarked(string $path, string $mime): never
|
||||||
const commentForm = commentTextarea ? commentTextarea.closest('.js-comment-form') : null;
|
const commentForm = commentTextarea ? commentTextarea.closest('.js-comment-form') : null;
|
||||||
const commentFeedback = commentForm ? commentForm.querySelector('.js-comment-feedback') : null;
|
const commentFeedback = commentForm ? commentForm.querySelector('.js-comment-feedback') : null;
|
||||||
const commentSubmitButton = commentForm ? commentForm.querySelector('button[type="submit"]') : null;
|
const commentSubmitButton = commentForm ? commentForm.querySelector('button[type="submit"]') : null;
|
||||||
const commentsList = document.querySelector('.js-comments-list');
|
|
||||||
|
|
||||||
const prependComment = (comment) => {
|
|
||||||
if (!commentsList || !comment || typeof comment !== 'object') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'cmt';
|
|
||||||
|
|
||||||
const author = document.createElement('strong');
|
|
||||||
author.textContent = String(comment.display_name || 'Пользователь');
|
|
||||||
|
|
||||||
const meta = document.createElement('span');
|
|
||||||
meta.className = 'muted';
|
|
||||||
meta.textContent = `· ${String(comment.created_at || '')}`;
|
|
||||||
|
|
||||||
item.appendChild(author);
|
|
||||||
item.append(' ');
|
|
||||||
item.appendChild(meta);
|
|
||||||
item.appendChild(document.createElement('br'));
|
|
||||||
|
|
||||||
const lines = String(comment.comment_text || '').split(/\r\n|\r|\n/);
|
|
||||||
lines.forEach((line, index) => {
|
|
||||||
item.append(document.createTextNode(line));
|
|
||||||
if (index < lines.length - 1) {
|
|
||||||
item.appendChild(document.createElement('br'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
commentsList.prepend(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCommentFeedback = (message, isError) => {
|
const setCommentFeedback = (message, isError) => {
|
||||||
if (!commentFeedback) {
|
if (!commentFeedback) {
|
||||||
|
|
@ -996,33 +945,7 @@ function outputWatermarked(string $path, string $mime): never
|
||||||
setCommentFeedback('', false);
|
setCommentFeedback('', false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const endpoints = [];
|
const response = await fetch(commentForm.action, {
|
||||||
const pushEndpoint = (url) => {
|
|
||||||
if (!url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!endpoints.includes(url)) {
|
|
||||||
endpoints.push(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pushEndpoint(commentForm.action || window.location.href);
|
|
||||||
|
|
||||||
const scriptPath = String(commentForm.dataset.scriptPath || '').trim();
|
|
||||||
if (scriptPath !== '') {
|
|
||||||
const fallback = new URL(scriptPath, window.location.origin);
|
|
||||||
fallback.search = window.location.search;
|
|
||||||
pushEndpoint(fallback.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = null;
|
|
||||||
let raw = '';
|
|
||||||
let payload = null;
|
|
||||||
let usedEndpoint = '';
|
|
||||||
|
|
||||||
for (const endpoint of endpoints) {
|
|
||||||
usedEndpoint = endpoint;
|
|
||||||
response = await fetch(endpoint, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -1031,42 +954,12 @@ function outputWatermarked(string $path, string $mime): never
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
raw = await response.text();
|
const payload = await response.json().catch(() => null);
|
||||||
try {
|
if (!response.ok || !payload || payload.ok !== true) {
|
||||||
payload = JSON.parse(raw);
|
throw new Error(payload && payload.message ? String(payload.message) : 'Не удалось отправить комментарий.');
|
||||||
} catch {
|
|
||||||
payload = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 404 && endpoints[endpoints.length - 1] !== endpoint) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
throw new Error('Не удалось отправить комментарий.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!payload) {
|
|
||||||
if (response.ok) {
|
|
||||||
window.location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawMessage = raw.trim() !== '' ? raw.slice(0, 220) : '';
|
|
||||||
throw new Error(rawMessage !== '' ? `HTTP ${response.status}: ${rawMessage}` : `HTTP ${response.status}: ${usedEndpoint}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok || payload.ok !== true) {
|
|
||||||
throw new Error(payload.message ? String(payload.message) : 'Не удалось отправить комментарий.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCommentFeedback(payload.message || 'Ваш комментарий отправлен.', false);
|
setCommentFeedback(payload.message || 'Ваш комментарий отправлен.', false);
|
||||||
if (payload.comment) {
|
|
||||||
prependComment(payload.comment);
|
|
||||||
}
|
|
||||||
commentTextarea.value = '';
|
commentTextarea.value = '';
|
||||||
commentTextarea.focus();
|
commentTextarea.focus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,21 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
function adminCheckForUpdates(string $projectRoot, string $branch, string $remoteName = 'origin', string $remoteUrl = ''): array
|
function adminCheckForUpdates(string $projectRoot, string $branch): array
|
||||||
{
|
{
|
||||||
if (!is_dir($projectRoot . '/.git')) {
|
if (!is_dir($projectRoot . '/.git')) {
|
||||||
throw new RuntimeException('Репозиторий не найден: .git отсутствует');
|
throw new RuntimeException('Репозиторий не найден: .git отсутствует');
|
||||||
}
|
}
|
||||||
|
|
||||||
$remoteName = adminNormalizeRemoteName($remoteName);
|
$fetch = adminRunShellCommand('git fetch origin ' . escapeshellarg($branch) . ' --prune', $projectRoot);
|
||||||
adminEnsureRemote($projectRoot, $remoteName, $remoteUrl);
|
|
||||||
|
|
||||||
$remoteRef = $remoteName . '/' . $branch;
|
|
||||||
|
|
||||||
$fetch = adminRunShellCommand('git fetch ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($branch) . ' --prune', $projectRoot);
|
|
||||||
if ($fetch['code'] !== 0) {
|
if ($fetch['code'] !== 0) {
|
||||||
throw new RuntimeException('Не удалось обновить данные из ' . $remoteName . ': ' . adminTailOutput($fetch['output']));
|
throw new RuntimeException('Не удалось обновить данные из origin: ' . adminTailOutput($fetch['output']));
|
||||||
}
|
}
|
||||||
|
|
||||||
$local = adminRunShellCommand('git rev-parse --short=12 HEAD', $projectRoot);
|
$local = adminRunShellCommand('git rev-parse --short=12 HEAD', $projectRoot);
|
||||||
$remote = adminRunShellCommand('git rev-parse --short=12 ' . escapeshellarg($remoteRef), $projectRoot);
|
$remote = adminRunShellCommand('git rev-parse --short=12 origin/' . escapeshellarg($branch), $projectRoot);
|
||||||
$behindRaw = adminRunShellCommand('git rev-list --count HEAD..' . escapeshellarg($remoteRef), $projectRoot);
|
$behindRaw = adminRunShellCommand('git rev-list --count HEAD..origin/' . escapeshellarg($branch), $projectRoot);
|
||||||
$aheadRaw = adminRunShellCommand('git rev-list --count ' . escapeshellarg($remoteRef) . '..HEAD', $projectRoot);
|
$aheadRaw = adminRunShellCommand('git rev-list --count origin/' . escapeshellarg($branch) . '..HEAD', $projectRoot);
|
||||||
|
|
||||||
if ($local['code'] !== 0 || $remote['code'] !== 0 || $behindRaw['code'] !== 0 || $aheadRaw['code'] !== 0) {
|
if ($local['code'] !== 0 || $remote['code'] !== 0 || $behindRaw['code'] !== 0 || $aheadRaw['code'] !== 0) {
|
||||||
throw new RuntimeException('Не удалось определить состояние ветки');
|
throw new RuntimeException('Не удалось определить состояние ветки');
|
||||||
|
|
@ -41,7 +36,6 @@ function adminCheckForUpdates(string $projectRoot, string $branch, string $remot
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'remote_name' => $remoteName,
|
|
||||||
'branch' => $branch,
|
'branch' => $branch,
|
||||||
'local_ref' => trim($local['output']),
|
'local_ref' => trim($local['output']),
|
||||||
'remote_ref' => trim($remote['output']),
|
'remote_ref' => trim($remote['output']),
|
||||||
|
|
@ -51,19 +45,15 @@ function adminCheckForUpdates(string $projectRoot, string $branch, string $remot
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function adminRunDeployScript(string $projectRoot, string $branch, string $scriptPath, string $phpBin, string $remoteName = 'origin', string $remoteUrl = ''): array
|
function adminRunDeployScript(string $projectRoot, string $branch, string $scriptPath, string $phpBin): array
|
||||||
{
|
{
|
||||||
if (!is_file($scriptPath)) {
|
if (!is_file($scriptPath)) {
|
||||||
throw new RuntimeException('Скрипт деплоя не найден: ' . $scriptPath);
|
throw new RuntimeException('Скрипт деплоя не найден: ' . $scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
$remoteName = adminNormalizeRemoteName($remoteName);
|
|
||||||
|
|
||||||
$run = adminRunShellCommand('bash ' . escapeshellarg($scriptPath), $projectRoot, [
|
$run = adminRunShellCommand('bash ' . escapeshellarg($scriptPath), $projectRoot, [
|
||||||
'BRANCH' => $branch,
|
'BRANCH' => $branch,
|
||||||
'PHP_BIN' => $phpBin,
|
'PHP_BIN' => $phpBin,
|
||||||
'REMOTE_NAME' => $remoteName,
|
|
||||||
'REMOTE_URL' => $remoteUrl,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -73,50 +63,6 @@ function adminRunDeployScript(string $projectRoot, string $branch, string $scrip
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function adminEnsureRemote(string $projectRoot, string $remoteName, string $remoteUrl): void
|
|
||||||
{
|
|
||||||
$getRemote = adminRunShellCommand('git remote get-url ' . escapeshellarg($remoteName), $projectRoot);
|
|
||||||
if ($getRemote['code'] !== 0) {
|
|
||||||
if ($remoteUrl === '') {
|
|
||||||
throw new RuntimeException('Remote ' . $remoteName . ' не найден');
|
|
||||||
}
|
|
||||||
|
|
||||||
$add = adminRunShellCommand('git remote add ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($remoteUrl), $projectRoot);
|
|
||||||
if ($add['code'] !== 0) {
|
|
||||||
throw new RuntimeException('Не удалось добавить remote ' . $remoteName . ': ' . adminTailOutput($add['output']));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($remoteUrl === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentUrl = trim($getRemote['output']);
|
|
||||||
if ($currentUrl === $remoteUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$set = adminRunShellCommand('git remote set-url ' . escapeshellarg($remoteName) . ' ' . escapeshellarg($remoteUrl), $projectRoot);
|
|
||||||
if ($set['code'] !== 0) {
|
|
||||||
throw new RuntimeException('Не удалось обновить remote ' . $remoteName . ': ' . adminTailOutput($set['output']));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function adminNormalizeRemoteName(string $remoteName): string
|
|
||||||
{
|
|
||||||
$remoteName = trim($remoteName);
|
|
||||||
if ($remoteName === '') {
|
|
||||||
return 'origin';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preg_match('/^[A-Za-z0-9._-]+$/', $remoteName)) {
|
|
||||||
throw new RuntimeException('Некорректное имя remote');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $remoteName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function adminRunShellCommand(string $command, string $cwd, array $env = []): array
|
function adminRunShellCommand(string $command, string $cwd, array $env = []): array
|
||||||
{
|
{
|
||||||
$envPrefix = '';
|
$envPrefix = '';
|
||||||
|
|
|
||||||
|
|
@ -144,10 +144,8 @@ function adminHandlePostAction(string $action, bool $isAjax, string $projectRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'check_updates': {
|
case 'check_updates': {
|
||||||
$remoteName = (string)($deployOptions['remote_name'] ?? 'origin');
|
|
||||||
$remoteUrl = (string)($deployOptions['remote_url'] ?? '');
|
|
||||||
$branch = (string)($deployOptions['branch'] ?? 'main');
|
$branch = (string)($deployOptions['branch'] ?? 'main');
|
||||||
$deployStatus = adminCheckForUpdates($projectRoot, $branch, $remoteName, $remoteUrl);
|
$deployStatus = adminCheckForUpdates($projectRoot, $branch);
|
||||||
$state = (string)($deployStatus['state'] ?? '');
|
$state = (string)($deployStatus['state'] ?? '');
|
||||||
|
|
||||||
if ($state === 'update_available') {
|
if ($state === 'update_available') {
|
||||||
|
|
@ -164,13 +162,11 @@ function adminHandlePostAction(string $action, bool $isAjax, string $projectRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'deploy_updates': {
|
case 'deploy_updates': {
|
||||||
$remoteName = (string)($deployOptions['remote_name'] ?? 'origin');
|
|
||||||
$remoteUrl = (string)($deployOptions['remote_url'] ?? '');
|
|
||||||
$branch = (string)($deployOptions['branch'] ?? 'main');
|
$branch = (string)($deployOptions['branch'] ?? 'main');
|
||||||
$scriptPath = (string)($deployOptions['script'] ?? ($projectRoot . '/scripts/deploy.sh'));
|
$scriptPath = (string)($deployOptions['script'] ?? ($projectRoot . '/scripts/deploy.sh'));
|
||||||
$phpBin = (string)($deployOptions['php_bin'] ?? 'php');
|
$phpBin = (string)($deployOptions['php_bin'] ?? 'php');
|
||||||
|
|
||||||
$deployStatus = adminCheckForUpdates($projectRoot, $branch, $remoteName, $remoteUrl);
|
$deployStatus = adminCheckForUpdates($projectRoot, $branch);
|
||||||
if (!(bool)($deployStatus['can_deploy'] ?? false)) {
|
if (!(bool)($deployStatus['can_deploy'] ?? false)) {
|
||||||
$state = (string)($deployStatus['state'] ?? '');
|
$state = (string)($deployStatus['state'] ?? '');
|
||||||
if ($state === 'up_to_date') {
|
if ($state === 'up_to_date') {
|
||||||
|
|
@ -186,13 +182,13 @@ function adminHandlePostAction(string $action, bool $isAjax, string $projectRoot
|
||||||
throw new RuntimeException('Нельзя применить обновление в текущем состоянии ветки.');
|
throw new RuntimeException('Нельзя применить обновление в текущем состоянии ветки.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployResult = adminRunDeployScript($projectRoot, $branch, $scriptPath, $phpBin, $remoteName, $remoteUrl);
|
$deployResult = adminRunDeployScript($projectRoot, $branch, $scriptPath, $phpBin);
|
||||||
$deployOutput = (string)($deployResult['output'] ?? '');
|
$deployOutput = (string)($deployResult['output'] ?? '');
|
||||||
if (!(bool)($deployResult['ok'] ?? false)) {
|
if (!(bool)($deployResult['ok'] ?? false)) {
|
||||||
throw new RuntimeException('Деплой завершился с ошибкой: ' . ($deployOutput !== '' ? $deployOutput : ('код ' . (int)($deployResult['code'] ?? 1))));
|
throw new RuntimeException('Деплой завершился с ошибкой: ' . ($deployOutput !== '' ? $deployOutput : ('код ' . (int)($deployResult['code'] ?? 1))));
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployStatus = adminCheckForUpdates($projectRoot, $branch, $remoteName, $remoteUrl);
|
$deployStatus = adminCheckForUpdates($projectRoot, $branch);
|
||||||
$message = 'Обновление выполнено.';
|
$message = 'Обновление выполнено.';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -360,31 +360,10 @@ function commentsByPhoto(int $photoId): array
|
||||||
return $st->fetchAll();
|
return $st->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentAdd(int $photoId, int $userId, string $text): array
|
function commentAdd(int $photoId, int $userId, string $text): void
|
||||||
{
|
{
|
||||||
$st = db()->prepare('INSERT INTO photo_comments(photo_id, user_id, comment_text) VALUES (:p,:u,:t)');
|
$st = db()->prepare('INSERT INTO photo_comments(photo_id, user_id, comment_text) VALUES (:p,:u,:t)');
|
||||||
$st->execute(['p' => $photoId, 'u' => $userId, 't' => $text]);
|
$st->execute(['p' => $photoId, 'u' => $userId, 't' => $text]);
|
||||||
|
|
||||||
$commentId = (int)db()->lastInsertId();
|
|
||||||
$detail = db()->prepare('SELECT c.*, u.display_name
|
|
||||||
FROM photo_comments c
|
|
||||||
LEFT JOIN comment_users u ON u.id=c.user_id
|
|
||||||
WHERE c.id=:id');
|
|
||||||
$detail->execute(['id' => $commentId]);
|
|
||||||
$row = $detail->fetch();
|
|
||||||
|
|
||||||
if (!$row) {
|
|
||||||
return [
|
|
||||||
'id' => $commentId,
|
|
||||||
'photo_id' => $photoId,
|
|
||||||
'user_id' => $userId,
|
|
||||||
'comment_text' => $text,
|
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
|
||||||
'display_name' => 'Пользователь',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentDelete(int $id): void
|
function commentDelete(int $id): void
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,10 @@ set -euo pipefail
|
||||||
# bash scripts/deploy.sh
|
# bash scripts/deploy.sh
|
||||||
# Optional env:
|
# Optional env:
|
||||||
# APP_DIR=/home/USER/www/photo-gallery
|
# APP_DIR=/home/USER/www/photo-gallery
|
||||||
# REMOTE_NAME=origin
|
|
||||||
# REMOTE_URL=git@github.com:wrkandreev/reframe.git
|
|
||||||
# BRANCH=main
|
# BRANCH=main
|
||||||
# PHP_BIN=php
|
# PHP_BIN=php
|
||||||
|
|
||||||
APP_DIR="${APP_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
APP_DIR="${APP_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||||
REMOTE_NAME="${REMOTE_NAME:-origin}"
|
|
||||||
REMOTE_URL="${REMOTE_URL:-}"
|
|
||||||
BRANCH="${BRANCH:-main}"
|
BRANCH="${BRANCH:-main}"
|
||||||
PHP_BIN="${PHP_BIN:-php}"
|
PHP_BIN="${PHP_BIN:-php}"
|
||||||
|
|
||||||
|
|
@ -45,16 +41,8 @@ if [ "$current_branch" != "$BRANCH" ]; then
|
||||||
git checkout "$BRANCH"
|
git checkout "$BRANCH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$REMOTE_URL" ]; then
|
git fetch --all --prune
|
||||||
if git remote get-url "$REMOTE_NAME" >/dev/null 2>&1; then
|
git reset --hard "origin/$BRANCH"
|
||||||
git remote set-url "$REMOTE_NAME" "$REMOTE_URL"
|
|
||||||
else
|
|
||||||
git remote add "$REMOTE_NAME" "$REMOTE_URL"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
git fetch "$REMOTE_NAME" "$BRANCH" --prune
|
|
||||||
git reset --hard "$REMOTE_NAME/$BRANCH"
|
|
||||||
|
|
||||||
# Run DB migrations required by current code
|
# Run DB migrations required by current code
|
||||||
"$PHP_BIN" scripts/migrate.php
|
"$PHP_BIN" scripts/migrate.php
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user