Skip to content

Commit

Permalink
Add an API for get the repository list by user id
Browse files Browse the repository at this point in the history
This API can also be used for getting other's repository list, but only
the public repository can be got.

The test case will be added after finishing the APIs related to
repository (such as creating a repository, updating a repository, etc.).

See #32.
  • Loading branch information
Kaiser-Yang committed Sep 18, 2024
1 parent 0701b7c commit 2c24dcc
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/main/java/edu/cmipt/gcs/constant/ApiPathConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ public class ApiPathConstant {
public static final String USER_CHECK_USERNAME_VALIDITY_API_PATH =
USER_API_PREFIX + "/username";
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";
}
6 changes: 6 additions & 0 deletions src/main/java/edu/cmipt/gcs/constant/ValidationConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ public class ValidationConstant {
// so we just use '*' to ignore the length check
public static final String USERNAME_PATTERN = "^[a-zA-Z0-9_]*$";
public static final String PASSWORD_PATTERN = "^[a-zA-Z0-9_.@]*$";
public static final int MIN_REPOSITORY_NAME_LENGTH = 1;
public static final int MAX_REPOSITORY_NAME_LENGTH = 255;
public static final int MIN_REPOSITORY_DESCRIPTION_LENGTH = 0;
public static final int MAX_REPOSITORY_DESCRIPTION_LENGTH = 255;
// the length will checked by @Size
public static final String REPOSITORY_NAME_PATTERN = "^[a-zA-Z0-9_-]*$";
}
60 changes: 60 additions & 0 deletions src/main/java/edu/cmipt/gcs/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package edu.cmipt.gcs.controller;

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

import edu.cmipt.gcs.constant.ApiPathConstant;
import edu.cmipt.gcs.constant.HeaderParameter;
import edu.cmipt.gcs.constant.ValidationConstant;
import edu.cmipt.gcs.enumeration.ErrorCodeEnum;
import edu.cmipt.gcs.exception.GenericException;
import edu.cmipt.gcs.pojo.error.ErrorVO;
import edu.cmipt.gcs.pojo.repository.RepositoryPO;
import edu.cmipt.gcs.pojo.repository.RepositoryVO;
import edu.cmipt.gcs.pojo.user.UserDTO;
import edu.cmipt.gcs.pojo.user.UserPO;
import edu.cmipt.gcs.pojo.user.UserVO;
import edu.cmipt.gcs.service.RepositoryService;
import edu.cmipt.gcs.service.UserService;
import edu.cmipt.gcs.util.JwtUtil;
import edu.cmipt.gcs.validation.group.UpdateGroup;
Expand All @@ -29,6 +33,10 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
Expand All @@ -46,6 +54,7 @@
@Tag(name = "User", description = "User Related APIs")
public class UserController {
@Autowired private UserService userService;
@Autowired private RepositoryService repositoryService;

@GetMapping(ApiPathConstant.USER_GET_USER_API_PATH)
@Operation(
Expand Down Expand Up @@ -180,6 +189,57 @@ public void deleteUser(
JwtUtil.blacklistToken(accessToken, refreshToken);
}

@GetMapping(ApiPathConstant.USER_PAGE_USER_REPOSITORY_API_PATH)
@Operation(
summary = "Page user repositories",
description = "Page user repositories. If the given token is trying to get other's repositories, only public repositories will be shown",
tags = {"User", "Get Method"})
@Parameters({
@Parameter(
name = HeaderParameter.ACCESS_TOKEN,
description = "Access token",
required = true,
in = ParameterIn.HEADER,
schema = @Schema(implementation = String.class)),
@Parameter(
name = "id",
description = "User id",
required = true,
in = ParameterIn.QUERY,
schema = @Schema(implementation = Long.class)),
@Parameter(
name = "page",
description = "Page number",
example = "1",
required = true,
in = ParameterIn.QUERY,
schema = @Schema(implementation = Integer.class)),
@Parameter(
name = "size",
description = "Page size",
example = "10",
required = true,
in = ParameterIn.QUERY,
schema = @Schema(implementation = Integer.class))
})
@ApiResponse(responseCode = "200", description = "User repositories paged successfully")
public List<RepositoryVO> pageUserRepositories(
@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);
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);
var res = repositoryService.page(new Page<>(page, size), wrapper).getRecords().stream().map(RepositoryVO::new).collect(Collectors.toList());
return res;
}

