--- tags: Design Review --- # 1101 Design Review ## Wireframe / Mockup {%figma https://www.figma.com/file/u9h7OTqQH5A6xKIJBsfFBz/Dokodemo-Design?node-id=0%3A1 %} https://www.figma.com/file/u9h7OTqQH5A6xKIJBsfFBz/Dokodemo-Design?node-id=0%3A1 ## Architecture ![](https://i.imgur.com/0FLanHv.jpg) ### build image to docker container - [name=frontend] 1. `docker build -t frontend:v0.0.0 .` -> frontend 是image name冒號後面為加上tag的樣子(建議手打比較快,最後的句點要記得打!) 2. 到gitlab container registry 找到 CLI commands 3. copy P an 1mage指令到vs code上執行 `docker push registry.gitlab.com/utaipei-edu/day-off-backend` -> 複製完會長這樣 - [name=backend] 1. `docker build -t backend:v0.0.0 .` -> backend 是image name冒號後面為加上tag的樣子(建議手打比較快,最後的句點要記得打!) 2. 到gitlab container registry 找到 CLI commands 3. Push an image指令到vs code上執行 `docker push registry.gitlab.com/utaipei-edu/day-off-frontend` -> 複製完會長這樣 ### combine frontend and backend 的 image ```bash! docker-compose -f docker-compospp.y1l up -d ``` ##### ->pull frontend and backend的image ##### ->build container和幫container取名字和tag ```bash! docker-compose -f docker-compose-db.yml up -d ``` ```bash! docker-compose -f docker-compose-migrate.yml up -d ``` - [name=backend] 要更改mySQL內部東西的時候會用到 ```bash! docker-compose -f docker-compose-nginx.yml up -d ``` ##### ->設定反向代理 ## Spec/Interface ## Flow (with Sequence diagram) lino fi1e path or https://docs.gitlab.com/ee/administration/integration/plaml.h1ml ```plantuml @startuml participant Frontend order 10 participant Backend order 20 participant DB order 25 participant SchoolAPI order 30 ==POST Login API== Frontend->Backend:student_id, password Backend->SchoolAPI:student_id, password alt succes SchoolAPI->Backend:Login Success token, ... opt if user not in User table Backend->DB:add user to User table end Backend->DB:write login record Backend->Frontend:Login Success: token, studentId, name, code, msg else failed SchoolAPI->Backend:http 401 | 403 | 504 | 500 Backend->Frontend:msg, detail end ==when server is shutdown== Frontend->Backend:student_id, password Backend->x SchoolAPI:student_id, password Backend->Frontend:http 500 @enduml ``` ```plantuml @startuml participant Frontend order 10 participant Backend order 20 participant DB order 25 participant SchoolAPI order 30 participant Redis order 40 ==POST get record API== Frontend->Backend:token Backend->SchoolAPI:check user alt valid SchoolAPI->Backend: user valid DB->Backend: 去 DB 抓所有的資料 alt Redis有資料 Redis->Backend: 去 Redis 抓課程查詢區間 else Redis無資料且DB也沒有資料 Backend->Backend: 找不到課程查詢區間,DB也沒有資料,用預設值 else Redis無資料但DB有資料 Backend->Backend: 找不到課程查詢區間,從DB拿出來的資料找 end Backend->SchoolAPI:get record by token SchoolAPI->Backend:record Backend->Backend: 比對資料,更新請假審核狀態 Backend->DB: 將更新後的請假審核狀態寫入DB DB->Backend:再去DB抓一次資料,資料查詢時已經分頁且過濾 Backend->Frontend:record, code, msg else invalid SchoolAPI->Backend: 資料驗證錯誤 Backend->Frontend:http 401 end ==when server is shutdown== Frontend->Backend:請假紀錄 Backend-x SchoolAPI:check user Backend->Frontend:http 500 @enduml ``` ```plantuml @startuml participant Frontend order 10 participant Backend order 20 participant DB order 25 participant SchoolAPI order 30 participant Redis order 40 ==POST Leave application API== Frontend->Backend:請假紀錄 Backend->SchoolAPI:check user alt valid SchoolAPI->Backend: user valid DB->Backend:get user and course data Backend->Backend:check payload data Backend->SchoolAPI:POST token, 請假紀錄 SchoolAPI->Backend:http 200 Success: data, ... Backend->Redis:更新課程查詢區間 Backend->DB:write 請假紀錄 alt write to DB success Backend->Frontend:code, msg else failed Backend->Backend:create background task for retry Backend->Frontend:http 500 end else invalid SchoolAPI->Backend: 資料驗證錯誤 Backend->Frontend:http 401 end ==when server is shutdown== Frontend->Backend:請假紀錄 Backend-x SchoolAPI:check user Backend->Frontend:http 500 @enduml ``` ```plantuml participant Frontend order 10 participant Backend order 20 participant DB order 25 participant SchoolAPI order 30 @startuml ==GET getCourse API== Frontend->Backend:token, search_date Backend->SchoolAPI:check user alt valid SchoolAPI->Backend: user valid Backend->SchoolAPI:GET token, search_date SchoolAPI->Backend:http 200: courses, ... Backend->DB:write courses to DB Backend->Backend:移除請假過的節次 Backend->Frontend:courses, code, msg else invalid SchoolAPI->Backend: 資料驗證錯誤 Backend->Frontend:http 401 end ==when server is shutdown== Frontend->Backend:token, search_date Backend-x SchoolAPI:check user Backend->Frontend:http 500 @enduml ``` ```plantuml participant Frontend order 10 participant Backend order 20 participant DB order 25 participant SchoolAPI order 30 @startuml ==POST logout API== Frontend->Backend:token, search_date Backend->SchoolAPI:check user alt valid SchoolAPI->Backend: user valid Backend->DB: remove user's token from DB Backend->SchoolAPI:POST school logout API Backend->Frontend:code, msg else invalid SchoolAPI->Backend: 資料驗證錯誤 Backend->Frontend:http 401 end ==when server is shutdown== Frontend->Backend:token, search_date Backend-x SchoolAPI:check user Backend->Frontend:http 500 @enduml ``` ```plantuml @startuml ==GET leave-type== Frontend->Backend:GET /api/v1/leave-type Backend->Backend:load all selectable leave-type Backend->Frontend:code, msg, leave_types @enduml ``` ```plantuml @startuml ==POST frontend-log== Frontend->Backend:POST log_level, log_message Backend->Backend:output frontend log in backend Backend->Frontend:code, msg @enduml ``` ### Frontend ```plantuml @startuml participant Frontend order 20 participant Backend order 30 participant User order 10 ==Login Page== User->Frontend:輸入帳號密碼 Frontend->Frontend: 按下登入按鈕:加密帳號密碼 Frontend->Backend: post送出已加密的帳號密碼 Backend->Frontend: check user password correct or not alt 帳號密碼錯誤 Backend->Frontend: 帳號密碼錯誤:msg detail Frontend->User: 顯示在螢幕上:帳號密碼錯誤 else 帳號密碼正確 Backend->Frontend: token, userName, StudentId Frontend->User: save token, change path to record page end ==Record Page== Frontend->Backend: get送出 page:1, token, record status(all, pending, approved, refused) Backend->Frontend: data alt 沒有資料 Frontend->User: no data page else loading Frontend->User: loading page else 成功拿到資料 Frontend->Frontend: 記錄current page number Frontend->User: 顯示請假紀錄 User->Frontend: User往下滑 Frontend->Backend: get送出 page:2, token, record status(all, pending, approved, refused) end @enduml ==Application Page== User->Frontend: 選擇假別 Frontend->Backend: get leave type Backend->Frontend: all leave type Frontend->User:彈出modal給user選擇假別 User->Frontend:選擇日期 Frontend->Backend: get course Backend->Frontend: all course of the user of the date Frontend->User:使用者螢幕出現選擇課堂的選項 User->Frontend:點按課堂 Frontend->User:彈出modal給user選擇課程 User->Frontend:選擇課堂 Frontend->User:顯示節次選項 User->Frontend:選擇節次 Frontend->User:預覽請假的按鈕亮起 alt 填寫緣由 User->Frontend:填寫緣由 User->Frontend:按下預覽按鈕 else User->Frontend:未填寫緣由(資料為null) User->Frontend:按下預覽按鈕 end Frontend->Backend:post送出請假資料 Backend->Frontend:請假是否成功 @enduml ``` ```plantuml @startuml participant Form order 0 participant Modal order 10 participant ComponentInsideModal order 20 Form->Form:當使用者點選表單 Form->Modal:data, modalType, title Modal->Modal:判斷modalType對應的元件 Modal->ComponentInsideModal:呼叫對應的元件(calendar, list, preview leave record) Modal->ComponentInsideModal:data ComponentInsideModal->ComponentInsideModal:顯示資料 ComponentInsideModal->Modal:當使用者選擇選項,回傳data Modal->Modal:setState讓按鈕亮起來 Modal->Modal:使用者點選確定,關閉表單 Modal->Form:將使用者選擇的data回傳 @enduml ``` ### Backend #### API1 - 取得請假紀錄 - [name=brian] Path: `/api/v1/record` Request: | Method | Header | query | |:------:|:----------------------------------------------------------------------------------------------- |:------------------ | | GET | Content-Type: application/json <br> x-trace-id: "\<uuid4\>" </br> token: Bearer "\<JWT TOKEN\>" | page: start from 1 | Response: - Status code 200 ```json! { "code":0, "msg":"SUCCESS", "data":{ "hasNextPage":true, "records":[ { "uuid":"3c4ea096-e04f-40f1-8911-6846f1084979", "name":"資料庫", "code":"1234", "date":"2022/10/18", "weekday":4, "sessions":[ 5, 6, 7 ], "teacher":"大衛", "leaveType":"病假", "status":"approved", "sendTime":"2022/10/17 12:30" } ] } } ``` #### API2 - 判斷登入 for frontend - [name=yaya] Path: `/api/v1/login` Request: | Method | Header |request body | |:------:|:-----------------------:|:---------------------------------------------------------------------------------------------| | POST | Content-Type: application/json <br> x-trace-id: "\<uuid4\>" </br> |```{"studentId": "test1","password": "1"} ```| Response: - Status code 200 ```json! { "code": 0, "msg": "SUCCESS", "data": [ { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZDRlOGQ5YTA2MWMxYTJjMDIxY2JlMTgiLCJpYXQiOjE1NjU4NTczMjAsImV4cCI6MTU2NTk0MzcyMH0.GQVyQJLmwXd2jQZsjZ8n6cAWD0HQGjvlp2Mk8kAsGy8", "userName": "金宣妘", "studentId": "U10816014", } ] } ``` - Status code 401 ```json! { "code": 2, "msg": "帳號或密碼錯誤,請重新輸入", "data": [] } { "code": 2, // 2 or 6? "msg": "帳號或密碼為空,請重新輸入", "data": [] } ``` - Status code 403 ```json! { "code": 3, "msg": "Suspended account", "data": [] } ``` - Status code 504 ```json! { "code": 4, "msg": "Timeout", "data": [] } ``` - Status code 500 ```json! { "code": 5, "msg": "Other", "data": [] } ``` ### API3 - Mock Get Leave History API Path: /api/v1/mock/record Request: | Method | Header | Parameter | Note | | :----: | :------------------------------------ | :--------- | :--- | | GET | Content-Type: application/json | student_id: string || | | x-trace-id: "\<uuid4\>" | from_date: date(ISO8601 - UTC+8) | e.g. 2022-10-19 | | | Authorization: Bearer "\<JWT TOKEN\>" | to_date: date(ISO8601 - UTC+8) | e.g. 2023-01-19 | Response: - Status code 200 ```json! { "code": 0, "msg": "Success", "data": [ { "name": "<name>", "code": "<code>", "date": "<date:ISO8601 - UTC+8>", "weekday": "<weekday:integer, 1-7>", "sessions": [<session:integer, 1-12>], "teacher": "<teacher_fullname>", "leave_type": <leave_type code> }, ... ] } ``` 1. `weekday` in range 1-7 stands for Mon. to Sun. 2. `sessions` array, items are integer in range 1-12 stands for 節次 in a day 3. `leave_type` should have latest leave status in each query, format can be decided by original system - Status code 400 ```json! { "msg": "Invalid query param: to_date", "code": 1100400 } ``` ## References leave_type code mapping: | 假別代碼 | 假別名稱 | 假別簡稱 | |:-------- | -------- |:-------- | | 0 | | | | 11 | 早退 | 退 | | 21 | 事假 | 事 | | 22 | 病假 | 病 | | 23 | 公假 | 公 | | 24 | 喪假 | 喪 | | 25 | 婚假 | 婚 | | 26 | 孕(產)假 | 產 | | 27 | 哺育假 | 哺 | | 28 | 防疫假 | 疫 | | 29 | 公傷假 | 傷 | | 30 | 生理假 | 生 | | 51 | 請假 | 假 | | 52 | 疫苗假 | 苗 | ## DB Schema --- tags: backend --- # DB schema ### user | attribute | Type | Example | Note | |:--------------- |:--------------------------- |:------------------------------------ |:-------------------------------- | | user_id | VARCHAR(36)</br>**Primary Key** | 842f638c-4f58-11ed-8453-0242ac1b0003 | uuid | | student_id | VARCHAR(256) | u11016025 | Index1 | | name | VARCHAR(256) | Brian | Index2 | | email | VARCHAR(256) | u11016025@go.utaipei.edu.tw | Index3 | | enabled | TINYINT(1) | 1 |0: 停權, 1: 授權 | | last_updated_at | TIMESTAMP | 2022-10-29 02:48:31 | Default: current time</br>onupdate: current time | | last_updated_by | VARCHAR(256) | Brian | | | created_at | TIMESTAMP | 2022-10-19 00:00:00 | Default: current time | | token | VARCHAR(512) | (token) | | ### leave_record | attribute | Type | Example | Note | |:--------------- |:------------------------------- |:------------------------------------ |:------------------------------------------------ | | record_id | VARCHAR(36)</br>**Primary Key** | 9a4e7638-e47f-41e7-b09b-ef6aad43276e | uuid | | user_id | VARCHAR(36)</br>**Foreign Key** | 842f638c-4f58-11ed-8453-0242ac1b0003 | | | date | TIMESTAMP | | | | weekday | SMALLINT | 1 | | | sessions | VARCHAR(256) | 1, 2,3 | | | leave_type | SMALLINT | 22 | | | status | SMALLINT | 1 | 0:審核, 1:通過, 拒絕 | | last_updated_at | TIMESTAMP | 2022-10-29 02:48:31 | Default: current time</br>onupdate: current time | | send_time | TIMESTAMP | 2022-10-19 00:00:00 | Default: current time | | course_id | VARCHAR(256)<br>**foreign key** | 9a4e7638-e47f-41e7-b09b-ef6aad43276e | | | last_check_at | TIMESTAMP | 2022-10-30 00:00:00 | 上一次與校務系統核對時間 | ### course | attribute | Type | Example | Note | |:--------------- |:--------------------------- |:------------------------------------ |:------------------------------------------------ | | course_id | VARCHAR(36)</br>**Primary Key** | 9a4e7638-e47f-41e7-b09b-ef6aad43276e | uuid | | code | VARCHAR(256) | 1234 | Index1 | | name | VARCHAR(256) | | Index2 | | weekday | SMALLINT | 1 | | | sessions | VARCHAR(256) | 1, 2, 3 | | | teacher | VARCHAR(256) | | | | last_updated_at | TIMESTAMP | 2022-10-19 02:48:31 | Default: current time</br>onupdate: current time | ### login_record | attribute | Type | Example | Note | |:--------------- |:--------------------------- |:------------------------------------ |:------------------------------------------------ | | login_record_id | VARCHAR(36)</br>**Primary Key** | 9a4e7638-e47f-41e7-b09b-ef6aad43276e | uuid | | user_id | VARCHAR(256)</br>**Foreign Key** | 842f638c-4f58-11ed-8453-0242ac1b0003 | uuid | | ip | VARCHAR(256) | | Index2 | | login_time | TIMESTAMP | | | | status | SMALLINT | 2 | error code | | user_agent | VARCHAR(512) | | | ![](https://i.imgur.com/PPZLREO.png) ## Prerequisites - 校務系統快生出來ˋˊ # Frontend 元件階層 - <font color="yellow">黃色</font>是網頁的頁面(eg: "localhost:3000/record") - <font color="pink">粉紅色</font>是在 "/src/pages" 底下的component - <font color="lightblue">淺藍色</font>是在 "/src/componenets"底下的component - <font color="lightgreen">綠色</font>是個別檔案底下的小元件或是render function ```plantuml @startuml hide empty description state login #yellow{ state loginPage{ loginPage: path=src/pages/login/loginPage.js } } @enduml ``` ```plantuml @startuml state record #yellow{ state menuLayout#pink{ menuLayout: path= src/pages/record/menuLayout.js menuLayout: rwd的menu,電腦menu會在頁面左半邊,手機menu會在floatingButton } state pageContent#lightgreen{ pageContent: path=src/componenets/record/categoryMenu.js } state categoryMenu#lightblue{ categoryMenu: path=src/componenets/record/categoryMenu.js } state floatingButton#lightblue{ floatingButton: path=src/componenets/record/floatingButton.js } state desktopMenu#lightblue{ desktopMenu: path=src/componenets/record/desktopMenu.js } state emptyComponent#lightblue{ emptyComponent: path=src/componenets/record/emptyComponent.js } state loadingAnimation#lightblue{ loadingAnimation: path=src/componenets/record/loadingAnimation.js } state recordCard#lightblue{ recordCard: path=src/componenets/record/recordCard.js } state recordDetail#lightblue{ recordDetail:path=src/componenets/record/recordCardDetail.js } state errorRecord#lightgreen{ errorRecord: path=/src/componenets/record/categoryMenu.js } state modal_from_application#lightblue{ modal_from_application: path=src/componenets/form/modal.js } state isLoading <<choice>> state hasData <<choice>> } menuLayout --> desktopMenu menuLayout --> categoryMenu categoryMenu --> floatingButton categoryMenu --> pageContent pageContent --> isLoading isLoading --> loadingAnimation:[isLoading: true] isLoading --> hasData :[isLoading:false] hasData -->emptyComponent:[no record data] hasData --> recordCard : [has record data] hasData --> errorRecord : [get record data error] recordCard --> modal_from_application modal_from_application --> recordDetail @enduml ``` ```plantuml @startuml state application #yellow{ state applicationPage #pink{ applicationPage: path=src/pages/form/applicationPage.js } state calendar #lightblue{ calendar: path=src/componenets/form/calendar.js } state form #lightblue{ form: path=src/componenets/form/form.js } state list #lightblue{ list: path=src/componenets/form/list.js } state loadingSpinner#lightblue{ loadingSpinner:src/componenets/form/loadingSpinner.js } state modal#lightblue{ modal:path=src/componenets/form/modal.js } state previewLeaveRecord#lightblue{ previewLeaveRecord: path=src/componenets/form/previewLeaveRecord.js } } applicationPage --> form form --> modal form --> loadingSpinner modal --> calendar modal --> list modal --> previewLeaveRecord @enduml ``` ```plantuml @startuml state result #yellow{ state failType <<choice>> state resultPage#pink{ resultPage: path=src/pages/result/resultPage.js } state resultExpire#lightblue{ resultExpire:path=src/componenets/result/resultExpire.js } state resultNetworkFail#lightblue{ resultNetworkFail:path=src/componenets/result/resultNetworkFail.js } state resultSuccess#lightblue{ resultSuccess:path=src/componenets/result/resultSuccess.js } state resultSystemFail#lightblue{ resultSystemFail:path=src/componenets/result/resultSystemFail.js } } resultPage --> failType failType --> resultSuccess :[請假成功:成功送出請假] failType --> resultExpire :[請假失敗:老師已送出點名單] failType --> resultNetworkFail:[請假失敗:基於使用者網路連線導致的錯誤] failType --> resultSystemFail :[請假失敗:因為校務系統或各種其他錯誤導致請假失敗] @enduml ```