diff --git a/pom.xml b/pom.xml
index 6507b1a..f4fee16 100644
--- a/pom.xml
+++ b/pom.xml
@@ -186,6 +186,13 @@
7.0.0.202409031743-r
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit.archive
+ 7.0.0.202409031743-r
+
+
org.springframework.boot
spring-boot-starter-freemarker
diff --git a/src/main/java/net/geedge/asw/common/util/Constants.java b/src/main/java/net/geedge/asw/common/util/Constants.java
index 80a11cc..1ab1ef2 100644
--- a/src/main/java/net/geedge/asw/common/util/Constants.java
+++ b/src/main/java/net/geedge/asw/common/util/Constants.java
@@ -146,7 +146,9 @@ public class Constants {
PLAYBOOK("playbook"),
- JOB("job");
+ JOB("job"),
+
+ RELEASE("release");
private String type;
diff --git a/src/main/java/net/geedge/asw/common/util/RCode.java b/src/main/java/net/geedge/asw/common/util/RCode.java
index d43dd33..e25bc3d 100644
--- a/src/main/java/net/geedge/asw/common/util/RCode.java
+++ b/src/main/java/net/geedge/asw/common/util/RCode.java
@@ -67,6 +67,9 @@ public enum RCode {
GIT_BINARY_CONFLICT_ERROR(203004, "Binary file conflict found; resolve conflicts in binary files manually"),
GIT_MERGE_NOT_SUPPORTED(203005, "Cannot merge in the {0} state"),
GIT_MERGE_TARGET_BRANCH_NOT_EXIST(203006, "The target branch {0} does not exist."),
+ GIT_TAG_ALREADY_EXISTS(203007, "Tag {0} already exists"),
+ GIT_TAG_NOT_FOUND(203008, "Tag {0} not found"),
+ GIT_TAG_ALREADY_IN_USE(203009,"Tag is already in use. Choose another tag."),
// Runner
diff --git a/src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java b/src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java
new file mode 100644
index 0000000..4184e95
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/controller/ApplicationReleaseController.java
@@ -0,0 +1,78 @@
+package net.geedge.asw.module.app.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.servlet.http.HttpServletResponse;
+import net.geedge.asw.common.util.*;
+import net.geedge.asw.module.app.entity.ApplicationReleaseEntity;
+import net.geedge.asw.module.app.service.IApplicationReleaseService;
+import net.geedge.asw.module.workspace.entity.WorkspaceEntity;
+import net.geedge.asw.module.workspace.service.IWorkspaceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/workspace")
+public class ApplicationReleaseController {
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Autowired
+ private IApplicationReleaseService releaseService;
+
+ @GetMapping("/{workspaceId}/release/{id}")
+ public R detail(@PathVariable("workspaceId") String workspaceId, @PathVariable("id") String id) {
+ ApplicationReleaseEntity record = releaseService.queryInfo(id);
+ return R.ok().putData("record", record);
+ }
+
+ @GetMapping("/{workspaceId}/release")
+ public R list(@PathVariable("workspaceId") String workspaceId, @RequestParam Map params) {
+ // workspaceId
+ params = T.MapUtil.defaultIfEmpty(params, new HashMap<>());
+ params.put("workspaceId", workspaceId);
+
+ Page page = releaseService.queryList(params);
+ return R.ok(page);
+ }
+
+ @PostMapping("/{workspaceId}/release")
+ public R add(@PathVariable("workspaceId") String workspaceId, @RequestBody Map requestBody) {
+ String name = T.MapUtil.getStr(requestBody, "name", "");
+ String tagName = T.MapUtil.getStr(requestBody, "tagName", "");
+ String description = T.MapUtil.getStr(requestBody, "description", "");
+ if (T.StrUtil.hasEmpty(name, tagName)) {
+ throw new ASWException(RCode.PARAM_CANNOT_EMPTY);
+ }
+
+ ApplicationReleaseEntity record = releaseService.saveRelease(workspaceId, name, tagName, description);
+ return R.ok().putData("record", record);
+ }
+
+ @DeleteMapping("/{workspaceId}/release/{id}")
+ public R delete(@PathVariable("workspaceId") String workspaceId, @PathVariable("id") String id) {
+ releaseService.removeRelease(id);
+ return R.ok();
+ }
+
+ @GetMapping("/{workspaceId}/release/{id}/file")
+ public void download(@PathVariable("workspaceId") String workspaceId,
+ @PathVariable("id") String id,
+ HttpServletResponse response) throws IOException {
+ ApplicationReleaseEntity release = releaseService.getById(id);
+ T.VerifyUtil.is(release).notNull(RCode.SYS_RECORD_NOT_FOUND);
+
+ WorkspaceEntity workspace = workspaceService.getById(workspaceId);
+ T.VerifyUtil.is(workspace).notNull(RCode.SYS_RECORD_NOT_FOUND);
+
+ String fileName = T.StrUtil.concat(true, workspace.getName(), "-", release.getTagName(), ".zip");
+ byte[] fileBytes = T.FileUtil.readBytes(T.FileUtil.file(release.getPath()));
+ ResponseUtil.downloadFile(response, MediaType.APPLICATION_OCTET_STREAM_VALUE, fileName, fileBytes);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/app/controller/TagController.java b/src/main/java/net/geedge/asw/module/app/controller/TagController.java
new file mode 100644
index 0000000..83ac810
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/app/controller/TagController.java
@@ -0,0 +1,52 @@
+package net.geedge.asw.module.app.controller;
+
+import net.geedge.asw.common.util.ASWException;
+import net.geedge.asw.common.util.R;
+import net.geedge.asw.common.util.RCode;
+import net.geedge.asw.common.util.T;
+import net.geedge.asw.module.app.service.ITagService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/workspace")
+public class TagController {
+
+ @Autowired
+ private ITagService tagService;
+
+ @GetMapping("/{workspaceId}/tag/{tagName}")
+ public R infoTag(@PathVariable("workspaceId") String workspaceId, @PathVariable("tagName") String name) {
+ Map