feat: Add qwik-react (#103)

This adds the following pages:

The landing page (/)
The pricing page (/pricing)
The contact page (/contact)
The changelog page (/changelog)
Terms Of Service page (/terms)
Privacy Policy (/privacy)
This commit is contained in:
Wanjohi
2024-08-30 16:19:58 +03:00
committed by GitHub
parent d13d3dc5d8
commit 73cec51728
102 changed files with 5096 additions and 105 deletions

33
apps/nexus/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# prod
dist/
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf
# deps
node_modules/
.wrangler
# env
.env
.env.production
.dev.vars
# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# misc
.DS_Store

12
apps/nexus/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Nexus
## Development
```
npm install
npm run dev
```
```
npm run deploy
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48.672001"
height="36.804001"
viewBox="0 0 12.8778 9.7377253"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg">
<g id="layer1">
<path
d="m 2.093439,1.7855532 h 8.690922 V 2.2639978 H 2.093439 Z m 0,2.8440874 h 8.690922 V 5.1080848 H 2.093439 Z m 0,2.8440866 h 8.690922 V 7.952172 H 2.093439 Z"
style="font-size:12px;fill:#ff4f01;fill-opacity:1;fill-rule:evenodd;stroke:#ff4f01;stroke-width:1.66201;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 590 B

BIN
apps/nexus/bun.lockb Normal file

Binary file not shown.

23
apps/nexus/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "@nestri/nexus",
"version": "0.0.1",
"private": true,
"description": "Nestri's core",
"scripts": {
"dev": "wrangler dev src/index.ts",
"deploy": "wrangler deploy --minify src/index.ts"
},
"dependencies": {
"hono": "^4.5.5"
},
"devDependencies": {
"@cf-wasm/resvg": "^0.1.21",
"@cloudflare/workers-types": "^4.20240529.0",
"@jsquash/avif": "^1.3.0",
"@jsquash/jpeg": "^1.4.0",
"@jsquash/resize": "^2.0.0",
"@nestri/cache": "workspace:*",
"tinycolor2": "^1.6.0",
"wrangler": "^3.72.2"
}
}

View File

@@ -0,0 +1,38 @@
import { Hono } from 'hono'
import { Resvg } from "@cf-wasm/resvg";
import { generateGradient, createAvatarSvg } from '../utils';
import { kvCaches } from "@nestri/cache"
const cacheOptions = {
key: "nexus",
namespace: "avatar-cache"
};
const app = new Hono()
const middleware = kvCaches(cacheOptions);
app.get('/:id', middleware, async (c) => {
const id = c.req.param('id');
const [name, fileType] = id.split('.');
const size = parseInt(c.req.query("size") || "200");
const validImageTypes = ["png"] //['jpg', 'png', 'webp', 'avif'];
if (!validImageTypes.includes(fileType)) {
return c.text('Invalid image type', 400);
}
const gradient = generateGradient(name || Math.random() + "");
// console.log(`gradient: ${JSON.stringify(gradient)}`)
const svg = createAvatarSvg(size, gradient, fileType);
const resvg = await Resvg.create(svg.toString());
const pngData = resvg.render()
const pngBuffer = pngData.asPng()
return c.newResponse(pngBuffer, 200, {
"Content-Type": `image/${fileType}`,
'Cache-Control': 'public, max-age=31536000, immutable'
})
})
export default app

View File

@@ -0,0 +1,72 @@
import { Hono } from 'hono'
import { kvCaches } from "@nestri/cache"
import resize, { initResize } from '@jsquash/resize';
import decodeJpeg, { init as initDecodeJpeg } from '@jsquash/jpeg/decode';
import encodeAvif, { init as initEncodeAvif } from '@jsquash/avif/encode.js';
import JPEG_DEC_WASM from "@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm";
import RESIZE_WASM from "@jsquash/resize/lib/resize/pkg/squoosh_resize_bg.wasm";
import AVIF_ENC_WASM from "@jsquash/avif/codec/enc/avif_enc.wasm";
const cacheOptions = {
key: "nexus",
namespace: "cover-cache"
};
const middleware = kvCaches(cacheOptions);
const decodeImage = async (buffer: ArrayBuffer) => {
await initDecodeJpeg(JPEG_DEC_WASM);
return decodeJpeg(buffer);
}
const resizeImage = async (image: { width: number; height: number }, width: number, height: number) => {
await initResize(RESIZE_WASM);
// Resize image with respect to aspect ratio
const aspectRatio = image.width / image.height;
const newWidth = width;
const newHeight = width / aspectRatio;
return resize(image, { width: newWidth, height: newHeight, fitMethod: "stretch" });
}
const encodeImage = async (image: { width: number; height: number }, format: string) => {
if (format === 'avif') {
await initEncodeAvif(AVIF_ENC_WASM);
return encodeAvif(image);
}
throw new Error(`Unsupported image format: ${format}`);
}
const app = new Hono()
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404))
app.get('/:id', middleware, async (c) => {
const [gameId, imageType] = c.req.param("id").split('.');
const width = parseInt(c.req.query("width") || "600");
//We don't even use this, but let us keep it for future use
const height = parseInt(c.req.query("height") || "900");
if (!gameId || !imageType) {
return c.text("Invalid image parameters", 400)
}
//Support Avif only because of it's small size
const validImageTypes = ["avif"] //['jpg', 'png', 'webp', 'avif'];
if (!validImageTypes.includes(imageType)) {
return c.text('Invalid image type', 400);
}
const imageUrl = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${gameId}/header.jpg`;
const image = await fetch(imageUrl);
if (!image.ok) {
return c.text('Image not found', 404);
}
const imageBuffer = await image.arrayBuffer();
const imageData = await decodeImage(imageBuffer);
const resizedImage = await resizeImage(imageData, width, height);
const resizedImageBuffer = await encodeImage(resizedImage, imageType);
return c.newResponse(resizedImageBuffer, 200, {
"Content-Type": `image/${imageType}`,
'Cache-Control': 'public, max-age=31536000, immutable'
})
})
export default app

View File

@@ -0,0 +1,72 @@
import { Hono } from 'hono'
import { kvCaches } from "@nestri/cache"
import resize, { initResize } from '@jsquash/resize';
import decodeJpeg, { init as initDecodeJpeg } from '@jsquash/jpeg/decode';
import encodeAvif, { init as initEncodeAvif } from '@jsquash/avif/encode.js';
import JPEG_DEC_WASM from "@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm";
import RESIZE_WASM from "@jsquash/resize/lib/resize/pkg/squoosh_resize_bg.wasm";
import AVIF_ENC_WASM from "@jsquash/avif/codec/enc/avif_enc.wasm";
const cacheOptions = {
key: "nexus",
namespace: "cover-cache"
};
const middleware = kvCaches(cacheOptions);
const decodeImage = async (buffer: ArrayBuffer) => {
await initDecodeJpeg(JPEG_DEC_WASM);
return decodeJpeg(buffer);
}
const resizeImage = async (image: { width: number; height: number }, width: number, height: number) => {
await initResize(RESIZE_WASM);
// Resize image with respect to aspect ratio
const aspectRatio = image.width / image.height;
const newWidth = width;
const newHeight = width / aspectRatio;
return resize(image, { width: newWidth, height: newHeight, fitMethod: "stretch" });
}
const encodeImage = async (image: { width: number; height: number }, format: string) => {
if (format === 'avif') {
await initEncodeAvif(AVIF_ENC_WASM);
return encodeAvif(image);
}
throw new Error(`Unsupported image format: ${format}`);
}
const app = new Hono()
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404))
app.get('/:id', middleware, async (c) => {
const [gameId, imageType] = c.req.param("id").split('.');
const width = parseInt(c.req.query("width") || "600");
//We don't even use this, but let us keep it for future use
const height = parseInt(c.req.query("height") || "900");
if (!gameId || !imageType) {
return c.text("Invalid image parameters", 400)
}
//Support Avif only because of it's small size
const validImageTypes = ["avif"] //['jpg', 'png', 'webp', 'avif'];
if (!validImageTypes.includes(imageType)) {
return c.text('Invalid image type', 400);
}
const imageUrl = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${gameId}/library_600x900_2x.jpg`;
const image = await fetch(imageUrl);
if (!image.ok) {
return c.text('Image not found', 404);
}
const imageBuffer = await image.arrayBuffer();
const imageData = await decodeImage(imageBuffer);
const resizedImage = await resizeImage(imageData, width, height);
const resizedImageBuffer = await encodeImage(resizedImage, imageType);
return c.newResponse(resizedImageBuffer, 200, {
"Content-Type": `image/${imageType}`,
'Cache-Control': 'public, max-age=31536000, immutable'
})
})
export default app

View File

@@ -0,0 +1,39 @@
import { Hono } from 'hono'
import { cors } from 'hono/cors';
import Cover from './cover'
import Avatar from './avatar'
import Banner from './banner'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello There! 👋🏾')
})
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404))
app.use(
'/*',
cors({
origin: (origin, c) => {
const allowedOriginPatterns = [
/^https:\/\/.*\.nestri\.pages\.dev$/,
/^https:\/\/.*\.nestri\.io$/,
/^http:\/\/localhost:\d+$/ // For local development
];
return allowedOriginPatterns.some(pattern => pattern.test(origin))
? origin
: 'https://nexus.nestri.io'
},
})
)
app.route('/cover', Cover)
app.route('/avatar', Avatar)
app.route("/banner", Banner)
export default app

18
apps/nexus/src/index.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Hono } from 'hono'
import Image from "./image"
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello There! 👋🏾')
})
app.get('/favicon.ico', (c) => {
return c.newResponse(null, 302, {
Location: 'https://nestri.pages.dev/favicon.svg'
})
})
app.route("/image", Image)
export default app

4
apps/nexus/src/types/api.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module '*wasm' {
const content: any;
export default content;
}

View File

@@ -0,0 +1,33 @@
export const createAvatarSvg = (size: number, gradient: { fromColor: string, toColor: string }, fileType?: string, text?: string) => (
<svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor={gradient.fromColor} />
<stop offset="100%" stopColor={gradient.toColor} />
</linearGradient>
</defs>
<rect fill="url(#gradient)" x="0" y="0" width={size} height={size} />
{fileType === "svg" && text && (
<text
x="50%"
y="50%"
alignmentBaseline="central"
dominantBaseline="central"
textAnchor="middle"
fill="#fff"
fontFamily="sans-serif"
fontSize={(size * 0.9) / text.length}
>
{text}
</text>
)}
</g>
</svg>
);

View File

@@ -0,0 +1,24 @@
import color from "tinycolor2";
function fnv1a(str: string) {
let hash = 0x811c9dc5;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return hash >>> 0;
}
export function generateGradient(username: string) {
const hash = fnv1a(username);
const hue1 = hash % 360;
const hue2 = (hash >> 16) % 360;
const c1 = color({ h: hue1, s: 0.8, l: 0.6 });
const c2 = color({ h: hue2, s: 0.8, l: 0.5 });
return {
fromColor: c1.toHexString(),
toColor: c2.toHexString(),
};
}

View File

@@ -0,0 +1,2 @@
export * from './gradient'
export * from './create-avatar'

17
apps/nexus/tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"lib": [
"ESNext"
],
"types": [
"@cloudflare/workers-types/2023-07-01"
],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
},
}

26
apps/nexus/wrangler.toml Normal file
View File

@@ -0,0 +1,26 @@
name = "nexus"
compatibility_date = "2024-08-14"
send_metrics = false
[[kv_namespaces]]
binding = "nexus"
id = "e21527f9f1ed4adbaca1fa23d7f147c9"
# [vars]
# MY_VAR = "my-variable"
# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# [[r2_buckets]]
# binding = "MY_BUCKET"
# bucket_name = "my-bucket"
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = ""
# [ai]
# binding = "AI"