neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Nền tảng học trực tuyến full-stack: học viên mua và học khoá học, làm bài kiểm tra và nhận chứng chỉ; giảng viên soạn bài; quản trị duyệt nội dung và xác nhận thanh toán chuyển khoản. Phân quyền ba vai, ví coin nội bộ, đăng nhập Google OAuth 2.0, phiên đăng nhập bằng cookie httpOnly.README 전문 보기 ↗
Nền tảng học trực tuyến full-stack: học viên mua và học khoá học, làm bài kiểm
tra và nhận chứng chỉ; giảng viên soạn bài; quản trị duyệt nội dung và xác nhận
thanh toán chuyển khoản. Phân quyền ba vai, ví coin nội bộ, đăng nhập Google
OAuth 2.0, phiên đăng nhập bằng cookie httpOnly.
Demo: learning-portal-s.vercel.app
Tài liệu API: thesang2610.github.io/learning-portal
| Framework | Next.js 16 — App Router, Turbopack |
| UI | React 19, TypeScript 5 |
| Styling | CSS Module SCSS (Sass) |
| Chất lượng | ESLint, Prettier, tsc --noEmit, husky + lint-staged |
| Máy chủ | Express 5 (CommonJS) |
| CSDL | MongoDB Atlas qua Mongoose 9 |
| Xác thực | JWT trong cookie httpOnly, Google OAuth 2.0 |
| Lưu trữ | Cloudinary (ảnh, video, tài liệu) |
| Nodemailer qua Gmail app password | |
| Kiểm thử | node:test — 158 test |
| Triển khai | Vercel (hai project riêng) |
| CI | GitHub Actions — hai job song song |
| Tài liệu API | Swagger UI trên GitHub Pages |
┌──────────────────────────┐
Trình duyệt ───► │ Next.js (Vercel) │
│ learning-portal-s │
│ SSR + tĩnh hoá tại biên │
└───────────┬──────────────┘
│ fetch, credentials: include
▼
┌──────────────────────────┐
│ Express 5 (Vercel) │ ◄── Google OAuth 2.0
│ learning-portal-s-api │ ──► Gmail SMTP
│ serverless function │
└───┬──────────────────┬───┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────┐
│ MongoDB Atlas │ │ Cloudinary │
│ 17 collection │ │ media, tệp │
└──────────────────┘ └──────────────┘
Frontend và API nằm khác site (.vercel.app thuộc Public Suffix List), nên
cookie phiên phải là sameSite: none. Hệ quả: trình duyệt gửi cookie kèm cả
request từ site khác, và middleware chongCsrf trở thành lớp chặn CSRF duy
nhất — không có sameSite đỡ lưng.
learning-portal/
├── frontend/ # Next.js 16 — giao diện
├── backend/ # Express 5 — REST API
├── docs/ # Swagger UI + openapi.json (GitHub Pages)
├── .github/workflows/ # CI hai job song song
└── README.md
docs/ phải ở gốc kho — GitHub Pages chỉ đọc thư mục gốc hoặc /docs ở
gốc, để nó trong thư mục con là trang tài liệu chết.
frontend/
├── public/ # Ảnh tĩnh, favicon, api-docs.html
├── app/ # App Router — 56 trang, 3 nhóm route
│ ├── globals.css # CSS toàn cục — nạp nền + bảng token
│ ├── api/auth/google/ # Đổi mã Google OAuth lấy phiên
│ │
│ ├── (portal)/ # KHU HỌC VIÊN — 19 trang
│ │ ├── page.tsx # Trang chủ
│ │ ├── courses/ # Danh sách, lọc theo danh mục
│ │ ├── course/ # Chi tiết khoá học
│ │ ├── learn/ # Trình phát bài học, theo dõi tiến độ
│ │ ├── collection/ # Khoá đã ghi danh
│ │ ├── payment/ # Nạp coin, mã VietQR
│ │ ├── share-document/ # Chia sẻ tài liệu
│ │ │ └── [id]/ # Chi tiết một tài liệu
│ │ ├── blog/[slug]/ # Bài viết
│ │ ├── gpa-calculator/ # Tính GPA
│ │ ├── convert-10-to-4/ # Quy đổi thang 10 sang thang 4
│ │ ├── calc-point/ # Tính điểm học phần
│ │ ├── user/profile/ # Hồ sơ, chứng chỉ đã đạt
│ │ ├── user/settings/ # Đổi mật khẩu, ảnh đại diện
│ │ ├── auth/callback/ # Đích quay về sau đăng nhập Google
│ │ └── help · terms · privacy/ # Trang tĩnh
│ │
│ ├── (instructor)/instructor/ # KHU GIẢNG VIÊN — 10 trang
│ │ ├── courses · course-create · course-detail/
│ │ ├── lessons · lesson-create · lesson-detail/
│ │ └── quiz-create · quiz-edit · quiz-stats/
│ │
│ └── (admin)/admin/ # KHU QUẢN TRỊ — 27 trang
│ ├── dashboard/ # Thống kê tổng quan
│ ├── users · courses · lessons · enrollments/
│ ├── orders/ # Xác nhận đơn chuyển khoản
│ ├── coin/ # Cộng, trừ, xem lịch sử ví
│ ├── certificates · reviews · posts · faqs/
│ ├── categories · providers · banners/
│ └── home-banners · home-most-popular/ # Sắp xếp trang chủ
│ home-new-releases · home-trending-now/
│
└── src/
├── components/ # 13 nhóm giao diện
│ ├── layout/ # Header theo vai, Footer
│ ├── home/ # Khối trang chủ
│ ├── courses/ # Thẻ khoá học, bộ lọc
│ ├── quiz/ # Làm bài, xem lại
│ ├── certificate/ # Chứng chỉ, trang xác minh
│ ├── document/ # Tải lên, xem tài liệu
│ ├── admin/ # Bảng, biểu mẫu, trình soạn bài
│ ├── auth · profile · settings · gpa/
│ ├── common/ # Dùng chung nhiều khu
│ └── ui/ # Nút, ô nhập, hộp thoại
│
├── services/ # 21 tệp gọi API, mỗi tệp một miền
│ ├── api.ts # fetch bọc sẵn, credentials: include
│ ├── serverFetch.ts # Dành cho Server Component
│ ├── apiHelper.ts # Xử lý lỗi, chuẩn hoá phản hồi
│ ├── course · lesson.api · enrollment.api/
│ ├── quizService · certificate · review/
│ ├── coin.api · order/ # Ví coin, đơn chuyển khoản
│ ├── document · post · faq · banner/
│ ├── categoryService · provider · userApi · adminService/
│ └── diaChiApi · quyDinh/
│
└── hooks/
└── userStore.ts # Nhớ người dùng giữa các lần dựng
Component không tự gọi fetch — luôn đi qua services/, để khi đổi cách
xác thực chỉ phải sửa một chỗ. serverFetch.ts tách riêng vì Server Component
không có cookie của trình duyệt, phải chuyển tiếp header thủ công.
backend/
├── index.js # Điểm vào: CORS, CSRF, gắn route
├── vercel.json # Cấu hình hàm serverless
│
└── src/
├── config/
│ ├── db.js # Kết nối MongoDB, dùng lại giữa các lần gọi
│ ├── cloudinary.js # Ưu tiên CLOUDINARY_URL, lùi về ba biến rời
│ ├── mail.js # Thiếu cấu hình thì bỏ qua, KHÔNG ném lỗi
│ └── payment.js # Sinh mã VietQR theo từng đơn
│
├── middlewares/
│ ├── authMiddleware.js # protect · instructor · admin
│ ├── csrf.js # Xét Origin, lùi về Referer
│ ├── loginRateLimit.js # Đếm hai khoá: ip|email VÀ ip riêng
│ ├── rateLimit.js # Giới hạn tần suất dùng chung
│ ├── cacheControl.js # Đặt no-store cho dữ liệu riêng tư
│ └── validObjectId.js # Chặn ObjectId sai dạng trước khi truy vấn
│
├── models/ # 17 lược đồ Mongoose
│ ├── User · Course · Lesson · Enrollment/
│ ├── Quiz · QuizAttempt · Certificate · Achievement/
│ ├── Order · GiaoDichCoin/ # Đơn hàng và sổ cái ví coin
│ ├── Post · Document · Review · Faq · Banner/
│ └── categoryModel · providerModel/
│
├── routes/ # 16 tệp, 136 endpoint
│ ├── userRoutes · adminRoutes/
│ ├── courseRoutes · lessonRoutes · enrollmentRoutes/
│ ├── quizRoutes · certificateRoutes · reviewRoutes/
│ ├── coinRoutes · orderRoutes/
│ └── postRoutes · documentRoutes · faqRoutes/
│ bannerRoutes · categoryRoutes · providerRoutes/
│
├── controllers/ # 18 tệp, logic nghiệp vụ
│ └── adminOrderController.js # Xác nhận đơn, cộng coin, gửi mail
│
└── utils/ # Công cụ dùng chung + tệp *.test.js
├── contentAccess.js # CỬA DUY NHẤT gác nội dung có phí
├── coinWallet.js · coin.js # Trừ/cộng coin bằng một lệnh ghi
├── cookieToken.js # NƠI DUY NHẤT đặt cookie phiên
├── postHtml.js # Lọc HTML chặn XSS lưu trữ
├── password.js # Băm bcrypt, quy tắc độ mạnh
├── contentFilter.js # Lọc từ ngữ vi phạm
├── queryParams.js # Phân trang, sắp xếp dùng chung
├── uploadCloud.js · textUtils.js · enrollment.js · orderMail.js/
└── checkDb · checkMail · checkCloudinary · adminResetPassword/
# Script chạy tay, không phải route
Tệp test đặt ngay cạnh tệp được kiểm, tên *.test.js — sửa một hàm thì
thấy ngay bài test của nó, không phải đi tìm ở cây thư mục khác.
git clone https://github.com/TheSang2610/learning-portal.git
cd learning-portal
cd backend
npm install
cp .env.example .env # rồi điền giá trị thật
npm run dev # http://localhost:5000
Biến môi trường trong backend/.env:
| Biến | Bắt buộc | Ý nghĩa |
|---|---|---|
MONGO_URI | có | Chuỗi kết nối MongoDB |
JWT_SECRET | có | Khoá ký token phiên |
FRONTEND_ORIGINS | có | Origin được phép gọi CORS, cách nhau dấu phẩy. Cái đầu tiên cũng là gốc để ghép link trong thư xác minh |
FRONTEND_URL | không | Ghi đè gốc dùng cho link xác minh, khi nó khác cái đầu của FRONTEND_ORIGINS |
CLOUDINARY_URL | có | Dạng gộp cloudinary://key:secret@cloud |
GOOGLE_CLIENT_ID | không | Bật đăng nhập Google |
MAIL_USER · MAIL_APP_PASSWORD | có trên bản chạy thật | Mail báo đơn chuyển khoản, và thư xác minh khi đăng ký. Thiếu thì đăng ký quay về hành vi cũ và lộ email nào đã tồn tại |
MAIL_ADMIN | không | Địa chỉ nhận thông báo quản trị. Trống thì gửi về MAIL_USER |
SO_TAI_KHOAN · TEN_TAI_KHOAN · NGAN_HANG | không | Hiển thị ở màn hình chuyển khoản |
Kiểm tra kết nối bên ngoài:
npm run check:db # thử kết nối MongoDB
npm run check:cloudinary # thử tải ảnh lên
npm run check:mail # gửi thử một mail thật
npm run check:ratelimit # kiểm bộ đếm chống dò mật khẩu trên CSDL thật
npm run check:dangky # kiểm đầu-cuối luồng đăng ký có xác minh email
cd frontend
npm install
echo "NEXT_PUBLIC_API_URL=http://localhost:5000" > .env.local
npm run dev # http://localhost:3000
Biến NEXT_PUBLIC_* bị nhúng thẳng vào JavaScript gửi xuống trình duyệt —
không đặt bí mật nào vào đó. Chúng cũng được cố định lúc build, nên đổi
giá trị trên Vercel xong phải deploy lại mới có tác dụng.
136 endpoint / 16 nhóm. Tài liệu đầy đủ, bấm thử được: Swagger UI
| Nhóm | Số lượng | Ví dụ |
|---|---|---|
| Quản trị | 23 | GET /api/admin/dashboard/statistics |
| Khoá học | 14 | PUT /api/courses/:id/publish |
| Người dùng | 13 | POST /api/users/login · POST /api/users/verify-email |
| Trắc nghiệm | 11 | POST /api/quizzes/:id/submit |
| Ghi danh | 10 | PUT /api/enrollments/:id/progress |
| Chứng chỉ | 9 | GET /api/certificates/verify/:code |
| Đánh giá | 9 | POST /api/reviews/:id/helpful |
| Bài viết | 8 | GET /api/posts/:slug |
| Ví coin | 5 | GET /api/coins/lich-su |
| Đơn hàng | 5 | PUT /api/admin/orders/:code/confirm |
| Tài liệu · Bài học · FAQ · Nhà cung cấp | 20 | |
| Banner · Danh mục | 8 |
Ba lớp bảo vệ trong middlewares/authMiddleware.js:
| Cho qua ai | |
|---|---|
protect | đã đăng nhập (đọc token từ cookie httpOnly) |
instructor | giảng viên và quản trị |
admin | chỉ quản trị |
Ghi lại những chỗ nhìn qua tưởng làm phức tạp thừa, nhưng bỏ đi là mở lại một lỗ hổng đã từng có thật.
Nội dung có phí đi qua đúng một cửa. backend/src/utils/contentAccess.js
xuất ra duocXemNoiDung(course, user), và mọi đường trả nội dung bài học đều
phải gọi nó. Trước đây cổng 402 chỉ đặt ở đường ghi danh, đường đọc bài không
kiểm gì — mở một tài khoản miễn phí rồi gọi thẳng GET /api/lessons/:id là lấy
được video của khoá có phí.
Trừ tiền bằng một lệnh ghi duy nhất. Điều kiện đủ tiền nằm bên trong
lệnh findOneAndUpdate, không phải một lệnh đọc riêng trước đó. Hai yêu cầu
chạy song song không thể cùng thấy "đủ tiền" rồi cùng trừ. Xác nhận đơn hàng
dùng đúng khuôn đó — truy vấn kèm trạng thái đang chờ sẽ trả về rỗng nếu một
quản trị viên khác vừa xác nhận xong.
Chặn dò mật khẩu bằng hai khoá đếm. loginRateLimit đếm theo cặp IP và
email, và theo IP riêng. Chỉ đếm theo cặp thì kẻ tấn công đổi email là bộ đếm
về 0 — thử một mật khẩu phổ biến trên hàng nghìn tài khoản vẫn lọt.
Lọc HTML theo dấu hiệu, không theo hình dạng. Bộ lọc từng chỉ chạy khi chuỗi
trông giống bài viết, mà danh sách nhận diện không có thẻ script — nên nội
dung chỉ gồm một thẻ script bị xếp là văn bản thường và lưu nguyên vẹn. Giờ nó
lọc khi thấy dấu hiệu nguy hiểm, bất kể chuỗi trông giống gì.
Quy đổi tiền làm tròn lên. Math.ceil(price / 1000) — để hệ thống không bao
giờ thu thiếu.
cd frontend && npm run verify # format + lint + typecheck + build
cd backend && npm test # 158 test
CI chạy đúng hai lệnh đó, tách thành hai job song song — frontend hỏng thì vẫn biết backend còn xanh hay không.
Test của backend là hàm thuần: không mở kết nối CSDL, không gọi Cloudinary.
CI không có secret nào, và MONGO_URI trỏ tới cụm dữ liệu thật đang chạy. CI
còn chạy node --check trên mọi tệp .js — backend chưa có linter nên đó là
lưới an toàn duy nhất bắt lỗi cú pháp ở tệp không test nào chạm tới.
Hai project Vercel riêng, mỗi cái build một thư mục:
| Thư mục | Project | Địa chỉ |
|---|---|---|
frontend/ | learning-portal-s | learning-portal-s.vercel.app |
backend/ | learning-portal-s-api | learning-portal-backend-ten.vercel.app |
Hiện deploy bằng tay, chạy npx vercel --prod trong đúng thư mục con. Khi
nối Git integration thì mỗi project phải đặt Root Directory trỏ vào thư mục
con của nó — bỏ bước đó thì Vercel build từ gốc và không thấy package.json nào.
Không bao giờ commit tệp
.env. Kho này chỉ chứa.env.examplevới giá trị mẫu. Mật khẩu máy chủ, chuỗi kết nối CSDL, khoá SMTP không được đặt trong README hay bất kỳ tệp nào được commit — kho công khai thì cả thế giới đọc được, và xoá đi cũng không cứu được vì lịch sử Git vẫn giữ lại.
Nguyễn Thế Sang — github.com/TheSang2610
Toàn bộ frontend và backend.