diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad670b25..235bc133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,10 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - # Gradle jib를 통한 이미지 배포 - - name: update image using jib + - name: make api documents + run: ./gradlew --info openapi3 + + - name: push image using jib run: ./gradlew --info jib tagging: diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 93c5f609..b96a1ae8 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -92,7 +92,7 @@ jib { } } to { - image = "youdong98/kpring-auth-application" + image = "kpring/auth-application" setAllowInsecureRegistries(true) tags = setOf("latest", version.toString()) } @@ -100,5 +100,3 @@ jib { jvmFlags = listOf("-Xms512m", "-Xmx512m") } } - -tasks.getByName("jib").dependsOn("openapi3") diff --git a/front/src/components/Auth/JoinBox.tsx b/front/src/components/Auth/JoinBox.tsx index 3f8f138e..89be8f6a 100644 --- a/front/src/components/Auth/JoinBox.tsx +++ b/front/src/components/Auth/JoinBox.tsx @@ -43,7 +43,7 @@ function JoinBox() { const submitJoin = async () => { try { const response = await axios.post( - "http://localhost:30002/api/v1/user", + "http://kpring.duckdns.org/user/api/v1/user", { email: values.email, password: values.password, diff --git a/front/src/components/Auth/LoginBox.tsx b/front/src/components/Auth/LoginBox.tsx index 29c7b62b..f437549b 100644 --- a/front/src/components/Auth/LoginBox.tsx +++ b/front/src/components/Auth/LoginBox.tsx @@ -5,6 +5,35 @@ import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import { useNavigate } from "react-router"; import { LoginValidation } from "../../hooks/LoginValidation"; +import { useLoginStore } from "../../store/useLoginStore"; + +async function login(email: string, password: string) { + try { + const response = await fetch( + "http://kpring.duckdns.org/user/api/v1/login", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + } + ); + + const data = await response.json(); + if (response.ok) { + console.log("로그인 성공:", data); + return data.data; + } else { + console.error("로그인 실패:", data); + return null; + } + } catch (error) { + console.error("API 호출 중 오류 발생:", error); + return null; + } +} + function LoginBox() { const { values, @@ -15,6 +44,7 @@ function LoginBox() { validatePassword, validators, } = LoginValidation(); + const { setTokens } = useLoginStore(); const onChangeHandler = ( field: string, event: React.ChangeEvent @@ -25,30 +55,20 @@ function LoginBox() { setErrors((prevErrors) => ({ ...prevErrors, [`${field}Error`]: error })); }; - const clickSubmitHandler = (e: React.FormEvent) => { + const clickSubmitHandler = async (e: React.FormEvent) => { e.preventDefault(); - - const emailError = validateEmail(values.email); - const passwordError = validatePassword(values.password); - - setErrors({ - emailError, - passwordError, - }); - - // setState가 비동기적으로 업데이트되어서 업데이트 완료 후 검사하도록 처리 - setTimeout(() => { - // 유효성 검사를 해서 모든 에러가 없을때만 실행이 되고 alert를 통해 사용자에게 성공 메세지를 보여줌 - if (!emailError && !passwordError) { - alert("로그인 성공!"); - setValues({ - email: "", - password: "", - }); - } - }, 0); + //console.log("폼 제출 시도:", values); + const result = await login(values.email, values.password); + if (result) { + //console.log("토큰 설정:", result); + setTokens(result.accessToken, result.refreshToken); + //navigate("/"); + } else { + console.error("로그인 실패."); + alert("로그인 실패. 이메일 혹은 비밀번호를 확인해 주세요."); + } }; - const navigation = useNavigate(); + const navigate = useNavigate(); return (
@@ -63,7 +83,8 @@ function LoginBox() { " border="1px solid #e4d4e7" padding="20px" - onSubmit={clickSubmitHandler}> + onSubmit={clickSubmitHandler} + >

디코타운에 어서오세요!

@@ -99,7 +120,8 @@ function LoginBox() { type="submit" variant="contained" startIcon={} - sx={{ width: "90%" }}> + sx={{ width: "90%" }} + > 로그인 @@ -108,7 +130,8 @@ function LoginBox() { color="secondary" startIcon={} sx={{ mt: "20px", width: "90%", mb: "20px" }} - onClick={() => navigation("/join")}> + onClick={() => navigate("/join")} + > 회원가입
diff --git a/front/src/store/useLoginStore.ts b/front/src/store/useLoginStore.ts new file mode 100644 index 00000000..d33b78fc --- /dev/null +++ b/front/src/store/useLoginStore.ts @@ -0,0 +1,17 @@ +import create from "zustand"; + +interface LoginState { + accessToken: string; + refreshToken: string; + setTokens: (accessToken: string, refreshToken: string) => void; +} + +export const useLoginStore = create((set) => ({ + accessToken: "", + refreshToken: "", + setTokens: (accessToken, refreshToken) => { + set({ accessToken, refreshToken }); + localStorage.setItem("dicoTown_AccessToken", accessToken); + localStorage.setItem("dicoTown_RefreshToken", refreshToken); + }, +})); diff --git a/infra/.helmignore b/infra/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/Chart.yaml b/infra/Chart.yaml deleted file mode 100644 index db697856..00000000 --- a/infra/Chart.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v2 -name: kpring -description: A Helm chart for Kubernetes - -type: application -version: 0.1.0 -appVersion: "1.16.0" - -dependencies: - - name: ingress-nginx - version: 4.x.x - repository: https://kubernetes.github.io/ingress-nginx \ No newline at end of file diff --git a/infra/charts/auth/.helmignore b/infra/charts/auth/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/charts/auth/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/auth/Chart.yaml b/infra/charts/auth/Chart.yaml deleted file mode 100644 index d74d2973..00000000 --- a/infra/charts/auth/Chart.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v2 -name: auth -description: kpring auth application - -type: application -version: 0.1.0 - -appVersion: "1.16.0" diff --git a/infra/charts/auth/templates/_helpers.tpl b/infra/charts/auth/templates/_helpers.tpl deleted file mode 100644 index 55c38e84..00000000 --- a/infra/charts/auth/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "swagger.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "swagger.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "swagger.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "swagger.labels" -}} -helm.sh/chart: {{ include "swagger.chart" . }} -{{ include "swagger.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "swagger.selectorLabels" -}} -app.kubernetes.io/name: {{ include "swagger.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "swagger.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "swagger.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/infra/charts/auth/templates/configmap.yaml b/infra/charts/auth/templates/configmap.yaml deleted file mode 100644 index bd9b7546..00000000 --- a/infra/charts/auth/templates/configmap.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: auth-config - namespace: {{ .Release.namespace }} - labels: - app: auth -data: - jwt.access.duration: "{{ .Values.service.jwt.duration.access }}" - jwt.refresh.duration: "{{ .Values.service.jwt.duration.refresh }}" -{{- end }} \ No newline at end of file diff --git a/infra/charts/auth/templates/deployment.yaml b/infra/charts/auth/templates/deployment.yaml deleted file mode 100644 index 45035cce..00000000 --- a/infra/charts/auth/templates/deployment.yaml +++ /dev/null @@ -1,44 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} - labels: - app: {{ .Values.service.name }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ .Values.service.name }} - template: - metadata: - labels: - app: {{ .Values.service.name }} - spec: - containers: - - name: {{ .Values.service.name }} - image: {{ .Values.service.image }} - ports: - - containerPort: {{ .Values.service.port }} - env: - - name: REDIS_HOST - value: {{ .Values.redis.name }} - - name: REDIS_PORT - value: "{{ .Values.redis.port }}" - - name: JWT_ACCESS_DURATION - valueFrom: - configMapKeyRef: - name: auth-config - key: jwt.access.duration - - name: JWT_REFRESH_DURATION - valueFrom: - configMapKeyRef: - name: auth-config - key: jwt.refresh.duration - - name: APPLICATION_PROFILE - valueFrom: - configMapKeyRef: - name: profile-config - key: profile -{{- end }} \ No newline at end of file diff --git a/infra/charts/auth/templates/redis-deployment.yaml b/infra/charts/auth/templates/redis-deployment.yaml deleted file mode 100644 index 6f9c056b..00000000 --- a/infra/charts/auth/templates/redis-deployment.yaml +++ /dev/null @@ -1,24 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis - namespace: {{ .Release.namespace }} - labels: - app: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - name: redis - image: redis:alpine - ports: - - containerPort: 6379 -{{- end }} \ No newline at end of file diff --git a/infra/charts/auth/templates/redis-service.yaml b/infra/charts/auth/templates/redis-service.yaml deleted file mode 100644 index d7c85dee..00000000 --- a/infra/charts/auth/templates/redis-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.redis.name }} - namespace: {{ .Release.namespace }} -spec: - type: ClusterIP - ports: - - port: {{ .Values.redis.port }} - protocol: TCP - selector: - app: {{ .Values.redis.name }} -{{- end }} diff --git a/infra/charts/auth/templates/service.yaml b/infra/charts/auth/templates/service.yaml deleted file mode 100644 index 153f851f..00000000 --- a/infra/charts/auth/templates/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} -spec: - {{- if eq .Values.global.profile "local" }} - type: NodePort - {{- else }} - type: ClusterIP - {{- end }} - - ports: - - port: 80 - targetPort: {{ .Values.service.port }} - protocol: TCP - {{- if eq .Values.global.profile "local" }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} - selector: - app: {{ .Values.service.name }} -{{- end }} diff --git a/infra/charts/auth/templates/tests/test-connection.yaml b/infra/charts/auth/templates/tests/test-connection.yaml deleted file mode 100644 index 5023d861..00000000 --- a/infra/charts/auth/templates/tests/test-connection.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ .Values.service.name }}-test-connection" - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ .Values.service.name }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/infra/charts/auth/values.yaml b/infra/charts/auth/values.yaml deleted file mode 100644 index 86222375..00000000 --- a/infra/charts/auth/values.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# auth service -service: - name: auth-application - image: youdong98/kpring-auth-application:latest - port: 8080 - nodePort: 30001 - jwt: - duration: - access: "3600000" - refresh: "86400000" - -redis: - name: redis - image: redis:alpine - port: 6379 diff --git a/infra/charts/gateway/.helmignore b/infra/charts/gateway/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/charts/gateway/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/gateway/Chart.yaml b/infra/charts/gateway/Chart.yaml deleted file mode 100644 index a32f8293..00000000 --- a/infra/charts/gateway/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: gateway -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/infra/charts/gateway/templates/ingress.yaml b/infra/charts/gateway/templates/ingress.yaml deleted file mode 100644 index 2cc6720a..00000000 --- a/infra/charts/gateway/templates/ingress.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ .Values.ingress.name }} - labels: - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - ingressClassName: {{ .Values.ingress.className }} - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ .pathType }} - backend: - service: - name: {{ .service }} - port: - number: 8080 - {{- end }} - {{- end }} diff --git a/infra/charts/gateway/values.yaml b/infra/charts/gateway/values.yaml deleted file mode 100644 index 4b6fa172..00000000 --- a/infra/charts/gateway/values.yaml +++ /dev/null @@ -1,22 +0,0 @@ -ingress: - name: kpring-ingress - enabled: false - className: "nginx" - annotations: - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/rewrite-target: /$2 - hosts: - - host: - paths: - - path: /auth(/|$)(.*) - pathType: ImplementationSpecific - service: auth-application - - path: /user(/|$)(.*) - pathType: ImplementationSpecific - service: user-application - - path: /server(/|$)(.*) - pathType: ImplementationSpecific - service: server-application - tls: [] - -resources: {} diff --git a/infra/charts/ingress-nginx-4.10.1.tgz b/infra/charts/ingress-nginx-4.10.1.tgz deleted file mode 100644 index 278d0a54..00000000 Binary files a/infra/charts/ingress-nginx-4.10.1.tgz and /dev/null differ diff --git a/infra/charts/server/.helmignore b/infra/charts/server/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/charts/server/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/server/Chart.yaml b/infra/charts/server/Chart.yaml deleted file mode 100644 index b68c3bb1..00000000 --- a/infra/charts/server/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: server -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/infra/charts/server/templates/deployment.yaml b/infra/charts/server/templates/deployment.yaml deleted file mode 100644 index 06baf167..00000000 --- a/infra/charts/server/templates/deployment.yaml +++ /dev/null @@ -1,44 +0,0 @@ -{{- if .Values.global.enable.server }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} - labels: - app: {{ .Values.service.name }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ .Values.service.name }} - template: - metadata: - labels: - app: {{ .Values.service.name }} - spec: - containers: - - name: {{ .Values.service.name }} - image: {{ .Values.service.image }} - ports: - - containerPort: {{ .Values.service.port }} - env: - # auth properties - - name: AUTH_SERVICE_URL - value: {{ .Values.api.authServiceUrl }} - - name: APPLICATION_PROFILE - valueFrom: - configMapKeyRef: - name: profile-config - key: profile - # mongo properties - - name: MONGO_HOST - value: {{ .Values.mongo.name }} - - name: MONGO_PORT - value: "{{ .Values.mongo.port }}" - - name: MONGO_USERNAME - value: {{ .Values.mongo.username }} - - name: MONGO_PASSWORD - value: {{ .Values.mongo.password }} - - name: MONGO_DATABASE - value: {{ .Values.mongo.database }} -{{- end }} diff --git a/infra/charts/server/templates/mongo-deployment.yaml b/infra/charts/server/templates/mongo-deployment.yaml deleted file mode 100644 index ef9cb5c3..00000000 --- a/infra/charts/server/templates/mongo-deployment.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.global.enable.server }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.mongo.name }} - namespace: {{ .Release.namespace }} - labels: - app: {{ .Values.mongo.name }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ .Values.mongo.name }} - template: - metadata: - labels: - app: {{ .Values.mongo.name }} - spec: - containers: - - name: {{ .Values.mongo.name }} - image: {{ .Values.mongo.image }} - ports: - - containerPort: {{ .Values.mongo.port }} - env: - - name: MONGO_INITDB_ROOT_USERNAME - value: {{ .Values.mongo.username }} - - name: MONGO_INITDB_ROOT_PASSWORD - value: {{ .Values.mongo.password}} - - name: MONGO_INITDB_DATABASE - value: {{ .Values.mongo.database }} -{{- end }} diff --git a/infra/charts/server/templates/mongo-service.yaml b/infra/charts/server/templates/mongo-service.yaml deleted file mode 100644 index 8a896422..00000000 --- a/infra/charts/server/templates/mongo-service.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.global.enable.server }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.mongo.name }} - namespace: {{ .Release.namespace }} -spec: - type: ClusterIP - - ports: - - port: {{ .Values.mongo.port }} - targetPort: {{ .Values.mongo.port }} - protocol: TCP - selector: - app: {{ .Values.mongo.name }} -{{- end }} diff --git a/infra/charts/server/templates/service.yaml b/infra/charts/server/templates/service.yaml deleted file mode 100644 index 52e756ec..00000000 --- a/infra/charts/server/templates/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.global.enable.server }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} -spec: - {{- if eq .Values.global.profile "local" }} - type: NodePort - {{- else }} - type: ClusterIP - {{- end }} - - ports: - - port: 80 - targetPort: {{ .Values.service.port }} - protocol: TCP - {{- if eq .Values.global.profile "local" }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} - selector: - app: {{ .Values.service.name }} -{{- end }} diff --git a/infra/charts/server/values.yaml b/infra/charts/server/values.yaml deleted file mode 100644 index 52b3f6fe..00000000 --- a/infra/charts/server/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# server service -service: - name: server-application - image: youdong98/kpring-server-application:latest - port: 8080 - nodePort: 30003 - -api: - authServiceUrl: http://auth-application - -mongo: - name: server-mongo - image: mongo:latest - port: 27017 - username: local-user - password: local@password1234 - database: mongodb diff --git a/infra/charts/user/.helmignore b/infra/charts/user/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/charts/user/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/user/Chart.yaml b/infra/charts/user/Chart.yaml deleted file mode 100644 index 8bfcab63..00000000 --- a/infra/charts/user/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: user -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/infra/charts/user/templates/deployment.yaml b/infra/charts/user/templates/deployment.yaml deleted file mode 100644 index 5a5b592d..00000000 --- a/infra/charts/user/templates/deployment.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if .Values.global.enable.user }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} - labels: - app: {{ .Values.service.name }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ .Values.service.name }} - template: - metadata: - labels: - app: {{ .Values.service.name }} - spec: - containers: - - name: {{ .Values.service.name }} - image: {{ .Values.service.image }} - ports: - - containerPort: {{ .Values.service.port }} - env: - - name: AUTH_SERVICE_URL - value: {{ .Values.api.authServiceUrl }} - - name: APPLICATION_PROFILE - valueFrom: - configMapKeyRef: - name: profile-config - key: profile - - name: MYSQL_USERNAME - value: {{ .Values.mysql.username }} - - name: MYSQL_PASSWORD - value: {{ .Values.mysql.password }} - - name: MYSQL_URL - value: {{ .Values.mysql.url }} -{{- end }} diff --git a/infra/charts/user/templates/mysql-deployment.yaml b/infra/charts/user/templates/mysql-deployment.yaml deleted file mode 100644 index 83453d2c..00000000 --- a/infra/charts/user/templates/mysql-deployment.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if .Values.global.enable.user }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.mysql.name }} - namespace: {{ .Release.namespace }} - labels: - app: {{ .Values.mysql.name }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ .Values.mysql.name }} - template: - metadata: - labels: - app: {{ .Values.mysql.name }} - spec: - containers: - - name: {{ .Values.mysql.name }} - image: {{ .Values.mysql.image }} - ports: - - containerPort: {{ .Values.mysql.port }} - env: - - name : MYSQL_ROOT_PASSWORD - value: {{ .Values.mysql.rootPassword }} - - name: MYSQL_USER - value: {{ .Values.mysql.username }} - - name: MYSQL_PASSWORD - value: {{ .Values.mysql.password }} - - name: MYSQL_DATABASE - value: {{ .Values.mysql.dbname }} -{{- end }} diff --git a/infra/charts/user/templates/mysql-service.yaml b/infra/charts/user/templates/mysql-service.yaml deleted file mode 100644 index 07d4544c..00000000 --- a/infra/charts/user/templates/mysql-service.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.global.enable.user }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.mysql.name }} - namespace: {{ .Release.namespace }} -spec: - type: ClusterIP - - ports: - - port: {{ .Values.mysql.port }} - targetPort: {{ .Values.mysql.port }} - protocol: TCP - selector: - app: {{ .Values.mysql.name }} -{{- end }} diff --git a/infra/charts/user/templates/service.yaml b/infra/charts/user/templates/service.yaml deleted file mode 100644 index 380e91d9..00000000 --- a/infra/charts/user/templates/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.global.enable.user }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} -spec: - {{- if eq .Values.global.profile "local" }} - type: NodePort - {{- else }} - type: ClusterIP - {{- end }} - - ports: - - port: 80 - targetPort: {{ .Values.service.port }} - protocol: TCP - {{- if eq .Values.global.profile "local" }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} - selector: - app: {{ .Values.service.name }} -{{- end }} diff --git a/infra/charts/user/values.yaml b/infra/charts/user/values.yaml deleted file mode 100644 index c393a713..00000000 --- a/infra/charts/user/values.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# user service -service: - name: user-application - image: youdong98/kpring-user-application:latest - port: 8080 - nodePort: 30002 - -api: - authServiceUrl: http://auth-application - -mysql: - name: user-mysql - image: mysql:8.0 - port: 3306 - rootPassword: root@password1234 - username: local-user - password: local@password1234 - url: jdbc:mysql://user-mysql:3306/localdb - dbname: localdb diff --git a/infra/compose.yml b/infra/compose.yml deleted file mode 100644 index 623d1962..00000000 --- a/infra/compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -# kubernetes on local environment -services: - registry: - image: registry:latest - ports: - - "5000:5000" - - swagger: - image: swaggerapi/swagger-ui - ports: - - "8080:8080" - environment: - URLS: "[{name: 'auth', url: 'http://kpring.duckdns.org/auth/static/openapi3.yaml'},{name: 'user', url: 'http://kpring.duckdns.org/user/static/openapi3.yaml'}, {name: 'server', url: 'http://kpring.duckdns.org/server/static/openapi3.yaml'}]" diff --git a/infra/readme.md b/infra/readme.md deleted file mode 100644 index 1ea86012..00000000 --- a/infra/readme.md +++ /dev/null @@ -1,53 +0,0 @@ -# Local kubernetes cluster에서 MSA 구동하기 - -## Summary -kubernetes on local 가이드 문서를 참고하여 환경을 구성하였다면 시작할 준비가 되었습니다. -만약 아직 로컬에서 kubernetes cluster가 설치되지 않았다면 먼저 설치하고 와주세요. - -## Run private image registry -먼저 배포에 필요한 모든 서비스에 대한 이미지가 필요합니다. 현재 dev 설정은 이 로컬 상에서의 개인 이미지 저장소를 활용하는 것을 전제로 모든 설정이 되어있기 때문에 이 방법을 사용하는 것을 권장합니다. -구동은 현재 readme.md 파일이 존재하는 디렉터리의 `compose.yml`를 통해서 필요한 서비스를 구축해주세요! - -## image build -gradle의 'jib' 테스크를 실행하여 개발한 서비스의 이미지를 개인 이미지 저장소에 등록해주세요. - -```shell -./gradlew jib -``` - -## deploy with helm chart - -helm chart 을 통해서 배포를 진행해주세요. 반드시 infra 디렉터리에서 다음 명령을 실행해주세요! -```shell -helm dependency update -helm install . -``` - -`helm dependency update` 명령은 helm이 의존하는 다른 helm을 설치하는 사전 작업입니다. nginx-ingress-controller 설치를 위해서 해당 과정이 `21-06-04` 기준으로 추가되었습니다. - -다음은 windows powershell을 통해서 배포하는 예시입니다. -```shell -PS C:\Users\ydong\OneDrive\document\side_project\ksping> cd infra -PS C:\Users\ydong\OneDrive\document\side_project\ksping\infra> helm install dev . -Error: INSTALLATION FAILED: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: ingress-nginx -PS C:\Users\ydong\OneDrive\document\side_project\ksping\infra> helm dependency build -Hang tight while we grab the latest from your chart repositories... -...Successfully got an update from the "elastic" chart repository -...Successfully got an update from the "nginx-ingress" chart repository -...Successfully got an update from the "sentry" chart repository -...Successfully got an update from the "bitnami" chart repository -...Successfully got an update from the "stable" chart repository -Update Complete. ⎈Happy Helming!⎈ -Saving 1 charts -Downloading ingress-nginx from repo https://kubernetes.github.io/ingress-nginx -Deleting outdated charts -PS C:\Users\ydong\OneDrive\document\side_project\ksping\infra> helm install dev . -NAME: dev -LAST DEPLOYED: Tue Jun 4 21:10:41 2024 -NAMESPACE: default -STATUS: deployed -REVISION: 1 -``` - -## Finish -이제 모든 서비스가 배포되었습니다. 로컬에서 서비스를 확인해주세요! \ No newline at end of file diff --git a/infra/templates/_helpers.tpl b/infra/templates/_helpers.tpl deleted file mode 100644 index 2d3a5659..00000000 --- a/infra/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "kpring.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "kpring.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "kpring.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "kpring.labels" -}} -helm.sh/chart: {{ include "kpring.chart" . }} -{{ include "kpring.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "kpring.selectorLabels" -}} -app.kubernetes.io/name: {{ include "kpring.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "kpring.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "kpring.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/infra/templates/profile-config.yaml b/infra/templates/profile-config.yaml deleted file mode 100644 index 7471c1dc..00000000 --- a/infra/templates/profile-config.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: profile-config - namespace: {{ .Release.namespace }} - labels: - app: profile -data: - profile: {{ .Values.global.profile }} diff --git a/infra/values.yaml b/infra/values.yaml deleted file mode 100644 index ac183f7f..00000000 --- a/infra/values.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# service enable setting -global: - profile: local - enable: - auth: true - user: true - server: true diff --git a/server/build.gradle.kts b/server/build.gradle.kts index c2fd02cc..2638a059 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -89,7 +89,7 @@ jib { } } to { - image = "youdong98/kpring-server-application" + image = "kpring/server-application" setAllowInsecureRegistries(true) tags = setOf("latest", version.toString()) } @@ -97,5 +97,3 @@ jib { jvmFlags = listOf("-Xms512m", "-Xmx512m") } } - -tasks.getByName("jib").dependsOn("openapi3") diff --git a/user/build.gradle.kts b/user/build.gradle.kts index 3c49ed7d..d4e13cfa 100644 --- a/user/build.gradle.kts +++ b/user/build.gradle.kts @@ -73,7 +73,7 @@ jib { } } to { - image = "youdong98/kpring-user-application" + image = "kpring/user-application" setAllowInsecureRegistries(true) tags = setOf("latest", version.toString()) } @@ -81,5 +81,3 @@ jib { jvmFlags = listOf("-Xms512m", "-Xmx512m") } } - -tasks.getByName("jib").dependsOn("openapi3") diff --git a/user/src/main/kotlin/kpring/user/controller/FriendController.kt b/user/src/main/kotlin/kpring/user/controller/FriendController.kt index 094672a9..a045a19a 100644 --- a/user/src/main/kotlin/kpring/user/controller/FriendController.kt +++ b/user/src/main/kotlin/kpring/user/controller/FriendController.kt @@ -54,6 +54,19 @@ class FriendController( return ResponseEntity.ok(ApiResponse(data = response)) } + @PatchMapping("/user/{userId}/friend/{friendId}") + fun acceptFriendRequest( + @RequestHeader("Authorization") token: String, + @PathVariable userId: Long, + @PathVariable friendId: Long, + ): ResponseEntity> { + val validationResult = authClient.getTokenInfo(token) + val validatedUserId = authValidator.checkIfAccessTokenAndGetUserId(validationResult) + authValidator.checkIfUserIsSelf(userId.toString(), validatedUserId) + val response = friendService.acceptFriendRequest(userId, friendId) + return ResponseEntity.ok(ApiResponse(data = response)) + } + @DeleteMapping("/user/{userId}/friend/{friendId}") fun deleteFriend( @RequestHeader("Authorization") token: String, diff --git a/user/src/main/kotlin/kpring/user/entity/Friend.kt b/user/src/main/kotlin/kpring/user/entity/Friend.kt index da9aeab5..62d376cc 100644 --- a/user/src/main/kotlin/kpring/user/entity/Friend.kt +++ b/user/src/main/kotlin/kpring/user/entity/Friend.kt @@ -17,4 +17,8 @@ class Friend( @Enumerated(EnumType.STRING) @Column(nullable = false) var requestStatus: FriendRequestStatus, -) +) { + fun updateRequestStatus(requestStatus: FriendRequestStatus) { + this.requestStatus = requestStatus + } +} diff --git a/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt b/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt index e0dea1b5..8b8f15ae 100644 --- a/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt +++ b/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt @@ -19,6 +19,11 @@ enum class UserErrorCode( ALREADY_FRIEND(HttpStatus.BAD_REQUEST, "4030", "이미 친구입니다."), NOT_SELF_FOLLOW(HttpStatus.BAD_REQUEST, "4031", "자기자신에게 친구요청을 보낼 수 없습니다"), + FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND( + HttpStatus.BAD_REQUEST, + "4032", + "해당하는 친구신청이 없거나 이미 친구입니다.", + ), ; override fun message(): String = this.message diff --git a/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt b/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt index ee5bc406..eadac275 100644 --- a/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt +++ b/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt @@ -14,4 +14,10 @@ interface FriendRepository : JpaRepository { userId: Long, requestStatus: FriendRequestStatus, ): List + + fun findByUserIdAndFriendIdAndRequestStatus( + userId: Long, + friendId: Long, + requestStatus: FriendRequestStatus, + ): Friend? } diff --git a/user/src/main/kotlin/kpring/user/service/FriendService.kt b/user/src/main/kotlin/kpring/user/service/FriendService.kt index aa903558..6fd10a30 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendService.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendService.kt @@ -6,15 +6,46 @@ import kpring.user.dto.response.GetFriendRequestsResponse import kpring.user.dto.response.GetFriendsResponse interface FriendService { + /** + * 로그인한 사용자에게 친구신청을 한 사람들의 목록을 조회하는 메서드 + * + * @param userId : 로그인한 사용자의 ID. + * @return 로그인한 사용자 ID, 해당 사용자에게 친구신청을 보낸 사람들의 리스트를 GetFriendRequestsResponse 리턴 + */ fun getFriendRequests(userId: Long): GetFriendRequestsResponse fun getFriends(userId: Long): GetFriendsResponse + /** + * 로그인한 사용자가 friendId를 가진 사용자에게 친구 신청을 하는 메서드 + * + * @param userId : 친구 요청을 하고자 하는 사용자의 ID. + * @param friendId : 친구 요청을 받는 사용자의 ID. + * @return 로그인한 사용자가 친구 신청을 보낸 사용자의 ID를 담고 있는 AddFriendResponse 리턴 + * @throws NOT_SELF_FOLLOW + * : 로그인한 사용자가 스스로에게 친구신청을 시도할 때 발생하는 예외 + * @throws ALREADY_FRIEND + * : 친구신청을 받은 사용자와 이미 친구일 때 발생하는 예외 + */ fun addFriend( userId: Long, friendId: Long, ): AddFriendResponse + /** + * 사용자가 친구 신청을 수락해, 두 사용자 간의 상태를 ACCEPTED 로 변경하는 메서드 + * + * @param userId : 친구 요청을 수락하는 사용자의 ID. + * @param friendId : 친구 요청을 보낸 사용자의 ID. + * @return 새로 친구가 된 사용자의 ID를 담고 있는 AddFriendResponse 리턴 + * @throws FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND + * : friendId가 보낸 친구 신청이 없거나, 해당 사용자와 이미 친구일 때 발생하는 예외 + */ + fun acceptFriendRequest( + userId: Long, + friendId: Long, + ): AddFriendResponse + fun deleteFriend( userId: Long, friendId: Long, diff --git a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt index db4db159..896ecefb 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt @@ -49,6 +49,19 @@ class FriendServiceImpl( return AddFriendResponse(friend.id!!) } + override fun acceptFriendRequest( + userId: Long, + friendId: Long, + ): AddFriendResponse { + val receivedFriend = getFriendshipWithStatus(userId, friendId, FriendRequestStatus.RECEIVED) + val requestedFriend = getFriendshipWithStatus(friendId, userId, FriendRequestStatus.REQUESTED) + + receivedFriend.updateRequestStatus(FriendRequestStatus.ACCEPTED) + requestedFriend.updateRequestStatus(FriendRequestStatus.ACCEPTED) + + return AddFriendResponse(friendId) + } + override fun deleteFriend( userId: Long, friendId: Long, @@ -73,4 +86,14 @@ class FriendServiceImpl( throw ServiceException(UserErrorCode.ALREADY_FRIEND) } } + + private fun getFriendshipWithStatus( + userId: Long, + friendId: Long, + requestStatus: FriendRequestStatus, + ): Friend { + return friendRepository + .findByUserIdAndFriendIdAndRequestStatus(userId, friendId, requestStatus) + ?: throw ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) + } } diff --git a/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt b/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt index 0367a6bf..53867dc6 100644 --- a/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt +++ b/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt @@ -344,4 +344,153 @@ internal class FriendControllerTest( } } } + describe("친구신청 수락 API") { + it("친구신청 수락 성공") { + // given + val data = + AddFriendResponse(friendId = CommonTest.TEST_FRIEND_ID) + + val response = ApiResponse(data = data) + every { authClient.getTokenInfo(any()) }.returns( + ApiResponse(data = TokenInfo(TokenType.ACCESS, CommonTest.TEST_USER_ID.toString())), + ) + every { authValidator.checkIfAccessTokenAndGetUserId(any()) } returns CommonTest.TEST_USER_ID.toString() + every { authValidator.checkIfUserIsSelf(any(), any()) } returns Unit + every { + friendService.acceptFriendRequest( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + } returns data + + // when + val result = + webTestClient.patch() + .uri( + "/api/v1/user/{userId}/friend/{friendId}", + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + .header("Authorization", CommonTest.TEST_TOKEN) + .exchange() + + // then + val docsRoot = + result + .expectStatus().isOk + .expectBody().json(objectMapper.writeValueAsString(response)) + + // docs + docsRoot + .restDoc( + identifier = "acceptFriendRequest200", + description = "친구신청 수락 API", + ) { + request { + path { + "userId" mean "사용자 아이디" + "friendId" mean "친구신청을 받은 사용자의 아이디" + } + header { + "Authorization" mean "Bearer token" + } + } + response { + body { + "data.friendId" type Strings mean "친구신청을 받은 사용자의 아이디" + } + } + } + } + it("친구신청 수락 실패 : 권한이 없는 토큰") { + // given + val response = + FailMessageResponse.builder().message(UserErrorCode.NOT_ALLOWED.message()).build() + every { authClient.getTokenInfo(any()) } throws ServiceException(UserErrorCode.NOT_ALLOWED) + + // when + val result = + webTestClient.patch() + .uri( + "/api/v1/user/{userId}/friend/{friendId}", + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + .header("Authorization", CommonTest.TEST_TOKEN) + .exchange() + + // then + val docsRoot = + result + .expectStatus().isForbidden + .expectBody().json(objectMapper.writeValueAsString(response)) + + // docs + docsRoot + .restDoc( + identifier = "acceptFriendRequest403", + description = "친구신청 수락 API", + ) { + request { + path { + "userId" mean "사용자 아이디" + "friendId" mean "친구신청을 받은 사용자의 아이디" + } + header { + "Authorization" mean "Bearer token" + } + } + response { + body { + "message" type Strings mean "에러 메시지" + } + } + } + } + it("친구신청 수락 실패 : 서버 내부 오류") { + // given + val response = + FailMessageResponse.serverError + every { authClient.getTokenInfo(any()) } throws RuntimeException("서버 내부 오류") + + // when + val result = + webTestClient.patch() + .uri( + "/api/v1/user/{userId}/friend/{friendId}", + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + .header("Authorization", CommonTest.TEST_TOKEN) + .exchange() + + // then + val docsRoot = + result + .expectStatus().isEqualTo(500) + .expectBody().json(objectMapper.writeValueAsString(response)) + + // docs + docsRoot + .restDoc( + identifier = "acceptFriendRequest500", + description = "친구신청 수락 API", + ) { + request { + path { + "userId" mean "사용자 아이디" + "friendId" mean "친구신청을 받은 사용자의 아이디" + } + header { + "Authorization" mean "Bearer token" + } + } + response { + body { + "message" type Strings mean "에러 메시지" + } + } + } + } + } }) diff --git a/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt b/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt index e5a24eaa..4b174825 100644 --- a/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt +++ b/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt @@ -107,7 +107,10 @@ internal class FriendServiceImplTest : FunSpec({ val friendList = listOf(mockk(relaxed = true)) every { - friendRepository.findAllByUserIdAndRequestStatus(CommonTest.TEST_USER_ID, FriendRequestStatus.RECEIVED) + friendRepository.findAllByUserIdAndRequestStatus( + CommonTest.TEST_USER_ID, + FriendRequestStatus.RECEIVED, + ) } returns friendList val response = friendService.getFriendRequests(CommonTest.TEST_USER_ID) @@ -116,4 +119,75 @@ internal class FriendServiceImplTest : FunSpec({ request.username shouldBe friend.username } } + + test("친구신청수락_성공") { + val receivedFriend = mockk(relaxed = true) + val requestedFriend = mockk(relaxed = true) + + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + FriendRequestStatus.RECEIVED, + ) + } returns receivedFriend + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_FRIEND_ID, + CommonTest.TEST_USER_ID, + FriendRequestStatus.REQUESTED, + ) + } returns requestedFriend + + every { receivedFriend.updateRequestStatus(any()) } just Runs + every { requestedFriend.updateRequestStatus(any()) } just Runs + + val response = + friendService.acceptFriendRequest(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID) + response.friendId shouldBe CommonTest.TEST_FRIEND_ID + + verify(exactly = 2) { + friendRepository.findByUserIdAndFriendIdAndRequestStatus(any(), any(), any()) + } + } + + test("친구신청수락_실패_해당하는 친구신청이 없는 케이스") { + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + FriendRequestStatus.RECEIVED, + ) + } throws ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) + + val exception = + shouldThrow { + friendService.acceptFriendRequest(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID) + } + exception.errorCode.message() shouldBe "해당하는 친구신청이 없거나 이미 친구입니다." + } + + test("친구신청수락_실패_이미 친구인 케이스") { + + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + FriendRequestStatus.RECEIVED, + ) + } throws ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_FRIEND_ID, + CommonTest.TEST_USER_ID, + FriendRequestStatus.REQUESTED, + ) + } throws ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) + + val exception = + shouldThrow { + friendService.acceptFriendRequest(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID) + } + exception.errorCode.message() shouldBe "해당하는 친구신청이 없거나 이미 친구입니다." + } })