Skip to content

recruitment-web — Repository Map

บทนี้เป็นแผนที่ไฟล์ของ recruitment-web (branch: prod, 200 tracked files) — CRA React 18 + Ant Design 5 + Recoil สำหรับเว็บสมัครงานสาธารณะ


แผนที่โฟลเดอร์ระดับ repo

recruitment-web/                          (branch: prod)
├── .env / .env.uat / .env.dev            # tracked ใน git — มี secret key จริง (SEC-RCWEB-01)
├── src/.env.example                      # ตัวอย่างใน src/ — CRA ไม่โหลดจากที่นี่
├── Dockerfile / default.conf             # multi-stage → nginx SPA
├── package.json                          # react-scripts 5, PORT=3006
├── public/
│   ├── index.html
│   └── manifest.json
├── postcss.config.js / tailwind.config.js
└── src/
    ├── index.js                          # RecoilRoot > BrowserRouter > App
    ├── App.js                            # theme, GA, route gate OTP
    ├── config.js                         # รวม REACT_APP_* เป็น object
    ├── config/
    │   ├── axios-rc.js                   # HTTP client หลัก → recruitment-api + refresh
    │   ├── axios-common.js               # client สำรอง — ไม่พบ caller
    │   └── routes.js
    ├── utils/
    │   ├── axios.js                      # client ที่ 3 — ใช้แค่ api/login.js (dead)
    │   ├── cryptoJs.js                   # AES encrypt/decrypt (secretKey + privateKey)
    │   ├── localStorage.js               # wrapper — expiry comment ปิดทุกจุด
    │   ├── file.js                       # FileReader → base64 data URL
    │   ├── formatDate.js / number.js / hexToRgba.js
    ├── api/
    │   ├── index.js / login.js           # dead code — ไม่มี import จาก pages
    ├── routes/
    │   ├── index.js
    │   └── mainRoutes.js                 # route table ทั้งแอป
    ├── recoil/states/default.js          # atoms: loading, theme, login popup, AI
    ├── components/
    │   ├── header/index.js               # login modal + OTP + decrypt หลัง login
    │   ├── footer/ / Form/* / modalRc.js / sharedModal.js ...
    ├── pages/
    │   ├── login/                        # form-signIn, form-otp, form-signOut, timers
    │   ├── home/                         # รายการประกาศงาน + search + AI recommend
    │   ├── jobDetail/                    # รายละเอียดตำแหน่ง + apply
    │   ├── jobHistory/                   # ประวัติการสมัคร (MyJob)
    │   ├── applicantProfile/             # ใบสมัคร 10 step — แกนหลัก
    │   ├── clear/                        # /RefreshData → localStorage.clear()
    │   └── notFound/
    ├── assets/                           # images, scss, theme, tutorial.pdf
    └── services/index.js

Boot sequence — จาก npm start / yarn build ถึงหน้าแรก

1. react-scripts start (PORT=3006) หรือ yarn build
       │  CRA โหลด .env* จาก root ของ project เท่านั้น
       │  `recruitment-web/package.json:40-41`

2. src/index.js
       │  createRoot → RecoilRoot → BrowserRouter → <App />
       │  `recruitment-web/src/index.js:11-18`

3. App.js
       │  ReactGA.initialize(REACT_APP_GA_ID)
       │  mute console นอก uat-jobs / localhost
       │  gate /myJob + /applicantProfile ด้วย decryptData(checkLogin)
       │  `recruitment-web/src/App.js:29-62`

4. routes/ → mainRoutes.js
       │  match path → page component
       │  `recruitment-web/src/routes/mainRoutes.js:9-42`

5. หน้า Home (path `/` หรือ `/:org`)
       │  HeaderHome + HomeContextProvider โหลด dropdown/job list

Route table ทั้งระบบ

จาก mainRoutes.js (branch: prod):

PathComponentAuth gate ใน App.js?Citation
/Homeไม่mainRoutes.js:11-13
/:orgHomeไม่mainRoutes.js:15-17
/JobDetail/:org/:jobIdJobDetailไม่mainRoutes.js:19-21
/MyJobJobHistoryใช่ — ต้องมี isOtpmainRoutes.js:23-25, App.js:59-61
/ApplicantProfileApplicantProfileใช่ — ต้องมี isOtpmainRoutes.js:27-29, App.js:59-61
/RefreshDataRefreshDataไม่ — clear แล้วไป /mainRoutes.js:31-33, pages/clear/index.js:6-8
/NotFoundNotFoundไม่mainRoutes.js:35-37
*<Navigate to="/" replace />mainRoutes.js:39-41

หมายเหตุ: path matching ของ React Router เป็น case-sensitive ตาม default ของ v6 — แต่ gate ใน App.js ใช้ pathname.toLowerCase().includes('myjob') / 'applicantprofile' (App.js:59) จึงครอบคลุมทั้ง /MyJob และ /myJob


Axios clients — สามตัวใน repo

ClientBase URL sourceAuth headerใช้จริง?Citation
config/axios-rc.jsREACT_APP_END_POINT_RECRUITMENT_APIAuthorization: Bearer {token} จาก localStorage.token + refresh บน 401/403ใช่ — ทุกหน้าหลักaxios-rc.js:10-78
config/axios-common.jsREACT_APP_END_POINT_COMMON_APIไม่ตั้ง (comment ปิด)ไม่พบ calleraxios-common.js:9-26
utils/axios.jsREACT_APP_KEY_URLAPI (config.baseUrl)Authorization = raw accessToken + CORS header ฝั่ง clientเฉพาะ api/login.js ที่ deadutils/axios.js:4-15, api/login.js:1

Refresh-token flow (axios-rc.js)

response 401 หรือ 403 และยังไม่ _retry
  │  `axios-rc.js:62`

POST {baseURL}/auth/refresh_token
  body: { refreshToken }
  header: x-refresh-token: true
  │  `axios-rc.js:34-37`

สำเร็จ → เขียน token/refreshToken ใหม่ แล้ว retry originalRequest
ล้มเหลว → message.error('Session หมดอายุ') + localStorage.clear() + redirect /
  │  `axios-rc.js:44-47`

Crypto helpers (utils/cryptoJs.js)

FunctionKey sourceพฤติกรรมCitation
encryptData(plaintext)config.secretKey (REACT_APP_SECRET_KEY)AES.encrypt → string; ถ้าไม่ใช่ string จะ JSON.stringify ก่อนcryptoJs.js:4-13
decryptData(encryptedData)config.secretKeyAES.decrypt → Utf8 → JSON.parsecryptoJs.js:15-21
encryptText(plaintext)config.privateKey (REACT_APP_PRIVATE_KEY)AES.encrypt field PII ก่อนส่ง APIcryptoJs.js:29-32
decryptText(encryptedText)config.privateKeyAES.decrypt field PII หลังรับจาก APIcryptoJs.js:23-27

encryptData/decryptData ใช้กับ object checkLogin (flag isOtp) ที่เก็บใน user-login storage — ไม่ใช่ field ใบสมัคร (header/index.js:63, App.js:52-55)

encryptText/decryptText ใช้กับ ~25+ field PII ของ applicant profile (content.js:706-748, header/index.js:104-148)

เนื่องจาก CRA bake REACT_APP_* เข้า bundle การ "เข้ารหัส" นี้ไม่ปกป้องความลับจากผู้ที่อ่าน JS bundle ได้ — ดู SEC-RCWEB-01


localStorage keys (utils/localStorage.js)

KeySetter / Getterเก็บอะไรExpiry จริง?Citation
user-loginsetUserStorage / getUserStorageemail, id, name, PII ใน data (JSON string ที่ decrypt แล้ว), checkLogin (AES ของ {isOtp}), profileImageไม่ — expiry comment ปิดที่บรรทัด 7, 19-26localStorage.js:35-46,1-28
tokenlocalStorage.setItem โดยตรงใน form-otpJWT access tokenไม่มี wrapperform-otp.js:54
refreshToken同上refresh tokenไม่มี wrapperform-otp.js:55
home-imagesetHomeImageStoragelogo/banner/theme ของ org unitไม่localStorage.js:50-60
rc-themesetThemeStorageสี theme (red / อื่น)ไม่localStorage.js:64-75
rc-job-recommend-aisetJobRecommendAIStoragecache แนะนำงาน AIไม่localStorage.js:79-90
rc-job-reqsetJobReqStoragejob ที่กำลังจะสมัคร ระหว่าง login flowไม่localStorage.js:97-107
search_historysetSearchHistoryStorageorg unit ที่เคยเลือกไม่localStorage.js:109-119

clearAllStorage() เรียก localStorage.clear() ทั้งก้อน (localStorage.js:93-95) — ใช้ตอน logout (header/index.js:270)

/RefreshData ก็ localStorage.clear() แล้ว navigate / (pages/clear/index.js:6-8)


Recoil atoms (recoil/states/default.js)

AtomDefaultใช้ทำอะไรCitation
isLoadingStatefalseoverlay loading ทั้งแอปdefault.js:5-8
loadingProgressStatenull% progress ตอน upload profiledefault.js:10-13
homeLogoState / homeBannerStatedefault logo / nullแสดงบนหน้า homedefault.js:15-23
themeState / themeColorStateDefaultTheme / 'red'CSS variablesdefault.js:25-37
isUpdateUserAfterSaveStatefalsetrigger header refresh หลัง savedefault.js:39-42
toggleLoginPopUpStatefalseบังคับเปิด login modaldefault.js:44-47
isAILoadingState / isGetJobRecommendAIStatefalseAI job recommend UIdefault.js:49-57

Applicant profile — โครงสร้าง 10 step

Progress labels จาก constant.js (progressSteps):

IndexTitle (UI)Module pathCitation
0ข้อมูลส่วนตัวpersonal/personal.jsconstant.js:54-56
1ข้อมูลการติดต่อcontact/contact.jsconstant.js:58-60
2ประวัติการศึกษาedu/education.js, edu/profesLicense.jsconstant.js:62-64
3ประสบการณ์การทำงานexp/index.jsconstant.js:66-68
4ความสามารถcapability/*constant.js:70-72
5บุคคลอ้างอิงreference/*constant.js:74-76
6เอกสารแบบattachment/*constant.js:78-80
7ประวัติดำเนินคดีcriminal/index.jsconstant.js:82-84
8ข่าวสารการรับสมัครinformation/index.jsconstant.js:86-88
9ข้อตกลงconsent/* + PDFViewerconstant.js:90-92

ไฟล์แนบประเภทมาตรฐาน (attachmentList): photo, cv, idCardCopy, houseRegisCopy, diploma, transcriptCopy, other (constant.js:23-52)

Accept MIME: PNG / JPEG / PDF; ขนาด ≤ 2MB ที่ client (constant.js:1-16, uploadForm.js:22-30)

Business logic รวมศูนย์ที่ content.js (~1,400 บรรทัด) — encrypt ก่อนส่ง, validate, reCAPTCHA, apply job


env files ที่พบใน repo

ไฟล์Tracked?ใช้เมื่อไหร่
.envใช่ค่า prod ที่ bake ตอน build ด้วยไฟล์นี้
.env.uatใช่ค่า UAT
.env.devใช่ค่า local (NODE_ENV=dev → OTP bypass)
src/.env.exampleใช่ตัวอย่างเท่านั้น — CRA อ่าน .env* จาก root ไม่ใช่ src/

Key ที่มีใน .env* (ชื่อเท่านั้น — ไม่ใส่ค่า secret ในเอกสารนี้):

PUBLIC_URL, REACT_APP_SECRET_KEY, REACT_APP_PRIVATE_KEY, REACT_APP_END_POINT_RECRUITMENT_API, REACT_APP_END_POINT_RECRUITMENT_API_ATTACHFILE, REACT_APP_END_POINT_COMMON_API, REACT_APP_END_POINT_WF_API_ATTACHFILE, REACT_APP_NODE_ENV, REACT_APP_GA_ID, REACT_APP_COOKIES, REACT_APP_RECAPTCHA_SITE_KET (typo — ดู QUAL-RCWEB-03)


Dead code ที่ยืนยันแล้ว

Pathหลักฐานว่า deadCitation
src/api/login.js + src/api/index.jsไม่มี component import; ใช้ endpoint login / login/check คนละชุดกับ OTP flow จริงapi/login.js:1-53
src/utils/axios.jsใช้เฉพาะผ่าน api/login.jsutils/axios.js:1-17
src/config/axios-common.jsไม่พบ import ... axios-common จาก pages/componentsaxios-common.js:1-28

Login จริงทั้งหมดวิ่งผ่าน components/header/index.js + pages/login/form-*.js + axios-rc.js