Skip to content

Commit

Permalink
refactor code, separate controller,services and routes
Browse files Browse the repository at this point in the history
  • Loading branch information
Fauzanmhr committed Aug 23, 2024
1 parent 9cad516 commit ee70766
Show file tree
Hide file tree
Showing 24 changed files with 1,380 additions and 161 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PORT=3000
API_URL=https://wajik-anime-api.vercel.app
NODE_ENV=production
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/jsLibraryMappings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/zannime.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions controllers/animeController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { fetchOngoingAnime, fetchAnimeDetails } from '../services/animeService.js';

export const getAllAnime = async (req, res) => {
const page = req.query.page || 1;
const result = await fetchOngoingAnime(page);
res.render('index', { animes: result.data, pagination: result.pagination });
};

export const getAnimeDetails = async (req, res) => {
const anime = await fetchAnimeDetails(req.params.slug);
if (anime) {
res.render('detail', { anime });
} else {
res.status(500).send('Error fetching anime details');
}
};

export const searchAnime = (req, res) => {
const { q: query, page = 1, limit = 20 } = req.query;
if (!query) return res.json({ results: [] });

const allAnimeData = req.app.locals.allAnimeData;
const filteredResults = allAnimeData.filter(anime => anime.judul.toLowerCase().includes(query.toLowerCase()));
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const results = filteredResults.slice(startIndex, endIndex);

res.json({
results,
currentPage: parseInt(page, 10),
totalPages: Math.ceil(filteredResults.length / limit)
});
};

export const getAllAnimeAjax = (req, res) => {
const { page = 1, limit = 20 } = req.query;
const allAnimeData = req.app.locals.allAnimeData;

const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const results = allAnimeData.slice(startIndex, endIndex);

res.json({
results,
currentPage: parseInt(page, 10),
totalPages: Math.ceil(allAnimeData.length / limit)
});
};

export const renderAllAnimePage = (req, res) => {
const { page = 1 } = req.query;
const limit = 20;
const allAnimeData = req.app.locals.allAnimeData;

const totalAnimes = allAnimeData.length;
const totalPages = Math.ceil(totalAnimes / limit);
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const paginatedAnimes = allAnimeData.slice(startIndex, endIndex);

const pagination = {
currentPage: page,
totalPages,
prevPage: page > 1 ? page - 1 : null,
nextPage: page < totalPages ? page + 1 : null
};

res.render('all-anime', { animes: paginatedAnimes, pagination });
};
12 changes: 12 additions & 0 deletions controllers/episodeController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { fetchEpisodeDetails } from '../services/episodeService.js';

export const getEpisodeDetails = async (req, res) => {
if (!req.query.slug) return res.redirect('/');

const episode = await fetchEpisodeDetails(req.query.slug);
if (episode) {
res.render('episode', { episode });
} else {
res.status(500).send('Error fetching episode details');
}
};
13 changes: 13 additions & 0 deletions controllers/genreController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { fetchGenres, fetchAnimeByGenre } from '../services/genreService.js';

export const getGenres = async (req, res) => {
const genres = await fetchGenres();
res.render('genres', { genres });
};

export const getAnimeByGenre = async (req, res) => {
const { slug } = req.params;
const { page = 1 } = req.query;
const result = await fetchAnimeByGenre(slug, page);
res.render('genre-anime', { animes: result.data, pagination: result.pagination, genre: slug });
};
15 changes: 15 additions & 0 deletions controllers/shortlinkController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { fetchShortlink } from '../services/shortlinkService.js';