@GetMapping(ApiPathConstant.USER_CHECK_EMAIL_VALIDITY_API_PATH)
@Operation(
summary = "Check email validity",
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/edu/cmipt/gcs/dao/RepositoryMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package edu.cmipt.gcs.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import edu.cmipt.gcs.pojo.repository.RepositoryPO;

public interface RepositoryMapper extends BaseMapper<RepositoryPO> {}
13 changes: 12 additions & 1 deletion src/main/java/edu/cmipt/gcs/enumeration/ErrorCodeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,18 @@ public enum ErrorCodeEnum {
USER_NOT_FOUND("USER_NOT_FOUND"),

USER_UPDATE_FAILED("USER_UPDATE_FAILED"),
USER_DELETE_FAILED("USER_DELETE_FAILED");
USER_DELETE_FAILED("USER_DELETE_FAILED"),

REPOSITORYDTO_ID_NULL("RepositoryDTO.id.Null"),
REPOSITORYDTO_ID_NOTNULL("RepositoryDTO.id.NotNull"),
REPOSITORYDTO_REPOSITORYNAME_SIZE("RepositoryDTO.repositoryName.Size"),
REPOSITORYDTO_REPOSITORYNAME_NOTBLANK("RepositoryDTO.repositoryName.NotBlank"),
REPOSITORYDTO_REPOSITORYDESCRIPTION_SIZE("RepositoryDTO.repositoryDescription.Size"),
REPOSITORYDTO_STAR_MIN("RepositoryDTO.star.Min"),
REPOSITORYDTO_FORK_MIN("RepositoryDTO.fork.Min"),
REPOSITORYDTO_WATCHER_MIN("RepositoryDTO.watcher.Min"),

REPOSITORYNAME_PATTERN_MISMATCH("REPOSITORYNAME_PATTERN_MISMATCH");

// code means the error code in the message.properties
private String code;
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/edu/cmipt/gcs/pojo/repository/RepositoryDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package edu.cmipt.gcs.pojo.repository;

import edu.cmipt.gcs.constant.ValidationConstant;
import edu.cmipt.gcs.validation.group.CreateGroup;
import edu.cmipt.gcs.validation.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

@Schema(description = "Repository Data Transfer Object")
public record RepositoryDTO(
@Schema(description = "Repository ID")
@Null(groups = CreateGroup.class, message = "REPOSITORYDTO_ID_NULL {RepositoryDTO.id.Null}")
@NotNull(
groups = UpdateGroup.class,
message = "REPOSITORYDTO_ID_NOTNULL {RepositoryDTO.id.NotNull}")
String id,
@Schema(
description = "Repository Name",
requiredMode = Schema.RequiredMode.REQUIRED,
example = "gcs")
@Size(
groups = {CreateGroup.class, UpdateGroup.class},
min = ValidationConstant.MIN_REPOSITORY_NAME_LENGTH,
max = ValidationConstant.MAX_REPOSITORY_NAME_LENGTH,
message = "REPOSITORYDTO_REPOSITORYNAME_SIZE {RepositoryDTO.repositoryName.Size}")
@NotBlank(
groups = {CreateGroup.class},
message = "REPOSITORYDTO_REPOSITORYNAME_NOTBLANK {RepositoryDTO.repositoryName.NotBlank}")
@Pattern(
regexp = ValidationConstant.REPOSITORY_NAME_PATTERN,
groups = {CreateGroup.class, UpdateGroup.class},
message = "REPOSITORYNAME_PATTERN_MISMATCH {REPOSITORYNAME_PATTERN_MISMATCH}")
String repositoryName,
@Schema(description = "Repository Description")
@Size(
groups = {CreateGroup.class, UpdateGroup.class},
min = ValidationConstant.MIN_REPOSITORY_DESCRIPTION_LENGTH,
max = ValidationConstant.MAX_REPOSITORY_DESCRIPTION_LENGTH,
message = "REPOSITORYDTO_REPOSITORYDESCRIPTION_SIZE {RepositoryDTO.repositoryDescription.Size}")
String repositoryDescription,
@Schema(description = "Whether or Not Private Repo")
Boolean isPrivate,
@Schema(description = "Star Count")
@Min(groups = {CreateGroup.class, UpdateGroup.class}, value = 0, message = "REPOSITORYDTO_STAR_MIN {RepositoryDTO.star.Min}")
Integer star,
@Schema(description = "Fork Count")
@Min(groups = {CreateGroup.class, UpdateGroup.class}, value = 0, message = "REPOSITORYDTO_FORK_MIN {RepositoryDTO.fork.Min}")
Integer fork,
@Schema(description = "Watcher Count")
@Min(groups = {CreateGroup.class, UpdateGroup.class}, value = 0, message = "REPOSITORYDTO_WATCHER_MIN {RepositoryDTO.watcher.Min}")
Integer watcher
) {}
38 changes: 38 additions & 0 deletions src/main/java/edu/cmipt/gcs/pojo/repository/RepositoryPO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package edu.cmipt.gcs.pojo.repository;

import com.baomidou.mybatisplus.annotation.TableName;

import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;

@Data
@TableName("t_repository")
public class RepositoryPO {
private Long id;
private String repositoryName;
private String repositoryDescription;
private Boolean isPrivate;
private Long userId;
private Integer star;
private Integer fork;
private Integer watcher;
private LocalDateTime gmtCreated;
private LocalDateTime gmtUpdated;
@TableLogic private LocalDateTime gmtDeleted;

public RepositoryPO(RepositoryDTO repositoryDTO, Long userId) {
try {
this.id = Long.valueOf(repositoryDTO.id());
} catch (NumberFormatException e) {
this.id = null;
}
this.repositoryName = repositoryDTO.repositoryName();
this.repositoryDescription = repositoryDTO.repositoryDescription();
this.isPrivate = repositoryDTO.isPrivate();
this.userId = userId;
this.star = repositoryDTO.star();
this.fork = repositoryDTO.fork();
this.watcher = repositoryDTO.watcher();
}
}
17 changes: 17 additions & 0 deletions src/main/java/edu/cmipt/gcs/pojo/repository/RepositoryVO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package edu.cmipt.gcs.pojo.repository;

import io.swagger.v3.oas.annotations.media.Schema;

public record RepositoryVO(
@Schema(description = "Repository ID") String id,
@Schema(description = "Repository Name") String repositoryName,
@Schema(description = "Repository Description") String repositoryDescription,
@Schema(description = "Whether or Not Private Repo") Boolean isPrivate,
@Schema(description = "Star Count") Integer star,
@Schema(description = "Fork Count") Integer fork,
@Schema(description = "Watcher Count") Integer watcher) {
public RepositoryVO(RepositoryPO repositoryPO) {
this(repositoryPO.getId().toString(), repositoryPO.getRepositoryName(), repositoryPO.getRepositoryDescription(),
repositoryPO.getIsPrivate(), repositoryPO.getStar(), repositoryPO.getFork(), repositoryPO.getWatcher());
}
}
2 changes: 1 addition & 1 deletion src/main/java/edu/cmipt/gcs/pojo/user/UserDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/
@Schema(description = "User Data Transfer Object")
public record UserDTO(
@Schema(description = "User ID", accessMode = Schema.AccessMode.READ_ONLY)
@Schema(description = "User ID")
@Null(groups = CreateGroup.class, message = "USERDTO_ID_NULL {UserDTO.id.Null}")
@NotNull(
groups = UpdateGroup.class,
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/edu/cmipt/gcs/service/RepositoryService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package edu.cmipt.gcs.service;

import com.baomidou.mybatisplus.extension.service.IService;

import edu.cmipt.gcs.pojo.repository.RepositoryPO;

public interface RepositoryService extends IService<RepositoryPO> {}
11 changes: 11 additions & 0 deletions src/main/java/edu/cmipt/gcs/service/RepositoryServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.cmipt.gcs.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import edu.cmipt.gcs.dao.RepositoryMapper;
import edu.cmipt.gcs.pojo.repository.RepositoryPO;

import org.springframework.stereotype.Service;

@Service
public class RepositoryServiceImpl extends ServiceImpl<RepositoryMapper, RepositoryPO> implements RepositoryService {}
11 changes: 11 additions & 0 deletions src/main/resources/message/message.properties
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,14 @@ USER_NOT_FOUND=User not found: {}

USER_UPDATE_FAILED=User update failed: {}
USER_DELETE_FAILED=User delete failed: {}

RepositoryDTO.id.Null=Repository id must be null when creating a new repository
RepositoryDTO.id.NotNull=Repository id cannot be null
RepositoryDTO.name.Size=Repository name must be between {min} and {max} characters
RepositoryDTO.name.NotBlank=Repository name cannot be blank
RepositoryDTO.description.Size=Repository description must be between {min} and {max} characters
RepositoryDTO.star.Min=Star must be greater than or equal to {value}
RepositoryDTO.fork.Min=Fork must be greater than or equal to {value}
RepositoryDTO.watcher.Min=Watcher must be greater than or equal to {value}

REPOSITORYNAME_PATTERN_MISMATCH=Repository name can only be alphanumeric, hyphen or underline

0 comments on commit 2c24dcc

Please sign in to comment.