Created
June 26, 2025 09:03
-
-
Save yuhangch/c4b432e4e8fa0753a407238e8f73738e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Context, Env, Hono } from 'hono'; | |
import { cors } from 'hono/cors'; | |
import { getReviewsHandler, postReviewsHandler } from './review'; | |
import { | |
getMomentHandler, | |
getMomentsCountHandler, | |
getMomentsHandler, | |
postMomentsHandler, | |
updateMomentsHandler | |
} from './moment'; | |
import { tmdbApi } from './tmdb'; | |
const app = new Hono(); | |
const token = 'your-token'; | |
async function AuthMiddleware(ctx: any, next: any) { | |
const { req } = ctx; | |
const auth = req.headers.get('authorization'); | |
if (auth && auth.startsWith('Bearer')) { | |
const bearer = bearerAuth({ token }); | |
return bearer(ctx, next); | |
} else if (auth && auth.startsWith('Basic')) { | |
const basic = basicAuth({ | |
username: 'hello', | |
password: 'world' | |
}); | |
return basic(ctx, next); | |
} else { | |
ctx.status = 401; | |
return ctx.text('Unauthorized'); | |
} | |
} | |
app.use('/blog-api/*', cors( | |
//@ts-ignore | |
{ allowOrigin: '*' }) | |
); | |
app.use('/blog-api/*', AuthMiddleware); | |
app.get('/blog-api/moments/info', getMomentsCountHandler); | |
app.get('/blog-api/moments/:id', getMomentHandler); | |
app.get('/blog-api/moments', getMomentsHandler); | |
app.get('/blog-api/reviews', getReviewsHandler); | |
app.post('/blog-api/reviews', postReviewsHandler); | |
app.post('/blog-api/moments', postMomentsHandler); | |
app.post('/blog-api/moments/:id', updateMomentsHandler); | |
app.use('/blog-api/tmdb/*', tmdbApi); | |
export default { | |
fetch: app.fetch | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DROP TABLE IF EXISTS moments; | |
CREATE TABLE IF NOT EXISTS moments ( | |
id integer PRIMARY KEY AUTOINCREMENT, | |
body text NOT NULL, | |
tags text WITH NULL, | |
star integer NOT NULL default 0, | |
created_at text NOT NULL, | |
deleted_at text WITH NULL | |
); | |
CREATE INDEX idx_moments_created_at ON moments (created_at); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Context, Env } from 'hono'; | |
const pageSize = 10; | |
export const postMomentsHandler = async (c: Context<Env, '/blog-api/moments'>) => { | |
// body text NOT NULL, | |
// tags text WITH NULL, | |
const { body, tags } = await c.req.json(); | |
if (!body) return c.text('Missing body for new post'); | |
if (!tags) return c.text('Missing tags value for new post'); | |
const DB = c.env!.DB as D1Database; | |
const { success,meta } = await DB.prepare(` | |
insert into moments (body, tags,created_at) values (?, ?, ?) | |
`).bind(body, tags, new Date().toISOString()).run(); | |
const lastRowId = meta.last_row_id; | |
if (success) { | |
c.status(201); | |
return c.text(lastRowId.toString()); | |
} else { | |
c.status(500); | |
return c.text('Something went wrong'); | |
} | |
}; | |
export const updateMomentsHandler = async (c: Context<Env, '/blog-api/moments/:id'>) => { | |
// body text NOT NULL, | |
// tags text WITH NULL, | |
const { id } = c.req.param(); | |
if (!id) return c.text('Missing id for new post'); | |
const numberId = parseInt(id); | |
const { body, tags } = await c.req.json(); | |
if (!body) return c.text('Missing body for new post'); | |
if (!tags) return c.text('Missing tags value for new post'); | |
const DB = c.env!.DB as D1Database; | |
const { success,meta } = await DB.prepare(` | |
update moments set body = ?, tags = ? where id = ? | |
`).bind(body, tags, numberId).run(); | |
if (success) { | |
c.status(200); | |
return c.text(numberId.toString()); | |
} else { | |
c.status(500); | |
return c.text('Something went wrong'); | |
} | |
}; | |
export const getMomentsCountHandler = async (c: Context<Env, '/blog-api/moments/info'>) => { | |
const DB = c.env!.DB as D1Database; | |
const result = await DB.prepare(` | |
select count(*) as count from moments | |
where deleted_at is null | |
`).first<any>(); | |
return c.json({ totalCount: result.count, maxPageIndex: Math.ceil(result.count / pageSize) }); | |
}; | |
export const getMomentHandler = async (c: Context<Env, '/blog-api/moments/:id'>) => { | |
const DB = c.env!.DB as D1Database; | |
const { id } = c.req.param(); | |
const result = await DB.prepare(` | |
select moments.*,title,title_en,imdb_id from moments | |
left join reviews on moments.id = reviews.moments_id | |
where id = ${id} | |
`).first<any>(); | |
if (result.deleted_at) { | |
result.body = '404'; | |
} | |
return c.json(result); | |
}; | |
export const getMomentsHandler = async (c: Context<Env, '/blog-api/moments'>) => { | |
let { page } = c.req.query(); | |
if (!page) page = '1'; | |
const offset = (parseInt(page) - 1) * pageSize; | |
const DB = c.env!.DB as D1Database; | |
const { results: moments } = await DB.prepare(` | |
select moments.*,title,title_en,imdb_id from moments | |
left join reviews on moments.id = reviews.moments_id | |
where deleted_at is null | |
order by created_at desc | |
limit 10 offset ${offset} | |
`).all(); | |
const count = await DB.prepare(` | |
select count(*) as count from moments | |
where deleted_at is null | |
`).first<any>(); | |
const maxPageIndex = Math.ceil(count.count / pageSize); | |
return c.json({ | |
moments, | |
next: parseInt(page) < maxPageIndex ? parseInt(page) + 1 : null, | |
prev: parseInt(page) > 1 ? parseInt(page) - 1 : null | |
}); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE TABLE reviews | |
( | |
imdb_id TEXT PRIMARY KEY, | |
title TEXT, | |
title_en TEXT, | |
media_type TEXT, | |
imdb_rating TEXT, | |
rating TEXT, | |
release_date TEXT, | |
rated_date TEXT, | |
moments_id INTEGER, | |
FOREIGN KEY (moments_id) REFERENCES moments(id) | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Context, Env } from 'hono'; | |
const pageSize = 50; | |
export const getReviewsHandler = async (c: Context<Env, '/blog-api/reviews'>) => { | |
let { page,media_type } = c.req.query(); | |
if (!page) page = '1'; | |
const offset = (parseInt(page) - 1) * pageSize; | |
const DB = c.env!.DB as D1Database; | |
const typeFilter = media_type ? `where media_type = '${media_type}'` : ''; | |
const { results: reviews } = await DB.prepare(` | |
select * from reviews | |
${typeFilter} | |
order by rated_date desc | |
limit ${pageSize} offset ${offset} | |
`).all(); | |
const count = await DB.prepare(` | |
select count(*) as count from reviews | |
`).first<any>(); | |
const maxPageIndex = Math.ceil(count.count / pageSize); | |
return c.json({ | |
reviews, | |
next: parseInt(page) < maxPageIndex ? parseInt(page) + 1 : null, | |
prev: parseInt(page) > 1 ? parseInt(page) - 1 : null | |
}); | |
}; | |
export const postReviewsHandler = async (c: Context<Env, '/blog-api/reviews'>) => { | |
//imdb_id title title_en media_type imdb_rating rating release_date rated_date moments_id | |
const { imdb_id, title, title_en, media_type, imdb_rating, rating, release_date, rated_date, moments_id } = await c.req.json(); | |
const DB = c.env!.DB as D1Database; | |
const { success } = await DB.prepare(` | |
insert into reviews (imdb_id, title, title_en, media_type, imdb_rating, rating, release_date, rated_date, moments_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?) | |
`).bind(imdb_id, title, title_en, media_type, imdb_rating, rating, release_date, rated_date, moments_id).run(); | |
if (success) { | |
c.status(201); | |
return c.text('Created'); | |
} else { | |
c.status(500); | |
return c.text('Something went wrong'); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Context, Env } from 'hono'; | |
const tmdbApiPath = 'https://api.themoviedb.org/'; | |
const tmdbSecret = 'tmdb-secret'; | |
export const tmdbApi = async (c: Context<Env, '/blog-api/tmdb/*'>) => { | |
// use request handler get processed request | |
const url = c.req.url.split("/blog-api/tmdb/")[1]; | |
if (url){ | |
let dest = tmdbApiPath + url; | |
// return processed response | |
const response = await fetch(dest, { | |
headers: { | |
'accept': 'application/json', | |
'Authorization': `Bearer ${tmdbSecret}` | |
} | |
}) | |
const result = await response.json(); | |
return c.json(result); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = "blog-api" | |
main = "src/index.ts" | |
compatibility_flags = [ "nodejs_compat" ] | |
compatibility_date = "2023-07-04" | |
[[d1_databases]] | |
binding = "DB" | |
database_name = "blog" | |
database_id = "xxxx-xxxx-xxxx" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment