Compare commits
No commits in common. "1d79ae557732feb9a5dc119d675fa5adeafca7a1" and "e3ba54ce357ed912fb1f8fce7944ecc8462f0335" have entirely different histories.
1d79ae5577
...
e3ba54ce35
57
.github/workflows/build-app-image.yml
vendored
57
.github/workflows/build-app-image.yml
vendored
@ -1,57 +0,0 @@
|
|||||||
name: App Image CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_app_image:
|
|
||||||
name: Build app image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GH_REGISTRY: git.sanin.dev
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
|
||||||
REPOSITORY: ${{ github.event.repository.name }}
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: https://github.com/actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: https://github.com/docker/setup-buildx-action@v3
|
|
||||||
with:
|
|
||||||
install: true
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: https://github.com/docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.GH_REGISTRY }}
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata for Docker image
|
|
||||||
if: "!startsWith(github.ref, 'refs/tags/v')"
|
|
||||||
id: meta
|
|
||||||
uses: https://github.com/docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ env.GH_REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
|
|
||||||
- name: Build and push develop Docker image
|
|
||||||
uses: https://github.com/docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
target: deploy
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
platforms: linux/amd64
|
|
||||||
cache-from: type=gha,scope=${{ github.workflow }}
|
|
||||||
cache-to: type=gha,mode=max,scope=${{ github.workflow }}
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -106,5 +106,4 @@ dist
|
|||||||
assets/css/
|
assets/css/
|
||||||
assets/js/
|
assets/js/
|
||||||
assets/webp/
|
assets/webp/
|
||||||
assets/images/webp/
|
|
||||||
config/
|
config/
|
23
.gitlab-ci.yml
Normal file
23
.gitlab-ci.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
image: docker:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
artix-packy-pusher:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- docker build --pull --no-cache -t "$CI_REGISTRY_IMAGE" .
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
script:
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
- docker push "$CI_REGISTRY_IMAGE"
|
39
Dockerfile
39
Dockerfile
@ -1,21 +1,36 @@
|
|||||||
FROM node:23-alpine AS base
|
FROM oven/bun:alpine AS baseimg
|
||||||
FROM base AS build-env
|
|
||||||
|
|
||||||
|
FROM baseimg AS dependencies
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
RUN apk add --no-cache libwebp libwebp-tools
|
|
||||||
COPY ./package*json ./
|
COPY ./package*json ./
|
||||||
RUN npm ci
|
COPY ./bun.lockb ./
|
||||||
|
RUN bun install --production --no-progress && \
|
||||||
|
chown -R bun .
|
||||||
|
|
||||||
|
|
||||||
|
FROM dependencies as build-env
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
RUN apk add --no-cache libwebp libwebp-tools
|
||||||
|
|
||||||
|
RUN bun install --no-progress
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build && \
|
|
||||||
npm exec tsc && \
|
|
||||||
npm ci --only=production --omit=dev
|
|
||||||
|
|
||||||
FROM base AS deploy
|
RUN bun run build.ts && \
|
||||||
|
chown -R bun .
|
||||||
|
|
||||||
WORKDIR /srv/bitch
|
|
||||||
|
|
||||||
RUN apk add --no-cache docker-cli
|
FROM baseimg as deploy
|
||||||
COPY --from=build-env /build .
|
WORKDIR /usr/src/bitch
|
||||||
|
HEALTHCHECK --timeout=3s \
|
||||||
|
CMD curl --fail http://localhost:8080/healthcheck || exit 1
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
COPY --from=dependencies /build .
|
||||||
|
COPY --from=build-env /build/assets ./assets
|
||||||
|
COPY . .
|
||||||
|
USER bun
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD [ "node", "--experimental-strip-types", "src/index.ts"]
|
CMD [ "bun", "run", "index.ts"]
|
@ -3,11 +3,13 @@
|
|||||||
To install dependencies:
|
To install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
To run:
|
To run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node --experimental-strip-types src/index.ts
|
bun run index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.0.23. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||||
|
Binary file not shown.
@ -1,93 +0,0 @@
|
|||||||
Copyright (c) 2020, “rikyozone”
|
|
||||||
(https://fontstruct.com/fontstructors/1749823/rikyozone)
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@ -1,16 +0,0 @@
|
|||||||
The font file in this archive was created using Fontstruct the free, online
|
|
||||||
font-building tool.
|
|
||||||
This font was created by “rikyozone”.
|
|
||||||
This font has a homepage where this archive and other versions may be found:
|
|
||||||
https://fontstruct.com/fontstructions/show/1775612
|
|
||||||
|
|
||||||
Try Fontstruct at https://fontstruct.com
|
|
||||||
It’s easy and it’s fun.
|
|
||||||
|
|
||||||
Fontstruct is copyright ©2020 Rob Meek
|
|
||||||
|
|
||||||
LEGAL NOTICE:
|
|
||||||
In using this font you must comply with the licensing terms described in the
|
|
||||||
file “license.txt” included with this archive.
|
|
||||||
If you redistribute the font file in this archive, it must be accompanied by all
|
|
||||||
the other files from this archive, including this one.
|
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 116 B |
171
build.ts
Normal file
171
build.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import child_process from 'child_process';
|
||||||
|
import uglifyjs from "uglify-js";
|
||||||
|
import * as sass from 'sass';
|
||||||
|
import * as csso from 'csso';
|
||||||
|
|
||||||
|
const spawn = child_process.spawn;
|
||||||
|
const fsp = fs.promises;
|
||||||
|
const STYLESDIR = 'styles';
|
||||||
|
const SCRIPTSDIR = 'scripts';
|
||||||
|
const IMAGESDIR = path.join('assets', 'images');
|
||||||
|
const STYLEOUTDIR = process.env.STYLEOUTDIR || path.join(import.meta.dir, 'assets', 'css');
|
||||||
|
const SCRIPTSOUTDIR = process.env.SCRIPTSOUTDIR || path.join(import.meta.dir, 'assets', 'js');
|
||||||
|
const IMAGESOUTDIR = process.env.IMAGESOUTDIR || path.join(import.meta.dir, 'assets', 'webp');
|
||||||
|
const STYLEOUTFILE = process.env.STYLEOUTFILE || 'styles.css';
|
||||||
|
const SQUASH = new RegExp('^[0-9]+-');
|
||||||
|
|
||||||
|
async function emptyDir(dir: string) {
|
||||||
|
await Promise.all((await fsp.readdir(dir, { withFileTypes: true })).map(f => path.join(dir, f.name)).map(p => fsp.rm(p, {
|
||||||
|
recursive: true,
|
||||||
|
force: true
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mkdir(dir: string | string[]) {
|
||||||
|
if (typeof dir === 'string') {
|
||||||
|
await fsp.mkdir(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await Promise.all(dir.map(mkdir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process styles
|
||||||
|
async function styles() {
|
||||||
|
await mkdir([STYLEOUTDIR, STYLESDIR]);
|
||||||
|
await emptyDir(STYLEOUTDIR);
|
||||||
|
let styles: string[] = [];
|
||||||
|
let files = await fsp.readdir(STYLESDIR);
|
||||||
|
await Promise.all(files.map(f => new Promise(async (res, reject) => {
|
||||||
|
let p = path.join(STYLESDIR, f);
|
||||||
|
console.log(`Processing style ${p}`);
|
||||||
|
let style = sass.compile(p).css;
|
||||||
|
if (f.charAt(0) !== '_') {
|
||||||
|
if (SQUASH.test(f)) {
|
||||||
|
styles.push(style);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let o = path.join(STYLEOUTDIR, f.substring(0, f.lastIndexOf('.')) + '.css');
|
||||||
|
await fsp.writeFile(o, csso.minify(style).css);
|
||||||
|
console.log(`Wrote ${o}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res(0);
|
||||||
|
})));
|
||||||
|
let out = csso.minify(styles.join('\n')).css;
|
||||||
|
let outpath = path.join(STYLEOUTDIR, STYLEOUTFILE);
|
||||||
|
await fsp.writeFile(outpath, out);
|
||||||
|
console.log(`Wrote ${outpath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process scripts
|
||||||
|
async function scripts() {
|
||||||
|
await mkdir([SCRIPTSOUTDIR, SCRIPTSDIR]);
|
||||||
|
await emptyDir(SCRIPTSOUTDIR);
|
||||||
|
let files = await fsp.readdir(SCRIPTSDIR);
|
||||||
|
await Promise.all(files.map(f => new Promise(async (res, reject) => {
|
||||||
|
let p = path.join(SCRIPTSDIR, f);
|
||||||
|
let o = path.join(SCRIPTSOUTDIR, f);
|
||||||
|
console.log(`Processing script ${p}`);
|
||||||
|
try {
|
||||||
|
await fsp.writeFile(o, uglifyjs.minify((await fsp.readFile(p)).toString()).code);
|
||||||
|
console.log(`Wrote ${o}`);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log(`error writing ${o}: ${ex}`);
|
||||||
|
}
|
||||||
|
res(0);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process images
|
||||||
|
async function images(dir = '') {
|
||||||
|
let p = path.join(IMAGESDIR, dir);
|
||||||
|
await mkdir(p);
|
||||||
|
if (dir.length === 0) {
|
||||||
|
await mkdir(IMAGESOUTDIR)
|
||||||
|
await emptyDir(IMAGESOUTDIR);
|
||||||
|
}
|
||||||
|
let files = await fsp.readdir(p, {
|
||||||
|
withFileTypes: true
|
||||||
|
});
|
||||||
|
if (files.length) {
|
||||||
|
await Promise.all(files.map(f => new Promise(async (res, reject) => {
|
||||||
|
if (f.isFile()) {
|
||||||
|
let outDir = path.join(IMAGESOUTDIR, dir);
|
||||||
|
let infile = path.join(p, f.name);
|
||||||
|
let outfile = path.join(outDir, f.name.substring(0, f.name.lastIndexOf('.')) + '.webp');
|
||||||
|
await mkdir(outDir);
|
||||||
|
console.log(`Processing image ${infile}`)
|
||||||
|
let process = spawn('cwebp', ['-mt', '-q', '50', infile, '-o', outfile]);
|
||||||
|
let timeout = setTimeout(() => {
|
||||||
|
reject('Timed out');
|
||||||
|
process.kill();
|
||||||
|
}, 30000);
|
||||||
|
process.on('exit', async (code) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`Wrote ${outfile}`);
|
||||||
|
res(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (f.isDirectory()) {
|
||||||
|
images(path.join(dir, f.name)).then(res).catch(reject);
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAbortError(err: unknown): boolean {
|
||||||
|
return typeof err === 'object' && err !== null && 'name' in err && err.name === 'AbortError';
|
||||||
|
}
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
await Promise.all([styles(), scripts(), images()]);
|
||||||
|
if (process.argv.indexOf('--watch') >= 0) {
|
||||||
|
console.log('watching for changes...');
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const watcher = fsp.watch(STYLESDIR);
|
||||||
|
for await (const _ of watcher)
|
||||||
|
await styles();
|
||||||
|
} catch (err) {
|
||||||
|
if (isAbortError(err))
|
||||||
|
return;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const watcher = fsp.watch(SCRIPTSDIR);
|
||||||
|
for await (const _ of watcher)
|
||||||
|
await scripts();
|
||||||
|
} catch (err) {
|
||||||
|
if (isAbortError(err))
|
||||||
|
return;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const watcher = fsp.watch(IMAGESDIR, {
|
||||||
|
recursive: true // no Linux ☹️
|
||||||
|
});
|
||||||
|
for await (const _ of watcher)
|
||||||
|
await images();
|
||||||
|
} catch (err) {
|
||||||
|
if (isAbortError(err))
|
||||||
|
return;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
})();
|
@ -10,4 +10,4 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/srv/bitch/config
|
- ./config:/usr/src/bitch/config
|
@ -1,7 +1,6 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import express, { application } from 'express';
|
import express, { application } from 'express';
|
||||||
import Path from 'path';
|
import Path from 'path';
|
||||||
import fsp from 'fs/promises';
|
|
||||||
|
|
||||||
function notStupidParseInt(v: string | undefined): number {
|
function notStupidParseInt(v: string | undefined): number {
|
||||||
return v === undefined ? NaN : parseInt(v);
|
return v === undefined ? NaN : parseInt(v);
|
||||||
@ -14,16 +13,11 @@ const dataPath: string = process.env.CONFIG || Path.join('config', 'config.json'
|
|||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('view options', { outputFunctionName: 'echo' });
|
app.set('view options', { outputFunctionName: 'echo' });
|
||||||
app.use('/assets', express.static('assets', { maxAge: '12m' }));
|
app.use('/assets', express.static('assets'));
|
||||||
|
|
||||||
let visitCount = JSON.parse((await fsp.readFile(dataPath)).toString())['visits'] as number || 0;
|
let visitCount = (await Bun.file(dataPath).json())['visits'] as number || 0;
|
||||||
|
|
||||||
app.get('/healthcheck', (req, res) => {
|
app.get('/', (_, res) => {
|
||||||
res.send('Healthy');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
console.log(req.headers['user-agent']);
|
|
||||||
res.render('index',
|
res.render('index',
|
||||||
{
|
{
|
||||||
visitCount
|
visitCount
|
||||||
@ -39,24 +33,16 @@ app.get('/', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendPixel = (_: express.Request, res: express.Response) => {
|
app.get('/cache-me.webp', (_, res) => {
|
||||||
res.set('Cache-Control', 'private, max-age=31557600'); // one year
|
res.set('Cache-Control', 'public, max-age=31557600'); // one year
|
||||||
visitCount++;
|
visitCount++;
|
||||||
res.sendFile(Path.join(import.meta.dirname, 'assets', 'images', 'webp', 'cache-me.webp'));
|
res.sendFile(Path.join(import.meta.dir, 'assets', 'webp', 'cache-me.webp'));
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/cache-me.webp', sendPixel);
|
|
||||||
app.get('/cache-me-2.webp', sendPixel);
|
|
||||||
|
|
||||||
app.all('*', (req, res) => {
|
|
||||||
console.log(`404: ${req.url} requested by ${req.ip} "${req.headers['user-agent']}"`);
|
|
||||||
res.redirect('/');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const webserver = app.listen(port, () => console.log(`jakehurwitzisabitch.com running on port ${port}`));
|
const webserver = app.listen(port, () => console.log(`jakehurwitzisabitch.com running on port ${port}`));
|
||||||
|
|
||||||
async function saveVisits() {
|
async function saveVisits() {
|
||||||
await fsp.writeFile(dataPath, JSON.stringify({
|
await Bun.write(dataPath, JSON.stringify({
|
||||||
visits: visitCount
|
visits: visitCount
|
||||||
}));
|
}));
|
||||||
}
|
}
|
1654
package-lock.json
generated
1654
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -1,18 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "jakehurwitzisabitch",
|
"name": "jakehurwitzisabitch",
|
||||||
"module": "src/index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.9",
|
||||||
"express": "4.21.2"
|
"express": "4.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
"@types/express": "4.17.21",
|
"@types/express": "4.17.21",
|
||||||
"forking-build-shit": "0.0.2",
|
"@types/csso": "5.0.4",
|
||||||
"typescript": "^5.0.0"
|
"@types/uglify-js": "3.17.4",
|
||||||
|
"csso": "5.0.5",
|
||||||
|
"sass": "1.66.1",
|
||||||
|
"uglify-js": "3.17.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"peerDependencies": {
|
||||||
"build": "npx build-shit",
|
"typescript": "^5.0.0"
|
||||||
"watch": "npx build-shit --watch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,3 @@
|
|||||||
@font-face {
|
|
||||||
font-family: '7segments';
|
|
||||||
src: url('/assets/fonts/7segments/7segments.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -13,7 +8,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: url('/assets/images/webp/bitch.webp');
|
background: url('/assets/webp/bitch.webp');
|
||||||
}
|
}
|
||||||
|
|
||||||
#w {
|
#w {
|
||||||
@ -41,12 +36,8 @@ body {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.v {
|
|
||||||
font-family: '7segments', 'Courier New', Courier, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pixel {
|
#pixel {
|
||||||
background: url('/cache-me-2.webp');
|
background: url('/cache-me.webp');
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: -99999999999999px;
|
left: -99999999999999px;
|
||||||
top: -20px;
|
top: -20px;
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext"],
|
||||||
"module": "NodeNext",
|
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
"esModuleInterop": true,
|
/* Bundler mode */
|
||||||
"skipLibCheck": true,
|
"moduleResolution": "bundler",
|
||||||
"moduleResolution": "node16",
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"allowImportingTsExtensions": true
|
|
||||||
},
|
/* Linting */
|
||||||
"include": ["src/**/*"],
|
"skipLibCheck": true,
|
||||||
"exclude": ["**/*.spec.ts"]
|
"strict": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
}
|
||||||
}
|
}
|
@ -7,13 +7,13 @@
|
|||||||
JAKE HURWITZ IS A BITCH
|
JAKE HURWITZ IS A BITCH
|
||||||
</title>
|
</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" href="/assets/css/styles.css?v3">
|
<link rel="stylesheet" href="/assets/css/styles.css?v1">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="w">
|
<div id="w">
|
||||||
<h1>Welcome to <strong>Jake Hurwitz is a bitch</strong> .com</h1>
|
<h1>Welcome to <strong>Jake Hurwitz is a bitch</strong> .com</h1>
|
||||||
<p><span class="v"><%= visitCount %></span> People have seen this site and agreed Jake Hurwitz is a bitch</p>
|
<p><%= visitCount %> People have seen this site and agreed Jake Hurwitz is a bitch</p>
|
||||||
<div id="pixel"><a href="https://youcantkillgod.com/">YOU CAN'T KILL GOD</a></div>
|
<div id="pixel"><a href="https://youcantkillgod.com/">YOU CAN'T KILL GOD</a></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user