export const decodeShortlink = async (req, res) => {
const { url: shortlink } = req.query;
if (!shortlink) return res.status(400).send('URL parameter is required');

try {
const redirectionUrl = await fetchShortlink(shortlink);
if (redirectionUrl) return res.send(redirectionUrl);
res.status(404).send('No redirection found');
} catch (error) {
console.error('Error decoding shortlink:', error);
res.status(500).send('Error decoding shortlink');
}
};
175 changes: 15 additions & 160 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import ky from 'ky';
import express from 'express';
import 'dotenv/config';
import path from 'path';
import { fileURLToPath } from 'url';
import * as https from 'node:https';
import http from 'http';
import {fileURLToPath} from 'url';
import animeRoutes from './routes/animeRoutes.js';
import genreRoutes from './routes/genreRoutes.js';
import episodeRoutes from './routes/episodeRoutes.js';
import shortlinkRoutes from './routes/shortlinkRoutes.js';
import {fetchAllAnimeData} from './services/animeService.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand All @@ -23,171 +25,24 @@ app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist
app.use('/js', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js')));
app.use('/js', express.static(path.join(__dirname, 'node_modules/@popperjs/core/dist/umd')));

let allAnimeData = [];

// Fetch all anime data from API and store in memory
const fetchAllAnimeData = async () => {
try {
const response = await ky.get(`${process.env.API_URL}/otakudesu/anime`).json();
allAnimeData = response.data.flatMap(item => item.anime);
} catch (error) {
console.error('Error fetching all anime data:', error);
}
const fetchAndStoreAnimeData = async () => {
app.locals.allAnimeData = await fetchAllAnimeData();
};

// Fetch data on server start
(async () => {
await fetchAllAnimeData();
await fetchAndStoreAnimeData();
})();

// Periodically update all anime data (every 60 minutes)
setInterval(fetchAllAnimeData, 60 * 60 * 1000);

// Home route to list content with pagination
app.get('/', async (req, res) => {
const page = req.query.page || 1;
try {
const response = await ky.get(`${process.env.API_URL}/otakudesu/ongoing?page=${page}`).json();
res.render('index', { animes: response.data, pagination: response.pagination });
} catch (error) {
console.error('Error fetching data:', error);
res.status(500).send('Error fetching data');
}
});

// Search route with pagination parameters
app.get('/search-ajax', (req, res) => {
const { q: query, page = 1, limit = 20 } = req.query;
if (!query) return res.json({ results: [] });

const filteredResults = allAnimeData.filter(anime => anime.judul.toLowerCase().includes(query.toLowerCase()));
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const results = filteredResults.slice(startIndex, endIndex);

res.json({
results,
currentPage: parseInt(page, 10),
totalPages: Math.ceil(filteredResults.length / limit)
});
});

// All anime route with pagination parameters
app.get('/all-anime-ajax', (req, res) => {
const { page = 1, limit = 20 } = req.query;

const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const results = allAnimeData.slice(startIndex, endIndex);

res.json({
results,
currentPage: parseInt(page, 10),
totalPages: Math.ceil(allAnimeData.length / limit)
});
});

// Anime detail route
app.get('/anime/:slug', async (req, res) => {
try {
const response = await ky.get(`${process.env.API_URL}/otakudesu/anime/${req.params.slug}`).json();
res.render('detail', { anime: response.data });
} catch (error) {
console.error('Error fetching anime details:', error);
res.status(500).send('Error fetching anime details');
}
});

// Episode detail route
app.get('/episode', async (req, res) => {
if (!req.query.slug) return res.redirect('/');

try {
const response = await ky.get(`${process.env.API_URL}/otakudesu/episode/${req.query.slug}`).json();
res.render('episode', { episode: response.data });
} catch (error) {
console.error('Error fetching episode details:', error);
res.status(500).send('Error fetching episode details');
}
});

// All anime route
app.get('/all-anime', (req, res) => {
const { page = 1 } = req.query;
const limit = 20;

const totalAnimes = allAnimeData.length;
const totalPages = Math.ceil(totalAnimes / limit);
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const paginatedAnimes = allAnimeData.slice(startIndex, endIndex);

const pagination = {
currentPage: page,
totalPages,
prevPage: page > 1 ? page - 1 : null,
nextPage: page < totalPages ? page + 1 : null
};

res.render('all-anime', { animes: paginatedAnimes, pagination });
});

// Genres route
app.get('/genres', async (req, res) => {
try {
const response = await ky.get(`${process.env.API_URL}/otakudesu/genres`).json();
res.render('genres', { genres: response.data });
} catch (error) {
console.error('Error fetching genres:', error);
res.status(500).send('Error fetching genres');
}
});

// Anime by genre route
app.get('/genres/:slug', async (req, res) => {
const { slug } = req.params;
const { page = 1 } = req.query;

try {
const response = await ky.get(`${process.env.API_URL}/otakudesu/genres/${slug}?page=${page}`).json();
res.render('genre-anime', { animes: response.data, pagination: response.pagination, genre: slug });
} catch (error) {
console.error('Error fetching anime by genre:', error);
res.status(500).send('Error fetching anime by genre');
}
});

// Function to fetch the shortlink and capture the initial redirection URL
const fetchShortlink = (url) => {
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https') ? https : http;

const request = protocol.get(url, (response) => {
if (response.statusCode >= 300 && response.statusCode < 400) {
return resolve(response.headers.location);
}
resolve(null);
});

request.on('error', reject);
request.end();
});
};

// Route to decode shortlink
app.get('/decode-shortlink', async (req, res) => {
const { url: shortlink } = req.query;
if (!shortlink) return res.status(400).send('URL parameter is required');
setInterval(fetchAndStoreAnimeData, 60 * 60 * 1000);

try {
const redirectionUrl = await fetchShortlink(shortlink);
if (redirectionUrl) return res.send(redirectionUrl);
res.status(404).send('No redirection found');
} catch (error) {
console.error('Error decoding shortlink:', error);
res.status(500).send('Error decoding shortlink');
}
});
// Use routes
app.use('/', animeRoutes);
app.use('/', genreRoutes);
app.use('/', episodeRoutes);
app.use('/', shortlinkRoutes);

// Start the server
app.listen(port, () => {
Expand Down
Loading

0 comments on commit ee70766

Please sign in to comment.