|
|
|
@ -1,202 +1,115 @@
|
|
|
|
# Spring Security Refresh Token with JWT in Spring Boot example
|
|
|
|
# AutoFlow Server Management (autoflow-server-mgmt)
|
|
|
|
|
|
|
|
|
|
|
|
Build JWT Refresh Token with Spring Security in the Spring Boot Application. You can know how to expire the JWT Token, then renew the Access Token with Refresh Token in HttpOnly Cookie.
|
|
|
|
이 프로젝트는 AutoFlow 시스템의 핵심 관리 서버로, 머신러닝 파이프라인 관리, 데이터셋 관리, 그리고 외부 시스템(Kubeflow, MLflow, OTA 등)과의 통합을 담당합니다. Spring Boot 기반의 견고한 아키텍처를 통해 데이터 기반의 워크플로우를 효율적으로 관리합니다.
|
|
|
|
|
|
|
|
|
|
|
|
The instruction can be found at:
|
|
|
|
---
|
|
|
|
[Spring Security Refresh Token with JWT](https://www.bezkoder.com/spring-security-refresh-token/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## User Registration, User Login and Authorization process.
|
|
|
|
## 🚀 주요 기능
|
|
|
|
The diagram shows flow of how we implement User Registration, User Login and Authorization process.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
### 1. 인증 및 권한 관리 (Authentication & Security)
|
|
|
|
|
|
|
|
- **JWT (JSON Web Token)** 기반 인증 지원
|
|
|
|
|
|
|
|
- **Refresh Token**을 통한 보안성 및 사용자 편의성 강화
|
|
|
|
|
|
|
|
- 쿠키 기반의 토큰 저장 방식 (`cuuva-jwt`, `cuuva-jwt-refresh`)
|
|
|
|
|
|
|
|
- 프로젝트 및 작업 단위별 세부 권한 제어
|
|
|
|
|
|
|
|
|
|
|
|
And this is for Refresh Token:
|
|
|
|
### 2. 프로젝트 및 데이터 관리 (Project & Data Management)
|
|
|
|
|
|
|
|
- **프로젝트(Project)** 생성, 수정, 삭제 및 멤버별 권한 관리
|
|
|
|
|
|
|
|
- **데이터 그룹(Data Group)** 및 **데이터셋(Dataset)**의 계층적 관리
|
|
|
|
|
|
|
|
- 외부 데이터셋 연동 및 관리 기능
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
### 3. ML 파이프라인 및 워크플로우 (ML Pipeline & Workflow)
|
|
|
|
|
|
|
|
- **Kubeflow** 통합: Experiments 및 Runs 관리, 파이프라인 업로드
|
|
|
|
|
|
|
|
- **MLflow** 통합: 실험 결과 및 메트릭 추적
|
|
|
|
|
|
|
|
- 워크플로우 생성 및 실행 관리
|
|
|
|
|
|
|
|
|
|
|
|
## Configure Spring Datasource, JPA, App properties
|
|
|
|
### 4. 파일 관리 (File Management)
|
|
|
|
Open `src/main/resources/application.properties`
|
|
|
|
- **MinIO** 및 **AWS S3** 연동을 통한 대용량 파일 저장 및 관리
|
|
|
|
|
|
|
|
- 멀티파트(Multipart) 파일 업로드 지원 (최대 500MB)
|
|
|
|
|
|
|
|
- 동적 MinIO 첨부 파일 관리 시스템
|
|
|
|
|
|
|
|
|
|
|
|
```properties
|
|
|
|
### 5. 외부 시스템 연동 (External Integrations)
|
|
|
|
spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false
|
|
|
|
- **OTA (Over-The-Air)** 연동: 외부 인증 및 패키지 검색 API 연동 지원
|
|
|
|
spring.datasource.username= root
|
|
|
|
- **Spring Batch**를 활용한 대용량 데이터 처리 및 통계 수집
|
|
|
|
spring.datasource.password= 123456
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
|
|
|
|
|
|
|
|
spring.jpa.hibernate.ddl-auto= update
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# App Properties
|
|
|
|
|
|
|
|
bezkoder.app.jwtSecret= bezKoderSecretKey
|
|
|
|
|
|
|
|
bezkoder.app.jwtExpirationMs= 3600000
|
|
|
|
|
|
|
|
bezkoder.app.jwtRefreshExpirationMs= 86400000
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Run Spring Boot application
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
mvn spring-boot:run
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Run following SQL insert statements
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
INSERT INTO roles(name) VALUES('ROLE_USER');
|
|
|
|
|
|
|
|
INSERT INTO roles(name) VALUES('ROLE_MODERATOR');
|
|
|
|
|
|
|
|
INSERT INTO roles(name) VALUES('ROLE_ADMIN');
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Related Posts:
|
|
|
|
|
|
|
|
> [Spring Boot, Spring Security: JWT Authentication & Authorization example](https://www.bezkoder.com/spring-boot-security-login-jwt/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [For MySQL/PostgreSQL](https://www.bezkoder.com/spring-boot-login-example-mysql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [For MongoDB](https://www.bezkoder.com/spring-boot-mongodb-login-example/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## More Practice:
|
|
|
|
|
|
|
|
> [Spring Boot File upload example with Multipart File](https://bezkoder.com/spring-boot-file-upload/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Exception handling: @RestControllerAdvice example in Spring Boot](https://bezkoder.com/spring-boot-restcontrolleradvice/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot Repository Unit Test with @DataJpaTest](https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot Rest Controller Unit Test with @WebMvcTest](https://www.bezkoder.com/spring-boot-webmvctest/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot Pagination & Sorting example](https://www.bezkoder.com/spring-boot-pagination-sorting-example/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> Validation: [Spring Boot Validate Request Body](https://www.bezkoder.com/spring-boot-validate-request-body/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> Documentation: [Spring Boot and Swagger 3 example](https://www.bezkoder.com/spring-boot-swagger-3/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> Caching: [Spring Boot Redis Cache example](https://www.bezkoder.com/spring-boot-redis-cache-example/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Associations:
|
|
|
|
|
|
|
|
> [Spring Boot One To Many example with Spring JPA, Hibernate](https://www.bezkoder.com/jpa-one-to-many/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot Many To Many example with Spring JPA, Hibernate](https://www.bezkoder.com/jpa-many-to-many/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [JPA One To One example with Spring Boot](https://www.bezkoder.com/jpa-one-to-one/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Deployment:
|
|
|
|
|
|
|
|
> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://www.bezkoder.com/deploy-spring-boot-aws-eb/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Docker Compose Spring Boot and MySQL example](https://www.bezkoder.com/docker-compose-spring-boot-mysql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Fullstack Authentication
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Vue.js JWT Authentication](https://bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 8 JWT Authentication](https://bezkoder.com/angular-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 10 JWT Authentication](https://bezkoder.com/angular-10-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 11 JWT Authentication](https://bezkoder.com/angular-11-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 12 JWT Authentication](https://www.bezkoder.com/angular-12-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 13 JWT Authentication](https://www.bezkoder.com/angular-13-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 14 JWT Authentication](https://www.bezkoder.com/angular-14-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 15 JWT Authentication](https://www.bezkoder.com/angular-15-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 16 JWT Authentication](https://www.bezkoder.com/angular-16-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + Angular 17 JWT Authentication](https://www.bezkoder.com/angular-17-spring-boot-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Fullstack CRUD App
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
> [Vue.js + Spring Boot + H2 Embedded database example](https://www.bezkoder.com/spring-boot-vue-js-crud-example/)
|
|
|
|
## 🛠 기술 스택 (Tech Stack)
|
|
|
|
|
|
|
|
|
|
|
|
> [Vue.js + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-vue-js-mysql/)
|
|
|
|
- **Language:** Java 17
|
|
|
|
|
|
|
|
- **Framework:** Spring Boot 3.5.6
|
|
|
|
|
|
|
|
- **Build Tool:** Gradle
|
|
|
|
|
|
|
|
- **Database:** MariaDB (JPA / Hibernate)
|
|
|
|
|
|
|
|
- **Security:** Spring Security, JWT (jjwt 0.11.5)
|
|
|
|
|
|
|
|
- **Storage:** MinIO, AWS S3
|
|
|
|
|
|
|
|
- **Batch Processing:** Spring Batch
|
|
|
|
|
|
|
|
- **API Documentation:** Springdoc OpenAPI (Swagger UI)
|
|
|
|
|
|
|
|
- **Etc:** Lombok, Jsoup, Caffeine Cache, WebFlux
|
|
|
|
|
|
|
|
|
|
|
|
> [Vue.js + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-vue-js-postgresql/)
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 8 + Spring Boot + Embedded database example](https://www.bezkoder.com/angular-spring-boot-crud/)
|
|
|
|
## ⚙️ 설정 방법 (Configuration)
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 8 + Spring Boot + MySQL example](https://bezkoder.com/angular-spring-boot-crud/)
|
|
|
|
### 사전 요구 사항
|
|
|
|
|
|
|
|
- Java 17 이상 설치
|
|
|
|
|
|
|
|
- MariaDB 설치 및 데이터베이스 생성 (`autoflow`)
|
|
|
|
|
|
|
|
- MinIO 또는 S3 접근 권한 필요
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 8 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-spring-boot-postgresql/)
|
|
|
|
### 환경 설정 (`application.properties`)
|
|
|
|
|
|
|
|
`src/main/resources/application.properties` 파일에서 다음 항목들을 설정해야 합니다:
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 10 + Spring Boot + MySQL example](https://bezkoder.com/angular-10-spring-boot-crud/)
|
|
|
|
```properties
|
|
|
|
|
|
|
|
# 데이터베이스 설정
|
|
|
|
> [Angular 10 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-10-spring-boot-postgresql/)
|
|
|
|
spring.datasource.url=jdbc:mariadb://{DB_HOST}:3306/autoflow
|
|
|
|
|
|
|
|
spring.datasource.username={USER}
|
|
|
|
> [Angular 11 + Spring Boot + MySQL example](https://bezkoder.com/angular-11-spring-boot-crud/)
|
|
|
|
spring.datasource.password={PASSWORD}
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 11 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-11-spring-boot-postgresql/)
|
|
|
|
# JWT 보안 설정
|
|
|
|
|
|
|
|
cuuva.app.jwtSecret={YOUR_SECRET_KEY}
|
|
|
|
> [Angular 12 + Spring Boot + Embedded database example](https://www.bezkoder.com/angular-12-spring-boot-crud/)
|
|
|
|
|
|
|
|
|
|
|
|
# MinIO 설정
|
|
|
|
> [Angular 12 + Spring Boot + MySQL example](https://www.bezkoder.com/angular-12-spring-boot-mysql/)
|
|
|
|
minio.endpoint={MINIO_ENDPOINT}
|
|
|
|
|
|
|
|
minio.access-key={ACCESS_KEY}
|
|
|
|
> [Angular 12 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/angular-12-spring-boot-postgresql/)
|
|
|
|
minio.secret-key={SECRET_KEY}
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 13 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-13-crud/)
|
|
|
|
# 외부 서비스 연동
|
|
|
|
|
|
|
|
kubeflow.url={KUBEFLOW_URL}
|
|
|
|
> [Angular 13 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-13-mysql/)
|
|
|
|
mlflow.url={MLFLOW_URL}
|
|
|
|
|
|
|
|
```
|
|
|
|
> [Angular 13 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-13-postgresql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 14 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-14-crud/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 14 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-14-mysql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 14 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-14-postgresql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 15 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-15-crud/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 15 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-15-mysql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 15 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-15-postgresql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 15 + Spring Boot + MongoDB example](https://www.bezkoder.com/spring-boot-angular-15-mongodb/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 16 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-16-crud/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 16 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-16-mysql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 16 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-16-postgresql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 16 + Spring Boot + MongoDB example](https://www.bezkoder.com/spring-boot-angular-16-mongodb/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 17 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-17-crud/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 17 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-17-mysql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 17 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-17-postgresql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Angular 17 + Spring Boot + MongoDB example](https://www.bezkoder.com/spring-boot-angular-17-mongodb/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [React + Spring Boot + MongoDB example](https://bezkoder.com/react-spring-boot-mongodb/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Run both Back-end & Front-end in one place:
|
|
|
|
|
|
|
|
> [Integrate Angular with Spring Boot Rest API](https://bezkoder.com/integrate-angular-spring-boot/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Integrate React.js with Spring Boot Rest API](https://bezkoder.com/integrate-reactjs-spring-boot/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Integrate Vue.js with Spring Boot Rest API](https://bezkoder.com/integrate-vue-spring-boot/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## More Practice:
|
|
|
|
|
|
|
|
> [Spring Boot File upload example with Multipart File](https://bezkoder.com/spring-boot-file-upload/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Exception handling: @RestControllerAdvice example in Spring Boot](https://bezkoder.com/spring-boot-restcontrolleradvice/)
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot Repository Unit Test with @DataJpaTest](https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/)
|
|
|
|
## 🏃 실행 방법 (Getting Started)
|
|
|
|
|
|
|
|
|
|
|
|
> [Spring Boot Pagination & Sorting example](https://www.bezkoder.com/spring-boot-pagination-sorting-example/)
|
|
|
|
### Gradle을 이용한 실행
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
|
|
./gradlew bootRun
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Associations:
|
|
|
|
### 소스 빌드 및 배포 (JAR)
|
|
|
|
> [JPA/Hibernate One To Many example](https://www.bezkoder.com/jpa-one-to-many/)
|
|
|
|
```bash
|
|
|
|
|
|
|
|
./gradlew build
|
|
|
|
|
|
|
|
# 빌드된 파일 위치: build/libs/autoflow-server-mgmt-0.0.1-SNAPSHOT.jar
|
|
|
|
|
|
|
|
java -jar build/libs/autoflow-server-mgmt-0.0.1-SNAPSHOT.jar
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [JPA/Hibernate Many To Many example](https://www.bezkoder.com/jpa-many-to-many/)
|
|
|
|
### API 문서 (Swagger)
|
|
|
|
|
|
|
|
서버 실행 후 아래 주소에서 API 명세를 확인할 수 있습니다:
|
|
|
|
|
|
|
|
- `http://localhost:8080/swagger-ui/index.html`
|
|
|
|
|
|
|
|
|
|
|
|
> [JPA/Hibernate One To One example](https://www.bezkoder.com/jpa-one-to-one/)
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
Deployment:
|
|
|
|
## 📁 프로젝트 구조 (Project Structure)
|
|
|
|
> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://www.bezkoder.com/deploy-spring-boot-aws-eb/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
> [Docker Compose Spring Boot and MySQL example](https://www.bezkoder.com/docker-compose-spring-boot-mysql/)
|
|
|
|
```
|
|
|
|
|
|
|
|
kr.re.etri.autoflow
|
|
|
|
|
|
|
|
├── controllers # API 컨트롤러 (Auth, Project, Data, etc.)
|
|
|
|
|
|
|
|
├── service # 비즈니스 로직 처리
|
|
|
|
|
|
|
|
├── repository # 데이터 액세스 계층 (JPA)
|
|
|
|
|
|
|
|
├── entity # 데이터베이스 엔티티
|
|
|
|
|
|
|
|
├── security # 시큐리티 설정 및 JWT 처리
|
|
|
|
|
|
|
|
├── batch # Spring Batch 작업 구성
|
|
|
|
|
|
|
|
├── payload # Request/Response DTO
|
|
|
|
|
|
|
|
├── models # 도메인 모델
|
|
|
|
|
|
|
|
├── exception # 전역 예외 처리
|
|
|
|
|
|
|
|
└── common # 공통 유틸리티 및 설정
|
|
|
|
|
|
|
|
```
|
|
|
|
|