#feature: add config page

This commit is contained in:
2025-12-25 18:39:23 +08:00
parent 3bcbd0661f
commit cd11a856bb
8 changed files with 704 additions and 119 deletions

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Config;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class ConfigController extends Controller
{
public function index(): JsonResponse
{
$configs = Config::query()
->orderBy('key')
->get();
return response()->json([
'success' => true,
'data' => [
'configs' => $configs,
],
]);
}
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'key' => ['required', 'string', 'max:255', 'unique:configs,key'],
'value' => ['nullable', 'string'],
'description' => ['nullable', 'string', 'max:255'],
]);
$config = Config::query()->create([
'key' => $data['key'],
'value' => $this->decodeValue($data['value'] ?? null),
'description' => $data['description'] ?? null,
]);
return response()->json([
'success' => true,
'data' => [
'config' => $config,
],
]);
}
public function update(Request $request, Config $config): JsonResponse
{
$data = $request->validate([
'key' => [
'required',
'string',
'max:255',
Rule::unique('configs', 'key')->ignore($config->id),
],
'value' => ['nullable', 'string'],
'description' => ['nullable', 'string', 'max:255'],
]);
$config->update([
'key' => $data['key'],
'value' => $this->decodeValue($data['value'] ?? null),
'description' => $data['description'] ?? null,
]);
return response()->json([
'success' => true,
'data' => [
'config' => $config->refresh(),
],
]);
}
public function destroy(Config $config): JsonResponse
{
$config->delete();
return response()->json([
'success' => true,
]);
}
private function decodeValue(?string $raw): mixed
{
if ($raw === null) {
return null;
}
$trimmed = trim($raw);
if ($trimmed === '') {
return null;
}
$decoded = json_decode($trimmed, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw ValidationException::withMessages([
'value' => 'value 必须是合法 JSON',
]);
}
return $decoded;
}
}

View File

@@ -163,79 +163,57 @@ class GitMonitorService
private function inspectRepository(string $repoKey, array $repoConfig, string $branch): array
{
$path = $this->resolveProjectPath($repoKey, $repoConfig);
$restoreBranch = null;
if (!is_dir($path) || !is_dir($path . DIRECTORY_SEPARATOR . '.git')) {
throw new \RuntimeException("Project path {$path} is not a valid git repository");
}
try {
$restoreBranch = $this->synchronizeRepository($path, $branch);
$head = $this->runGit($path, ['git', 'rev-parse', 'HEAD']);
$this->synchronizeRepository($path, $branch);
$remoteBranch = 'origin/' . $branch;
$head = $this->runGit($path, ['git', 'rev-parse', $remoteBranch]);
$lastChecked = $this->configService->getNested(self::LAST_CHECKED_KEY, $repoKey);
$commits = $this->collectCommits($path, $branch, $lastChecked);
$lastChecked = $this->configService->getNested(self::LAST_CHECKED_KEY, $repoKey);
$commits = $this->collectCommits($path, $remoteBranch, $lastChecked);
$issues = [
'develop_merges' => [],
'missing_functions' => [],
];
$issues = [
'develop_merges' => [],
'missing_functions' => [],
];
foreach ($commits as $commit) {
if ($this->isDevelopMerge($path, $commit)) {
$issues['develop_merges'][] = $this->getCommitMetadata($path, $commit);
}
$missingFunctions = $this->detectMissingFunctions($path, $commit);
if (!empty($missingFunctions)) {
$issues['missing_functions'][] = [
'commit' => $this->getCommitMetadata($path, $commit),
'details' => $missingFunctions,
];
}
foreach ($commits as $commit) {
if ($this->isDevelopMerge($path, $commit)) {
$issues['develop_merges'][] = $this->getCommitMetadata($path, $commit);
}
$this->updateLastChecked($repoKey, $head);
return [
'repository' => $repoKey,
'display' => $repoConfig['display'] ?? ucfirst($repoKey),
'branch' => $branch,
'path' => $path,
'head' => $head,
'commits_scanned' => count($commits),
'issues' => $issues,
];
} finally {
if ($restoreBranch) {
try {
$this->runGit($path, ['git', 'checkout', $restoreBranch]);
} catch (\Throwable $e) {
Log::warning('Failed to restore branch', [
'repository' => $repoKey,
'branch' => $restoreBranch,
'error' => $e->getMessage(),
]);
}
$missingFunctions = $this->detectMissingFunctions($path, $commit);
if (!empty($missingFunctions)) {
$issues['missing_functions'][] = [
'commit' => $this->getCommitMetadata($path, $commit),
'details' => $missingFunctions,
];
}
}
$this->updateLastChecked($repoKey, $head);
return [
'repository' => $repoKey,
'display' => $repoConfig['display'] ?? ucfirst($repoKey),
'branch' => $branch,
'path' => $path,
'head' => $head,
'commits_scanned' => count($commits),
'issues' => $issues,
];
}
private function synchronizeRepository(string $path, string $branch): ?string
private function synchronizeRepository(string $path, string $branch): void
{
$currentBranch = trim($this->runGit($path, ['git', 'rev-parse', '--abbrev-ref', 'HEAD']));
$restoreBranch = $currentBranch !== $branch && $currentBranch !== 'HEAD'
? $currentBranch
: null;
$this->runGit($path, ['git', 'fetch', 'origin']);
$this->runGit($path, ['git', 'fetch', 'origin', $branch]);
$this->runGit($path, ['git', 'fetch', 'origin', self::DEVELOP_BRANCH]);
$this->checkoutBranch($path, $branch);
$this->runGit($path, ['git', 'pull', '--ff-only', 'origin', $branch]);
return $restoreBranch;
$this->runGit($path, ['git', 'show-ref', '--verify', "refs/remotes/origin/{$branch}"]);
}
private function checkoutBranch(string $path, string $branch): void
@@ -468,8 +446,41 @@ class GitMonitorService
{
$projects = $this->configService->get('workspace.repositories');
if (!is_array($projects) || empty($projects)) {
throw new \RuntimeException('configs 表未设置 workspace.repositories。');
if ($projects === null) {
$fallback = $this->buildFallbackProjects(config('git-monitor.enabled_projects', []));
if (!empty($fallback)) {
Log::warning('configs 表未设置 workspace.repositories已使用 git-monitor.enabled_projects。');
}
return $fallback;
}
if (!is_array($projects)) {
Log::warning('configs 表 workspace.repositories 配置格式不正确,已降级使用 git-monitor.enabled_projects。');
return $this->buildFallbackProjects(config('git-monitor.enabled_projects', []));
}
return $projects;
}
/**
* @return array<string, array<string, mixed>>
*/
private function buildFallbackProjects(array $enabled): array
{
$projects = [];
$jiraKeyMap = [
'portal-be' => 'WP',
'agent-be' => 'AM',
];
foreach ($enabled as $repoKey) {
if (!is_string($repoKey) || $repoKey === '') {
continue;
}
$projects[$repoKey] = [
'jira_project' => $jiraKeyMap[$repoKey] ?? $repoKey,
];
}
return $projects;