Skip to content

Commit

Permalink
Merge branch 'develop' into ci#30
Browse files Browse the repository at this point in the history
  • Loading branch information
HyeonJun0530 authored Aug 13, 2024
2 parents c48cd07 + 916cd69 commit f64c98d
Show file tree
Hide file tree
Showing 27 changed files with 664 additions and 25 deletions.
10 changes: 6 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.h2database:h2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// Rest docs
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

// test
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.assertj:assertj-core:3.26.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.h2database:h2'

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
Expand Down
14 changes: 14 additions & 0 deletions src/docs/asciidoc/api/domain/organization/Organization.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[Organization]]
== Organization API

=== Organization 생성 API

==== HTTP Request

include::{snippets}/organization-controller-test/create/http-request.adoc[]
include::{snippets}/organization-controller-test/create/request-fields.adoc[]

==== HTTP Response

include::{snippets}/organization-controller-test/create/http-response.adoc[]
include::{snippets}/organization-controller-test/create/response-fields-data.adoc[]
3 changes: 1 addition & 2 deletions src/docs/asciidoc/common/greet-status.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@
:icons: font

[[greet-status]]
== Greet status

=== Greet status
include::{snippets}/common-doc-controller-test/enums/custom-response-fields-greetStatus.adoc[]
7 changes: 7 additions & 0 deletions src/docs/asciidoc/common/organization-role.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:doctype: book
:icons: font

[[organization-role]]
=== Organization role

include::{snippets}/common-doc-controller-test/enums/custom-response-fields-organizationRole.adoc[]
9 changes: 7 additions & 2 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ endif::[]

[[Hello-API]]
== Hello API

include::api/test/hello.adoc[]
include::common/greet-status.adoc[]

[[enums]]
== Enums
include::common/greet-status.adoc[]
include::common/organization-role.adoc[]

include::api/domain/organization/Organization.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.wootecam.festivals.domain.organization.controller;

