docs: menambahkan dokumentasi api dengan openapi 3.0.0
This commit is contained in:
+24
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user