initial commit

This commit is contained in:
fiatcode 2025-10-18 10:37:22 +07:00
commit 83337e0574
14 changed files with 2825 additions and 0 deletions

45
src/data/db.ts Normal file
View file

@ -0,0 +1,45 @@
import { DatabaseSync } from 'node:sqlite';
import type { Quote, Translation } from '../types.js';
const MAX_QUOTE_ID = 250000;
const db = new DatabaseSync('quotes.db');
function getRandomQuote(tableName: string = 'quotes'): Quote {
const randomId = Math.floor(Math.random() * MAX_QUOTE_ID) + 1;
const row = db
.prepare(`SELECT * FROM ${tableName} WHERE id = ?`)
.get(randomId);
if (!row) {
throw new Error(`Quote with id ${randomId} not found.`);
}
return {
id: row['id'] as number,
text: row['quote'] as string,
author: row['author'] as string,
};
}
function getQuote(id: number, tableName: string = 'quotes'): Quote {
const row = db.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).get(id);
if (!row) {
throw new Error(`Quote with id ${id} not found.`);
}
return {
id: row['id'] as number,
text: row['quote'] as string,
author: row['author'] as string,
};
}
function listTranslations(): Translation[] {
const rows = db.prepare(`SELECT * FROM translations`).all();
return rows.map((row) => {
return {
id: row['id'] as number,
lang: row['lang'] as string,
tableName: row['table_name'] as string,
};
});
}
export { getQuote, getRandomQuote, listTranslations };

38
src/data/unsplash.ts Normal file
View file

@ -0,0 +1,38 @@
import type { UnsplashImage } from '../types';
async function listRandomImages(count: number = 10): Promise<UnsplashImage[]> {
const headers = {
Authorization: `Client-ID ${process.env.UNSPLASH_ACCESS_KEY}`,
'Accept-Version': 'v1',
};
const response = await fetch(
`https://api.unsplash.com/photos/random?count=${count}`,
{ headers }
);
const data = await response.json();
const images: UnsplashImage[] = Array.isArray(data)
? data.map((img: any) => ({
id: img.id,
description: img.alt_description ?? 'No description',
color: img.color,
blurHash: img.blur_hash,
url: img.urls.regular,
originUrl: buildUtmUrl(img.links.html),
authorName: img.user.name,
authorBio: img.user.bio ?? 'No bio',
authorLocation: img.user.location,
authorTotalLikes: img.user.total_likes,
authorTotalPhotos: img.user.total_photos,
authorIsForHire: img.user.for_hire,
authorProfileImageUrl: img.user.profile_image.large,
authorUrl: buildUtmUrl(img.user.links.html),
}))
: [];
return images;
}
function buildUtmUrl(url: string): string {
return `${url}?utm_source=kuwot-api&utm_medium=referral`;
}
export { listRandomImages };

20
src/index.ts Normal file
View file

@ -0,0 +1,20 @@
import 'dotenv/config';
import express from 'express';
import {
onGetImages,
onGetQuoteById,
onGetRandomQuote,
onGetTranslations,
} from './routes.js';
const app = express();
app.get('/quotes/random', onGetRandomQuote);
app.get('/quotes/:id', onGetQuoteById);
app.get('/translations', onGetTranslations);
app.get('/images', onGetImages);
const port = process.env.PORT ? Number(process.env.PORT) : 8080;
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});

48
src/routes.ts Normal file
View file

@ -0,0 +1,48 @@
import type { Request, Response } from 'express';
import { getQuote, getRandomQuote, listTranslations } from './data/db.js';
import { listRandomImages } from './data/unsplash.js';
function onGetRandomQuote(_req: Request, res: Response): void {
try {
const quote = getRandomQuote();
res.status(200).send(quote);
} catch (error: any) {
res.status(500).send({ error: error.message });
}
}
function onGetQuoteById(req: Request, res: Response): void {
const idParam = req.params['id'];
if (!idParam) {
res.status(400).send({ error: 'Quote ID is required.' });
return;
}
try {
const id = parseInt(idParam);
const quote = getQuote(id);
res.status(200).send(quote);
} catch (error: any) {
res.status(500).send({ error: error.message });
}
}
function onGetTranslations(_req: Request, res: Response): void {
try {
const translations = listTranslations();
res.status(200).send(translations);
} catch (error: any) {
res.status(500).send({ error: error.message });
}
}
async function onGetImages(_req: Request, res: Response): Promise<void> {
try {
const images = await listRandomImages();
res.status(200).send(images);
} catch (error: any) {
res.status(500).send({ error: error.message });
}
}
export { onGetImages, onGetQuoteById, onGetRandomQuote, onGetTranslations };

28
src/types.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
export interface Quote {
id: number;
text: string;
author: string;
}
export interface Translation {
id: number;
lang: string;
tableName: string;
}
export interface UnsplashImage {
id: string;
description: string;
color: string;
blurHash: string;
url: string;
originUrl: string;
authorName: string;
authorBio: string;
authorLocation: string;
authorTotalLikes: number;
authorTotalPhotos: number;
authorIsForHire: boolean;
authorProfileImageUrl: string;
authorUrl: string;
}