import com.wootecam.festivals.domain.organization.dto.OrganizationCreateDto;
import com.wootecam.festivals.domain.organization.dto.OrganizationIdDto;
import com.wootecam.festivals.domain.organization.service.OrganizationService;
import com.wootecam.festivals.global.api.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/organizations")
@RequiredArgsConstructor
public class OrganizationController {

private final OrganizationService organizationService;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public ApiResponse<OrganizationIdDto> createOrganization(
@Valid @RequestBody OrganizationCreateDto organizationCreateDto) {
Long organizationId = organizationService.createOrganization(organizationCreateDto);

return ApiResponse.of(new OrganizationIdDto(organizationId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wootecam.festivals.domain.organization.dto;

import com.wootecam.festivals.domain.organization.entity.Organization;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record OrganizationCreateDto(
@NotNull(message = "조직 이름은 필수입니다.")
@Size(min = 1, max = 20, message = "조직 이름은 1자 이상 20자 이하여야 합니다.")
String name,

@Size(max = 200, message = "조직 설명은 200자 이하여야 합니다.")
String detail,
String profileImg) {

public Organization toEntity() {
return Organization.builder()
.name(this.name())
.detail(this.detail())
.profileImg(this.profileImg())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.wootecam.festivals.domain.organization.dto;

public record OrganizationIdDto(Long organizationId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.wootecam.festivals.domain.organization.entity;

import com.wootecam.festivals.global.audit.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Organization extends BaseEntity {

public static final int NAME_MIN_LENGTH = 1;
public static final int NAME_MAX_LENGTH = 20;
public static final int DETAIL_MAX_LENGTH = 200;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "organization_id")
private Long id;
@Column(name = "member_name", length = NAME_MAX_LENGTH, nullable = false)
private String name;
private String profileImg;
@Column(length = DETAIL_MAX_LENGTH)
private String detail;
private boolean isDeleted;

@Builder
private Organization(String name, String profileImg, String detail) {
validateNameLength(name);
if (detail != null) {
validateDetailLength(detail);
}

this.name = Objects.requireNonNull(name, "organization must be provided.");
this.profileImg = profileImg;
this.detail = detail;
this.isDeleted = false;
}

private void validateNameLength(String name) {
if (name.length() < NAME_MIN_LENGTH) {
throw new IllegalArgumentException("조직 이름은 1자 이상이어야 합니다.");
}
if (name.length() > NAME_MAX_LENGTH) {
throw new IllegalArgumentException("조직 이름은 20자 이상이어야 합니다.");
}
}

private void validateDetailLength(String detail) {
if (detail.length() > DETAIL_MAX_LENGTH) {
throw new IllegalArgumentException("조직 설명은 200자 이하이어야 합니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.wootecam.festivals.domain.organization.entity;

import com.wootecam.festivals.global.audit.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class OrganizationMember extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "organization__member_id")
private Long id;
@Column(nullable = false)
private Long organizationId;
@Column(nullable = false)
private Long memberId;
private OrganizationRole role;

@Builder
private OrganizationMember(Long organizationId, Long memberId, OrganizationRole role) {
this.organizationId = organizationId;
this.memberId = memberId;
this.role = role;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.wootecam.festivals.domain.organization.entity;

import com.wootecam.festivals.global.docs.EnumType;

public enum OrganizationRole implements EnumType {

ADMIN("관리자"),
MEMBER("일반 사용자");

private final String description;

OrganizationRole(String description) {
this.description = description;
}

@Override
public String getName() {
return this.name();
}

public String getDescription() {
return description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.wootecam.festivals.domain.organization.repository;

import com.wootecam.festivals.domain.organization.entity.OrganizationMember;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrganizationMemberRepository extends JpaRepository<OrganizationMember, Long> {

List<OrganizationMember> findByOrganizationId(Long organizationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wootecam.festivals.domain.organization.repository;

import com.wootecam.festivals.domain.organization.entity.Organization;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrganizationRepository extends JpaRepository<Organization, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.wootecam.festivals.domain.organization.service;

import com.wootecam.festivals.domain.organization.dto.OrganizationCreateDto;
import com.wootecam.festivals.domain.organization.entity.Organization;
import com.wootecam.festivals.domain.organization.entity.OrganizationMember;
import com.wootecam.festivals.domain.organization.entity.OrganizationRole;
import com.wootecam.festivals.domain.organization.repository.OrganizationMemberRepository;
import com.wootecam.festivals.domain.organization.repository.OrganizationRepository;
import com.wootecam.festivals.global.utils.AuthenticationUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrganizationService {

private final OrganizationRepository organizationRepository;
private final OrganizationMemberRepository organizationMemberRepository;

@Transactional
public Long createOrganization(OrganizationCreateDto organizationCreateDto) {
Organization newOrganization = saveOrganization(organizationCreateDto);
registerOrganizationAdmin(newOrganization);

return newOrganization.getId();
}

private Organization saveOrganization(OrganizationCreateDto organizationCreateDto) {
return organizationRepository.save(organizationCreateDto.toEntity());
}

private void registerOrganizationAdmin(Organization newOrganization) {
Long loginMemberId = AuthenticationUtils.getLoginMemberId();
OrganizationMember organizationMember = OrganizationMember.builder()
.organizationId(newOrganization.getId())
.memberId(loginMemberId)
.role(OrganizationRole.ADMIN)
.build();
organizationMemberRepository.save(organizationMember);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.wootecam.festivals.global.auth;

public record Authentication(Long memberId, String name, String email) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.wootecam.festivals.global.auth;

public final class AuthenticationContext {

private static final ThreadLocal<Authentication> AUTHENTICATION = new ThreadLocal<>();

private AuthenticationContext() {
}

public static Authentication getAuthentication() {
return AUTHENTICATION.get();
}

public static void setAuthentication(Authentication authentication) {
AUTHENTICATION.set(authentication);
}

public static void clear() {
AUTHENTICATION.remove();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ApiExceptionHandler {

@ExceptionHandler(ApiException.class)
@ExceptionHandler(value = IllegalArgumentException.class)
public ResponseEntity<ApiErrorResponse> handleIllegalArgumentException(IllegalArgumentException exception) {
ApiErrorResponse errorResponse = ApiErrorResponse.of(GlobalErrorCode.INTERNAL_SERVER_ERROR.getCode(),
exception.getMessage());

log.error("{}", errorResponse);

return ResponseEntity
.status(GlobalErrorCode.INTERNAL_SERVER_ERROR.getHttpStatus())
.body(errorResponse);
}

@ExceptionHandler(value = ApiException.class)
public ResponseEntity<ApiErrorResponse> handleApiException(ApiException exception) {
ApiErrorResponse errorResponse = ApiErrorResponse.of(exception.getErrorCode().getCode(),
exception.getErrorDescription());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.wootecam.festivals.global.utils;

import com.wootecam.festivals.global.auth.AuthenticationContext;

public final class AuthenticationUtils {

private AuthenticationUtils() {
}

public static Long getLoginMemberId() {
return AuthenticationContext.getAuthentication().memberId();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wootecam.festivals.docs.utils;

import com.wootecam.festivals.domain.organization.entity.OrganizationRole;
import com.wootecam.festivals.global.docs.EnumType;
import com.wootecam.festivals.global.docs.TestController.GreetStatus;
import java.util.Arrays;
Expand All @@ -19,8 +20,9 @@ public class CommonDocController {
@GetMapping("/enums")
public EnumDocs findEnums() {
Map<String, String> greetStatus = getDocs(GreetStatus.values());
Map<String, String> organizationRole = getDocs(OrganizationRole.values());

return new EnumDocs(greetStatus);
return new EnumDocs(greetStatus, organizationRole);
}

private Map<String, String> getDocs(EnumType[] enumTypes) {
Expand Down
Loading

0 comments on commit f64c98d

Please sign in to comment.