# Internify API Documentation [![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/) Backend API untuk Website Penerimaan Magang HUMIC Engineering - Dibangun dengan Express.js, Prisma ORM, dan MySQL. --- ## ๐Ÿ“‘ Table of Contents 1. [Introduction](#introduction) 2. [Quick Start](#-quick-start) - [Prerequisites](#prerequisites) - [Installation](#installation) - [Environment Setup](#environment-setup) - [Database Setup](#database-setup) - [Running the Server](#running-the-server) 3. [Project Structure](#-project-structure) 4. [Architecture](#-architecture) - [Technology Stack](#technology-stack) - [Module Architecture](#module-architecture-4-layer-pattern) - [Request Flow](#request-flow) 5. [Development Guide](#-development-guide) - [Creating a New Module](#creating-a-new-module) - [Using Wrapper Functions](#using-wrapper-functions) - [Validation with Joi](#validation-with-joi) - [File Upload Handling](#file-upload-handling) 6. [API Documentation](#-api-documentation) 7. [Testing](#-testing) 8. [Deployment](#-deployment) 9. [Contributing](#-contributing) 10. [Troubleshooting](#-troubleshooting) --- ## Introduction Project intern HUMIC Batch 4 & 5 - Website Penerimaan Magang **Framework:** [Express.js](https://expressjs.com/) **ORM:** [Prisma](https://www.prisma.io/) **Database:** MySQL **Authentication:** JWT (RS256) ### ๐Ÿ‘ฅ Development Team **Team Humic Intern Batch 4** |**Nama**| **Role** | |:-------|:-----------:| |REINHARD EFRAIM SITUMEANG |UI/UX WEBSITE| |SHAFA SALMA PERMANA|UI/UX MOBILE| |MUHAMMAD FARIED GUNAWAN|FRONT END DEVELOPER| |YOHANES JANUARICO ANDIAWAN|BACK END DEVELOPER | |REIHAN RAMADHANA ANWARI |BACK END DEVELOPER | |DELKANO MARZUKI BERUTU|MOBILE DEVELOPER | **Team Humic Intern Batch 5** |**Nama**| **Role** | |:-------|:-----------:| |BILLY MARCELO |FRONTEND DEVELOPER| |MAHESA BAGUS RADITYA |BACK END DEVELOPER| |SASI AFRILIA |UI/UX WEBSITE| ### ๐Ÿ—„๏ธ Database Structure ![Entity Relationship Diagram](https://raw.githubusercontent.com/Ditrogen/backend-intern-humic/main/src/docs/ERD.png) The database consists of the following tables: 1. **mahasiswa**: Menyimpan data lengkap mahasiswa yang mendaftar magang. 2. **admin**: Digunakan untuk login dan mengelola lowongan. 3. **lowongan_magang**: Informasi detail tentang posisi magang yang tersedia. 4. **lamaran_magang**: Data lamaran yang diajukan mahasiswa. 5. **hasil_research**: Menyimpan hasil riset proyek dari mahasiswa atau tim. 6. **partnership**: Data partner/institusi yang bekerja sama. 7. **faq**: Data pertanyaan dan jawaban yang sering ditanyakan (Frequently Asked Questions). --- ## ๐Ÿš€ Quick Start ### Prerequisites Pastikan Anda sudah menginstall: - **Node.js** (v16 atau lebih tinggi) - **npm** atau **yarn** - **MySQL** (v8 atau lebih tinggi) - **Git** ### Installation #### 1. Clone Repository ```bash git clone https://github.com/Ditrogen/backend-intern-humic.git cd backend-intern-humic ``` #### 2. Install Dependencies ```bash npm install ``` ### Environment Setup #### 3. Buat File .env Salin file `.env.example` menjadi `.env`: ```bash cp .env.example .env ``` #### 4. Konfigurasi Environment Variables Edit file `.env` dan isi dengan konfigurasi Anda: ```env # Database Configuration DATABASE_URL="mysql://username:password@localhost:3306/internify_db" # Server Configuration PORT=9000 # Email Configuration (untuk notifikasi) AUTH_EMAIL='your-email@gmail.com' AUTH_PASSWORD='your-app-specific-password' # JWT Configuration JWT_PRIVATE_KEY=-----BEGIN RSA PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END RSA PRIVATE KEY----- JWT_PUBLIC_KEY=-----BEGIN PUBLIC KEY-----\nYOUR_PUBLIC_KEY\n-----END PUBLIC KEY----- # ReCaptcha Configuration RECAPTCHA_SECRET_KEY=your-recaptcha-secret-key RECAPTCHA_SITE_KEY=your-recaptcha-site-key ``` > **๐Ÿ’ก Tips:** Untuk generate RSA key pair, gunakan OpenSSL: > > ```bash > # Generate private key > openssl genrsa -out private.pem 2048 > # Generate public key > openssl rsa -in private.pem -pubout -out public.pem > ``` ### Database Setup #### 5. Buat Database MySQL ```bash mysql -u root -p CREATE DATABASE internify_db; EXIT; ``` #### 6. Generate Prisma Client ```bash npx prisma generate ``` #### 7. Run Database Migration ```bash npx prisma migrate dev ``` > Ini akan membuat semua tabel yang diperlukan berdasarkan schema Prisma. #### 8. (Optional) Seed Database Jika ada data seeder: ```bash npx prisma db seed ``` ### Running the Server #### Development Mode ```bash npm run dev ``` Server akan berjalan di `http://localhost:9000` #### Production Mode ```bash npm start ``` > **โš ๏ธ Perhatian:** > > - Gunakan `npm run dev` untuk development > - Gunakan `npm start` untuk production ### Testing the API Setelah server berjalan, buka Swagger UI untuk testing: ``` http://localhost:9000/api-docs ``` > **๐Ÿ“ Note:** Swagger hanya tersedia jika `NODE_ENV` bukan `production` dan `SWAGGER_ENABLED` bernilai `true` --- ## ๐Ÿ“š Project Structure ``` backend-intern-humic/ โ”œโ”€โ”€ prisma/ โ”‚ โ”œโ”€โ”€ schema.prisma # Prisma schema definition โ”‚ โ”œโ”€โ”€ prisma.config.js # Prisma configuration โ”‚ โ””โ”€โ”€ migrations/ # Database migrations โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ index.js # Main application entry point โ”‚ โ”œโ”€โ”€ docs/ โ”‚ โ”‚ โ”œโ”€โ”€ swagger.js # Swagger/OpenAPI configuration โ”‚ โ”‚ โ””โ”€โ”€ ERD.png # Entity Relationship Diagram โ”‚ โ”œโ”€โ”€ helpers/ โ”‚ โ”‚ โ”œโ”€โ”€ db/ # Database connection (Prisma client) โ”‚ โ”‚ โ”œโ”€โ”€ error/ # Custom error classes โ”‚ โ”‚ โ”œโ”€โ”€ http-status/ # HTTP status codes โ”‚ โ”‚ โ”œโ”€โ”€ infra/ # Infrastructure config โ”‚ โ”‚ โ””โ”€โ”€ utils/ # Utility functions โ”‚ โ”‚ โ”œโ”€โ”€ validator.js # Joi validation helpers โ”‚ โ”‚ โ”œโ”€โ”€ wrapper.js # Response wrapper functions โ”‚ โ”‚ โ””โ”€โ”€ fileHelper.js # File handling utilities โ”‚ โ”œโ”€โ”€ middleware/ โ”‚ โ”‚ โ”œโ”€โ”€ multer.js # File upload configuration โ”‚ โ”‚ โ”œโ”€โ”€ recaptcha.js # ReCaptcha verification โ”‚ โ”‚ โ””โ”€โ”€ verifyJWT.js # JWT authentication โ”‚ โ”œโ”€โ”€ modules/ # Feature modules (MVC pattern) โ”‚ โ”‚ โ”œโ”€โ”€ admin/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ controllers/ # Handle HTTP requests โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Business logic โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ repositories/ # Database operations โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ models/ # Joi validation schemas โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ index.js # Module exports โ”‚ โ”‚ โ”œโ”€โ”€ auth/ โ”‚ โ”‚ โ”œโ”€โ”€ batch/ โ”‚ โ”‚ โ”œโ”€โ”€ faq/ โ”‚ โ”‚ โ”œโ”€โ”€ feedback/ โ”‚ โ”‚ โ”œโ”€โ”€ hasilResearch/ โ”‚ โ”‚ โ”œโ”€โ”€ lamaranMagang/ โ”‚ โ”‚ โ”œโ”€โ”€ lowonganMagang/ โ”‚ โ”‚ โ”œโ”€โ”€ mahasiswa/ โ”‚ โ”‚ โ””โ”€โ”€ partnership/ โ”‚ โ”œโ”€โ”€ routes/ # API route definitions โ”‚ โ”‚ โ”œโ”€โ”€ admin.routes.js โ”‚ โ”‚ โ”œโ”€โ”€ auth.routes.js โ”‚ โ”‚ โ”œโ”€โ”€ ... โ”‚ โ””โ”€โ”€ uploads/ # File upload directory (development) โ”œโ”€โ”€ .env # Environment variables (not committed) โ”œโ”€โ”€ .env.example # Environment variables template โ”œโ”€โ”€ package.json # Node.js dependencies and scripts โ”œโ”€โ”€ vercel.json # Vercel deployment configuration โ””โ”€โ”€ README.md # This file ``` ### Folder Explanation | Folder/File | Purpose | | ----------------- | ----------------------------------------------- | | `prisma/` | Prisma ORM schema, config, dan migrations | | `src/index.js` | Entry point Express application | | `src/helpers/` | Utility functions, error classes, DB connection | | `src/middleware/` | Express middleware (auth, upload, validation) | | `src/modules/` | Feature modules following 4-layer architecture | | `src/routes/` | API endpoint definitions | | `.env` | Environment variables (credentials, config) | | `vercel.json` | Serverless deployment configuration | --- ## ๐Ÿ›๏ธ Architecture ### Technology Stack | Technology | Version | Purpose | | -------------- | ------- | -------------------------------- | | **Node.js** | v16+ | Runtime environment | | **Express.js** | 4.x | Web framework | | **Prisma** | 6.16.2 | ORM (Object-Relational Mapping) | | **MySQL** | 8.x | Relational database | | **JWT** | - | Authentication (RS256 signature) | | **Joi** | - | Schema validation | | **Multer** | - | File upload handling | | **Nodemailer** | - | Email service | | **Swagger** | - | API documentation | | **Vercel** | - | Serverless deployment | ### Module Architecture (4-Layer Pattern) Setiap module di folder `src/modules/` mengikuti **arsitektur 4 layer** untuk separation of concerns: ``` module/ โ”œโ”€โ”€ controllers/ โ†’ Layer 1: Menangani HTTP request/response โ”œโ”€โ”€ services/ โ†’ Layer 2: Business logic dan pemrosesan data โ”œโ”€โ”€ repositories/ โ†’ Layer 3: Operasi database (Prisma queries) โ”œโ”€โ”€ models/ โ†’ Layer 4: Validasi input (Joi schemas) โ””โ”€โ”€ index.js โ†’ Module exports ``` #### Layer Responsibilities **1. Controllers** (`controllers/`) - Menerima HTTP request dari route - Validasi input menggunakan Joi models - Memanggil service layer - Mengembalikan HTTP response dengan wrapper - Error handling **2. Services** (`services/`) - Business logic utama - Validasi business rules - Memanggil repository untuk database operations - Data transformation - Orchestration antar repositories **3. Repositories** (`repositories/`) - Database operations (CRUD) menggunakan Prisma - Query construction - Data mapping - Tidak ada business logic **4. Models** (`models/`) - Joi schema definitions - Input validation rules - Error messages (Indonesian) ### Request Flow ```mermaid graph LR A[Client] -->|HTTP Request| B[Route] B --> C[Controller] C -->|Validate| D[Joi Model] D -->|Valid| E[Service] E -->|Business Logic| E E --> F[Repository] F -->|Prisma Query| G[MySQL Database] G -->|Data| F F --> E E --> C C -->|Wrapper| H[HTTP Response] H --> A ``` **Step-by-step:** 1. **Client** mengirim HTTP request 2. **Route** menerima request dan memanggil controller 3. **Controller** validasi input menggunakan **Joi Model** 4. **Controller** memanggil **Service** 5. **Service** memproses business logic 6. **Service** memanggil **Repository** 7. **Repository** query database menggunakan **Prisma** 8. **Database** mengembalikan data ke **Repository** 9. **Repository** mengembalikan data ke **Service** 10. **Service** mengembalikan data ke **Controller** 11. **Controller** mengirim response dengan **wrapper** 12. **Client** menerima HTTP response ### Key Features โœ… **JWT Authentication (RS256)** - RSA signature-based tokens dengan token expiry 1 day โœ… **Joi Validation** - Schema validation untuk semua input dengan pesan error dalam Bahasa Indonesia โœ… **File Upload Management** - Automatic file cleanup on errors, support multiple file types โœ… **Automatic Status Updates** - Lowongan magang status auto-update berdasarkan tanggal โœ… **CORS Configuration** - Configurable CORS untuk cross-origin requests โœ… **Centralized Error Handling** - Custom error classes dengan pesan konsisten โœ… **Response Wrapper** - Standardized API response format โœ… **Swagger Documentation** - Interactive API documentation dengan Swagger UI --- ## ๐Ÿ› ๏ธ Development Guide ### Creating a New Module Ikuti langkah-langkah berikut untuk membuat module baru: ## Cara Membuat Module (Langkah demi Langkah) ### Langkah 1: Buat Struktur Module ```bash modules/ yourModule/ โ”œโ”€โ”€ index.js # Export semua komponen โ”œโ”€โ”€ controllers/ โ”‚ โ””โ”€โ”€ yourModule.controller.js โ”œโ”€โ”€ services/ โ”‚ โ””โ”€โ”€ yourModule.service.js โ”œโ”€โ”€ repositories/ โ”‚ โ””โ”€โ”€ yourModule.repository.js โ””โ”€โ”€ models/ โ””โ”€โ”€ yourModule.model.js ``` ### Langkah 2: Buat Validation Model (models/) **File:** `models/yourModule.model.js` ```javascript const joi = require("joi"); // Validasi untuk membuat record baru const createYourModuleModel = joi.object({ name: joi.string().required().messages({ "string.base": "Nama harus berupa teks.", "any.required": "Nama wajib diisi.", }), email: joi.string().email().required().messages({ "string.email": "Format email tidak valid.", "any.required": "Email wajib diisi.", }), age: joi.number().min(18).messages({ "number.base": "Umur harus berupa angka.", "number.min": "Umur minimal 18 tahun.", }), }); // Validasi untuk update record const updateYourModuleModel = joi.object({ name: joi.string().messages({ "string.base": "Nama harus berupa teks.", }), email: joi.string().email().messages({ "string.email": "Format email tidak valid.", }), age: joi.number().min(18).messages({ "number.base": "Umur harus berupa angka.", "number.min": "Umur minimal 18 tahun.", }), }); module.exports = { createYourModuleModel, updateYourModuleModel, }; ``` ### Langkah 3: Buat Repository (repositories/) **File:** `repositories/yourModule.repository.js` ```javascript const prisma = require("../../../helpers/db/db_connection"); class YourModuleRepository { // Buat record baru async create(data) { return prisma.yourModel.create({ data: data, }); } // Ambil semua record async findAll() { return prisma.yourModel.findMany(); } // Ambil berdasarkan ID async findById(id) { return prisma.yourModel.findUnique({ where: { id: parseInt(id) }, }); } // Update berdasarkan ID async updateById(id, data) { return prisma.yourModel.update({ where: { id: parseInt(id) }, data: data, }); } // Hapus berdasarkan ID async deleteById(id) { return prisma.yourModel.delete({ where: { id: parseInt(id) }, }); } } module.exports = new YourModuleRepository(); ``` ### Langkah 4: Buat Service (services/) **File:** `services/yourModule.service.js` ```javascript const yourModuleRepository = require("../repositories/yourModule.repository"); const { data, error } = require("../../../helpers/utils/wrapper"); const { NotFoundError, BadRequestError } = require("../../../helpers/error"); class YourModuleService { // Buat record baru async createYourModule(moduleData) { try { // Business logic di sini (contoh: cek duplikat) const existing = await yourModuleRepository.findByEmail(moduleData.email); if (existing) { return error(new BadRequestError("Email Sudah Terdaftar")); } const result = await yourModuleRepository.create(moduleData); return data(result); } catch (err) { return error(err); } } // Ambil semua record async getAllYourModule() { try { const results = await yourModuleRepository.findAll(); return data(results); } catch (err) { return error(err); } } // Ambil berdasarkan ID async getYourModuleById(id) { try { const result = await yourModuleRepository.findById(id); if (!result) { return error(new NotFoundError("Data Tidak Ditemukan")); } return data(result); } catch (err) { return error(err); } } // Update berdasarkan ID async updateYourModule(id, moduleData) { try { // Cek apakah data ada const checkResult = await this.getYourModuleById(id); if (checkResult.err) { return checkResult; } const result = await yourModuleRepository.updateById(id, moduleData); return data(result); } catch (err) { return error(err); } } // Hapus berdasarkan ID async deleteYourModule(id) { try { // Cek apakah data ada const checkResult = await this.getYourModuleById(id); if (checkResult.err) { return checkResult; } const result = await yourModuleRepository.deleteById(id); return data(result); } catch (err) { return error(err); } } } module.exports = new YourModuleService(); ``` ### Langkah 5: Buat Controller (controllers/) **File:** `controllers/yourModule.controller.js` ```javascript const yourModuleService = require("../services/yourModule.service"); const { createYourModuleModel, updateYourModuleModel, } = require("../models/yourModule.model"); const { response } = require("../../../helpers/utils/wrapper"); const { isValidPayload } = require("../../../helpers/utils/validator"); const { InternalServerError } = require("../../../helpers/error"); const { SUCCESS, ERROR } = require("../../../helpers/http-status/status_code"); class YourModuleController { // CREATE - POST /your-module-api/add async addYourModule(req, res) { try { const payload = req.body; // Validasi input const validatePayload = isValidPayload(payload, createYourModuleModel); if (validatePayload.err) { return response( res, "fail", validatePayload, "Validasi gagal", ERROR.EXPECTATION_FAILED, ); } // Panggil service const result = await yourModuleService.createYourModule( validatePayload.data, ); if (result.err) { return response(res, "fail", result); } return response( res, "success", result, "Data berhasil ditambahkan", SUCCESS.CREATED, ); } catch (err) { return response( res, "fail", { err: new InternalServerError(err.message) }, "Terjadi kesalahan yang tidak terduga", ERROR.INTERNAL_ERROR, ); } } // READ ALL - GET /your-module-api/get async getAllYourModule(req, res) { try { const result = await yourModuleService.getAllYourModule(); if (result.err) { return response(res, "fail", result); } return response( res, "success", result, "Data berhasil diambil", SUCCESS.OK, ); } catch (err) { return response( res, "fail", { err: new InternalServerError(err.message) }, "Terjadi kesalahan yang tidak terduga", ERROR.INTERNAL_ERROR, ); } } // READ ONE - GET /your-module-api/get/:id async getYourModuleById(req, res) { try { const { id } = req.params; const result = await yourModuleService.getYourModuleById(id); if (result.err) { return response(res, "fail", result); } return response( res, "success", result, "Data berhasil diambil", SUCCESS.OK, ); } catch (err) { return response( res, "fail", { err: new InternalServerError(err.message) }, "Terjadi kesalahan yang tidak terduga", ERROR.INTERNAL_ERROR, ); } } // UPDATE - PATCH /your-module-api/update/:id async updateYourModule(req, res) { try { const { id } = req.params; const payload = req.body; // Validasi input const validatePayload = isValidPayload(payload, updateYourModuleModel); if (validatePayload.err) { return response( res, "fail", validatePayload, "Validasi gagal", ERROR.EXPECTATION_FAILED, ); } // Panggil service const result = await yourModuleService.updateYourModule( id, validatePayload.data, ); if (result.err) { return response(res, "fail", result); } return response( res, "success", result, "Data berhasil diperbarui", SUCCESS.OK, ); } catch (err) { return response( res, "fail", { err: new InternalServerError(err.message) }, "Terjadi kesalahan yang tidak terduga", ERROR.INTERNAL_ERROR, ); } } // DELETE - DELETE /your-module-api/delete/:id async deleteYourModule(req, res) { try { const { id } = req.params; const result = await yourModuleService.deleteYourModule(id); if (result.err) { return response(res, "fail", result); } return response( res, "success", result, "Data berhasil dihapus", SUCCESS.OK, ); } catch (err) { return response( res, "fail", { err: new InternalServerError(err.message) }, "Terjadi kesalahan yang tidak terduga", ERROR.INTERNAL_ERROR, ); } } } module.exports = new YourModuleController(); ``` ### Langkah 6: Buat Index (index.js) **File:** `index.js` ```javascript const yourModuleController = require("./controllers/yourModule.controller"); module.exports = { yourModuleController, }; ``` ### Langkah 7: Buat Routes **File:** `routes/yourModule.routes.js` ```javascript const express = require("express"); const { yourModuleController } = require("../modules/yourModule"); const { verifyJWT } = require("../middleware/verifyJWT"); const router = express.Router(); // Route publik router.get("/get", yourModuleController.getAllYourModule); router.get("/get/:id", yourModuleController.getYourModuleById); // Route yang dilindungi (butuh JWT) router.post("/add", verifyJWT, yourModuleController.addYourModule); router.patch("/update/:id", verifyJWT, yourModuleController.updateYourModule); router.delete("/delete/:id", verifyJWT, yourModuleController.deleteYourModule); module.exports = router; ``` ### Langkah 8: Daftarkan Routes di index.js **File:** `src/index.js` ```javascript const yourModuleRoutes = require("./routes/yourModule.routes"); // ... kode lainnya app.use("/your-module-api", yourModuleRoutes); ``` --- ### Using Wrapper Functions ### Response Wrapper Fungsi `response` menstandarisasi semua response API. **Lokasi:** `helpers/utils/wrapper.js` **Penggunaan di Controller:** ```javascript const { response } = require("../../../helpers/utils/wrapper"); const { SUCCESS, ERROR } = require("../../../helpers/http-status/status_code"); // Response sukses return response( res, // Object response Express "success", // Status: 'success' atau 'fail' result, // Object data dari service "Pesan sukses custom", // Pesan opsional SUCCESS.OK, // HTTP status code (200) ); // Response error return response( res, "fail", result, // Berisi { err: ErrorObject } "Pesan error custom", // Pesan opsional ERROR.NOT_FOUND, // HTTP status code (404) ); ``` ### Data/Error Wrapper (Layer Service) Fungsi `data` dan `error` membungkus hasil service. **Lokasi:** `helpers/utils/wrapper.js` **Penggunaan di Service:** ```javascript const { data, error } = require('../../../helpers/utils/wrapper'); const { NotFoundError, BadRequestError } = require('../../../helpers/error'); // Kembalikan data sukses async getSomething() { try { const result = await repository.find(); return data(result); // Return: { err: null, data: result } } catch (err) { return error(err); // Return: { err: err, data: null } } } // Kembalikan custom error async checkSomething() { try { const item = await repository.findById(id); if (!item) { return error(new NotFoundError('Data Tidak Ditemukan')); } return data(item); } catch (err) { return error(err); } } ``` ### Class Error yang Tersedia **Lokasi:** `helpers/error/` ```javascript const { BadRequestError, // 400 - Input tidak valid UnauthorizedError, // 401 - Tidak terautentikasi ForbiddenError, // 403 - Tidak ada izin NotFoundError, // 404 - Resource tidak ditemukan ConflictError, // 409 - Data duplikat ExpectationFailedError, // 417 - Validasi gagal InternalServerError, // 500 - Server error ServiceUnavailableError, // 503 - Service mati GatewayTimeoutError, // 504 - Timeout } = require("../../../helpers/error"); // Contoh penggunaan return error(new BadRequestError("Email Sudah Terdaftar")); return error(new NotFoundError("Mahasiswa Tidak Ditemukan")); return error(new ForbiddenError("Akses Ditolak")); ``` ### HTTP Status Code **Lokasi:** `helpers/http-status/status_code.js` ```javascript const { SUCCESS, ERROR } = require("../../../helpers/http-status/status_code"); // Success codes SUCCESS.OK; // 200 SUCCESS.CREATED; // 201 // Error codes ERROR.BAD_REQUEST; // 400 ERROR.UNAUTHORIZED; // 401 ERROR.FORBIDDEN; // 403 ERROR.NOT_FOUND; // 404 ERROR.CONFLICT; // 409 ERROR.EXPECTATION_FAILED; // 417 ERROR.INTERNAL_ERROR; // 500 ERROR.SERVICE_UNAVAILABLE; // 503 ERROR.GATEWAY_TIMEOUT; // 504 ``` --- ## Contoh Lengkap: Tambah Record Baru ### 1. Client mengirim request ```javascript POST /your-module-api/add Headers: { Authorization: "Bearer JWT_TOKEN" } Body: { "name": "John", "email": "john@example.com", "age": 25 } ``` ### 2. Route menerima dan memanggil controller ```javascript router.post("/add", verifyJWT, yourModuleController.addYourModule); ``` ### 3. Controller validasi input ```javascript const validatePayload = isValidPayload(payload, createYourModuleModel); if (validatePayload.err) { // Return validation error dengan status 417 return response(res, "fail", validatePayload, "Validasi gagal", 417); } ``` ### 4. Controller memanggil service ```javascript const result = await yourModuleService.createYourModule(validatePayload.data); ``` ### 5. Service memproses business logic ```javascript // Cek duplikat const existing = await repository.findByEmail(data.email); if (existing) { return error(new BadRequestError("Email Sudah Terdaftar")); } // Buat record const result = await repository.create(data); return data(result); ``` ### 6. Repository query database ```javascript return prisma.yourModel.create({ data: data }); ``` ### 7. Response mengalir kembali **Sukses:** ```javascript // Service return: { err: null, data: createdRecord } // Controller cek: if (result.err) โ†’ false, lanjut // Controller return response dengan status 201 Response: { "message": "Data berhasil ditambahkan", "data": { "id": 1, "name": "John", "email": "john@example.com" } } ``` **Error:** ```javascript // Service return: { err: BadRequestError, data: null } // Controller cek: if (result.err) โ†’ true // Controller return error response Response: { "message": "Email Sudah Terdaftar", "error": "BadRequestError" } ``` --- ## Contoh Validasi ### Validasi Input dengan Joi ```javascript const validatePayload = isValidPayload(payload, createYourModuleModel); // Sukses validatePayload = { err: null, data: { name: "John", email: "john@example.com", age: 25 }, }; // Validasi gagal validatePayload = { err: { details: [ { message: "Email wajib diisi" }, { message: "Umur minimal 18 tahun" }, ], }, data: null, }; ``` ### Response untuk Validation Error ```javascript if (validatePayload.err) { return response( res, 'fail', validatePayload, 'Validasi gagal', ERROR.EXPECTATION_FAILED // 417 ); } // Client menerima: { "message": "Validasi gagal", "errors": [ "Email wajib diisi", "Umur minimal 18 tahun" ] } ``` --- ## Contoh Upload File ### Controller dengan Upload File ```javascript const multer = require('../../../middleware/multer'); const { deleteFileIfExists } = require('../../../helpers/utils/fileHelper'); // Route dengan multer router.post('/add', verifyJWT, multer.single('image'), controller.add); // Controller async add(req, res) { try { const payload = req.body; const imageFile = req.file; // Ambil file yang diupload // Validasi const validatePayload = isValidPayload(payload, createModel); if (validatePayload.err) { // Hapus file yang diupload jika validasi gagal if (imageFile?.filename) { await deleteFileIfExists(imageFile.filename); } return response(res, 'fail', validatePayload, 'Validasi gagal', 417); } // Panggil service dengan file const result = await service.create(validatePayload.data, imageFile); if (result.err) { // Hapus file yang diupload jika service gagal if (imageFile?.filename) { await deleteFileIfExists(imageFile.filename); } return response(res, 'fail', result); } return response(res, 'success', result, 'Data berhasil ditambahkan', 201); } catch (err) { // Hapus file yang diupload saat terjadi error tidak terduga if (req.file?.filename) { await deleteFileIfExists(req.file.filename); } return response(res, 'fail', { err: new InternalServerError(err.message) }); } } ``` --- ## ๐Ÿงช Testing ### 1. Test dengan Swagger UI Buka Swagger documentation di browser: ``` http://localhost:9000/api-docs ``` Swagger UI menyediakan: - Interactive API testing - Request/response examples - Schema definitions - Authentication testing ### 2. Test dengan curl **Contoh Testing Endpoints:** ```bash # Create curl -X POST http://localhost:9000/your-module-api/add \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"john@example.com","age":25}' # Get all curl http://localhost:9000/your-module-api/get # Get by ID curl http://localhost:9000/your-module-api/get/1 # Update curl -X PATCH http://localhost:9000/your-module-api/update/1 \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"John Updated"}' # Delete curl -X DELETE http://localhost:9000/your-module-api/delete/1 \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` --- ## ๐Ÿ“ API Documentation ### Database Schema ![Entity Relationship Diagram](https://raw.githubusercontent.com/Ditrogen/backend-intern-humic/main/src/docs/ERD.png) Database terdiri dari 7 tabel utama: 1. **mahasiswa**: Data lengkap mahasiswa yang mendaftar magang 2. **admin**: Data admin untuk login dan mengelola lowongan 3. **lowongan_magang**: Informasi detail posisi magang yang tersedia 4. **lamaran_magang**: Data lamaran yang diajukan mahasiswa 5. **hasil_research**: Hasil riset proyek dari mahasiswa atau tim 6. **partnership**: Data partner/institusi yang bekerja sama 7. **faq**: Frequently Asked Questions ### API Modules ## Project Architecture ### Technology Stack - **Runtime**: Node.js - **Framework**: Express.js 4.x - **ORM**: Prisma 6.16.2 - **Database**: MySQL - **Authentication**: JWT (RS256 with signature) - **Validation**: Joi - **File Upload**: Multer - **Email Service**: Nodemailer - **Documentation**: Swagger/OpenAPI - **Deployment**: Vercel (Serverless) ### Project Structure ``` src/ โ”œโ”€โ”€ index.js # Main application entry point โ”œโ”€โ”€ config/ # Configuration files โ”œโ”€โ”€ docs/ # Documentation and ERD โ”œโ”€โ”€ helpers/ # Helper utilities โ”‚ โ”œโ”€โ”€ db/ # Database connection (Prisma) โ”‚ โ”œโ”€โ”€ error/ # Custom error classes โ”‚ โ”œโ”€โ”€ http-status/ # HTTP status codes โ”‚ โ”œโ”€โ”€ infra/ # Infrastructure config โ”‚ โ””โ”€โ”€ utils/ # Utility functions (validator, wrapper, file helper) โ”œโ”€โ”€ middleware/ # Express middleware โ”‚ โ”œโ”€โ”€ multer.js # File upload configuration โ”‚ โ”œโ”€โ”€ recaptcha.js # ReCaptcha verification โ”‚ โ””โ”€โ”€ verifyJWT.js # JWT authentication โ”œโ”€โ”€ modules/ # Feature modules (MVC pattern) โ”‚ โ”œโ”€โ”€ admin/ โ”‚ โ”œโ”€โ”€ auth/ โ”‚ โ”œโ”€โ”€ hasilResearch/ โ”‚ โ”œโ”€โ”€ lamaranMagang/ โ”‚ โ”œโ”€โ”€ lowonganMagang/ โ”‚ โ”œโ”€โ”€ mahasiswa/ โ”‚ โ””โ”€โ”€ partnership/ โ”œโ”€โ”€ routes/ # API route definitions โ””โ”€โ”€ uploads/ # File upload directory (local) ``` ### Module Architecture (MVC Pattern) Each module follows this structure: ``` module/ โ”œโ”€โ”€ index.js # Module exports โ”œโ”€โ”€ controllers/ # Handle HTTP requests/responses โ”œโ”€โ”€ services/ # Business logic layer โ”œโ”€โ”€ repositories/ # Database access layer (Prisma) โ”œโ”€โ”€ models/ # Joi validation schemas โ””โ”€โ”€ helpers/ (optional) # Module-specific helpers ``` **Data Flow:** ``` Route โ†’ Controller โ†’ Service โ†’ Repository โ†’ Database โ†“ Route โ† Controller โ† Service โ† Repository ``` --- ## Key Features ### 1. **JWT Authentication (RS256)** - Uses RSA signature-based tokens - Token expiry: 1 day - Signature field for additional security - Middleware: `verifyJWT` ### 2. **Joi Validation** - All API inputs validated with Joi schemas - Indonesian error messages - Located in `models/` directory of each module ### 3. **File Upload Management** - **Development**: Files stored in `src/uploads/` - **Production**: Files stored in `/tmp/uploads/` (Vercel) - Automatic file cleanup on validation/service errors - Supports: Images (JPG, PNG), PDFs - Max file size: 4.5MB ### 4. **Automatic Status Updates** - `lowonganMagang` status automatically updates based on dates: - `akan-datang`: Before start date - `aktif`: Between start and end date - `selesai`: After end date ### 5. **CORS Configuration** - Open to all origins (configurable) - Supports credentials - Pre-flight request handling ### 6. **Error Handling** - Centralized error classes - Indonesian error messages - Consistent error response format --- ## API Modules ### 1. **Admin Module** Manage admin accounts and authentication. **Endpoints:** - `POST /admin-api/add` - Create admin (requires JWT) - `GET /admin-api/get` - Get all admins (requires JWT) ### 2. **Auth Module** Authentication and authorization. **Endpoints:** - `POST /auth-api/login` - Admin login - `GET /auth-api/me` - Get authenticated admin details (requires JWT) ### 3. **Lowongan Magang Module** Manage internship vacancies. **Endpoints:** - `POST /lowongan-magang-api/add` - Create vacancy (requires JWT) - `GET /lowongan-magang-api/get` - Get all vacancies - `GET /lowongan-magang-api/get/id/:id` - Get vacancy by ID - `GET /lowongan-magang-api/get/kelompok/:kelompok_peminatan` - Get by specialization - `GET /lowongan-magang-api/get/kelompok-all` - Get all specializations - `PATCH /lowongan-magang-api/update/:id` - Update vacancy (requires JWT) - `DELETE /lowongan-magang-api/delete/:id` - Delete vacancy (requires JWT) **Features:** - Automatic status calculation - Image upload support - Filter by specialization group ### 4. **Lamaran Magang Module** Manage internship applications. **Endpoints:** - `POST /lamaran-magang-api/add/:id_lowongan_magang` - Submit application (with ReCaptcha) - `POST /lamaran-magang-api/add-mobile/:id_lowongan_magang` - Submit from mobile (no ReCaptcha) - `GET /lamaran-magang-api/get` - Get all applications (requires JWT) - `GET /lamaran-magang-api/get/:id_lamaran_magang` - Get application by ID (requires JWT) - `PATCH /lamaran-magang-api/update/:id_lamaran_magang` - Update status (requires JWT) - `GET /lamaran-magang-api/export` - Export to Excel (requires JWT) - `DELETE /lamaran-magang-api/delete/:id` - Delete application (requires JWT) **Features:** - CV and portfolio upload (multiple files) - Email confirmation on submission - Excel export functionality - Automatic file cleanup on errors ### 5. **Mahasiswa Module** Manage student data. **Endpoints:** - `GET /mahasiswa-api/get` - Get all students (requires JWT) **Note:** Students are automatically created when submitting applications. ### 6. **Partnership Module** Manage partner organizations. **Endpoints:** - `POST /partnership-api/add` - Add partner (requires JWT) - `GET /partnership-api/get` - Get all partners - `GET /partnership-api/get/:id` - Get partner by ID - `PATCH /partnership-api/update/:id` - Update partner (requires JWT) - `DELETE /partnership-api/delete/:id` - Delete partner (requires JWT) **Features:** - Logo image upload - Automatic file cleanup on errors ### 7. **Hasil Research Module** Manage research results/publications. **Endpoints:** - `POST /hasil-research-api/add` - Add research (requires JWT) - `GET /hasil-research-api/get` - Get all research - `GET /hasil-research-api/get/:id` - Get research by ID - `PATCH /hasil-research-api/update/:id` - Update research (requires JWT) - `DELETE /hasil-research-api/delete/:id` - Delete research (requires JWT) **Features:** - Image upload support - Automatic file cleanup on errors ### 8. **FAQ Module** Manage Frequently Asked Questions. **Endpoints:** - `POST /faq-api/add` - Add FAQ (requires JWT - Admin only) - `GET /faq-api/get` - Get all FAQs (Public) - `GET /faq-api/get/:id` - Get FAQ by ID (Public) - `PATCH /faq-api/update/:id` - Update FAQ (requires JWT - Admin only) - `DELETE /faq-api/delete/:id` - Delete FAQ (requires JWT - Admin only) --- ### Environment Variables Reference Berikut penjelasan lengkap setiap environment variable: | Variable | Type | Required | Description | Example | | ---------------------- | ------ | -------- | --------------------------------- | -------------------------------- | | `DATABASE_URL` | string | Yes | MySQL connection string | `mysql://user:pass@host:3306/db` | | `PORT` | number | Yes | Port server akan berjalan | `9000` | | `AUTH_EMAIL` | string | Yes | Email untuk mengirim notifikasi | `admin@example.com` | | `AUTH_PASSWORD` | string | Yes | App-specific password Gmail | `xxxx xxxx xxxx xxxx` | | `JWT_PRIVATE_KEY` | string | Yes | RSA private key untuk signing JWT | See .env.example | | `JWT_PUBLIC_KEY` | string | Yes | RSA public key untuk verify JWT | See .env.example | | `RECAPTCHA_SECRET_KEY` | string | Yes | Google ReCaptcha secret key | `6Lxxxxxxx...` | | `RECAPTCHA_SITE_KEY` | string | Yes | Google ReCaptcha site key | `6Lxxxxxxx...` | > **๐Ÿ“ Note:** > > - Untuk `AUTH_PASSWORD`, gunakan [App Password](https://support.google.com/accounts/answer/185833) dari Google > - Untuk generate JWT keys, gunakan OpenSSL (lihat Quick Start section) > - Untuk ReCaptcha keys, daftar di [Google ReCaptcha](https://www.google.com/recaptcha/admin) --- ### Database Migration Commands Berikut command untuk mengelola database migrations: ### Initial Setup ```bash npx prisma generate npx prisma migrate dev --name init ``` ### Reset Database ```bash npx prisma migrate reset ``` ### Deploy to Production ```bash npx prisma migrate deploy ``` --- ## ๐Ÿš€ Deployment ### Deployment ke Vercel ### 1. **Vercel Configuration** Create `vercel.json`: ```json { "version": 2, "builds": [ { "src": "src/index.js", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "src/index.js" } ] } ``` ### 2. **Environment Variables** Set all `.env` variables in Vercel dashboard. ### 3. **Database Connection** Ensure `DATABASE_URL` points to your production MySQL instance. ### 4. **Build Command** ```bash npm run vercel-build ``` ### 5. **Important Notes** - Files uploaded to `/tmp/uploads/` (temporary) - Serverless functions have 10-second timeout - Cold start may affect first request --- ## CI/CD Pipeline (GitHub Actions) Automatic migration on push to main branch. **Workflow:** 1. Push to main branch 2. GitHub Actions triggers 3. Runs `prisma migrate deploy` 4. Database updated automatically --- ## ๐Ÿ‘ฅ Contributing ### Development Workflow ### 1. **Adding a New Feature** ```bash # 1. Create feature branch git checkout -b feature/new-feature # 2. Create module structure modules/ newModule/ โ”œโ”€โ”€ index.js โ”œโ”€โ”€ controllers/ โ”œโ”€โ”€ services/ โ”œโ”€โ”€ repositories/ โ”œโ”€โ”€ models/ # 3. Update Prisma schema (if needed) # Edit prisma/schema.prisma # 4. Run migration npx prisma migrate dev --name add_new_feature # 5. Create route file routes/newModule.routes.js # 6. Register route in src/index.js app.use("/new-module-api", newModuleRoutes); # 7. Test locally npm run dev # 8. Commit and push git add . git commit -m "feat: add new feature" git push origin feature/new-feature ``` ### 2. **Testing API Endpoints** Use Swagger UI: `http://localhost:9000/api-docs` Or use tools like: - Postman - Thunder Client - curl ### 3. **File Upload Testing** ```bash # Test with curl curl -X POST http://localhost:9000/lowongan-magang-api/add \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -F "image=@/path/to/image.jpg" \ -F "posisi=Backend Developer" \ -F "kelompok_peminatan=Software Engineering" \ -F "paid=paid" ``` --- --- ## ๐Ÿ”ง Troubleshooting ### Common Issues & Solutions ### 1. **CORS Error** **Problem:** Frontend can't access API **Solution:** Check CORS configuration in `src/index.js` ### 2. **File Upload Fails** **Problem:** "File tidak ditemukan" **Solution:** Check `fileHelper.js` upload directory configuration ### 3. **JWT Token Invalid** **Problem:** "Invalid or expired token" **Solution:** - Check token expiry (1 day) - Verify JWT_PUBLIC_KEY in .env - Ensure signature field matches ### 4. **Prisma Client Error** **Problem:** "PrismaClient is unable to run in this browser environment" **Solution:** ```bash npx prisma generate ``` ### 5. **Express 5.x Route Error** **Problem:** "Missing parameter name" **Solution:** Downgrade to Express 4.x (already fixed in package.json) --- --- ### API Response Format ### Success Response ```json { "message": "Success message", "data": { ... } } ``` ### Error Response ```json { "message": "Error message", "error": "Detailed error description" } ``` ### Validation Error ```json { "message": "Validation failed", "errors": ["Field 'email' wajib diisi", "Field 'password' minimal 8 karakter"] } ``` --- --- ### Security Best Practices 1. **Never commit `.env` file** 2. **Use strong JWT secrets** 3. **Validate all inputs with Joi** 4. **Sanitize file uploads** 5. **Limit file upload size (4.5MB)** 6. **Use HTTPS in production** 7. **Enable ReCaptcha for public endpoints** 8. **Implement rate limiting (recommended)** --- --- ### Performance Optimization 1. **File Cleanup**: Automatically removes orphaned files 2. **Index Optimization**: Database indexes on frequently queried fields 3. **Connection Pooling**: Prisma handles connection pooling 4. **Status Caching**: Status calculations cached in helpers --- --- ### Contributing Guidelines ### Code Style - Use CommonJS (`require`/`module.exports`) - Follow existing module structure - Use Indonesian for error messages - Add JSDoc comments for functions - Use meaningful variable names ### Commit Messages Follow conventional commits: ``` feat: add new feature fix: fix bug docs: update documentation refactor: refactor code test: add tests chore: update dependencies ``` ### Pull Request Process 1. Create feature branch 2. Make changes 3. Test locally 4. Update documentation 5. Create pull request 6. Wait for review 7. Merge after approval --- ### Changelog ### Version 2.0.0 (Current) - โœ… Prisma ORM migration - โœ… JWT RS256 authentication - โœ… Joi validation with Indonesian messages - โœ… File upload with automatic cleanup - โœ… Vercel deployment configuration - โœ… Swagger documentation - โœ… CI/CD with GitHub Actions - โœ… CORS configuration - โœ… Express 4.x compatibility --- ## ๐Ÿ“š Quick Reference ### Useful Commands ```bash # Development npm run dev # Start development server npm start # Start production server # Database npx prisma generate # Generate Prisma client npx prisma migrate dev # Create and apply migration npx prisma migrate deploy # Deploy migrations to production npx prisma migrate reset # Reset database npx prisma studio # Open Prisma Studio GUI # Testing curl http://localhost:9000/api-docs # Open API documentation # Git git checkout -b feature/name # Create new feature branch git add . # Stage changes git commit -m "message" # Commit changes git push origin branch-name # Push to remote ``` ### Important URLs | Service | URL | Purpose | | ----------------- | -------------------------------- | ------------------- | | **Local API** | `http://localhost:9000` | Development server | | **Swagger Docs** | `http://localhost:9000/api-docs` | API documentation | | **Prisma Studio** | `http://localhost:5555` | Database GUI | | **Production** | Your Vercel URL | Production endpoint | ### Project Links - ๐ŸŒ **Repository**: [GitHub - Ditrogen/backend-intern-humic](https://github.com/Ditrogen/backend-intern-humic) - ๐Ÿ“– **Prisma Docs**: [https://www.prisma.io/docs](https://www.prisma.io/docs) - ๐Ÿ“˜ **Express Docs**: [https://expressjs.com](https://expressjs.com) - ๐Ÿ” **JWT Info**: [https://jwt.io](https://jwt.io) --- ## ๐Ÿ“„ License This API is licensed under the [MIT License](https://opensource.org/licenses/MIT). --- ## ๐Ÿ’ฌ Support Jika ada pertanyaan atau masalah: 1. Buka issue di [GitHub Issues](https://github.com/Ditrogen/backend-intern-humic/issues) 2. Hubungi team development 3. Baca dokumentasi di README ini --- **Made with โค๏ธ by HUMIC Engineering Intern Team**