Skip to content

Commit

Permalink
Finish the api for creating repository
Browse files Browse the repository at this point in the history
We finish the api for creating repository, and the test for it.

See #32.
  • Loading branch information
Kaiser-Yang committed Sep 20, 2024
1 parent 6467f70 commit 06ec4e2
Show file tree
Hide file tree
Showing 22 changed files with 388 additions and 50 deletions.
11 changes: 8 additions & 3 deletions README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
| `profiles` | `list` | `["dev"]` | 启动的配置类型。 |
| `deployLogLevel` | `string` | `"info"` | 部署脚本的日志级别。 |
| `skipTest` | `bool` | `true` | 是否跳过测试。 |
| `createGitUser` | `bool` | `true` | 是否创建 `git` 用户。 |
| `gitUserName` | `string` | `"git"` | 用于保存 `git` 仓库的用户名。 |
| `gitUserPassword` | `string` | `"git"` | 用于保存 `git` 仓库的用户密码。 |
| `gitRepositoryDirectory` | `string` | `"/home/git/repository"` | `git` 仓库存放目录。不要使用 `~`|
| `gitServerDomain` | `string` | `"localhost"` | 服务器域名。 |
| `gitRepositorySuffix` | `string` | `".git"` | `git` 仓库后缀。 |
| `deployWithDocker` | `bool` | `true` | 是否使用 `Docker` 进行部署。 |
| `dockerName` | `string` | `"gcs-backend"` | `Docker` 容器名称。 |
| `dockerImage` | `string` | `"ubuntu:latest"` | `Docker` 镜像。 |
| `dockerPortMapping` | `list` | `["8080:8080"]` | `Docker` 端口映射。 |
| `dockerWithGpu` | `bool` | `false` | `Docker` 是否使用 `GPU`|
| `dockerSrcPath` | `string` | `"/opt/gcs-back-end-src"` | `Docker` 中源码路径。源码会被拷贝到该路径进行编译。 |
| `repositoryDirectory` | `string` | `"/home/git/repositories"` | `git` 仓库存放目录。 |
| `serviceEnable` | `bool` | `true` | 是否启用 `systemd` 服务。 |
| `serviceName` | `string` | `"gcs"` | 服务名称。 |
| `serviceDescription` | `string` | `"Git server center back-end service"` | 服务描述。 |
Expand All @@ -25,7 +28,7 @@
| `serviceStartJavaCommand` | `string` | `"/usr/bin/java"` | 服务启动的 `Java` 命令。 |
| `serviceStartJavaArgs` | `list` | `["-jar"]` | 服务启动的 `Java` 参数。 |
| `serviceStartJarFile` | `string` | `"/opt/gcs/gcs.jar"` | 服务启动的 `Jar` 文件。脚本会将 `maven` 打包出来的文件拷贝到该位置。 |
| `serviceSuffix` | `string` | `"service"` | `systemd` 服务文件后缀。 |
| `serviceSuffix` | `string` | `".service"` | `systemd` 服务文件后缀。 |
| `serviceWorkingDirectory` | `string` | `"/opt/gcs"` | `systemd` 服务工作目录。 |
| `serviceRestartPolicy` | `string` | `"always"` | `systemd` 服务重启策略。 |
| `serviceRestartDelaySeconds` | `int` | `5` | `systemd` 服务重启延迟时间。 |
Expand All @@ -44,3 +47,5 @@
| `druidLoginUsername` | `string` | `"druid"` | `Druid` 登录用户名。 |
| `druidLoginPassword` | `string` | `"druid"` | `Druid` 登录密码。 |
| `frontEndUrl` | `string` | `"http://localhost:3000"` | 前端地址。 |
| `deleteGitUser` | `bool` | `true` | 清理时是否删除 `git` 用户。 |
| `deleteServiceUser` | `bool` | `true` | 清理时是否删除 `service` 用户。 |
4 changes: 3 additions & 1 deletion config_debug.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
],
"postgresqlUserName": "gcs_debug",
"postgresqlUserPassword": "gcs_debug",
"postgresqlDatabaseName": "gcs_debug"
"postgresqlDatabaseName": "gcs_debug",
"deleteGitUser": false,
"deleteServiceUser": false
}
13 changes: 9 additions & 4 deletions config_default.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
],
"deployLogLevel": "info",
"skipTest": true,
"createGitUser": true,
"gitUserName": "git",
"gitUserPassword": "git",
"gitRepositoryDirectory": "/home/git/repository",
"gitServerDomain": "localhost",
"gitRepositorySuffix": ".git",
"deployWithDocker": true,
"dockerName": "gcs-back-end",
"dockerImage": "ubuntu:20.04",
Expand All @@ -14,7 +18,6 @@
],
"dockerWithGpu": false,
"dockerSrcPath": "/opt/gcs-back-end-src",
"repositoryDirectory": "/home/git/repositories",
"serviceEnable": true,
"serviceName": "gcs",
"serviceDescription": "Git server center back-end service",
Expand All @@ -26,7 +29,7 @@
"-jar"
],
"serviceStartJarFile": "/opt/gcs/gcs.jar",
"serviceSuffix": "service",
"serviceSuffix": ".service",
"serviceWorkingDirectory": "/opt/gcs",
"serviceRestartPolicy": "always",
"serviceRestartDelaySeconds": 5,
Expand All @@ -48,5 +51,7 @@
"postgresqlPort": 5432,
"druidLoginUsername": "druid",
"druidLoginPassword": "druid",
"frontEndUrl": "http://localhost:3000"
"frontEndUrl": "http://localhost:3000",
"deleteGitUser": true,
"deleteServiceUser": true
}
47 changes: 38 additions & 9 deletions script/deploy_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import inspect

