Migrate frontend to shadcn/ui + add Gitea branch panel in endpoint editor
shadcn/ui migration: - Installed shadcn components: Button, Input, Label, Card, Badge, Checkbox, Switch, Tabs, Dialog, Select, Tooltip, Toaster (sonner) - Migrated all pages: Login, Dashboard, Settings, Endpoints, EndpointEditor, Folders, ApiKeys, Logs - Replaced react-hot-toast with sonner - Added CSS variables for theming - Updated tailwind.config.js for shadcn Gitea branch panel in EndpointEditor: - GiteaBranchPanel component: branch selector, save to branch, compare with main, create PR, merge, commit history - New backend endpoints: getEndpointFileContent, commitEndpoint, getEndpointCommits, getEndpointFromBranch, commitEndpointToBranch - Frontend giteaApi: extended with endpoint-specific methods Fixes: - Fixed sync-all PostgreSQL-compatible UPDATE LIMIT - .gitignore: added tmp/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,42 @@ export const getEndpointInfo = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getEndpointFileContent = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { branch } = req.query as { branch: string };
|
||||
if (!branch) return res.status(400).json({ error: 'branch query param required' });
|
||||
const content = await giteaService.getEndpointFromBranch(id, branch);
|
||||
if (!content) return res.status(404).json({ error: 'Not found' });
|
||||
res.json(content);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const commitEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { branch, message, changes } = req.body;
|
||||
if (!branch || !message) return res.status(400).json({ error: 'branch and message required' });
|
||||
const result = await giteaService.commitEndpointToBranch(id, branch, message, changes || {});
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const getEndpointCommits = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { branch } = req.query as { branch?: string };
|
||||
const commits = await giteaService.getEndpointCommitHistory(id, branch);
|
||||
res.json(commits);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const createPR = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { title, head, base, body } = req.body;
|
||||
|
||||
@@ -12,6 +12,9 @@ import {
|
||||
compareBranches,
|
||||
getCommitHistory,
|
||||
getEndpointInfo,
|
||||
getEndpointFileContent,
|
||||
commitEndpoint,
|
||||
getEndpointCommits,
|
||||
createPR,
|
||||
mergePR,
|
||||
} from '../controllers/giteaController';
|
||||
@@ -40,6 +43,9 @@ router.get('/commits', getCommitHistory);
|
||||
|
||||
// Endpoint-specific
|
||||
router.get('/endpoints/:id/info', getEndpointInfo);
|
||||
router.get('/endpoints/:id/file-content', getEndpointFileContent);
|
||||
router.post('/endpoints/:id/commit', commitEndpoint);
|
||||
router.get('/endpoints/:id/commits', getEndpointCommits);
|
||||
|
||||
// Pull Requests
|
||||
router.post('/pulls', createPR);
|
||||
|
||||
@@ -419,6 +419,100 @@ class GiteaService {
|
||||
return { synced, errors };
|
||||
}
|
||||
|
||||
// --- Endpoint branch operations ---
|
||||
|
||||
private async getEndpointRepoPath(endpointId: string): Promise<{ basePath: string; endpoint: any } | null> {
|
||||
const result = await mainPool.query(
|
||||
`SELECT e.*, f.name as folder_name, d.name as database_name
|
||||
FROM endpoints e
|
||||
LEFT JOIN folders f ON e.folder_id = f.id
|
||||
LEFT JOIN databases d ON e.database_id = d.id
|
||||
WHERE e.id = $1`,
|
||||
[endpointId]
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
|
||||
const ep = result.rows[0];
|
||||
const folder = ep.folder_name ? this.sanitize(ep.folder_name) : '_root';
|
||||
const basePath = `endpoints/${folder}/${this.sanitize(ep.name)}`;
|
||||
return { basePath, endpoint: ep };
|
||||
}
|
||||
|
||||
async getEndpointFromBranch(endpointId: string, branch: string): Promise<any> {
|
||||
if (!await this.isEnabled()) return null;
|
||||
const cfg = await this.getConfig();
|
||||
if (!cfg) return null;
|
||||
|
||||
const info = await this.getEndpointRepoPath(endpointId);
|
||||
if (!info) return null;
|
||||
|
||||
const { basePath } = info;
|
||||
|
||||
const metaData = await this.api('GET', `/repos/${cfg.owner}/${cfg.repo}/contents/${basePath}/endpoint.json?ref=${encodeURIComponent(branch)}`);
|
||||
let metadata = null;
|
||||
if (metaData?.content) {
|
||||
metadata = JSON.parse(Buffer.from(metaData.content, 'base64').toString('utf-8'));
|
||||
}
|
||||
|
||||
let query = null;
|
||||
const sqlData = await this.api('GET', `/repos/${cfg.owner}/${cfg.repo}/contents/${basePath}/query.sql?ref=${encodeURIComponent(branch)}`);
|
||||
if (sqlData?.content) {
|
||||
query = Buffer.from(sqlData.content, 'base64').toString('utf-8');
|
||||
}
|
||||
|
||||
let script = null;
|
||||
for (const ext of ['js', 'py']) {
|
||||
const scriptData = await this.api('GET', `/repos/${cfg.owner}/${cfg.repo}/contents/${basePath}/main.${ext}?ref=${encodeURIComponent(branch)}`);
|
||||
if (scriptData?.content) {
|
||||
script = Buffer.from(scriptData.content, 'base64').toString('utf-8');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { metadata, query, script, branch, repo_path: basePath };
|
||||
}
|
||||
|
||||
async commitEndpointToBranch(
|
||||
endpointId: string,
|
||||
branch: string,
|
||||
message: string,
|
||||
changes: { query?: string; script?: string; metadata?: any }
|
||||
): Promise<{ sha: string | null }> {
|
||||
if (!await this.isEnabled()) throw new Error('Gitea not enabled');
|
||||
|
||||
const info = await this.getEndpointRepoPath(endpointId);
|
||||
if (!info) throw new Error('Endpoint not found');
|
||||
|
||||
const { basePath } = info;
|
||||
let lastSha: string | null = null;
|
||||
|
||||
if (changes.metadata) {
|
||||
lastSha = await this.commitFile(
|
||||
`${basePath}/endpoint.json`,
|
||||
JSON.stringify(changes.metadata, null, 2),
|
||||
message,
|
||||
branch
|
||||
);
|
||||
}
|
||||
|
||||
if (changes.query !== undefined) {
|
||||
lastSha = await this.commitFile(`${basePath}/query.sql`, changes.query, message, branch);
|
||||
}
|
||||
|
||||
if (changes.script !== undefined) {
|
||||
const ext = changes.metadata?.script_language === 'python' ? 'py' : 'js';
|
||||
lastSha = await this.commitFile(`${basePath}/main.${ext}`, changes.script, message, branch);
|
||||
}
|
||||
|
||||
return { sha: lastSha };
|
||||
}
|
||||
|
||||
async getEndpointCommitHistory(endpointId: string, branch?: string): Promise<GiteaCommit[]> {
|
||||
const info = await this.getEndpointRepoPath(endpointId);
|
||||
if (!info) return [];
|
||||
return this.getCommitHistory(info.basePath, branch);
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
async getEndpointGiteaInfo(endpointId: string): Promise<any> {
|
||||
|
||||
Reference in New Issue
Block a user