home back

This commit is contained in:
Nathan Rashleigh committed 2025-05-01 23:32:49 +10:00
1 parent a79c11496b
commit bdc22c5c42
26 files changed
+170 -107

No files matched your search

-29
View File
@@ -1,29 +0,0 @@
import { FreshContext } from "$fresh/server.ts"
export async function errorMiddleware(req: Request, ctx: FreshContext): Promise<Response> {
try {
const response = await ctx.next()
return response
} catch (error) {
console.error(`Error handling ${req.method} ${new URL(req.url).pathname}:`, error)
// Check if this is an API route
const isApiRoute = new URL(req.url).pathname.startsWith("/api/")
if (isApiRoute) {
return new Response(JSON.stringify({
error: "Internal server error",
message: error.message
}), {
status: 500,
headers: { "Content-Type": "application/json" }
})
}
// For non-API routes, return a redirect to an error page or render an error component
return new Response(null, {
status: 302,
headers: { Location: "/?error=server_error" }
})
}
}
@@ -1,30 +1,33 @@
import { FreshContext } from "$fresh/server.ts"
import { getCookies, setCookie } from "$std/http/cookie.ts"
import { createPg } from "api/pg.ts"
import { getCookies } from "$std/http/cookie.ts"
import { CratesCtx } from 'api/mw.service.ts'
// Define public paths that don't require authentication
const PROTECTED = [
"/home",
"/api/auth",
"/home",
]
// Sessions storage shared between routes
// In production, this would be Redis or a database
export const sessions = new Map<string, { userId: string, expiresAt: Date }>()
export async function authMiddleware(req: Request, ctx: FreshContext): Promise<Response> {
export async function authMiddleware(req: Request, ctx: CratesCtx): Promise<Response> {
const { pg } = ctx.state
const url = new URL(req.url)
const path = url.pathname
// Allow public paths without authentication
if (!PROTECTED.includes(path)) {
return await ctx.next()
}
// Check for authentication
const cookies = getCookies(req.headers)
const sessionId = cookies.session
// Allow public paths without authentication
if (!PROTECTED.includes(path)) {
console.log('skip', path)
return await ctx.next()
}
if (!sessionId) {
if (path.startsWith("/api/")) {
return new Response(JSON.stringify({ error: "Not logged in" }), {
@@ -51,7 +54,6 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
}
// Get user from database
const pg = createPg(console)
const user = await pg.userFindId(session.userId)
if (!user) {
@@ -59,8 +61,5 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
}
ctx.state.user = user
ctx.state.pg = pg
return await ctx.next()
}
+10
View File
@@ -0,0 +1,10 @@
import { FreshContext } from "$fresh/server.ts"
export async function errorMiddleware(req: Request, ctx: FreshContext): Promise<Response> {
try {
const response = await ctx.next()
return response
} catch (error) {
console.error(`Error handling ${req.method} ${new URL(req.url).pathname}:`, error)
}
}
+25
View File
@@ -0,0 +1,25 @@
import { FreshContext } from "$fresh/server.ts";
import { User } from 'prisma/generated/index.ts'
import { Pg } from 'api/pg.ts'
import { Offers } from 'api/offers.ts'
import { createPg } from 'api/pg.ts'
import createOffers from 'api/offers.ts'
export interface CratesState {
user: User
pg: Pg;
offers: Offers
}
export type CratesCtx = FreshContext<CratesState>
export async function serviceMiddleware(_req: Request, ctx: FreshContext): Promise<Response> {
const pg = createPg(console)
const offers = createOffers(pg)
ctx.state.pg = pg
ctx.state.offers = offers
return await ctx.next()
}
+19 -18
View File
@@ -1,13 +1,9 @@
import type Log from 'bunyan';
import type { PrismaClient, Want, User, Listing, Offer, } from '@prisma/client';
import assert from 'assert/strict';
import groupBy from 'lodash/groupBy';
import sortBy from 'lodash/sortBy';
import flatMap from 'lodash/flatMap';
import { Context } from 'koa';
import { assert } from "$std/assert/mod.ts";
import { groupBy, sortBy, flatMap } from 'lodash'
import { logAxiosError, wantDetails } from './utils';
import { Pg } from './pg';
import type { User } from 'prismaClient';
import { Pg } from "api/pg.ts";
import { UUID } from 'node:crypto'
export enum Status {
Complete = 'Complete',
@@ -21,10 +17,10 @@ function sortOffers(o) {
return Object.values(Status).indexOf(o.status);
}
export default function createOffers(log: Log, pg: Pg) {
export default function createOffers(pg: Pg) {
async function generate() {
const listings = flatMap(await pg.interestingListings());
log.debug('generateOffers', { listings: listings.length });
console.debug('generateOffers', { listings: listings.length });
const shipping = 7.5; // yolt
@@ -51,30 +47,35 @@ export default function createOffers(log: Log, pg: Pg) {
// TODO probably need to take one of each condition of record from a store
// eg. they have a VG+ and a M we should make offers for each
log.trace('generated offers', sorted);
console.trace('generated offers', sorted);
const inserts = await pg.offersUpdate(sorted);
return inserts;
}
async function accept(ctx: Context) {
const { id } = ctx.request.body;
ctx.assert(id, 401, 'no id supplied');
async function accept(user: User, id: UUID) {
assert(id, 'no id supplied');
// check the user actually has this offer
const offer = await pg.userOfferFind(ctx.state.user, id);
const offer = await pg.userOfferFind(user, id);
return pg.offerStatusSet([offer.id], Status.Accepted);
}
async function statusSet(user: User, ids: UUID[], status: Status) {
log.info({ status, ids }, 'setting offer status');
console.info({ status, ids }, 'setting offer status');
// Validate the inputs
assert(Array.isArray(ids), "ids must be an array");
assert(ids.length > 0, "ids array cannot be empty");
assert(ids.every(id => isUUID(id)), "All ids must be valid UUIDs");
assert(Object.values(Status).includes(status), "Invalid status value");
// const offer = await pg.offerFindId(ids);
const update = await pg.offerStatusSet(ids, status);
// forget about this for now
// if (status === Status.Complete) {
// log.info({ id, status }, 'completed offer, removing want...');
// console.info({ id, status }, 'completed offer, removing want...');
// await pg.wantRm(offer.wantId);
// }
+1 -1
View File
@@ -1,6 +1,6 @@
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"
import { load } from "$std/dotenv/mod.ts"
import { PrismaClient } from "prisma/generated/index.ts";
import { PrismaClient } from "prismaClient"
// Load environment variables
await load({ export: true })
+34
View File
@@ -0,0 +1,34 @@
import type { Want, User } from "prismaClient";
export function logFetchError(e: Response, requestUrl: string, responseData: any) {
console.warn({
status: e.status,
statusText: e.statusText,
url: requestUrl,
data: responseData,
rateLimiting: {
limit: e.headers.get('x-discogs-ratelimit'),
remaining: e.headers.get('x-discogs-ratelimit-remaining'),
used: e.headers.get('x-discogs-ratelimit-used'),
},
});
}
export function wantDetails(want: Want) {
const data = want.data as any;
if (!data) console.warn(want);
return {
title: data.title,
release: data.id,
artist: data.artists_sort,
id: want.id
}
}
export function isAdmin(user: User) {
return user.email === 'ntr@mnml.gg';
}
export function offerDetails() {}
File renamed without changes.
File renamed without changes.
File renamed without changes.
+5 -5
View File
@@ -936,7 +936,7 @@
]
},
{
"file": "api/auth.middleware.ts",
"file": "api/mw.auth.ts",
"settings":
{
"buffer_size": 2440,
@@ -1546,7 +1546,7 @@
[
"/home/ntr/crates/crates/deno.jsonc",
"/home/ntr/crates/crates/crates.sublime-project",
"/home/ntr/crates/crates/api/auth.middleware.ts",
"/home/ntr/crates/crates/api/mw.auth.ts",
"/home/ntr/crates/crates/import_map.json",
"/home/ntr/crates/crates/api/pg.ts",
"/home/ntr/crates/crates.sublime-project",
@@ -1816,7 +1816,7 @@
},
{
"buffer": 2,
"file": "api/auth.middleware.ts",
"file": "api/mw.auth.ts",
"semi_transient": false,
"settings":
{
@@ -1850,7 +1850,7 @@
}
],
"lsp_active": true,
"lsp_uri": "file:///home/ntr/crates/crates/api/auth.middleware.ts",
"lsp_uri": "file:///home/ntr/crates/crates/api/mw.auth.ts",
"show_definitions": false,
"syntax": "Packages/JavaScript/TypeScript.sublime-syntax",
"tab_size": 4,
@@ -1941,7 +1941,7 @@
],
[
"middle",
"api/auth.middleware.ts"
"api/mw.auth.ts"
],
[
"pg",
+4 -2
View File
@@ -4,7 +4,7 @@
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --watch=static/,routes/ dev.ts",
"start": "deno run -A --watch=routes/,.env dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"prisma-clean": "rm -rf prisma/generated",
@@ -24,6 +24,7 @@
"@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4/",
"@twind/preset-autoprefix": "https://esm.sh/@twind/preset-autoprefix@1.0.7/",
"lodash": "https://deno.land/x/lodash@4.17.15-es/lodash.js",
"less": "npm:less@4.2.0",
"lucide-preact": "npm:lucide-preact@^0.475.0",
"@prisma/client": "npm:@prisma/client@^6.6.0",
@@ -31,7 +32,8 @@
"api/": "./api/",
"api": "./api/",
"prisma/": "./prisma/",
"prisma": "./prisma/"
"prisma": "./prisma/",
"prismaClient": "./prisma/generated/index.ts",
},
"fmt": {
+3
View File
@@ -3,6 +3,9 @@ import { defineConfig } from "$fresh/server.ts";
// import twindConfig from "./twind.config.ts";
import lessPlugin from "./less.plugin.ts";
const less = lessPlugin()
await less.buildStart()
export default defineConfig({
plugins: [
// twind(twindConfig),
+4
View File
@@ -3,8 +3,10 @@
// This file is automatically updated during development when running `dev.ts`.
import * as $_404 from './routes/_404.tsx'
import * as $_500 from './routes/_500.tsx'
import * as $_app from './routes/_app.tsx'
import * as $_middleware from './routes/_middleware.ts'
import * as $api_middleware from './routes/api/_middleware.ts'
import * as $api_auth_login from './routes/api/auth/login.ts'
import * as $api_auth_logout from './routes/api/auth/logout.ts'
import * as $api_auth_register from './routes/api/auth/register.ts'
@@ -20,8 +22,10 @@ import type { Manifest } from '$fresh/server.ts'
const manifest = {
routes: {
'./routes/_404.tsx': $_404,
'./routes/_500.tsx': $_500,
'./routes/_app.tsx': $_app,
'./routes/_middleware.ts': $_middleware,
'./routes/api/_middleware.ts': $api_middleware,
'./routes/api/auth/login.ts': $api_auth_login,
'./routes/api/auth/logout.ts': $api_auth_logout,
'./routes/api/auth/register.ts': $api_auth_register,
+2 -10
View File
@@ -1,5 +1,5 @@
import { Plugin } from "$fresh/server.ts";
import { ensureDir, exists } from "$std/fs/mod.ts";
import { ensureDir } from "$std/fs/mod.ts";
import { join } from "$std/path/mod.ts";
import { dirname } from "$std/path/mod.ts";
@@ -12,7 +12,7 @@ interface LessPluginOptions {
}
export default function lessPlugin(): Plugin {
const path = "static/style"
const path = "components/style"
const outDir = "static/css"
// Compile the Less file using the Less package
@@ -47,13 +47,5 @@ export default function lessPlugin(): Plugin {
await compileLess(inputPath, outputPath);
console.log("Less styles compiled during build start");
},
// async renderAsync(ctx) {
// await ctx.renderAsync();
// // Add stylesheet link to the result links
// const cssPath = `/css/style.css`;
// return { links: [{ rel: "stylesheet", href: cssPath }] }
// }
};
}
-5
View File
@@ -10,9 +10,4 @@ import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
import config from "./fresh.config.ts";
import lessPlugin from './less.plugin.ts'
const less = lessPlugin()
await less.buildStart()
await start(manifest, config);
+28
View File
@@ -0,0 +1,28 @@
import { PageProps } from "$fresh/server.ts";
import { Head } from "$fresh/runtime.ts";
export default function Error500Page({ error }: PageProps) {
return (
<>
<Head>
<title>500 - Server Error</title>
</Head>
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/swirl.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">500 - Server Error</h1>
<p class="my-4">
{(error as Error).message}
</p>
<a href="/" class="underline">Go back home</a>
</div>
</div>
</>
);
}
+4 -11
View File
@@ -1,12 +1,5 @@
import { FreshContext } from "$fresh/server.ts";
import { Pg } from 'api/pg.ts'
import { User } from 'prisma/generated/index.ts'
import { authMiddleware } from 'api/auth.middleware.ts'
import { errorMiddleware } from 'api/error.middleware.ts'
import { errorMiddleware } from 'api/mw.error.ts'
import { serviceMiddleware } from 'api/mw.service.ts'
import { authMiddleware } from 'api/mw.auth.ts'
interface State {
pg: Pg;
user: User
}
export const handler = [errorMiddleware, authMiddleware]
export const handler = [serviceMiddleware, authMiddleware]
+2
View File
@@ -0,0 +1,2 @@
import { authMiddleware } from 'api/mw.auth.ts'
export const handler = [authMiddleware]
+1 -1
View File
@@ -3,7 +3,7 @@ import { setCookie } from "$std/http/cookie.ts"
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"
import { createPg } from "api/pg.ts"
import { sessions } from "api/auth.middleware.ts"
import { sessions } from "api/mw.auth.ts"
export const handler: Handlers = {
async POST(req, _ctx) {
+1 -1
View File
@@ -1,6 +1,6 @@
import { Handlers } from "$fresh/server.ts"
import { getCookies, setCookie } from "$std/http/cookie.ts"
import { sessions } from "../../../api/auth.middleware.ts"
import { sessions } from "../../../api/mw.auth.ts"
export const handler: Handlers = {
async POST(req, _ctx) {
+1 -1
View File
@@ -1,7 +1,7 @@
import { Handlers } from "$fresh/server.ts"
import { setCookie } from "$std/http/cookie.ts"
import { createPg } from "../../../api/pg.ts"
import { sessions } from "../../../api/auth.middleware.ts"
import { sessions } from "../../../api/mw.auth.ts"
export const handler: Handlers = {
async POST(req, _ctx) {
+3 -1
View File
@@ -1,9 +1,11 @@
import { Handlers } from "$fresh/server.ts"
import * as offersService from "api/offers.ts"
export const handler: Handlers = {
async GET(_req, ctx) {
const user = ctx.state.user
const offers = await pg.userOffers(user)
const offers = await offersService.userOffers(user)
return new Response(JSON.stringify(offers), {
status: 200,
+8 -6
View File
@@ -1,17 +1,19 @@
import { Handlers, PageProps } from "$fresh/server.ts";
import { createPg, Pg } from "../api/pg.ts";
import Main from "../components/main.tsx";
import { User } from 'prisma/generated/index.ts'
import { CratesCtx } from "api/mw.service.ts"
export const handler: Handlers = {
async GET(req, ctx) {
const user: User = ctx.state.user;
const pg: Pg = createPg(console);
async GET(_req, ctx: CratesCtx) {
const { pg, offers, user } = ctx.state
console.log(user)
const wants = await pg.userWants(user);
const offers = await pg.userOffers(user);
const uOffers = await offers.userOffers(user);
return ctx.render({ wants, offers });
return ctx.render({ wants, offers: uOffers });
},
};
+1 -1
View File
@@ -2,7 +2,7 @@ import { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies, setCookie } from "$std/http/cookie.ts";
import { z } from "npm:zod";
import Swirl from "../components/swirl.tsx";
import { sessions } from "api/auth.middleware.ts";
import { sessions } from "api/mw.auth.ts";
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts";
import { createPg } from "api/pg.ts";
+1 -1
View File
@@ -3,7 +3,7 @@ import { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies, setCookie } from "$std/http/cookie.ts";
import { z } from "npm:zod";
import Swirl from "../components/swirl.tsx";
import { sessions } from "../api/auth.middleware.ts";
import { sessions } from "../api/mw.auth.ts";
import { createPg } from "../api/pg.ts";
export const handler: Handlers = {