essential_packages = ['python-is-python3', 'postgresql postgresql-client',
'openjdk-17-jdk-headless', 'maven', 'systemd']
'openjdk-17-jdk-headless', 'maven', 'systemd', 'sudo']
sudo_cmd = os.popen('command -v sudo').read().strip()
apt_updated = False
message_tmp = '''\
Expand Down Expand Up @@ -90,7 +90,7 @@ def create_systemd_service(config):
[config.serviceStartJarFile])
wanted_by = parse_iterable_into_str(config.serviceWantedBy)
after = parse_iterable_into_str(config.serviceAfter)
service_full_path = f'{config.serviceSystemdDirectory}/{config.serviceName}.{config.serviceSuffix}'
service_full_path = f'{config.serviceSystemdDirectory}/{config.serviceName}{config.serviceSuffix}'
gcs_file_content = f"""\
[Unit]
Description={config.serviceDescription}
Expand Down Expand Up @@ -334,7 +334,8 @@ def create_or_update_user(username, password):
if username == None or username == "":
return
if os.system(f"cat /etc/passwd | grep -w -E '^{username}'") != 0:
command = f'{sudo_cmd} useradd {username}'
# use -m to create the home directory for user
command = f'{sudo_cmd} useradd -m {username}'
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
Expand All @@ -355,7 +356,11 @@ def create_or_update_user(username, password):

def write_other_config(config):
other_config_map = {
"frontEndUrl": "front-end.url"
"frontEndUrl": "front-end.url",
"gitServerDomain": "git.server.domain",
"gitUserName": "git.user.name",
"gitRepositoryDirectory": "git.repository.directory",
"gitRepositorySuffix": "git.repository.suffix",
}
try:
lines = None
Expand All @@ -374,6 +379,7 @@ def write_other_config(config):
except Exception as e:
command_checker(1, f"Error: {e}")


def deploy_on_ubuntu(config):
assert(config != None)
if config.inDocker:
Expand All @@ -393,9 +399,19 @@ def deploy_on_ubuntu(config):
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
create_or_update_user(config.gitUserName, config.gitUserPassword)
create_or_update_user(config.serviceUser, config.serviceUserPassword)
# let the service user can use git command as the git user without password
sudoers_entry = f"{config.serviceUser} ALL=(git) NOPASSWD: /usr/bin/git"
try:
res = subprocess.run(f"echo '{sudoers_entry}' | {sudo_cmd} tee /etc/sudoers.d/{config.serviceUser}", shell=True);
command_checker(res.returncode, f"Failed to create /etc/sudoers.d/{config.serviceUser}")
res = subprocess.run(f"{sudo_cmd} chmod 440 /etc/sudoers.d/{config.serviceUser}", shell=True)
command_checker(res.returncode, f"Failed to chmod 440 /etc/sudoers.d/{config.serviceUser}")
except subprocess.CalledProcessError as e:
command_checker(1, f"Error: {e}")

if config.deploy:
create_or_update_user(config.serviceUser, config.serviceUserPassword)
if not os.path.exists(os.path.dirname(config.serviceStartJarFile)):
command = f'{sudo_cmd} mkdir -p {os.path.dirname(config.serviceStartJarFile)}'
res = os.system(command)
Expand All @@ -411,6 +427,15 @@ def deploy_on_ubuntu(config):
deploy_with_systemd(config)


def delete_user(username):
if username == None or username == "":
return
if os.system(f"cat /etc/passwd | grep -w -E '^{username}'") == 0:
command = f'{sudo_cmd} userdel {username}'
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)

def clean(config):
if config.deployWithDocker:
res = os.system(f"docker stop {config.dockerName}")
Expand All @@ -426,8 +451,8 @@ def clean(config):
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
if os.path.exists(f'{config.serviceSystemdDirectory}/{config.serviceName}.{config.serviceSuffix}'):
command = f'''{sudo_cmd} rm -rf {config.serviceSystemdDirectory}/{config.serviceName}.{config.serviceSuffix} && \\
if os.path.exists(f'{config.serviceSystemdDirectory}/{config.serviceName}{config.serviceSuffix}'):
command = f'''{sudo_cmd} rm -rf {config.serviceSystemdDirectory}/{config.serviceName}{config.serviceSuffix} && \\
{sudo_cmd} systemctl daemon-reload'''
res = os.system(command)
message = message_tmp.format(command, res)
Expand All @@ -451,11 +476,15 @@ def clean(config):
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
if os.system(f"cat /etc/passwd | grep -w -E '^{config.serviceUser}'") == 0:
command = f'{sudo_cmd} userdel {config.serviceUser}'
if os.path.exists(f'/etc/sudoers.d/{config.serviceUser}'):
command = f'{sudo_cmd} rm -rf /etc/sudoers.d/{config.serviceUser}'
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
if config.deleteGitUser:
delete_user(config.gitUserName)
if config.deleteServiceUser:
delete_user(config.serviceUser)
command = f'mvn clean'
res = os.system(command)
message = message_tmp.format(command, res)
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/edu/cmipt/gcs/constant/ApiPathConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ public class ApiPathConstant {
public static final String USER_DELETE_USER_API_PATH = USER_API_PREFIX + "/delete";
public static final String USER_PAGE_USER_REPOSITORY_API_PATH =
USER_API_PREFIX + "/page/repository";

public static final String REPOSITORY_API_PREFIX = ALL_API_PREFIX + "/repository";
public static final String REPOSITORY_CREATE_REPOSITORY_API_PATH = REPOSITORY_API_PREFIX + "/create";
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void signUp(@Validated(CreateGroup.class) @RequestBody UserDTO user) {
}
boolean res = userService.save(new UserPO(user));
if (!res) {
throw new RuntimeException("Failed to sign up user");
throw new GenericException(ErrorCodeEnum.USER_CREATE_FAILED, user);
}
}

