Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: using the Hono web framework #16

Merged
merged 6 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.git
.gitignore
*.md
dist
.env
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM node:20-alpine AS base

FROM base AS builder

RUN npm i -g pnpm
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY . .

RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run -r build
RUN pnpm deploy --filter=pixel-profile-server --prod /prod/pixel-profile-server

FROM base AS runner
WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 hono

COPY --from=builder /prod/pixel-profile-server /app/packages/pixel-profile-server
COPY --from=builder /app/packages/pixel-profile/fonts/ /app/packages/pixel-profile/fonts/

USER hono
ENV NODE_ENV=production
ENV PORT 3000
EXPOSE ${PORT}

CMD ["node", "--experimental-modules", "/app/packages/pixel-profile-server/dist/node.js"]
3 changes: 3 additions & 0 deletions api/handle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { vercelHandle } from 'pixel-profile-server'

export default vercelHandle
17 changes: 10 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
},
"scripts": {
"start": "concurrently \"pnpm dev:vercel\" \"pnpm dev:pixel-profile\"",
"start:server": "node --experimental-modules packages/pixel-profile-server/dist/node.js",
"dev:vercel": "vercel dev --yes",
"dev:pixel-profile": "turbo dev --filter=pixel-profile",
"build": "pnpm --filter \"pixel-profile\" run build",
"dev:server": "turbo dev --filter=pixel-profile-server",
"build": "pnpm -r build",
"release": "bumpp --commit --push --tag -r && pnpm publish ./packages/pixel-profile --access=public",
"format": "prettier --write .",
"prepare": "husky install",
"lint": "eslint -c .eslintrc.json --ext .ts,.tsx api utils packages/pixel-profile/src",
"lint": "eslint -c .eslintrc.json --ext .ts,.tsx api packages/pixel-profile/src packages/pixel-profile-server/src",
"lint:fix": "pnpm lint --fix",
"test": "vitest run",
"test-type": "pnpm --filter=pixel-profile run test-type"
Expand All @@ -30,28 +32,29 @@
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vercel/node": "^3.0.18",
"bumpp": "^9.3.0",
"concurrently": "^7.3.0",
"cross-env": "^7.0.3",
"eslint": "^8.25.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^16.0.3",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"bumpp": "^9.3.0",
"concurrently": "^7.3.0",
"husky": "^8.0.3",
"lint-staged": "^15.2.0",
"prettier": "^3.1.1",
"turbo": "^1.6.3",
"typescript": "^5.3.3",
"vercel": "^33.5.3",
"vitest": "^1.3.1"
},
"dependencies": {
"axios": "^1.6.2",
"pixel-profile": "workspace:*",
"ts-known": "^0.1.3"
"pixel-profile-server": "workspace:*"
},
"lint-staged": {
"*.{js,css,md}": "prettier --write",
Expand Down
47 changes: 47 additions & 0 deletions packages/pixel-profile-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "pixel-profile-server",
"version": "0.0.1",
"description": "",
"homepage": "https://github.com/LuciNyan/pixel-profile",
"bugs": {
"url": "https://github.com/LuciNyan/pixel-profile/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/LuciNyan/pixel-profile.git"
},
"module": "./dist/index.js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"dev": "cross-env NODE_ENV=development tsup src/index.ts --watch",
"build": "tsup",
"start": "node --experimental-modules dist/node.js"
},
"type": "module",
"author": "LuciNyan",
"license": "MIT",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.js"
}
},
"devDependencies": {
"@types/node": "^20.11.0",
"tsup": "^8.0.2",
"typescript": "^5.3.3"
},
"dependencies": {
"@hono/node-server": "^1.8.2",
"axios": "^1.6.2",
"dotenv": "^16.3.1",
"hono": "^4.0.8",
"pixel-profile": "workspace:*",
"ts-known": "^0.1.3"
},
"packageManager": "pnpm@8.7.0",
"engines": {
"node": ">=18.17.0"
}
}
10 changes: 10 additions & 0 deletions packages/pixel-profile-server/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import githubStatsHandle from './github-stats'
import statusHandle from './status'
import { Hono } from 'hono'

const app = new Hono().basePath('/api')

app.route('/github-stats', githubStatsHandle)
app.route('/status', statusHandle)

export { app }
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { CONSTANTS, parseArray, parseBoolean, parseString } from '../utils/index.js'
import type { VercelRequest, VercelResponse } from '@vercel/node'
import { CONSTANTS, parseArray, parseBoolean, parseString } from './utils'
import { Hono } from 'hono'
import { stream } from 'hono/streaming'
import { clamp, fetchStats, renderStats } from 'pixel-profile'

