initial commit
This commit is contained in:
commit
83337e0574
14 changed files with 2825 additions and 0 deletions
45
src/data/db.ts
Normal file
45
src/data/db.ts
Normal 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
38
src/data/unsplash.ts
Normal 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
20
src/index.ts
Normal 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
48
src/routes.ts
Normal 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
28
src/types.d.ts
vendored
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue