@weibaohui/dsh-git-server 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -2
- package/client/bundle.js +657 -75
- package/client/index.js +657 -75
- package/package.json +1 -1
- package/server/dist/api/v1.js +71 -3
- package/server/dist/dshapi.js +558 -66
- package/server/dist/gitx/git.js +39 -3
- package/server/dist/gitx/service.js +4 -3
- package/server/dist/hook.js +31 -2
- package/server/dist/notify.js +55 -0
- package/src/index.js +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weibaohui/dsh-git-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "dsh 插件 · Git 服务器:独立端口跑完整 Git 服务(HTTP clone/push、网页端、issue/PR/wiki/发版/标签/里程碑/webhook),可复用 user-management 账号,局域网可 clone,设置页一键启停、崩溃自动拉起。",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/server/dist/api/v1.js
CHANGED
|
@@ -146,6 +146,9 @@ function toIssueComment(cm, issue, repo) {
|
|
|
146
146
|
html_url: `${repo.HTMLURL()}/issues/${issue.index}#issuecomment-${cm.id}`,
|
|
147
147
|
user: poster ? toUser(poster, true) : null,
|
|
148
148
|
body: cm.content ?? '',
|
|
149
|
+
// dsh 扩展字段:type=2 表示「提交关单」事件(commit_sha 指向关单提交)
|
|
150
|
+
type: cm.type ?? 0,
|
|
151
|
+
commit_sha: cm.commit_sha ?? '',
|
|
149
152
|
created_at: new Date((cm.created_unix ?? 0) * 1000).toISOString(),
|
|
150
153
|
updated_at: new Date((cm.updated_unix ?? 0) * 1000).toISOString(),
|
|
151
154
|
};
|
|
@@ -587,7 +590,61 @@ export function registerAPIRoutes(m) {
|
|
|
587
590
|
await fn(ctx);
|
|
588
591
|
});
|
|
589
592
|
m.post('/api/v1/repos/migrate', reqTokenWrap(async (ctx) => {
|
|
590
|
-
|
|
593
|
+
const body = (await ctx.c.form());
|
|
594
|
+
let cloneAddr = String(body.clone_addr ?? '').trim();
|
|
595
|
+
const repoName = String(body.repo_name ?? body.name ?? '').trim();
|
|
596
|
+
if (!/^https?:\/\//.test(cloneAddr)) {
|
|
597
|
+
ctx.errorStatus(422, 'only http(s) clone addresses are supported');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
// 可选的 basic 凭据注入(gogs MigrateRepoForm.auth_username/auth_password)
|
|
601
|
+
const authUser = String(body.auth_username ?? '');
|
|
602
|
+
if (authUser && !cloneAddr.includes('@')) {
|
|
603
|
+
cloneAddr = cloneAddr.replace(/^(https?:\/\/)/, `$1${encodeURIComponent(authUser)}:${encodeURIComponent(String(body.auth_password ?? ''))}@`);
|
|
604
|
+
}
|
|
605
|
+
if (!repoName || !/^[a-zA-Z0-9_.-]+$/.test(repoName) || repoName.length > 100) {
|
|
606
|
+
ctx.c.JSON(422, [{ fieldNames: ['repo_name'], classification: 'RequiredError', message: 'Required' }]);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const owner = ctx.user;
|
|
610
|
+
if (db.getRepoByName(owner.name, repoName)) {
|
|
611
|
+
ctx.errorStatus(422, 'The repository with the same name already exists.');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const isMirror = !!body.mirror;
|
|
615
|
+
const { createRepositoryRecord } = await import('../repox.js');
|
|
616
|
+
const repo = await createRepositoryRecord(ctx.user, owner, {
|
|
617
|
+
name: repoName,
|
|
618
|
+
description: String(body.description ?? ''),
|
|
619
|
+
private: conf.forcePrivate || !!body.private,
|
|
620
|
+
autoInit: false,
|
|
621
|
+
mirror: isMirror,
|
|
622
|
+
});
|
|
623
|
+
const dir = repo.RepoPath();
|
|
624
|
+
const fs = await import('node:fs');
|
|
625
|
+
const path = await import('node:path');
|
|
626
|
+
try {
|
|
627
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
628
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
629
|
+
await git.git(process.cwd(), 'clone', '--mirror', cloneAddr, dir);
|
|
630
|
+
const svc = await import('../gitx/service.js');
|
|
631
|
+
svc.createDelegateHooks(dir);
|
|
632
|
+
await git.updateServerInfo(dir);
|
|
633
|
+
const def = await git.getDefaultBranch(dir);
|
|
634
|
+
const { size } = await git.countObjects(dir);
|
|
635
|
+
db.updateRepoColumns(repo.id, { is_bare: 0, ...(def ? { default_branch: def } : {}), size });
|
|
636
|
+
if (isMirror) {
|
|
637
|
+
const now = Math.floor(Date.now() / 1000);
|
|
638
|
+
db.db().prepare('INSERT INTO mirror (repo_id, interval, enable_prune, updated_unix, next_update_unix) VALUES (?,?,0,?,?)').run(repo.id, 0, now, now);
|
|
639
|
+
}
|
|
640
|
+
ctx.c.JSON(201, toRepository(db.getRepoByID(repo.id), { admin: true, push: true, pull: true }));
|
|
641
|
+
}
|
|
642
|
+
catch (e) {
|
|
643
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
644
|
+
db.db().prepare('DELETE FROM repository WHERE id = ?').run(repo.id);
|
|
645
|
+
db.db().prepare('UPDATE user SET num_repos = MAX(num_repos - 1, 0) WHERE id = ?').run(owner.id);
|
|
646
|
+
ctx.errorStatus(500, 'clone failed: ' + String(e?.message ?? e).slice(0, 160));
|
|
647
|
+
}
|
|
591
648
|
}));
|
|
592
649
|
m.delete('/api/v1/repos/:username/:reponame', repoGroup(async (ctx) => {
|
|
593
650
|
if (ctx.repo.AccessMode < db.AccessMode.OWNER) {
|
|
@@ -1219,7 +1276,7 @@ function registerRepoSubRoutes(m, _wrap, reqTokenWrap, repoGroup, repoGroupNoTok
|
|
|
1219
1276
|
path: e.name,
|
|
1220
1277
|
mode: e.mode === '160000' ? '160000' : type === '040000' ? '040000' : e.mode,
|
|
1221
1278
|
type: e.type,
|
|
1222
|
-
size: e.type === 'blob' ?
|
|
1279
|
+
size: e.type === 'blob' ? e.size : 0,
|
|
1223
1280
|
sha: e.sha,
|
|
1224
1281
|
url: `${conf.externalURL}api/v1/repos/${repo.FullName()}/git/trees/${e.sha}`,
|
|
1225
1282
|
});
|
|
@@ -1374,6 +1431,8 @@ function registerRepoSubRoutes(m, _wrap, reqTokenWrap, repoGroup, repoGroupNoTok
|
|
|
1374
1431
|
const { issueAction, ActionType } = await import('../db/actions.js');
|
|
1375
1432
|
const issue = db.getIssueByID(issueID);
|
|
1376
1433
|
await issueAction(ActionType.CREATE_ISSUE, ctx.user, repo, issue);
|
|
1434
|
+
const { markIssueUnread } = await import('../notify.js');
|
|
1435
|
+
markIssueUnread(issue, ctx.UserID(), { mentionContent: String(body.body ?? ''), assigned: !!assigneeID });
|
|
1377
1436
|
ctx.c.JSON(201, toIssue(db.getIssueByID(issueID), repo));
|
|
1378
1437
|
}));
|
|
1379
1438
|
m.get('/api/v1/repos/:username/:reponame/issues/comments', issuesGroup(async (ctx) => {
|
|
@@ -1431,9 +1490,12 @@ function registerRepoSubRoutes(m, _wrap, reqTokenWrap, repoGroup, repoGroupNoTok
|
|
|
1431
1490
|
if (body.body !== undefined)
|
|
1432
1491
|
cols['content'] = String(body.body);
|
|
1433
1492
|
const isWriter = ctx.repo.AccessMode >= db.AccessMode.WRITE;
|
|
1493
|
+
let assigneeChanged = false;
|
|
1434
1494
|
if (isWriter && body.assignee !== undefined) {
|
|
1435
1495
|
const assignee = body.assignee ? db.getUserByUsername(String(body.assignee)) : null;
|
|
1436
|
-
|
|
1496
|
+
const nextID = assignee?.id ?? null;
|
|
1497
|
+
assigneeChanged = (nextID ?? 0) !== (issue.assignee_id ?? 0) && nextID !== null;
|
|
1498
|
+
cols['assignee_id'] = nextID;
|
|
1437
1499
|
}
|
|
1438
1500
|
if (isWriter && body.milestone !== undefined) {
|
|
1439
1501
|
cols['milestone_id'] = Number(body.milestone) || null;
|
|
@@ -1443,6 +1505,10 @@ function registerRepoSubRoutes(m, _wrap, reqTokenWrap, repoGroup, repoGroupNoTok
|
|
|
1443
1505
|
}
|
|
1444
1506
|
db.updateIssueColumns(issue.id, cols);
|
|
1445
1507
|
db.refreshIssueCounts(repo.id);
|
|
1508
|
+
if (assigneeChanged || body.body !== undefined || body.state !== undefined) {
|
|
1509
|
+
const { markIssueUnread } = await import('../notify.js');
|
|
1510
|
+
markIssueUnread(db.getIssueByID(issue.id), ctx.UserID(), { mentionContent: String(body.body ?? ''), assigned: assigneeChanged });
|
|
1511
|
+
}
|
|
1446
1512
|
ctx.c.JSON(201, toIssue(db.getIssueByID(issue.id), repo));
|
|
1447
1513
|
}));
|
|
1448
1514
|
m.get('/api/v1/repos/:username/:reponame/issues/:index/comments', issuesGroup(async (ctx) => {
|
|
@@ -1481,6 +1547,8 @@ function registerRepoSubRoutes(m, _wrap, reqTokenWrap, repoGroup, repoGroupNoTok
|
|
|
1481
1547
|
const info = db.db().prepare('INSERT INTO comment (type, poster_id, issue_id, content, created_unix, updated_unix) VALUES (0,?,?,?,?,?)').run(ctx.UserID(), issue.id, content, now, now);
|
|
1482
1548
|
db.db().prepare('UPDATE issue SET num_comments = num_comments + 1 WHERE id = ?').run(issue.id);
|
|
1483
1549
|
const cm = db.getCommentByID(Number(info.lastInsertRowid));
|
|
1550
|
+
const { markIssueUnread } = await import('../notify.js');
|
|
1551
|
+
markIssueUnread(issue, ctx.UserID(), { mentionContent: content });
|
|
1484
1552
|
ctx.c.JSON(201, toIssueComment(cm, issue, repo));
|
|
1485
1553
|
}));
|
|
1486
1554
|
m.delete('/api/v1/repos/:username/:reponame/issues/:index/comments/:id', issuesGroup(async (ctx) => {
|