Expand Down Expand Up @@ -149,7 +149,7 @@ public ResponseEntity<Void> refreshToken(
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken,
@RequestHeader(HeaderParameter.REFRESH_TOKEN) String refreshToken) {
JwtUtil.blacklistToken(accessToken);
HttpHeaders headers = JwtUtil.generateHeaders(JwtUtil.getID(refreshToken), false);
HttpHeaders headers = JwtUtil.generateHeaders(JwtUtil.getId(refreshToken), false);
return ResponseEntity.ok().headers(headers).build();
}
}
61 changes: 61 additions & 0 deletions src/main/java/edu/cmipt/gcs/controller/RepositoryController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package edu.cmipt.gcs.controller;

import edu.cmipt.gcs.service.RepositoryService;
import edu.cmipt.gcs.util.JwtUtil;
import edu.cmipt.gcs.validation.group.CreateGroup;
import edu.cmipt.gcs.constant.ApiPathConstant;
import edu.cmipt.gcs.constant.HeaderParameter;
import edu.cmipt.gcs.enumeration.ErrorCodeEnum;
import edu.cmipt.gcs.exception.GenericException;
import edu.cmipt.gcs.pojo.repository.RepositoryDTO;
import edu.cmipt.gcs.pojo.repository.RepositoryPO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.media.Schema;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

