created API For Aplication Absensi

This commit is contained in:
2025-10-14 14:08:11 +07:00
commit 96d206d892
56 changed files with 6533 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
const response = require('../../../helpers/responses')
const db = require('../../../../models/migration')
const errorHandler = require('../../../middlewares/errorHandler')
const { sequelize, Op } = require('../../../../models/migration')
const User = db.User
const path = require("path");
const fs = require("fs");
const Story = db.Story;
const StoryReader = db.StoryReader
const Serial = db.Serial;
const StoryRating = db.StoryRating
const StoryLike = db.StoryLike
const Category = db.Category;
const DifficultyLevel = db.DifficultyLevel
const AgeTarget = db.AgeTarget
// UPDATE
const update = async (req, res) => {
const t = await sequelize.transaction();
try {
const id = req.user.id;
const users = await User.findOne({
where: { id },
transaction: t,
});
if (!users) {
await t.rollback();
return response.failed(res, 404, "User tidak ditemukan atau bukan milik Anda");
}
const body = req.body;
let avatarUrl = users.avatar_url; // default tetap avatar lama
// 🔹 Kalau ada file diupload
if (req.file) {
const uploadDir = path.join(process.cwd(), "public", "uploads", "avatars");
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
// Simpan file
const fileName = `${Date.now()}-${req.file.originalname}`;
const filePath = path.join(uploadDir, fileName);
fs.writeFileSync(filePath, req.file.buffer);
// Bisa pakai URL public (misal /uploads/avatars/xxx.jpg)
avatarUrl = `/uploads/avatars/${fileName}`;
}
const updatedUser = await users.update(
{
...body,
avatar_url: avatarUrl,
},
{ transaction: t }
);
await t.commit();
return response.success(res, updatedUser, "Profil berhasil diperbarui");
} catch (error) {
await t.rollback();
errorHandler(error, req, res);
return response.failed(res, 500, error.message);
}
};
// GET ALL
const getProfile = async (req, res) => {
try {
const user_id = req.user.id
const user = await User.findOne({
where: { id: user_id }
})
if (!user) {
return response.failed(res, 404, 'User tidak ditemukan')
}
// Mapper agar sesuai response yang kamu mau
const result = {
id: user.id,
email: user.email,
display_name: user.name, // atau field display_name jika ada
role: user.role,
avatar_url: user.avatar_url || null,
bio: user.bio || null,
birth: user.birth || null,
created_at: user.created_at,
updated_at: user.updated_at
}
return response.success(res, result, 'Profile berhasil dimuat')
} catch (error) {
errorHandler(error, req, res)
return response.failed(res, 500, error.message)
}
}
const getOverview = async (req, res) => {
try {
const user_id = req.user.id;
// Total cerita
const totalStories = await Story.count({ where: { user_id } });
const totalPublished = await Story.count({ where: { user_id, is_published: true } });
// Total pembaca unik
const totalReaders = await StoryReader.count({
include: [{ model: Story, where: { user_id }, attributes: [] }],
distinct: true,
col: "user_id"
});
// Rating rata-rata (FIX: kualifikasi kolom supaya tidak ambiguous)
const ratingResult = await StoryRating.findOne({
attributes: [
[sequelize.fn("AVG", sequelize.col("StoryRating.rating")), "avgRating"]
],
include: [{ model: Story, where: { user_id }, attributes: [] }],
raw: true
});
const avgRating = ratingResult && ratingResult.avgRating
? Number(parseFloat(ratingResult.avgRating).toFixed(1))
: 0;
// === Ambil semua serial user ===
const serialsRaw = await Serial.findAll({
where: { user_id },
attributes: [
"id",
"title",
"description",
"reading_time",
"rating",
"cover_image_url",
"createdAt",
"is_active"
],
include: [{ model: Category, attributes: ["id", "title", "emoji"] }],
order: [["createdAt", "DESC"]]
});
// Ambil semua story user (sekalian untuk lookup cover)
const stories = await Story.findAll({
where: { user_id },
attributes: [
"id",
"title",
"synopsis",
"is_published",
"cover_image_url",
"createdAt",
"series_id"
],
include: [
{ model: Category, attributes: ["id", "title", "emoji"] },
{ model: DifficultyLevel, attributes: ["id", "title", "emoji"] },
{ model: AgeTarget, attributes: ["id", "title", "emoji"] }
],
order: [["createdAt", "DESC"]]
});
// Buat map serial_id → serial
const serialMap = {};
serialsRaw.forEach(serial => {
serialMap[serial.id] = serial.toJSON();
});
// Buat map serial_id → story pertama (untuk fallback cover)
const storiesBySerial = {};
for (const story of stories) {
if (story.series_id && !storiesBySerial[story.series_id]) {
storiesBySerial[story.series_id] = story.cover_image_url;
}
}
// === Gabungkan serial dengan fallback cover ===
const serials = serialsRaw.map(serial => {
let coverImage = serial.cover_image_url;
if (!coverImage && storiesBySerial[serial.id]) {
coverImage = storiesBySerial[serial.id];
}
const storiesOfSerial = stories.filter(s => s.series_id === serial.id);
return {
id: serial.id,
title: serial.title,
description: serial.description,
reading_time: serial.reading_time,
rating: serial.rating,
cover_image_url: coverImage,
is_active: serial.is_active,
createdAt: serial.createdAt,
category: serial.Category
? { id: serial.Category.id, title: serial.Category.title, emoji: serial.Category.emoji }
: null,
story: storiesOfSerial.map(s => ({
id: s.id,
title: s.title,
cover_image_url: s.cover_image_url,
is_published: s.is_published
})),
total_episodes: storiesOfSerial.length
};
});
// === Gabungkan story dengan serial data ===
const storiesWithSerial = stories.map(story => {
const serial = story.series_id ? serialMap[story.series_id] || null : null;
let coverImage = story.cover_image_url;
// Fallback cover dari serial
if (!coverImage && serial) {
coverImage = serial.cover_image_url || storiesBySerial[serial.id] || null;
}
return {
...story.toJSON(),
cover_image_url: coverImage,
Serial: serial
};
});
// === Response ===
return response.success(
res,
{
total_stories: totalStories,
total_published: totalPublished,
total_readers: totalReaders,
average_rating: avgRating,
stories: storiesWithSerial,
serials
},
"Overview berhasil dimuat"
);
} catch (error) {
errorHandler(error, req, res);
return response.failed(res, 500, error.message);
}
};
module.exports = {
update,
getProfile,
getOverview
}