diff --git a/backend/pms/README.md b/backend/pms/README.md
index 4fc0db9..be41bbc 100644
--- a/backend/pms/README.md
+++ b/backend/pms/README.md
@@ -8,7 +8,7 @@
docker-compose up -d --build
```
----
+
#### 2. Create DB
@@ -16,7 +16,7 @@ docker-compose up -d --build
CREATE DATABASE "pms-db";
```
----
+
#### 3. Member Details
@@ -35,13 +35,13 @@ CREATE DATABASE "pms-db";
> password: 123
```
----
+
#### 4. Open API Documentation
```http://localhost:8080/swagger-ui/index.html```
----
+
#### 5. Create Access Token
@@ -64,9 +64,12 @@ response:
"refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6W3sicm9sZSI6IkFkbWluIn1dLCJleHAiOjE3MDcwNzY3NjEsImlhdCI6MTcwNzA3NTU2MSwiZW1haWwiOiJhQGEuY29tIn0.HmEI79h6_IZBsZDv73kMd6XTcfz5PJBq2WrZPXNXBt1vco-osuq5PiEzDPIAn_KYTVvlb8CSlEybyJMqss8tKQ"
}
```
+
---
+
+
## II. File Service
@@ -88,7 +91,7 @@ Response
}
```
----
+
#### 2. Display or Download API
@@ -97,8 +100,12 @@ Request:
curl --location 'http://localhost:8080/api/v1/files/16a1ddc7-b440-4dc7-8b4b-aba60a567d4a/download'
```
+
+
---
+
+
## III. Email Service
@@ -147,3 +154,56 @@ curl --location 'http://localhost:8080/api/v1/emails/send' \
"recipient": "a@a.com"
}'
```
+
+
+
+----
+
+
+
+## IV. System Design
+
+### 1. Token Issuance
+
+
+
+![Token Issuance](out/design/1_issue_token/1_issue_token.png)
+
+
+
+### 2. Token Verification
+![Token Verification](out/design/2_verify_token/2_verify_token.png)
+
+
+
+### 3. Token Renewal
+
+![Token Renewal](out/design/3_renew_token/3_renew_token.png)
+
+
+
+### 4. File Service
+
+![File Service](out/design/4_file_service/4_file_service.png)
+
+
+
+
+### 5. SMTP Service
+![alt text](out/design/5_smtp/5_smtp.png)
+
+
+
+Testing
+
+```plantuml.server
+@startuml
+ test -> hello: hi
+@enduml
+```
+
+```plantuml
+@startuml
+ test -> hello: hi
+@enduml
+```
\ No newline at end of file
diff --git a/backend/pms/design/1_issue_token.plantuml b/backend/pms/design/1_issue_token.plantuml
new file mode 100644
index 0000000..28b4de0
--- /dev/null
+++ b/backend/pms/design/1_issue_token.plantuml
@@ -0,0 +1,44 @@
+@startuml
+
+ participant Frontend as front
+ participant Backend as back
+ participant "JWT Filter" as filter
+ participant AuthSerivce as auth
+
+ |||
+ autonumber "0."
+ front -> back: request to create a token
+ note left
+ POST: **//.../auth/token//**
+ {
+ "email": "a@a.com"
+ "password": "***"
+ }
+ end note
+ back -> filter: request token
+ filter -> filter: skip JWT token verification
+ filter -> auth: request token
+ auth -> auth: authenticate email & password \n if fail send message "Invalid Credential"
+ auth -> auth:
+ note right
+ create and sign a JWT access and refresh token
+ with secret key.
+ claims:
+ - email
+ - role
+ - issued date
+ - expired date
+ end note
+
+ auth --> front: issue a token response:
+ note right of front
+ {
+ "accessToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij"
+ "refreshToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij"
+ }
+ end note
+
+|||
+
+
+@enduml
\ No newline at end of file
diff --git a/backend/pms/design/2_verify_token.plantuml b/backend/pms/design/2_verify_token.plantuml
new file mode 100644
index 0000000..2d40f4b
--- /dev/null
+++ b/backend/pms/design/2_verify_token.plantuml
@@ -0,0 +1,56 @@
+@startuml
+
+ participant Frontend as front
+ participant Backend as back
+ participant "JWT Filter" as filter
+ participant AuthSerivce as auth
+ participant ProfileController as controller
+
+ skinparam participant {
+ padding 50
+ }
+
+ |||
+ autonumber "0."
+ front -> back: request to get authenticated resource
+ note right front
+ GET: **//.../profile//**
+ Header:
+ Authorization: Bearer ASDFkoeq...
+ end note
+ |||
+ back -> filter: request resource
+ |||
+ filter -> filter: verify Bearer JWT token
+ |||
+ filter -> auth: request resource
+ auth -> auth: verify JWT token with secret \n if fail send message "Invalid Token"
+ |||
+ auth -> auth: validate JWT token if expired \n if fail send message "Expired Token"
+ |||
+ auth -> auth:
+ note right
+ extract info from JWT Token
+ claims:
+ - email
+ - role
+ end note
+ auth -> filter: return JWT Info
+ |||
+ filter -> filter: authenticated email and role into SecurityContextHolder
+ filter -> controller: forward requst to access resoure
+ controller -> controller: get resource
+
+ controller --> front: response profile:
+ note right of front
+ {
+ "name":"Mr.a"
+ "email": "a@a.com"
+ ...
+ }
+ end note
+
+|||
+
+
+@enduml
\ No newline at end of file
diff --git a/backend/pms/design/3_renew_token.plantuml b/backend/pms/design/3_renew_token.plantuml
new file mode 100644
index 0000000..03bab23
--- /dev/null
+++ b/backend/pms/design/3_renew_token.plantuml
@@ -0,0 +1,60 @@
+@startuml
+
+ participant Frontend as front
+ participant Backend as back
+ participant "JWT Filter" as filter
+ participant AuthSerivce as auth
+ skinparam participant {
+ padding 20
+ }
+
+|||
+autonumber "0."
+note over front
+ POST: **//.../auth/token/refresh//**
+ Header:
+ Authorization: Bearer ASDFkoeq...
+end note
+front -> back: request to renew token
+back -> filter: request to filter
+|||
+filter -> filter: verify Bearer JWT token
+|||
+filter -> auth: request to auth
+auth -> auth: verify JWT refresh token with secret \n if fail send message "Invalid Token"
+|||
+auth -> auth: validate JWT refresh token if expired \n if fail send message "Expired Token"
+|||
+auth -> auth:
+note right
+ extract info from JWT refresh token
+ claims:
+ - email
+ - role
+end note
+|||
+auth -> auth: validate and authenticate user
+|||
+auth -> auth:
+note right
+ create and sign a JWT access and refresh token
+ with secret key.
+ claims:
+ - email
+ - role
+ - issued date
+ - expired date
+end note
+
+auth --> front: issue a token response:
+note right of front
+ {
+ "accessToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij"
+ "refreshToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij"
+ }
+end note
+
+|||
+
+
+@enduml
\ No newline at end of file
diff --git a/backend/pms/design/4_file_service.plantuml b/backend/pms/design/4_file_service.plantuml
new file mode 100644
index 0000000..7a16df2
--- /dev/null
+++ b/backend/pms/design/4_file_service.plantuml
@@ -0,0 +1,106 @@
+@startuml
+
+ participant Frontend as front
+ participant Backend as back
+ participant "JWT Filter" as filter
+ participant AuthSerivce as auth
+ participant FileController as controller
+ participant FileService as service
+
+ skinparam participant {
+ padding 50
+ }
+ skinparam BoxPadding 50
+
+ |||
+
+group Upload File
+ autonumber "0."
+ front -> back: request to upload file
+ note over front
+ POST: **//.../files/upload//**
+ Content-Type:
+ multipart/form-data;
+ boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
+ Header:
+ Authorization: Bearer ASDFkoeq...
+ Form:
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW
+ Content-Disposition:
+ form-data; name="file"; filename=".../avatar.jpg"
+ Content-Type: image/jpeg
+
+ end note
+ |||
+ back -> filter: upload file
+ |||
+ filter -> filter: verify Bearer JWT token
+ |||
+ filter -> auth: upload file
+ auth -> auth: verify JWT token with secret \n if fail send message "Invalid Token"
+ |||
+ auth -> auth: validate JWT token if expired \n if fail send message "Expired Token"
+ |||
+ auth -> auth:
+ note right
+ extract info from JWT Token
+ claims:
+ - email
+ - role
+ end note
+ auth -> filter: return JWT Info
+ |||
+ filter -> filter: authenticated email and role into SecurityContextHolder
+ filter -> controller: upload file
+ controller -> service: send file to servcie
+ service -> service:
+ note right
+ - generate fileKey
+ - extract file metadata (filename, size)
+ - write the file to file storage mapped by fileKey
+ - generate a url to download file
+ - save (filekey, file metatdata, url) to table
+ end note
+ |||
+ service -> controller: response FileInfo
+
+
+
+ controller --> front: response file info:
+ note right of front
+ {
+ "url": "http://localhost:8080/api/v1/files/51e2d007-1799-4f97-b3fd-2090b4cad885/download",
+ "key": "51e2d007-1799-4f97-b3fd-2090b4cad885"
+ }
+ end note
+
+|||
+end
+|||
+
+group Download File
+|||
+
+ autonumber "0."
+ front -> back: download file
+ note over front
+ GET: **//.../files/{filekey}/download//**
+
+ end note
+ |||
+ back -> filter: download file
+ |||
+ filter -> filter: skip JWT token verification
+ |||
+ filter -> controller: download file
+ controller -> service: send file to servcie
+ service -> service: get file metadata by **{filekey}** from DB and \nvalidate if not exist throw message "file not found"
+ service -> service: read file by filekey
+ service -> controller: return file
+ controller -> front: stream file to client
+
+|||
+end
+
+
+@enduml
\ No newline at end of file
diff --git a/backend/pms/design/5_smtp.plantuml b/backend/pms/design/5_smtp.plantuml
new file mode 100644
index 0000000..602e5b2
--- /dev/null
+++ b/backend/pms/design/5_smtp.plantuml
@@ -0,0 +1,57 @@
+@startuml
+
+ participant Frontend as front
+ participant Backend as back
+ participant "JWT Filter" as filter
+ participant AuthSerivce as auth
+ participant EmailController as controller
+ participant EmailService as service
+
+ skinparam participant {
+ padding 50
+ }
+
+ |||
+ autonumber "0."
+ front -> back: request to send an email
+ note right front
+ GET: **//.../files/send//**
+ Header:
+ Authorization: Bearer ASDFkoeq...
+ {
+ "email": "a@a.com", // recipient
+ "content": "everyone good",
+ "recipient": "dengbunthai@gmail.com"
+ }
+ end note
+ |||
+ back -> filter: request to send an email
+ |||
+ filter -> filter: verify Bearer JWT token
+ |||
+ filter -> auth: request to send an email
+ auth -> auth: verify JWT token with secret \n if fail send message "Invalid Token"
+ |||
+ auth -> auth: validate JWT token if expired \n if fail send message "Expired Token"
+ |||
+ auth -> auth:
+ note right
+ extract info from JWT Token
+ claims:
+ - email
+ - role
+ end note
+ auth -> filter: return JWT Info
+ |||
+ filter -> filter: authenticated email and role into SecurityContextHolder
+ filter -> controller: forward requst to send the email
+ controller -> service: request service to send email
+ service -> Recipient: send email to recipient
+
+
+ controller --> front: response 204 no content
+
+|||
+
+
+@enduml
\ No newline at end of file
diff --git a/backend/pms/out/design/1_issue_token/1_issue_token.png b/backend/pms/out/design/1_issue_token/1_issue_token.png
new file mode 100644
index 0000000..71f23df
Binary files /dev/null and b/backend/pms/out/design/1_issue_token/1_issue_token.png differ
diff --git a/backend/pms/out/design/2_verify_token/2_verify_token.png b/backend/pms/out/design/2_verify_token/2_verify_token.png
new file mode 100644
index 0000000..d5b7f4b
Binary files /dev/null and b/backend/pms/out/design/2_verify_token/2_verify_token.png differ
diff --git a/backend/pms/out/design/3_renew_token/3_renew_token.png b/backend/pms/out/design/3_renew_token/3_renew_token.png
new file mode 100644
index 0000000..944e6d2
Binary files /dev/null and b/backend/pms/out/design/3_renew_token/3_renew_token.png differ
diff --git a/backend/pms/out/design/4_file_service/4_file_service.png b/backend/pms/out/design/4_file_service/4_file_service.png
new file mode 100644
index 0000000..f7c350a
Binary files /dev/null and b/backend/pms/out/design/4_file_service/4_file_service.png differ
diff --git a/backend/pms/out/design/5_smtp/5_smtp.png b/backend/pms/out/design/5_smtp/5_smtp.png
new file mode 100644
index 0000000..09409a1
Binary files /dev/null and b/backend/pms/out/design/5_smtp/5_smtp.png differ