export default async (req: VercelRequest, res: VercelResponse) => {
const githubStats = new Hono()

githubStats.get('/', async (c) => {
const { req, res } = c
const {
background,
cache_seconds = `${CONSTANTS.CARD_CACHE_SECONDS}`,
Expand All @@ -17,9 +21,9 @@ export default async (req: VercelRequest, res: VercelResponse) => {
show_total_stars,
username,
theme
} = req.query
} = req.query()

res.setHeader('Content-Type', 'image/png')
res.headers.set('Content-Type', 'image/png')

try {
const showStats = parseArray(show)
Expand All @@ -45,7 +49,7 @@ export default async (req: VercelRequest, res: VercelResponse) => {

cacheSeconds = process.env.CACHE_SECONDS ? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds : cacheSeconds

res.setHeader(
res.headers.set(
'Cache-Control',
`max-age=${cacheSeconds / 2}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`
)
Expand All @@ -60,18 +64,28 @@ export default async (req: VercelRequest, res: VercelResponse) => {
}

const result = await renderStats(stats, options)
const resultUint8Array = Uint8Array.from(result)

return res.send(result)
return stream(c, async (stream) => {
// Write a process to be executed when aborted.
stream.onAbort(() => {
console.log('Aborted!')
})
// Write a Uint8Array.
await stream.write(resultUint8Array)
})
} catch (err) {
console.log(err)

res.setHeader(
res.headers.set(
'Cache-Control',
`max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${
CONSTANTS.ERROR_CACHE_SECONDS
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`
)

return res.send(1)
return c.html('')
}
}
})

export default githubStats
2 changes: 2 additions & 0 deletions packages/pixel-profile-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { app } from './app'
export { vercelHandle } from './vercel'
12 changes: 12 additions & 0 deletions packages/pixel-profile-server/src/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'dotenv/config'
import { app } from './app'
import { serve } from '@hono/node-server'

const PORT = Number(process.env.PORT) || 3000

console.log(`The server is start, Port is ${PORT}`)

serve({
fetch: app.fetch,
port: PORT
})
10 changes: 10 additions & 0 deletions packages/pixel-profile-server/src/status/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import patInfo from './pat-info'
import up from './up'
import { Hono } from 'hono'

const status = new Hono()

status.route('/pat-info', patInfo)
status.route('/up', up)

export default status
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,36 @@
* Copyright (c) 2020 Anurag Hazra
* https://github.com/anuraghazra/github-readme-stats/blob/master/api/status/pat-info.js
*/
import { dateDiff, hasMessage, isPATError } from '../../utils/index.js'
import { VercelRequest, VercelResponse } from '@vercel/node'
import { dateDiff, hasMessage, isPATError } from '../utils/index.js'
import type { AxiosResponse } from 'axios'
import { Hono } from 'hono'
import { request } from 'pixel-profile'

const patInfo = new Hono()

export const RATE_LIMIT_SECONDS = 60 * 5

export default async (_: VercelRequest, res: VercelResponse): Promise<void> => {
res.setHeader('Content-Type', 'application/json')
patInfo.get('/', async (c) => {
const { res } = c
res.headers.set('Content-Type', 'application/json')
try {
// Add header to prevent abuse.
const PATsInfo = await getPATInfo(uptimeFetcher, {})
if (PATsInfo) {
res.setHeader('Cache-Control', `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`)
res.headers.set('Cache-Control', `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`)
}
res.send(JSON.stringify(PATsInfo, null, 2))

return c.json(PATsInfo)
} catch (err) {
console.error(err)

res.setHeader('Cache-Control', 'no-store')
res.headers.set('Cache-Control', 'no-store')

if (hasMessage(err)) {
res.send(`Something went wrong: ${err.message}`)
return c.html(`Something went wrong: ${err.message}`)
}
}
}
})

const uptimeFetcher = (variables: Record<PropertyKey, unknown>, github_token: string): Promise<AxiosResponse> => {
return request(
Expand Down Expand Up @@ -168,3 +172,5 @@ const getPATInfo = async (fetcher: typeof uptimeFetcher, variables: Record<Prope
details: sortedDetails
}
}

export default patInfo
34 changes: 18 additions & 16 deletions api/status/up.ts → ...ges/pixel-profile-server/src/status/up.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@
* Copyright (c) 2020 Anurag Hazra
* https://github.com/anuraghazra/github-readme-stats/blob/master/api/status/up.js
*/
import { hasMessage, parseString } from '../../utils/index.js'
import { VercelRequest, VercelResponse } from '@vercel/node'
import { hasMessage, parseString } from '../utils/index.js'
import { AxiosResponse } from 'axios'
import { Hono } from 'hono'
import { request, retryer } from 'pixel-profile'

export const RATE_LIMIT_SECONDS = 60 * 5

export default async (req: VercelRequest, res: VercelResponse): Promise<void> => {
const _type = parseString(req.query.type)
const up = new Hono()

up.get('/', async (c) => {
const { req, res } = c
const _type = parseString(req.query().type)

const type = _type ? _type.toLowerCase() : 'boolean'

res.setHeader('Content-Type', 'application/json')
res.headers.set('Content-Type', 'application/json')

try {
let PATsValid = true
Expand All @@ -25,32 +28,29 @@ export default async (req: VercelRequest, res: VercelResponse): Promise<void> =>
}

if (PATsValid) {
res.setHeader('Cache-Control', `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`)
res.headers.set('Cache-Control', `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`)
} else {
res.setHeader('Cache-Control', 'no-store')
res.headers.set('Cache-Control', 'no-store')
}

switch (type) {
case 'shields':
res.send(shieldsUptimeBadge(PATsValid))
break
return c.json(shieldsUptimeBadge(PATsValid))
case 'json':
res.send({ up: PATsValid })
break
return c.json({ up: PATsValid })
default:
res.send(PATsValid)
break
return c.json(PATsValid)
}
} catch (err) {
console.error(err)

res.setHeader('Cache-Control', 'no-store')
res.headers.set('Cache-Control', 'no-store')

if (hasMessage(err)) {
res.send(`Something went wrong: ${err.message}`)
return c.json(`Something went wrong: ${err.message}`)
}
}
}
})

const uptimeFetcher = (variables: Record<PropertyKey, unknown>, github_token: string): Promise<AxiosResponse> => {
return request(
Expand Down Expand Up @@ -93,3 +93,5 @@ const shieldsUptimeBadge = (up: boolean): ShieldsResponse => {
isError
}
}

export default up
File renamed without changes.
6 changes: 6 additions & 0 deletions packages/pixel-profile-server/src/vercel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { app } from './app'
import { handle } from '@hono/node-server/vercel'

const vercelHandle = handle(app)

export { vercelHandle }
14 changes: 14 additions & 0 deletions packages/pixel-profile-server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
},
"include": [
"src"
],
"exclude": ["node_modules"]
}
Loading
Loading