docs: menambahkan dokumentasi api dengan openapi 3.0.0

This commit is contained in:
Muhammad Zhafran Ilham
2026-06-02 08:15:17 +07:00
parent b00d5c520f
commit c138521424
13 changed files with 2520 additions and 128 deletions
+24
View File
@@ -5,6 +5,7 @@ import helmet from 'helmet';
import morgan from 'morgan';
import routes from './routes';
import { errorHandler, CustomError } from './middleware/errorHandler';
import { setupSwaggerDocs } from './docs/swagger';
const app = express();
@@ -25,6 +26,27 @@ app.use('/api/uploads', express.static(path.join(__dirname, '../uploads')));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
/**
* @swagger
* /:
* get:
* summary: Endpoint root API
* description: Endpoint dasar.
* responses:
* 200:
* description: Informasi umum API berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Welcome to the Internify API
* documentation:
* type: string
* example: Check /api/health untuk status
*/
app.get('/', (_req: Request, res: Response) => {
res.status(200).json({
message: 'Welcome to the Internify API',
@@ -34,6 +56,8 @@ app.get('/', (_req: Request, res: Response) => {
app.use('/api', routes);
setupSwaggerDocs(app);
app.use((req: Request, _res: Response, next: NextFunction) => {
const error: CustomError = new Error(`Not Found - ${req.originalUrl}`);
error.statusCode = 404;
+211
View File
@@ -0,0 +1,211 @@
import path from 'path';
import type { Application } from 'express';
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const port = process.env.PORT || '5000';
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'Internify LMS API - HUMIC Engineering',
version: '1.0.0',
description: 'Dokumentasi REST API Internify LMS untuk autentikasi, pengelolaan lowongan magang, lamaran, dan konten platform.',
contact: {
name: 'HUMIC Engineering',
url: 'https://humic.telkomuniversity.ac.id/'
}
},
servers: [
{
url: process.env.NODE_ENV === 'production'
? 'https://your-production-domain.example.com'
: `http://localhost:${port}`,
description: process.env.NODE_ENV === 'production'
? 'Prod'
: 'Dev'
}
],
tags: [
{
name: 'Auth',
description: 'Endpoint autentikasi untuk login admin/peserta dan profil pengguna.'
},
{
name: 'Admin',
description: 'Endpoint utilitas dan pengelolaan sistem yang terkait admin.'
},
{
name: 'Batch',
description: 'Endpoint pengelolaan batch/angkatan magang.'
},
{
name: 'Lowongan Magang',
description: 'Endpoint pengelolaan lowongan magang.'
},
{
name: 'Lamaran Magang',
description: 'Endpoint pengelolaan lamaran magang.'
},
{
name: 'Mahasiswa',
description: 'Endpoint pengelolaan data mahasiswa/peserta.'
},
{
name: 'Partnership',
description: 'Endpoint pengelolaan data mitra/partnership.'
},
{
name: 'Hasil Research',
description: 'Endpoint pengelolaan hasil research/proyek.'
},
{
name: 'FAQ',
description: 'Endpoint pengelolaan FAQ (pertanyaan yang sering diajukan).'
},
{
name: 'Feedback',
description: 'Endpoint pengelolaan feedback/testimoni peserta.'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'Token JWT yang diperoleh dari endpoint /api/auth/login'
}
},
responses: {
UnauthorizedError: {
description: 'Token akses tidak ada atau tidak valid',
content: {
'application/json': {
examples: {
tokenMissing: {
value: {
status: 'error',
statusCode: 401,
message: 'Not authorized to access this route, token is missing'
}
},
tokenInvalid: {
value: {
status: 'error',
statusCode: 401,
message: 'Not authorized to access this route, token is invalid or expired'
}
},
userNotExist: {
value: {
status: 'error',
statusCode: 401,
message: 'User belonging to this token no longer exists'
}
}
}
}
}
},
ForbiddenError: {
description: 'Pengguna tidak memiliki izin untuk mengakses resource ini',
content: {
'application/json': {
example: {
status: 'error',
statusCode: 403,
message: 'You do not have permission to perform this action'
}
}
}
},
NotFoundError: {
description: 'Resource yang diminta tidak ditemukan',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
status: {
type: 'string',
example: 'error'
},
statusCode: {
type: 'integer',
example: 404
},
message: {
type: 'string',
example: 'Not Found - /api/endpoint-yang-tidak-ada'
}
}
}
}
}
},
ValidationError: {
description: 'Validasi request gagal',
content: {
'application/json': {
example: {
status: 'error',
statusCode: 400,
message: 'Mohon lengkapi semua field wajib yang diperlukan'
}
}
}
},
InternalServerError: {
description: 'Terjadi kesalahan pada server internal',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
status: {
type: 'string',
example: 'error'
},
statusCode: {
type: 'integer',
example: 500
},
message: {
type: 'string',
example: 'Internal Server Error'
}
}
}
}
}
}
}
}
},
apis: [
path.join(__dirname, '../app.ts'),
path.join(__dirname, '../app.js'),
path.join(__dirname, '../routes/*.ts'),
path.join(__dirname, '../controllers/*.ts'),
path.join(__dirname, '../routes/*.js'),
path.join(__dirname, '../controllers/*.js')
]
};
const swaggerSpec = swaggerJsdoc(swaggerOptions);
export const setupSwaggerDocs = (app: Application) => {
const swaggerEnabledValue = process.env.SWAGGER_ENABLE ?? process.env.SWAGGER_ENABLE ?? 'true';
const isSwaggerEnabled = swaggerEnabledValue.toLowerCase() === 'true';
if (!isSwaggerEnabled) {
return;
}
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
console.log('Swagger UI available at /api/docs');
};
export default setupSwaggerDocs;
+238 -9
View File
@@ -16,9 +16,110 @@ const generateToken = (payload: { id: string; email: string; role: string }) =>
};
/**
* @route POST /api/auth/register
* @desc Daftarin user baru dgn role STUDENT (Mahasiswa)
* @access Public
* @swagger
* /api/auth/register:
* post:
* summary: Registrasi akun mahasiswa baru
* description: Membuat akun pengguna baru dengan role `STUDENT` beserta profil mahasiswa.
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* - namaDepan
* - kontak
* - jurusan
* - universitas
* - negara
* properties:
* email:
* type: string
* example: student@example.com
* password:
* type: string
* example: Student123!
* namaDepan:
* type: string
* example: Alya
* namaBelakang:
* type: string
* example: Putri
* kontak:
* type: string
* example: 08123456789
* jurusan:
* type: string
* example: Informatika
* universitas:
* type: string
* example: Telkom University
* negara:
* type: string
* example: Indonesia
* cvPath:
* type: string
* example: /uploads/docs/cv.pdf
* portofolioPath:
* type: string
* example: /uploads/docs/portofolio.pdf
* motivasi:
* type: string
* example: Saya tertarik mengikuti program ini.
* relevantSkills:
* type: string
* example: React, Node.js
* responses:
* 201:
* description: Registrasi mahasiswa berhasil
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: Registrasi mahasiswa berhasil
* data:
* type: object
* properties:
* id:
* type: string
* example: cku3xq2v0000xk8w7a1b2c3d4
* email:
* type: string
* example: student@example.com
* role:
* type: string
* example: STUDENT
* createdAt:
* type: string
* format: date-time
* example: 2026-06-02T10:00:00.000Z
* 400:
* description: Validasi registrasi gagal
* content:
* application/json:
* examples:
* field_wajib_kosong:
* value:
* status: error
* statusCode: 400
* message: Mohon lengkapi semua field wajib yang diperlukan
* email_sudah_terdaftar:
* value:
* status: error
* statusCode: 400
* message: Email sudah terdaftar
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.post('/register', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -100,9 +201,78 @@ router.post('/register', async (req: Request, res: Response, next: NextFunction)
});
/**
* @route POST /api/auth/login
* @desc Autentikasi user biar dapet token
* @access Public
* @swagger
* /api/auth/login:
* post:
* summary: Login pengguna dan mendapatkan token JWT
* description: Melakukan autentikasi pengguna (admin/mahasiswa) dan mengembalikan token akses.
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* example: admin@internify.com
* password:
* type: string
* example: AdminInternify123!
* responses:
* 200:
* description: Login berhasil
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* data:
* type: object
* properties:
* token:
* type: string
* example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
* user:
* type: object
* properties:
* id:
* type: string
* example: cku3xq2v0000xk8w7a1b2c3d4
* email:
* type: string
* example: admin@internify.com
* role:
* type: string
* example: ADMIN
* profile:
* type: object
* description: Data profil admin atau mahasiswa
* 401:
* description: Autentikasi gagal
* content:
* application/json:
* example:
* status: error
* statusCode: 401
* message: Email atau password salah
* 400:
* description: Validasi login gagal
* content:
* application/json:
* example:
* status: error
* statusCode: 400
* message: Mohon masukkan email dan password
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.post('/login', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -161,9 +331,68 @@ router.post('/login', async (req: Request, res: Response, next: NextFunction) =>
});
/**
* @route GET /api/auth/me
* @desc Dapetin info ringkas user yg login buat navbar
* @access Private
* @swagger
* /api/auth/me:
* get:
* summary: Ambil profil pengguna yang sedang login
* description: Mengembalikan informasi ringkas user berdasarkan token JWT aktif.
* tags: [Auth]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Profil pengguna berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: object
* properties:
* id:
* type: string
* example: cku3xq2v0000xk8w7a1b2c3d4
* email:
* type: string
* example: admin@internify.com
* role:
* type: string
* example: ADMIN
* nama_depan:
* type: string
* example: Super
* 401:
* description: Token tidak valid atau tidak tersedia
* content:
* application/json:
* examples:
* token_missing:
* value:
* status: error
* statusCode: 401
* message: Not authorized to access this route, token is missing
* token_invalid:
* value:
* status: error
* statusCode: 401
* message: Not authorized to access this route, token is invalid or expired
* user_token_tidak_ada:
* value:
* status: error
* statusCode: 401
* message: User belonging to this token no longer exists
* 404:
* description: Pengguna tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: User not found
*/
router.get('/me', protect, async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
+202 -15
View File
@@ -16,9 +16,29 @@ function mapToFrontend(faq: any) {
}
/**
* @route GET /api/faq-api/get
* @desc Dapetin semua list FAQ
* @access Public
* @swagger
* /api/faq-api/get:
* get:
* summary: Ambil seluruh FAQ
* description: Mengembalikan daftar semua pertanyaan dan jawaban FAQ.
* tags: [FAQ]
* responses:
* 200:
* description: Data FAQ berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -35,9 +55,42 @@ router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
});
/**
* @route GET /api/faq-api/get/:id
* @desc Dapetin detail satu FAQ
* @access Public
* @swagger
* /api/faq-api/get/{id}:
* get:
* summary: Ambil detail FAQ berdasarkan ID
* description: Mengembalikan detail satu item FAQ.
* tags: [FAQ]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID FAQ
* responses:
* 200:
* description: Detail FAQ berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: object
* 404:
* description: FAQ tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: FAQ tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -62,9 +115,58 @@ router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) =
});
/**
* @route POST /api/faq-api/add
* @desc Tambah FAQ baru (khusus admin ya)
* @access Private (Admin)
* @swagger
* /api/faq-api/add:
* post:
* summary: Tambah FAQ baru
* description: Menambahkan item FAQ baru. Hanya dapat diakses oleh admin.
* tags: [FAQ]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - pertanyaan
* - jawaban
* properties:
* pertanyaan:
* type: string
* example: Apakah magang ini bisa remote?
* jawaban:
* type: string
* example: Ya, beberapa posisi mendukung skema remote.
* responses:
* 201:
* description: FAQ berhasil ditambahkan
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: FAQ berhasil ditambahkan
* 400:
* description: Validasi FAQ gagal
* content:
* application/json:
* example:
* status: error
* statusCode: 400
* message: Pertanyaan dan jawaban harus diisi
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.post('/add', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -94,9 +196,60 @@ router.post('/add', protect, restrictTo('ADMIN'), async (req: AuthenticatedReque
});
/**
* @route PATCH /api/faq-api/update/:id
* @desc Update data FAQ (khusus admin)
* @access Private (Admin)
* @swagger
* /api/faq-api/update/{id}:
* patch:
* summary: Perbarui FAQ
* description: Memperbarui item FAQ berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [FAQ]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID FAQ
* requestBody:
* required: false
* content:
* application/json:
* schema:
* type: object
* properties:
* pertanyaan:
* type: string
* jawaban:
* type: string
* responses:
* 200:
* description: FAQ berhasil diperbarui
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: FAQ berhasil diupdate
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: FAQ tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: FAQ tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.patch('/update/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -132,9 +285,43 @@ router.patch('/update/:id', protect, restrictTo('ADMIN'), async (req: Authentica
});
/**
* @route DELETE /api/faq-api/delete/:id
* @desc Hapus data FAQ (khusus admin)
* @access Private (Admin)
* @swagger
* /api/faq-api/delete/{id}:
* delete:
* summary: Hapus FAQ
* description: Menghapus item FAQ berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [FAQ]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID FAQ
* responses:
* 200:
* description: FAQ berhasil dihapus
* content:
* application/json:
* example:
* status: true
* message: FAQ berhasil dihapus
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: FAQ tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: FAQ tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.delete('/delete/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
+269 -15
View File
@@ -22,9 +22,29 @@ function mapToFrontend(feedback: any) {
}
/**
* @route GET /api/feedback-api/get
* @desc Dapetin semua feedback dari mahasiswa
* @access Public
* @swagger
* /api/feedback-api/get:
* get:
* summary: Ambil semua feedback
* description: Mengembalikan daftar seluruh feedback/testimoni peserta.
* tags: [Feedback]
* responses:
* 200:
* description: Data feedback berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -41,9 +61,42 @@ router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
});
/**
* @route GET /api/feedback-api/get/:id
* @desc Dapetin detail satu feedback
* @access Public
* @swagger
* /api/feedback-api/get/{id}:
* get:
* summary: Ambil detail feedback berdasarkan ID
* description: Mengembalikan detail satu feedback/testimoni.
* tags: [Feedback]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID feedback
* responses:
* 200:
* description: Detail feedback berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: object
* 404:
* description: Feedback tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Feedback tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -68,9 +121,95 @@ router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) =
});
/**
* @route POST /api/feedback-api/add
* @desc Tambah feedback baru (bisa admin / student)
* @access Private (Admin/Student)
* @swagger
* /api/feedback-api/add:
* post:
* summary: Tambah feedback baru
* description: Menambahkan feedback/testimoni baru. Dapat diakses user yang sudah login.
* tags: [Feedback]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* required:
* - nama
* - universitas
* - posisi
* - batch
* - tahun
* - pesan
* properties:
* nama:
* type: string
* example: Alya Putri
* universitas:
* type: string
* example: Telkom University
* posisi:
* type: string
* example: Frontend Developer Intern
* batch:
* type: integer
* example: 3
* tahun:
* type: integer
* example: 2026
* pesan:
* type: string
* example: Pengalaman magang sangat membantu perkembangan skill saya.
* image:
* type: string
* format: binary
* responses:
* 201:
* description: Feedback berhasil ditambahkan
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Feedback berhasil ditambahkan
* 400:
* description: Validasi input feedback gagal
* content:
* application/json:
* examples:
* field_wajib:
* value:
* status: error
* statusCode: 400
* message: Mohon lengkapi semua field yang wajib diisi
* angka_tidak_valid:
* value:
* status: error
* statusCode: 400
* message: Batch dan tahun harus berupa angka
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.post('/add', protect, upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -116,9 +255,90 @@ router.post('/add', protect, upload.single('image'), async (req: AuthenticatedRe
});
/**
* @route PATCH /api/feedback-api/update/:id
* @desc Update data feedback (bisa admin / student)
* @access Private (Admin/Student)
* @swagger
* /api/feedback-api/update/{id}:
* patch:
* summary: Perbarui feedback
* description: Memperbarui data feedback berdasarkan ID. Dapat diakses user yang sudah login.
* tags: [Feedback]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID feedback
* requestBody:
* required: false
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* nama:
* type: string
* universitas:
* type: string
* posisi:
* type: string
* batch:
* type: integer
* tahun:
* type: integer
* pesan:
* type: string
* image:
* type: string
* format: binary
* responses:
* 200:
* description: Feedback berhasil diperbarui
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Feedback berhasil diupdate
* 400:
* description: Validasi data feedback gagal
* content:
* application/json:
* example:
* status: error
* statusCode: 400
* message: Batch dan tahun harus berupa angka
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Feedback tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Feedback tidak ditemukan
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.patch('/update/:id', protect, upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -173,9 +393,43 @@ router.patch('/update/:id', protect, upload.single('image'), async (req: Authent
});
/**
* @route DELETE /api/feedback-api/delete/:id
* @desc Hapus feedback (khusus admin)
* @access Private (Admin)
* @swagger
* /api/feedback-api/delete/{id}:
* delete:
* summary: Hapus feedback
* description: Menghapus data feedback berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Feedback]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID feedback
* responses:
* 200:
* description: Feedback berhasil dihapus
* content:
* application/json:
* example:
* status: true
* message: Feedback berhasil dihapus
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Feedback tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Feedback tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.delete('/delete/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
+87 -9
View File
@@ -20,9 +20,30 @@ router.use('/feedback-api', feedbackRouter);
router.use('/faq-api', faqRouter);
/**
* @route GET /api/health
* @desc Endpoint untuk memeriksa kesehatan/status server
* @access Public
* @swagger
* /api/health:
* get:
* summary: Cek status kesehatan server
* description: Digunakan untuk memastikan API berjalan normal.
* tags: [Admin]
* responses:
* 200:
* description: Server berjalan dengan baik
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: OK
* message:
* type: string
* example: Server is running smoothly
* timestamp:
* type: string
* format: date-time
* example: 2026-06-02T10:00:00.000Z
*/
router.get('/health', (_req: Request, res: Response) => {
res.status(200).json({
@@ -33,9 +54,46 @@ router.get('/health', (_req: Request, res: Response) => {
});
/**
* @route GET /api/users
* @desc Mendapatkan daftar semua user dari database menggunakan Prisma
* @access Public
* @swagger
* /api/users:
* get:
* summary: Ambil daftar seluruh pengguna
* description: Mengembalikan seluruh data user dasar dari database.
* tags: [Admin]
* responses:
* 200:
* description: Daftar pengguna berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* count:
* type: integer
* example: 2
* data:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* example: cku3xq2v0000xk8w7a1b2c3d4
* email:
* type: string
* example: admin@internify.com
* role:
* type: string
* example: ADMIN
* createdAt:
* type: string
* format: date-time
* example: 2026-06-02T10:00:00.000Z
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/users', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -58,9 +116,29 @@ router.get('/users', async (_req: Request, res: Response, next: NextFunction) =>
});
/**
* @route GET /api/error-test
* @desc Mensimulasikan error server internal untuk memverifikasi middleware penanganan error global
* @access Public
* @swagger
* /api/error-test:
* get:
* summary: Simulasi error server untuk pengujian
* description: Endpoint utilitas untuk menguji middleware penanganan error global.
* tags: [Admin]
* responses:
* 500:
* description: Error simulasi berhasil dipicu
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: error
* statusCode:
* type: integer
* example: 500
* message:
* type: string
* example: This is a test error to verify our custom global error handler!
*/
router.get('/error-test', (_req: Request, _res: Response, next: NextFunction) => {
try {
+296 -18
View File
@@ -39,9 +39,124 @@ function mapToFrontend(lamaran: any) {
}
/**
* @route POST /api/lamaran-magang-api/add/:lowonganId
* @desc Kirim pendaftaran magang baru
* @access Public
* @swagger
* /api/lamaran-magang-api/add/{lowonganId}:
* post:
* summary: Kirim lamaran magang baru
* description: Mengirim pendaftaran magang ke lowongan tertentu beserta CV dan portofolio (PDF).
* tags: [Lamaran Magang]
* parameters:
* - in: path
* name: lowonganId
* required: true
* schema:
* type: string
* description: ID lowongan magang
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* required:
* - nama_depan
* - email
* - kontak
* - universitas
* - negara
* - jurusan
* - motivasi
* - cv
* - portofolio
* properties:
* nama_depan:
* type: string
* example: Alya
* nama_belakang:
* type: string
* example: Putri
* email:
* type: string
* example: alya@example.com
* kontak:
* type: string
* example: 081234567890
* universitas:
* type: string
* example: Telkom University
* negara:
* type: string
* example: Indonesia
* jurusan:
* type: string
* example: Informatika
* batch:
* type: integer
* example: 3
* motivasi:
* type: string
* example: Saya ingin belajar langsung dari tim engineering.
* relevant_skills:
* type: string
* example: React, TypeScript, Node.js
* cv:
* type: string
* format: binary
* portofolio:
* type: string
* format: binary
* responses:
* 201:
* description: Lamaran berhasil dikirim
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Pendaftaran magang berhasil dikirim
* 400:
* description: Validasi pengajuan lamaran gagal
* content:
* application/json:
* examples:
* field_wajib:
* value:
* status: error
* statusCode: 400
* message: Mohon lengkapi semua field wajib yang diperlukan
* dokumen_wajib:
* value:
* status: error
* statusCode: 400
* message: CV dan Portofolio wajib diunggah!
* 404:
* description: Lowongan magang tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Lowongan magang tidak ditemukan
* 500:
* description: Terjadi kesalahan server (termasuk validasi tipe file upload)
* content:
* application/json:
* examples:
* tipe_file_pdf:
* value:
* status: error
* statusCode: 500
* message: CV dan Portofolio harus dalam format PDF!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.post(
'/add/:lowonganId',
@@ -177,9 +292,35 @@ router.post(
);
/**
* @route GET /api/lamaran-magang-api/get
* @desc Dapetin semua data pelamar (admin aja ya)
* @access Private (Admin)
* @swagger
* /api/lamaran-magang-api/get:
* get:
* summary: Ambil seluruh data lamaran
* description: Mengambil daftar seluruh lamaran magang. Hanya dapat diakses oleh admin.
* tags: [Lamaran Magang]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Data lamaran berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get', protect, restrictTo('ADMIN'), async (_req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -202,9 +343,48 @@ router.get('/get', protect, restrictTo('ADMIN'), async (_req: AuthenticatedReque
});
/**
* @route GET /api/lamaran-magang-api/get/:id
* @desc Dapetin detail satu lamaran berdasarkan id
* @access Private (Admin)
* @swagger
* /api/lamaran-magang-api/get/{id}:
* get:
* summary: Ambil detail lamaran berdasarkan ID
* description: Mengambil detail satu lamaran magang. Hanya dapat diakses oleh admin.
* tags: [Lamaran Magang]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID lamaran magang
* responses:
* 200:
* description: Detail lamaran berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: object
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Lamaran tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Lamaran tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -235,9 +415,69 @@ router.get('/get/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRe
});
/**
* @route PATCH /api/lamaran-magang-api/update/:id
* @desc Ganti status lamaran (diterima, ditolak, dll)
* @access Private (Admin)
* @swagger
* /api/lamaran-magang-api/update/{id}:
* patch:
* summary: Perbarui status lamaran
* description: Mengubah status lamaran menjadi `DIPROSES`, `DITERIMA`, atau `DITOLAK`. Hanya admin.
* tags: [Lamaran Magang]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID lamaran magang
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - status
* properties:
* status:
* type: string
* enum: [DIPROSES, DITERIMA, DITOLAK]
* example: DITERIMA
* responses:
* 200:
* description: Status lamaran berhasil diperbarui
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Status lamaran berhasil diupdate
* 400:
* description: Validasi status lamaran gagal
* content:
* application/json:
* examples:
* status_kosong:
* value:
* status: error
* statusCode: 400
* message: Mohon masukkan status baru
* status_tidak_valid:
* value:
* status: error
* statusCode: 400
* message: Status tidak valid
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.patch('/update/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -281,9 +521,28 @@ router.patch('/update/:id', protect, restrictTo('ADMIN'), async (req: Authentica
});
/**
* @route DELETE /api/lamaran-magang-api/delete
* @desc Hapus semua data pelamar
* @access Private (Admin)
* @swagger
* /api/lamaran-magang-api/delete:
* delete:
* summary: Hapus seluruh data lamaran
* description: Menghapus seluruh data lamaran magang. Hanya dapat diakses oleh admin.
* tags: [Lamaran Magang]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Seluruh data pelamar berhasil dihapus
* content:
* application/json:
* example:
* status: true
* message: Seluruh data pelamar berhasil dihapus
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.delete('/delete', protect, restrictTo('ADMIN'), async (_req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -298,9 +557,28 @@ router.delete('/delete', protect, restrictTo('ADMIN'), async (_req: Authenticate
});
/**
* @route GET /api/lamaran-magang-api/export
* @desc Ekspor data pelamar ke file Excel (.xlsx)
* @access Private (Admin)
* @swagger
* /api/lamaran-magang-api/export:
* get:
* summary: Ekspor data lamaran ke file Excel
* description: Mengunduh data lamaran dalam format `.xlsx`. Hanya dapat diakses oleh admin.
* tags: [Lamaran Magang]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: File Excel berhasil dibuat
* content:
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
* schema:
* type: string
* format: binary
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/export', protect, restrictTo('ADMIN'), async (_req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
+340 -18
View File
@@ -42,9 +42,42 @@ function mapToFrontend(lowongan: any) {
}
/**
* @route GET /api/lowongan-magang-api/get
* @desc Ambil semua daftar lowongan magang nih
* @access Public
* @swagger
* /api/lowongan-magang-api/get:
* get:
* summary: Ambil seluruh lowongan magang
* description: Mengembalikan daftar semua lowongan magang yang tersedia.
* tags: [Lowongan Magang]
* responses:
* 200:
* description: Daftar lowongan berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* example: lowongan-frontend
* posisi:
* type: string
* example: Frontend Developer Intern
* kelompok_peminatan:
* type: string
* example: Web Engineering
* status_lowongan:
* type: string
* example: DIBUKA
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -61,9 +94,30 @@ router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
});
/**
* @route GET /api/lowongan-magang-api/get/kelompok-all
* @desc Ambil semua kelompok peminatan yg unik
* @access Public
* @swagger
* /api/lowongan-magang-api/get/kelompok-all:
* get:
* summary: Ambil semua kelompok peminatan unik
* description: Mengembalikan daftar kategori peminatan tanpa duplikasi.
* tags: [Lowongan Magang]
* responses:
* 200:
* description: Daftar kelompok peminatan berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: string
* example: ["Web Engineering", "Design & Creative"]
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/kelompok-all', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -82,9 +136,52 @@ router.get('/get/kelompok-all', async (_req: Request, res: Response, next: NextF
});
/**
* @route GET /api/lowongan-magang-api/get/id/:id
* @desc Ambil detail lowongan magang berdasarkan id
* @access Public
* @swagger
* /api/lowongan-magang-api/get/id/{id}:
* get:
* summary: Ambil detail lowongan berdasarkan ID
* description: Mengembalikan detail satu lowongan magang berdasarkan parameter `id`.
* tags: [Lowongan Magang]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: ID lowongan
* responses:
* 200:
* description: Detail lowongan berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: object
* properties:
* id:
* type: string
* example: lowongan-frontend
* posisi:
* type: string
* example: Frontend Developer Intern
* kelompok_peminatan:
* type: string
* example: Web Engineering
* 404:
* description: Lowongan tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Lowongan tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/id/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -107,9 +204,105 @@ router.get('/get/id/:id', async (req: Request, res: Response, next: NextFunction
});
/**
* @route POST /api/lowongan-magang-api/add
* @desc Tambah lowongan magang baru (khusus admin ya)
* @access Private (Admin)
* @swagger
* /api/lowongan-magang-api/add:
* post:
* summary: Tambah lowongan magang baru
* description: Membuat lowongan magang baru. Hanya dapat diakses oleh admin.
* tags: [Lowongan Magang]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* required:
* - posisi
* - kelompok_peminatan
* - lokasi
* - jobdesk
* - durasi_awal
* - durasi_akhir
* - paid
* properties:
* posisi:
* type: string
* example: Frontend Developer Intern
* kelompok_peminatan:
* type: string
* example: Web Engineering
* lokasi:
* type: string
* example: Remote
* jobdesk:
* type: string
* example: Mengembangkan antarmuka responsif menggunakan React.
* kualifikasi:
* type: string
* example: Memahami React JS, ES6 JavaScript, HTML, CSS.
* benefit:
* type: string
* example: Sertifikat magang resmi, jam kerja fleksibel.
* durasi_awal:
* type: string
* format: date
* example: 2026-06-01
* durasi_akhir:
* type: string
* format: date
* example: 2026-09-01
* paid:
* type: string
* enum: [PAID, UNPAID]
* example: PAID
* image:
* type: string
* format: binary
* responses:
* 201:
* description: Lowongan berhasil ditambahkan
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Internship lowongan berhasil ditambahkan
* data:
* type: object
* 400:
* description: Validasi input lowongan gagal
* content:
* application/json:
* example:
* status: error
* statusCode: 400
* message: Mohon lengkapi semua field yang wajib diisi
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.post('/add', protect, restrictTo('ADMIN'), upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -166,9 +359,98 @@ router.post('/add', protect, restrictTo('ADMIN'), upload.single('image'), async
});
/**
* @route PATCH /api/lowongan-magang-api/update/:id
* @desc Update data lowongan magang (khusus admin)
* @access Private (Admin)
* @swagger
* /api/lowongan-magang-api/update/{id}:
* patch:
* summary: Perbarui data lowongan magang
* description: Memperbarui data lowongan berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Lowongan Magang]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: ID lowongan
* requestBody:
* required: false
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* posisi:
* type: string
* kelompok_peminatan:
* type: string
* lokasi:
* type: string
* jobdesk:
* type: string
* kualifikasi:
* type: string
* benefit:
* type: string
* durasi_awal:
* type: string
* format: date
* durasi_akhir:
* type: string
* format: date
* paid:
* type: string
* enum: [PAID, UNPAID]
* status_lowongan:
* type: string
* enum: [DIBUKA, DITUTUP]
* image:
* type: string
* format: binary
* responses:
* 200:
* description: Lowongan berhasil diperbarui
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Lowongan berhasil diupdate
* data:
* type: object
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Lowongan tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Lowongan tidak ditemukan
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.patch('/update/:id', protect, restrictTo('ADMIN'), upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -229,9 +511,49 @@ router.patch('/update/:id', protect, restrictTo('ADMIN'), upload.single('image')
});
/**
* @route DELETE /api/lowongan-magang-api/delete/:id
* @desc Hapus lowongan magang (khusus admin)
* @access Private (Admin)
* @swagger
* /api/lowongan-magang-api/delete/{id}:
* delete:
* summary: Hapus lowongan magang
* description: Menghapus data lowongan berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Lowongan Magang]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: ID lowongan
* responses:
* 200:
* description: Lowongan berhasil dihapus
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Lowongan berhasil dihapus
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Lowongan tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Lowongan tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.delete('/delete/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
+238 -15
View File
@@ -17,9 +17,29 @@ function mapToFrontend(partner: any) {
}
/**
* @route GET /api/partnership-api/get
* @desc Dapetin semua data partnerships
* @access Public
* @swagger
* /api/partnership-api/get:
* get:
* summary: Ambil seluruh data partnership
* description: Mengembalikan daftar seluruh mitra partnership.
* tags: [Partnership]
* responses:
* 200:
* description: Data partnership berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -36,9 +56,44 @@ router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
});
/**
* @route GET /api/partnership-api/get/:id
* @desc Dapetin detail satu partnership
* @access Public
* @swagger
* /api/partnership-api/get/{id}:
* get:
* summary: Ambil detail partnership berdasarkan ID
* description: Mengembalikan detail satu data partnership.
* tags: [Partnership]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID partnership
* responses:
* 200:
* description: Detail partnership berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* 404:
* description: Partnership tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Partnership tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -63,9 +118,78 @@ router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) =
});
/**
* @route POST /api/partnership-api/add
* @desc Tambah partnership baru (khusus admin ya)
* @access Private (Admin)
* @swagger
* /api/partnership-api/add:
* post:
* summary: Tambah partnership baru
* description: Menambahkan data partnership baru. Hanya dapat diakses oleh admin.
* tags: [Partnership]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* required:
* - nama_partner
* - image
* properties:
* nama_partner:
* type: string
* example: PT Teknologi Nusantara
* image:
* type: string
* format: binary
* responses:
* 201:
* description: Partnership berhasil ditambahkan
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Partnership berhasil ditambahkan
* 400:
* description: Validasi input partnership gagal
* content:
* application/json:
* examples:
* nama_wajib:
* value:
* status: error
* statusCode: 400
* message: Nama partnership harus diisi
* image_wajib:
* value:
* status: error
* statusCode: 400
* message: Thumbnail/image harus diunggah
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.post('/add', protect, restrictTo('ADMIN'), upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -102,9 +226,74 @@ router.post('/add', protect, restrictTo('ADMIN'), upload.single('image'), async
});
/**
* @route PATCH /api/partnership-api/update/:id
* @desc Update data partnership (khusus admin)
* @access Private (Admin)
* @swagger
* /api/partnership-api/update/{id}:
* patch:
* summary: Perbarui data partnership
* description: Memperbarui data partnership berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Partnership]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID partnership
* requestBody:
* required: false
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* nama_partner:
* type: string
* image:
* type: string
* format: binary
* responses:
* 200:
* description: Partnership berhasil diperbarui
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Partnership berhasil diupdate
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Partnership tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Partnership tidak ditemukan
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.patch('/update/:id', protect, restrictTo('ADMIN'), upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -145,9 +334,43 @@ router.patch('/update/:id', protect, restrictTo('ADMIN'), upload.single('image')
});
/**
* @route DELETE /api/partnership-api/delete/:id
* @desc Hapus data partnership (khusus admin)
* @access Private (Admin)
* @swagger
* /api/partnership-api/delete/{id}:
* delete:
* summary: Hapus partnership
* description: Menghapus data partnership berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Partnership]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID partnership
* responses:
* 200:
* description: Partnership berhasil dihapus
* content:
* application/json:
* example:
* status: true
* message: Partnership berhasil dihapus
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Partnership tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Partnership tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.delete('/delete/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
+240 -15
View File
@@ -19,9 +19,29 @@ function mapToFrontend(product: any) {
}
/**
* @route GET /api/hasil-research-api/get
* @desc Dapetin semua hasil riset/products
* @access Public
* @swagger
* /api/hasil-research-api/get:
* get:
* summary: Ambil semua hasil research
* description: Mengembalikan daftar seluruh project hasil research.
* tags: [Hasil Research]
* responses:
* 200:
* description: Data hasil research berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
try {
@@ -38,9 +58,42 @@ router.get('/get', async (_req: Request, res: Response, next: NextFunction) => {
});
/**
* @route GET /api/hasil-research-api/get/:id
* @desc Dapetin detail satu hasil riset/product
* @access Public
* @swagger
* /api/hasil-research-api/get/{id}:
* get:
* summary: Ambil detail hasil research berdasarkan ID
* description: Mengembalikan detail satu project hasil research.
* tags: [Hasil Research]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID hasil research
* responses:
* 200:
* description: Detail hasil research berhasil diambil
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* data:
* type: object
* 404:
* description: Project tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Project tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -65,9 +118,78 @@ router.get('/get/:id', async (req: Request, res: Response, next: NextFunction) =
});
/**
* @route POST /api/hasil-research-api/add
* @desc Tambah hasil riset baru (khusus admin ya)
* @access Private (Admin)
* @swagger
* /api/hasil-research-api/add:
* post:
* summary: Tambah hasil research baru
* description: Menambahkan data project hasil research baru. Hanya dapat diakses oleh admin.
* tags: [Hasil Research]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* required:
* - nama_project
* - deskripsi
* - link_project
* properties:
* nama_project:
* type: string
* example: Sistem Monitoring Tanaman IoT
* deskripsi:
* type: string
* example: Project monitoring kelembapan tanah berbasis IoT.
* link_project:
* type: string
* example: https://example.com/project/monitoring-tanaman
* image:
* type: string
* format: binary
* responses:
* 201:
* description: Hasil research berhasil ditambahkan
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Research project berhasil ditambahkan
* 400:
* description: Validasi input hasil research gagal
* content:
* application/json:
* example:
* status: error
* statusCode: 400
* message: Mohon lengkapi semua field yang wajib diisi
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.post('/add', protect, restrictTo('ADMIN'), upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -101,9 +223,78 @@ router.post('/add', protect, restrictTo('ADMIN'), upload.single('image'), async
});
/**
* @route PATCH /api/hasil-research-api/update/:id
* @desc Update data hasil riset (khusus admin)
* @access Private (Admin)
* @swagger
* /api/hasil-research-api/update/{id}:
* patch:
* summary: Perbarui hasil research
* description: Memperbarui data hasil research berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Hasil Research]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID hasil research
* requestBody:
* required: false
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* nama_project:
* type: string
* deskripsi:
* type: string
* link_project:
* type: string
* image:
* type: string
* format: binary
* responses:
* 200:
* description: Hasil research berhasil diperbarui
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: boolean
* example: true
* message:
* type: string
* example: Research project berhasil diupdate
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Project tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Project tidak ditemukan
* 500:
* description: Terjadi kesalahan server (termasuk validasi file upload)
* content:
* application/json:
* examples:
* file_bukan_gambar:
* value:
* status: error
* statusCode: 500
* message: File upload harus berupa gambar!
* internal:
* value:
* status: error
* statusCode: 500
* message: Internal Server Error
*/
router.patch('/update/:id', protect, restrictTo('ADMIN'), upload.single('image'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {
@@ -146,9 +337,43 @@ router.patch('/update/:id', protect, restrictTo('ADMIN'), upload.single('image')
});
/**
* @route DELETE /api/hasil-research-api/delete/:id
* @desc Hapus data hasil riset (khusus admin)
* @access Private (Admin)
* @swagger
* /api/hasil-research-api/delete/{id}:
* delete:
* summary: Hapus hasil research
* description: Menghapus data hasil research berdasarkan ID. Hanya dapat diakses oleh admin.
* tags: [Hasil Research]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID hasil research
* responses:
* 200:
* description: Hasil research berhasil dihapus
* content:
* application/json:
* example:
* status: true
* message: Research project berhasil dihapus
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* description: Project tidak ditemukan
* content:
* application/json:
* example:
* status: error
* statusCode: 404
* message: Project tidak ditemukan
* 500:
* $ref: '#/components/responses/InternalServerError'
*/
router.delete('/delete/:id', protect, restrictTo('ADMIN'), async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
try {