@RestController
@Tag(name = "Repository", description = "Repository Related APIs")
public class RepositoryController {
@Autowired private RepositoryService repositoryService;

@PostMapping(ApiPathConstant.REPOSITORY_CREATE_REPOSITORY_API_PATH)
@Operation(
summary = "Create a repository",
description = "Create a repository with the given information",
tags = {"Repository", "Post Method"})
@Parameters({
@Parameter(
name = HeaderParameter.ACCESS_TOKEN,
description = "Access token",
required = true,
in = ParameterIn.HEADER,
schema = @Schema(implementation = String.class))
})
@ApiResponse(responseCode = "200", description = "Repository created successfully")
public void createRepository(@Validated(CreateGroup.class) @RequestBody RepositoryDTO repository, @RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken) {
String userId = JwtUtil.getId(accessToken);
RepositoryPO repositoryPO = new RepositoryPO(repository, userId);
QueryWrapper<RepositoryPO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", repositoryPO.getUserId());
queryWrapper.eq("repository_name", repositoryPO.getRepositoryName());
if (repositoryService.exists(queryWrapper)) {
throw new GenericException(ErrorCodeEnum.REPOSITORY_ALREADY_EXISTS, repository);
}
if (!repositoryService.save(repositoryPO)) {
throw new GenericException(ErrorCodeEnum.REPOSITORY_CREATE_FAILED, repository);
}
}
}
13 changes: 6 additions & 7 deletions src/main/java/edu/cmipt/gcs/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edu.cmipt.gcs.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import edu.cmipt.gcs.constant.ApiPathConstant;
Expand Down Expand Up @@ -129,8 +130,7 @@ public ResponseEntity<UserVO> updateUser(
}
// for the null fields, mybatis-plus will ignore by default
assert user.id() != null;
boolean res = userService.updateById(new UserPO(user));
if (!res) {
if (!userService.updateById(new UserPO(user))) {
throw new GenericException(ErrorCodeEnum.USER_UPDATE_FAILED, user);
}
UserVO userVO = new UserVO(userService.getById(Long.valueOf(user.id())));
Expand Down Expand Up @@ -181,8 +181,7 @@ public void deleteUser(
if (userService.getById(id) == null) {
throw new GenericException(ErrorCodeEnum.USER_NOT_FOUND, id);
}
boolean res = userService.removeById(id);
if (!res) {
if (!userService.removeById(id)) {
throw new GenericException(ErrorCodeEnum.USER_DELETE_FAILED, id);
}
JwtUtil.blacklistToken(accessToken, refreshToken);
Expand Down Expand Up @@ -224,20 +223,20 @@ public void deleteUser(
schema = @Schema(implementation = Integer.class))
})
@ApiResponse(responseCode = "200", description = "User repositories paged successfully")
public List<RepositoryVO> pageUserRepositories(
public List<RepositoryVO> pageUserRepository(
@RequestParam("id") Long userId,
@RequestParam("page") Integer page,
@RequestParam("size") Integer size,
@RequestHeader(HeaderParameter.ACCESS_TOKEN) String accessToken) {
QueryWrapper<RepositoryPO> wrapper = new QueryWrapper<RepositoryPO>();
String idInToken = JwtUtil.getID(accessToken);
String idInToken = JwtUtil.getId(accessToken);
assert idInToken != null;
if (!idInToken.equals(userId.toString())) {
// the user only can see the public repositories of others
wrapper.eq("is_private", false);
}
wrapper.eq("user_id", userId);
return repositoryService.page(new Page<>(page, size), wrapper).getRecords().stream()
return repositoryService.list(new Page<>(page, size), wrapper).stream()
.map(RepositoryVO::new)
.collect(Collectors.toList());
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/edu/cmipt/gcs/enumeration/ErrorCodeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum ErrorCodeEnum {

USER_NOT_FOUND("USER_NOT_FOUND"),

USER_CREATE_FAILED("USER_CREATE_FAILED"),
USER_UPDATE_FAILED("USER_UPDATE_FAILED"),
USER_DELETE_FAILED("USER_DELETE_FAILED"),

Expand All @@ -39,11 +40,16 @@ public enum ErrorCodeEnum {
REPOSITORYDTO_REPOSITORYNAME_SIZE("RepositoryDTO.repositoryName.Size"),
REPOSITORYDTO_REPOSITORYNAME_NOTBLANK("RepositoryDTO.repositoryName.NotBlank"),
REPOSITORYDTO_REPOSITORYDESCRIPTION_SIZE("RepositoryDTO.repositoryDescription.Size"),
REPOSITORYDTO_STAR_NULL("RepositoryDTO.star.Null"),
REPOSITORYDTO_STAR_MIN("RepositoryDTO.star.Min"),
REPOSITORYDTO_FORK_NULL("RepositoryDTO.fork.Null"),
REPOSITORYDTO_FORK_MIN("RepositoryDTO.fork.Min"),
REPOSITORYDTO_WATCHER_NULL("RepositoryDTO.watcher.Null"),
REPOSITORYDTO_WATCHER_MIN("RepositoryDTO.watcher.Min"),

REPOSITORYNAME_PATTERN_MISMATCH("REPOSITORYNAME_PATTERN_MISMATCH");
REPOSITORYNAME_PATTERN_MISMATCH("REPOSITORYNAME_PATTERN_MISMATCH"),
REPOSITORY_ALREADY_EXISTS("REPOSITORY_ALREADY_EXISTS"),
REPOSITORY_CREATE_FAILED("REPOSITORY_CREATE_FAILED");

// code means the error code in the message.properties
private String code;
Expand Down
Loading

0 comments on commit 06ec4e2

Please sign in to comment.