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
+183 -163

No files matched your search

+25 -36
View File
@@ -18,8 +18,11 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
const url = new URL(req.url)
const path = url.pathname
console.log("authing...")
// Allow public paths without authentication
if (PUBLIC_PATHS.includes(path)) {
console.log("noauth")
return await ctx.next()
}
@@ -38,46 +41,32 @@ export async function authMiddleware(req: Request, ctx: FreshContext): Promise<R
// Redirect to login page for non-API routes
return new Response(null, {
status: 302,
headers: { Location: "/" }
headers: { Location: "/login" }
})
}
try {
// Verify session
const session = sessions.get(sessionId)
// Verify session
const session = sessions.get(sessionId)
if (!session || new Date() > session.expiresAt) {
if (session) {
sessions.delete(sessionId)
}
throw new Error("Invalid or expired session")
if (!session || new Date() > session.expiresAt) {
if (session) {
sessions.delete(sessionId)
}
// Get user from database
const pg = createPg(console)
const user = await pg.userFindId(session.userId)
if (!user) {
throw new Error("User not found")
}
// Add user to state for use in handlers
ctx.state.user = user
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: "/" }
})
throw new Error("Invalid or expired session")
}
// Get user from database
const pg = createPg(console)
const user = await pg.userFindId(session.userId)
if (!user) {
throw new Error("User not found")
}
ctx.state.user = user
ctx.state.pg = pg
console.log("woas")
return await ctx.next()
}
+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 { load } from "$std/dotenv/mod.ts"
import { PrismaClient } from "prisma/generated/index.js";
import { PrismaClient } from "prisma/generated/index.ts";
// Load environment variables
await load({ export: true })
@@ -32,7 +32,7 @@ export function createPg(log: Console) {
return prisma.user.findUnique({ where: { email } })
}
async function userWants(user: any) {
async function userWants(user: User) {
const wants = await prisma.want.findMany({
where: { userId: user.id },
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({
where: {
userId: user.id,
+17 -15
View File
@@ -1,11 +1,7 @@
import { h } from 'preact';
import { route } from 'preact-router';
import { useSelector } from 'react-redux'
// import { route } from 'preact-router';
import { State } from './../store';
import * as toast from './../toast';
import OfferGroup from './../components/offer.group';
import OfferGroup from "./offer.group.tsx";
const GroupNav = (want) => {
if (!want._count.offers) return null
@@ -19,19 +15,25 @@ const GroupNav = (want) => {
);
};
function Home() {
const offers = useSelector((state: State) => state.offers);
const wants = useSelector((state: State) => state.wants);
function Main({ wants, offers }) {
if (offers && offers.length === 0) {
toast.info('no offers', 'You have no offers yet, search for some more items.');
route('/search');
// Redirect to search if no offers
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 (
<main >
<main>
<nav>
<div class='header'>
<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 { ExternalLink, Check } from 'react-feather';
import { useState, useCallback } from 'preact/hooks';
import _ from 'lodash'
import { useSelector, useDispatch } from 'react-redux'
import { State } from './../store';
import http from '../http';
import { ExternalLink, Check } from 'lucide-preact';
import _ from 'lodash';
function OfferLine(offer) {
const ws = useSelector((state: State) => state.ws);
function setStatus(status) {
ws.sendOfferStatusSet(offer.id, status);
}
// Server-side rendering version doesn't support interactive status changes
const accepted = offer.status === 'Accepted';
const classes = `${accepted ? 'accepted' : ''}`;
const btnText = accepted
? offer.status
: 'Accept';
const negBtn = accepted
? <button onClick={() => setStatus('Pending')} class='neg'>Cancel</button>
: <button onClick={() => setStatus('Declined')} class='neg'>Decline</button>;
return (
<div class="line">
<div class='number'>${offer.total}</div>
<div class='number'>{offer.condition}</div>
<button onClick={() => setStatus('Accepted')} disabled={accepted} class={classes} >{btnText}</button>
{negBtn}
<div class='number'>{offer.status}</div>
</div>
)
}
function OfferPriceBtn(offer) {
const ws = useSelector((state: State) => state.ws);
const accepted = offer.status === 'Accepted';
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 btnText = accepted
? offer.status
@@ -55,7 +34,7 @@ function OfferPriceBtn(offer) {
: 'Click to accept this offer';
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 steps = offers.map(o => o.total)
const initialValue = _.last(offers.filter(o => o.status === 'Accepted'))?.total || 0
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}` : '-'
const acceptedPriceLabel = initialValue ? `$${initialValue}` : '-'
return (
<div key={id}>
@@ -101,8 +58,8 @@ function OfferConditionSlider([condition, offers], i, groupId, rangeMin, rangeMa
step={step}
min={rangeMin}
max={rangeMax}
value={acceptedPrice}
onChange={e => onChange((e.target as HTMLInputElement)) } />
value={initialValue}
disabled={true} />
<datalist id={markersId}>
{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) {
const ws = useSelector((state: State) => state.ws);
const format = group.details.formats[0].text ? `${group.details.formats[0].text} - ` : '';
const descriptions = group.details.formats.map(f => f.descriptions.join(' ')).join(' ')
const detailText = `${format} ${descriptions}`;
+4 -16
View File
@@ -1,21 +1,9 @@
import { h } from 'preact';
import { ExternalLink, Check } from 'react-feather';
import { useState } from 'preact/hooks';
import { useSelector, useDispatch } from 'react-redux'
import { State } from './../store';
import http from '../http';
import { ExternalLink, Check } from 'lucide-preact';
export default function Offer(offer) {
const ws = useSelector((state: State) => state.ws);
const { want: { data: details } } = offer;
function setStatus(status) {
ws.sendOfferStatusSet(offer.id, status);
}
const accepted = offer.status === 'Accepted';
const classes = `${accepted ? 'accepted' : ''}`;
const btnText = accepted
@@ -23,8 +11,8 @@ export default function Offer(offer) {
: 'Accept';
const negBtn = accepted
? <button onClick={() => setStatus('Pending')} class='neg'>Cancel</button>
: <button onClick={() => setStatus('Declined')} class='neg'>Decline</button>;
? <button disabled={true} class='neg'>Cancel</button>
: <button disabled={true} class='neg'>Decline</button>;
return (
<div class="offer">
@@ -36,7 +24,7 @@ export default function Offer(offer) {
<div class='number'>shipped by itself: ${offer.comparison}</div>
</section>
<img src={details.thumb} />
<button onClick={() => setStatus('Accepted')} disabled={accepted} class={classes} >{btnText}</button>
<button disabled={true} class={classes}>{btnText}</button>
{negBtn}
</div>
)
+6 -2
View File
@@ -4,10 +4,11 @@
"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 --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",
"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",
"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/preset-tailwind": "https://esm.sh/@twind/preset-tailwind@1.1.4/",
"@twind/preset-autoprefix": "https://esm.sh/@twind/preset-autoprefix@1.0.7/",
"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/",
+5 -3
View File
@@ -1,7 +1,8 @@
import { defineConfig } from "$fresh/server.ts";
import twind from "$fresh/plugins/twindv1.ts";
import twindConfig from "./twind.config.ts";
// import twind from "$fresh/plugins/twindv1.ts";
// import twindConfig from "./twind.config.ts";
import { authMiddleware } from "api/auth.middleware.ts";
import { errorMiddleware } from "api/error.middleware.ts";
import lessPlugin from "./less.plugin.ts";
export default defineConfig({
@@ -10,6 +11,7 @@ export default defineConfig({
lessPlugin()
],
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_register from './routes/api/auth/register.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 $login from './routes/login.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/register.ts': $api_auth_register,
'./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/login.tsx': $login,
'./routes/register.tsx': $register,
+1
View File
@@ -4,6 +4,7 @@
},
"dependencies": {
"@prisma/client": "^6.6.0",
"lodash": "^4.17.21",
"zod": "^3.24.3"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client"
output = "./generated"
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 { 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/auth.middleware.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 = {
async GET(req, ctx) {
@@ -16,7 +15,7 @@ export const handler: Handlers = {
if (sessionId && sessions.has(sessionId)) {
return new Response("", {
status: 307,
headers: { Location: "/" },
headers: { Location: "/home" },
});
}
@@ -80,7 +79,7 @@ export const handler: Handlers = {
});
// Set cookie and redirect
const headers = new Headers({ Location: "/" });
const headers = new Headers({ Location: "/home" });
setCookie(headers, {
name: "session",
value: sessionId,