first commit

This commit is contained in:
Nicola Zambello 2024-03-25 11:17:19 +02:00
commit d35821b915
Signed by: nzambello
GPG key ID: 56E4A92C2C1E50BA
11 changed files with 460 additions and 0 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
node_modules
docs
data
.gitea

12
.editorConfig Normal file
View file

@ -0,0 +1,12 @@
[*]
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[{*.css,*.scss,*.less,*.overrides,*.variables}]
indent_size = 4
[{*.js,*.jsx,*.json,*.ts,*.tsx}]
indent_size = 2

View file

@ -0,0 +1,63 @@
name: Docker CI
on:
push:
branches:
- main
env:
node-version: 20.x
jobs:
release:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
env:
DOCKER_ORG: nzambello
DOCKER_LATEST: nightly
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
git.nzambello.dev/nzambello/resize-img-api
labels: |
org.label-schema.docker.cmd=docker run -d -p 8787:8787 git.nzambello.dev/nzambello/resize-img-api:latest
flavor: latest=false
tags: |
type=ref,event=branch
type=sha
type=raw,value=latest,enable={{is_default_branch}}
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: git.nzambello.dev
username: ${{ gitea.repository_owner }}
password: ${{ secrets.ACTIONS_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ gitea.event_name != 'pull_request' }}
tags: |
git.nzambello.dev/nzambello/resize-img-api:latest
labels: $${{ steps.meta.outputs.labels }}

175
.gitignore vendored Normal file
View file

@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

6
Dockerfile Normal file
View file

@ -0,0 +1,6 @@
FROM oven/bun:alpine
WORKDIR /app
COPY . .
RUN bun install --production --frozen-lockfile
CMD ["bun", "start"]
EXPOSE 8787

67
README.md Normal file
View file

@ -0,0 +1,67 @@
# resize-img-api
The structure of the API path is:
```
/api/imgresize/:width/:height/:url
```
Where `:width` and `:height` can be numbers in pixels or `auto`.
The `:url` is the URL of the image to be resized and should be URL encoded, which can be done in JS with `encodeURIComponent`(). See [MDN ref](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent).
Example encoded URL for [https://memori.ai/logo.png](https://memori.ai/logo.png):
```
https%3A%2F%2Fmemori.ai%2Flogo.png
```
Then call the API as, for example:
```
/api/imgresize/200/200/https%3A%2F%2Fmemori.ai%2Flogo.png
```
You can also specify a format using the querystring `?format=` and indicating one of the following: avif, gif, heif, jpeg, jpg, jp2, pdf, png, svg, tiff, webp.
## Docker
To build the Docker image:
```bash
docker build -t resize-img-api .
```
To run the Docker container:
```bash
docker run -p 8787:8787 resize-img-api
```
Using the published image:
```bash
docker run -p 8787:8787 git.nzambello.dev/nzambello/resize-img-api:latest
```
## Development
To install dependencies:
```bash
bun install
```
To run:
```bash
bun start
```
Or in development mode with hot reloading:
```bash
bun dev
```
This project was created using `bun init` in bun v1.0.26. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

BIN
bun.lockb Executable file

Binary file not shown.

10
docker-compose.yml Normal file
View file

@ -0,0 +1,10 @@
version: "3.8"
services:
resize-image-api:
image: nzambello/resize-image-api:latest
build:
context: .
dockerfile: Dockerfile
ports:
- "8787:8787"

99
index.ts Normal file
View file

@ -0,0 +1,99 @@
import { Hono } from "hono";
import { cors } from "hono/cors";
import { etag } from "hono/etag";
import { logger } from "hono/logger";
import { prettyJSON } from "hono/pretty-json";
import { html } from "hono/html";
import sharp from "sharp";
const homepage = html`
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charSet="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="og:type" content="website" />
<title>Resize Images API</title>
<meta name="description" content="Resize images via API">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
</head>
<body>
<main class="container">
<h1>Resize images API</h1>
<style>
h1 {
margin-top: 2rem;
margin-bottom: 3rem;
text-align: center;
}
</style>
<article style="max-width: 600px; margin-left: auto; margin-right: auto; padding: 3rem 2rem;">
<p>The structure of the API path is:</p>
<p><pre>/api/imgresize/:width/:height/:url</pre></p>
<p>Where <code>width</code> and <code>height</code> can be numbers in pixels or <code>auto</code>.</p>
<p>The URL should be URL encoded, which can be done in JS with <code>encodeURIComponent</code>. See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent" target="_blank" rel="noopener noreferrer">MDN ref</a>.</p>
<p>Example for <a href="https://memori.ai/logo.png" target="_blank" rel="noopener noreferrer">https://memori.ai/logo.png</a>:</p>
<p><pre>https%3A%2F%2Fmemori.ai%2Flogo.png</pre></p>
<p>Then call the API as, for example:</p>
<p><pre>/api/imgresize/200/200/https%3A%2F%2Fmemori.ai%2Flogo.png</pre></p>
<p>You can also specify a format using the querystring <code>?format=</code> and indicating one of the following: avif, gif, heif, jpeg, jpg, jp2, pdf, png, svg, tiff, webp.</p>
</article>
</main>
</body>
</html>
`;
const app = new Hono();
app.use(prettyJSON());
app.use(etag(), logger());
app.use("/api/*", cors());
app.get("/", (c) => {
return c.html(homepage);
});
app.get("/api/imgresize/:width/:height/:url", async (c) => {
const { width, height, url } = c.req.param();
const format = c.req.query("format");
c.header("Cache-Control", "s-maxage=31536000, stale-while-revalidate");
c.header("Content-Type", `image/jpeg`);
const decodedUrl = decodeURIComponent(url as string);
const readStream = await fetch(decodedUrl, { cache: "no-cache" });
const input = Buffer.from(await readStream.arrayBuffer());
const w = width && width !== "auto" ? Number(width) : undefined;
const h = height && height !== "auto" ? Number(height) : undefined;
const outputBuffer = await sharp(input)
// outputBuffer contains JPEG image data
// no wider and no higher than w and h pixels
// and no larger than the input image
.resize({
width: w,
height: h,
fit: sharp.fit.inside,
withoutEnlargement: true,
})
.toFormat(
format && sharp.format[format as keyof typeof sharp.format] !== undefined
? sharp.format[format as keyof typeof sharp.format]
: sharp.format.jpeg
)
.toBuffer();
c.status(200);
return c.body(outputBuffer);
});
export default {
port: Bun.env.PORT || 8787,
fetch: app.fetch,
};

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "resize-img-api",
"scripts": {
"dev": "bun run --hot index.ts",
"start": "bun run index.ts"
},
"dependencies": {
"hono": "^4.1.4",
"sharp": "^0.33.3"
},
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}

7
tsconfig.json Normal file
View file

@ -0,0 +1,7 @@
{
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}