prisma types

This commit is contained in:
Nathan Rashleigh committed 2025-04-29 20:34:01 +10:00
1 parent ff371798a9
commit d868baade5
17 files changed
+166 -146

No files matched your search

+8 -19
View File
@@ -18,8 +18,11 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
const url = new URL(req.url) const url = new URL(req.url)
const path = url.pathname const path = url.pathname
console.log("authing...")
// Allow public paths without authentication // Allow public paths without authentication
if (PUBLIC_PATHS.includes(path)) { if (PUBLIC_PATHS.includes(path)) {
console.log("noauth")
return await ctx.next() return await ctx.next()
} }
@@ -38,11 +41,10 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
// Redirect to login page for non-API routes // Redirect to login page for non-API routes
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: "/" } headers: { Location: "/login" }
}) })
} }
try {
// Verify session // Verify session
const session = sessions.get(sessionId) const session = sessions.get(sessionId)
@@ -61,23 +63,10 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
throw new Error("User not found") throw new Error("User not found")
} }
// Add user to state for use in handlers
ctx.state.user = user ctx.state.user = user
ctx.state.pg = pg
console.log("woas")
return await ctx.next() return await ctx.next()
} catch (error) {
console.error("Auth error:", error)
if (path.startsWith("/api/")) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" }
})
}
// Redirect to login page for non-API routes
return new Response(null, {
status: 302,
headers: { Location: "/" }
})
}
} }
+29
View File
@@ -0,0 +1,29 @@
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" }
})
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts" import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"
import { load } from "$std/dotenv/mod.ts" import { load } from "$std/dotenv/mod.ts"
import { PrismaClient } from "prisma/generated/index.js"; import { PrismaClient } from "prisma/generated/index.ts";
// Load environment variables // Load environment variables
await load({ export: true }) await load({ export: true })
@@ -32,7 +32,7 @@ export function createPg(log: Console) {
return prisma.user.findUnique({ where: { email } }) return prisma.user.findUnique({ where: { email } })
} }
async function userWants(user: any) { async function userWants(user: User) {
const wants = await prisma.want.findMany({ const wants = await prisma.want.findMany({
where: { userId: user.id }, where: { userId: user.id },
include: { _count: { select: { offers: true } } }, include: { _count: { select: { offers: true } } },
@@ -46,7 +46,7 @@ export function createPg(log: Console) {
}) })
} }
async function userOffers(user: any) { async function userOffers(user: User) {
return prisma.offer.findMany({ return prisma.offer.findMany({
where: { where: {
userId: user.id, userId: user.id,
+17 -15
View File
@@ -1,11 +1,7 @@
import { h } from 'preact'; import { h } from 'preact';
import { route } from 'preact-router'; // import { route } from 'preact-router';
import { useSelector } from 'react-redux'
import { State } from './../store'; import OfferGroup from "./offer.group.tsx";
import * as toast from './../toast';
import OfferGroup from './../components/offer.group';
const GroupNav = (want) => { const GroupNav = (want) => {
if (!want._count.offers) return null if (!want._count.offers) return null
@@ -19,19 +15,25 @@ const GroupNav = (want) => {
); );
}; };
function Home() { function Main({ wants, offers }) {
const offers = useSelector((state: State) => state.offers);
const wants = useSelector((state: State) => state.wants);
if (offers && offers.length === 0) { if (offers && offers.length === 0) {
toast.info('no offers', 'You have no offers yet, search for some more items.'); // Redirect to search if no offers
route('/search'); if (typeof window !== 'undefined') {
// route('/search');
}
} }
if (!offers || !wants) return <div>loading...</div>; if (!offers || !wants) {
return (
<div>
<p>No wants or offers found.</p>
<a href="/search" class="button">Search Records</a>
</div>
);
}
return ( return (
<main > <main>
<nav> <nav>
<div class='header'> <div class='header'>
<span>Record</span> <span>Record</span>
@@ -46,4 +48,4 @@ function Home() {
); );
} }
export default Home; export default Main;
+8 -53
View File
@@ -1,50 +1,29 @@
import { h } from 'preact'; import { h } from 'preact';
import { ExternalLink, Check } from 'react-feather';
import { useState, useCallback } from 'preact/hooks'; import { useState, useCallback } from 'preact/hooks';
import _ from 'lodash' import { ExternalLink, Check } from 'lucide-preact';
import _ from 'lodash';
import { useSelector, useDispatch } from 'react-redux'
import { State } from './../store';
import http from '../http';
function OfferLine(offer) { function OfferLine(offer) {
const ws = useSelector((state: State) => state.ws); // Server-side rendering version doesn't support interactive status changes
function setStatus(status) {
ws.sendOfferStatusSet(offer.id, status);
}
const accepted = offer.status === 'Accepted'; const accepted = offer.status === 'Accepted';
const classes = `${accepted ? 'accepted' : ''}`; const classes = `${accepted ? 'accepted' : ''}`;
const btnText = accepted const btnText = accepted
? offer.status ? offer.status
: 'Accept'; : 'Accept';
const negBtn = accepted
? <button onClick={() => setStatus('Pending')} class='neg'>Cancel</button>
: <button onClick={() => setStatus('Declined')} class='neg'>Decline</button>;
return ( return (
<div class="line"> <div class="line">
<div class='number'>${offer.total}</div> <div class='number'>${offer.total}</div>
<div class='number'>{offer.condition}</div> <div class='number'>{offer.condition}</div>
<button onClick={() => setStatus('Accepted')} disabled={accepted} class={classes} >{btnText}</button> <div class='number'>{offer.status}</div>
{negBtn}
</div> </div>
) )
} }
function OfferPriceBtn(offer) { function OfferPriceBtn(offer) {
const ws = useSelector((state: State) => state.ws);
const accepted = offer.status === 'Accepted'; const accepted = offer.status === 'Accepted';
const confirmed = offer.status === 'Confirmed'; const confirmed = offer.status === 'Confirmed';
function setStatus() {
if (accepted) return ws.sendOfferStatusSet(offer.id, 'Pending')
ws.sendOfferStatusSet(offer.id, 'Accepted');
}
const classes = `${accepted ? 'accepted' : ''} ${confirmed ? 'confirmed' : ''}`; const classes = `${accepted ? 'accepted' : ''} ${confirmed ? 'confirmed' : ''}`;
const btnText = accepted const btnText = accepted
? offer.status ? offer.status
@@ -55,7 +34,7 @@ function OfferPriceBtn(offer) {
: 'Click to accept this offer'; : 'Click to accept this offer';
return ( return (
<button disabled={confirmed} title={title} onClick={() => setStatus()} class={classes}>${offer.total} {offer.condition}</button> <button disabled={true} title={title} class={classes}>${offer.total} {offer.condition}</button>
) )
} }
@@ -65,29 +44,7 @@ function OfferConditionSlider([condition, offers], i, groupId, rangeMin, rangeMa
const markersId = `${id}-markers` const markersId = `${id}-markers`
const steps = offers.map(o => o.total) const steps = offers.map(o => o.total)
const initialValue = _.last(offers.filter(o => o.status === 'Accepted'))?.total || 0 const initialValue = _.last(offers.filter(o => o.status === 'Accepted'))?.total || 0
const acceptedPriceLabel = initialValue ? `$${initialValue}` : '-'
const ws = useSelector((state: State) => state.ws);
const [acceptedPrice, setAcceptedPrice] = useState(initialValue)
const sendOffers = useCallback(_.debounce(function send(v) {
const accepted = offers.filter(o => Number(o.total) <= v).map(o => o.id)
const pending = offers.filter(o => Number(o.total) > v).map(o => o.id)
ws.sendOfferStatusSet(accepted, 'Accepted')
ws.sendOfferStatusSet(pending, 'Pending')
}, 1000), [])
function onChange(target) {
const highest = () => {
return _.findLast(steps, s => target.value >= Number(s)) || 0
}
const v = highest()
target.value = v
setAcceptedPrice(v)
sendOffers(v)
}
const acceptedPriceLabel = acceptedPrice ? `$${acceptedPrice}` : '-'
return ( return (
<div key={id}> <div key={id}>
@@ -101,8 +58,8 @@ function OfferConditionSlider([condition, offers], i, groupId, rangeMin, rangeMa
step={step} step={step}
min={rangeMin} min={rangeMin}
max={rangeMax} max={rangeMax}
value={acceptedPrice} value={initialValue}
onChange={e => onChange((e.target as HTMLInputElement)) } /> disabled={true} />
<datalist id={markersId}> <datalist id={markersId}>
{offers.map(o => <option value={o.total}></option>)} {offers.map(o => <option value={o.total}></option>)}
@@ -113,8 +70,6 @@ function OfferConditionSlider([condition, offers], i, groupId, rangeMin, rangeMa
export default function OfferGroup(group, i) { export default function OfferGroup(group, i) {
const ws = useSelector((state: State) => state.ws);
const format = group.details.formats[0].text ? `${group.details.formats[0].text} - ` : ''; const format = group.details.formats[0].text ? `${group.details.formats[0].text} - ` : '';
const descriptions = group.details.formats.map(f => f.descriptions.join(' ')).join(' ') const descriptions = group.details.formats.map(f => f.descriptions.join(' ')).join(' ')
const detailText = `${format} ${descriptions}`; const detailText = `${format} ${descriptions}`;
+4 -16
View File
@@ -1,21 +1,9 @@
import { h } from 'preact'; import { h } from 'preact';
import { ExternalLink, Check } from 'react-feather'; import { ExternalLink, Check } from 'lucide-preact';
import { useState } from 'preact/hooks';
import { useSelector, useDispatch } from 'react-redux'
import { State } from './../store';
import http from '../http';
export default function Offer(offer) { export default function Offer(offer) {
const ws = useSelector((state: State) => state.ws);
const { want: { data: details } } = offer; const { want: { data: details } } = offer;
function setStatus(status) {
ws.sendOfferStatusSet(offer.id, status);
}
const accepted = offer.status === 'Accepted'; const accepted = offer.status === 'Accepted';
const classes = `${accepted ? 'accepted' : ''}`; const classes = `${accepted ? 'accepted' : ''}`;
const btnText = accepted const btnText = accepted
@@ -23,8 +11,8 @@ export default function Offer(offer) {
: 'Accept'; : 'Accept';
const negBtn = accepted const negBtn = accepted
? <button onClick={() => setStatus('Pending')} class='neg'>Cancel</button> ? <button disabled={true} class='neg'>Cancel</button>
: <button onClick={() => setStatus('Declined')} class='neg'>Decline</button>; : <button disabled={true} class='neg'>Decline</button>;
return ( return (
<div class="offer"> <div class="offer">
@@ -36,7 +24,7 @@ export default function Offer(offer) {
<div class='number'>shipped by itself: ${offer.comparison}</div> <div class='number'>shipped by itself: ${offer.comparison}</div>
</section> </section>
<img src={details.thumb} /> <img src={details.thumb} />
<button onClick={() => setStatus('Accepted')} disabled={accepted} class={classes} >{btnText}</button> <button disabled={true} class={classes}>{btnText}</button>
{negBtn} {negBtn}
</div> </div>
) )
+6 -2
View File
@@ -4,10 +4,11 @@
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -", "cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)", "manifest": "deno task cli manifest $(pwd)",
"start": "deno run --unstable-detect-cjs -A --watch=static/,routes/ dev.ts", "start": "deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build", "build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts", "preview": "deno run -A main.ts",
"prisma-generate": "deno run -A --env-file npm:prisma generate", "prisma-clean": "rm -rf prisma/generated",
"prisma-generate": "deno task prisma-clean && deno run -A --env-file npm:prisma generate",
"prisma:migrate": "deno run -A --env-file npm:prisma migrate dev", "prisma:migrate": "deno run -A --env-file npm:prisma migrate dev",
"update": "deno run -A -r https://fresh.deno.dev/update ." "update": "deno run -A -r https://fresh.deno.dev/update ."
}, },
@@ -22,7 +23,10 @@
"@twind/core": "https://esm.sh/@twind/core@1.1.3", "@twind/core": "https://esm.sh/@twind/core@1.1.3",
"@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4/", "@twind/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4/",
"@twind/preset-autoprefix": "https://esm.sh/@twind/preset-autoprefix@1.0.7/", "@twind/preset-autoprefix": "https://esm.sh/@twind/preset-autoprefix@1.0.7/",
"less": "npm:less@4.2.0", "less": "npm:less@4.2.0",
"lucide-preact": "npm:lucide-preact@^0.475.0",
"@prisma/client": "npm:@prisma/client@^6.6.0",
"api/": "./api/", "api/": "./api/",
"api": "./api/", "api": "./api/",
+5 -3
View File
@@ -1,7 +1,8 @@
import { defineConfig } from "$fresh/server.ts"; import { defineConfig } from "$fresh/server.ts";
import twind from "$fresh/plugins/twindv1.ts"; // import twind from "$fresh/plugins/twindv1.ts";
import twindConfig from "./twind.config.ts"; // import twindConfig from "./twind.config.ts";
import { authMiddleware } from "api/auth.middleware.ts"; import { authMiddleware } from "api/auth.middleware.ts";
import { errorMiddleware } from "api/error.middleware.ts";
import lessPlugin from "./less.plugin.ts"; import lessPlugin from "./less.plugin.ts";
export default defineConfig({ export default defineConfig({
@@ -10,6 +11,7 @@ export default defineConfig({
lessPlugin() lessPlugin()
], ],
middleware: { middleware: {
auth: authMiddleware auth: authMiddleware,
error: errorMiddleware,
} }
}); });
+6
View File
@@ -8,6 +8,9 @@ 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_logout from './routes/api/auth/logout.ts'
import * as $api_auth_register from './routes/api/auth/register.ts' import * as $api_auth_register from './routes/api/auth/register.ts'
import * as $api_auth_status from './routes/api/auth/status.ts' import * as $api_auth_status from './routes/api/auth/status.ts'
import * as $api_offers from './routes/api/offers.ts'
import * as $api_wants from './routes/api/wants.ts'
import * as $home from './routes/home.tsx'
import * as $index from './routes/index.tsx' import * as $index from './routes/index.tsx'
import * as $login from './routes/login.tsx' import * as $login from './routes/login.tsx'
import * as $register from './routes/register.tsx' import * as $register from './routes/register.tsx'
@@ -22,6 +25,9 @@ const manifest = {
'./routes/api/auth/logout.ts': $api_auth_logout, './routes/api/auth/logout.ts': $api_auth_logout,
'./routes/api/auth/register.ts': $api_auth_register, './routes/api/auth/register.ts': $api_auth_register,
'./routes/api/auth/status.ts': $api_auth_status, './routes/api/auth/status.ts': $api_auth_status,
'./routes/api/offers.ts': $api_offers,
'./routes/api/wants.ts': $api_wants,
'./routes/home.tsx': $home,
'./routes/index.tsx': $index, './routes/index.tsx': $index,
'./routes/login.tsx': $login, './routes/login.tsx': $login,
'./routes/register.tsx': $register, './routes/register.tsx': $register,
+1
View File
@@ -4,6 +4,7 @@
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.6.0", "@prisma/client": "^6.6.0",
"lodash": "^4.17.21",
"zod": "^3.24.3" "zod": "^3.24.3"
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client"
output = "./generated" output = "./generated"
moduleFormat = "esm" moduleFormat = "esm"
} }
+18
View File
@@ -0,0 +1,18 @@
import { FreshContext } from "$fresh/server.ts";
import { Pg } from 'api/pg.ts'
import { User } from 'prisma/generated/index.d.ts'
interface State {
pg: Pg;
user: User
}
export async function handler(
req: Request,
ctx: FreshContext<State>,
) {
ctx.state.data = "myData";
const resp = await ctx.next();
resp.headers.set("server", "fresh server");
return resp;
}
+15
View File
@@ -0,0 +1,15 @@
import { Handlers } from "$fresh/server.ts"
import { createPg } from "../../api/pg.ts"
export const handler: Handlers = {
async GET(_req, ctx) {
const user = ctx.state.user
const pg = createPg(console)
const offers = await pg.userOffers(user)
return new Response(JSON.stringify(offers), {
status: 200,
headers: { "Content-Type": "application/json" }
})
}
}
+15
View File
@@ -0,0 +1,15 @@
import { Handlers } from "$fresh/server.ts"
import { createPg } from "../../api/pg.ts"
export const handler: Handlers = {
async GET(_req, ctx) {
const user = ctx.state.user
const pg = createPg(console)
const wants = await pg.userWants(user)
return new Response(JSON.stringify(wants), {
status: 200,
headers: { "Content-Type": "application/json" }
})
}
}
+26
View File
@@ -0,0 +1,26 @@
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.d.ts'
export const handler: Handlers = {
async GET(req, ctx) {
const user: User = ctx.state.user;
const pg: Pg = createPg(console);
const wants = await pg.userWants(user);
const offers = await pg.userOffers(user);
return ctx.render({ wants, offers });
},
};
export default function Home({ data }: PageProps) {
const { wants, offers } = data || {};
return (
<div id="mcrates">
<Main wants={wants} offers={offers} />
</div>
);
}
-29
View File
@@ -1,29 +0,0 @@
import { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies } from "$std/http/cookie.ts";
import { sessions } from "../api/auth.middleware.ts";
export const handler: Handlers = {
async GET(req, ctx) {
const cookies = getCookies(req.headers);
const sessionId = cookies.session;
// Check if session exists and is valid
if (!sessionId || !sessions.has(sessionId)) {
return new Response("", {
status: 307,
headers: { Location: "/login" },
});
}
return ctx.render();
},
};
export default function Home() {
return (
<div id="mcrates">
<h1>Welcome to crates</h1>
<p>You are logged in!</p>
</div>
);
}
+4 -5
View File
@@ -1,11 +1,10 @@
import { h } from "preact";
import { Handlers, PageProps } from "$fresh/server.ts"; import { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies, setCookie } from "$std/http/cookie.ts"; import { getCookies, setCookie } from "$std/http/cookie.ts";
import { z } from "npm:zod"; import { z } from "npm:zod";
import Swirl from "../components/swirl.tsx"; import Swirl from "../components/swirl.tsx";
import { sessions } from "../api/auth.middleware.ts"; import { sessions } from "api/auth.middleware.ts";
import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"; import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts";
import { createPg } from "../api/pg.ts"; import { createPg } from "api/pg.ts";
export const handler: Handlers = { export const handler: Handlers = {
async GET(req, ctx) { async GET(req, ctx) {
@@ -16,7 +15,7 @@ export const handler: Handlers = {
if (sessionId && sessions.has(sessionId)) { if (sessionId && sessions.has(sessionId)) {
return new Response("", { return new Response("", {
status: 307, status: 307,
headers: { Location: "/" }, headers: { Location: "/home" },
}); });
} }
@@ -80,7 +79,7 @@ export const handler: Handlers = {
}); });
// Set cookie and redirect // Set cookie and redirect
const headers = new Headers({ Location: "/" }); const headers = new Headers({ Location: "/home" });
setCookie(headers, { setCookie(headers, {
name: "session", name: "session",
value: sessionId, value: sessionId,