@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weibaohui/dsh-git-server",
3
- "version": "0.1.3",
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",
@@ -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
- ctx.errorStatus(422, 'Migration is not supported by this build.');
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' ? await git.entrySize(repoDir, e.sha) : 0,
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
- cols['assignee_id'] = assignee?.id ?? null;
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) => {