prisma types
This commit is contained in:
1 parent
ff371798a9
commit
d868baade5
17 files changed
+184
-164
No files matched your search
@@ -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)
|
||||
|
||||
if (!session || new Date() > session.expiresAt) {
|
||||
if (session) {
|
||||
sessions.delete(sessionId)
|
||||
}
|
||||
throw new Error("Invalid or expired session")
|
||||
}
|
||||
// Verify session
|
||||
const session = sessions.get(sessionId)
|
||||
|
||||
// Get user from database
|
||||
const pg = createPg(console)
|
||||
const user = await pg.userFindId(session.userId)
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found")
|
||||
if (!session || new Date() > session.expiresAt) {
|
||||
if (session) {
|
||||
sessions.delete(sessionId)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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,8 +24,8 @@ 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
@@ -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/",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"zod": "^3.24.3"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
provider = "prisma-client"
|
||||
output = "./generated"
|
||||
moduleFormat = "esm"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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" }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user