Finish front end tests

This commit is contained in:
Gabriel Brown 2024-10-07 13:45:42 -05:00
parent a2bb8023c9
commit 9f7d142ff4
34 changed files with 854 additions and 69 deletions

0
.env.example Normal file → Executable file
View File

0
.eslintrc.cjs Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

0
drizzle.config.ts Normal file → Executable file
View File

0
next.config.js Normal file → Executable file
View File

0
package.json Normal file → Executable file
View File

0
pnpm-lock.yaml generated Normal file → Executable file
View File

0
postcss.config.cjs Normal file → Executable file
View File

0
prettier.config.js Normal file → Executable file
View File

0
public/favicon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,27 +0,0 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { updateUserPushToken } from '~/server/functions';
import { middleware } from "~/middleware";
type Data = {
userId: string;
pushToken: string;
};
export const POST = async (request: NextRequest) => {
const middlewareResponse = await middleware(request);
if (middlewareResponse) return middlewareResponse;
try {
const { userId, pushToken } = await request.json() as Data;
console.log('Received request:', { userId, pushToken });
console.log('Updating push token for user:', userId);
await updateUserPushToken(parseInt(userId), pushToken);
console.log('Push token updated successfully');
return NextResponse.json({ message: "Push token updated successfully" });
} catch (error) {
console.error('Error in updatePushToken:', error);
return NextResponse.json({ message: "Error updating push token" }, { status: 500 });
}
};

View File

@ -18,6 +18,12 @@ export const POST = async (request: NextRequest) => {
console.log("Received request:", { userId, oldPassword, newPassword }); console.log("Received request:", { userId, oldPassword, newPassword });
console.log("Changing password for user:", userId); console.log("Changing password for user:", userId);
if (oldPassword === newPassword) {
return NextResponse.json(
{ message: "New password cannot be the same as the old password" },
{ status: 400 }
);
}
await changePassword(userId, oldPassword, newPassword); await changePassword(userId, oldPassword, newPassword);
return NextResponse.json({ message: "Password changed successfully" }); return NextResponse.json({ message: "Password changed successfully" });
} catch (error) { } catch (error) {

View File

@ -3,27 +3,20 @@ import { NextResponse } from "next/server";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { logout } from "~/server/functions"; import { logout } from "~/server/functions";
import { middleware } from "~/middleware"; import { middleware } from "~/middleware";
import jwt from "jsonwebtoken";
export const POST = async (request: NextRequest) => { export const POST = async (request: NextRequest) => {
const middlewareResponse = await middleware(request); const middlewareResponse = await middleware(request);
if (middlewareResponse) return middlewareResponse; if (middlewareResponse) return middlewareResponse;
try { try {
const { token } = await request.json() as { token: string }; const { refreshToken } = await request.json() as { refreshToken: string };
if (!token) if (!refreshToken)
return NextResponse.json({ message: "Token is required" },{ status: 400 }); return NextResponse.json({ message: "Refresh token is required" }, { status: 400 });
try { await logout(refreshToken);
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: number };
if (!decoded.userId)
throw new Error("Invalid token");
await logout(decoded.userId);
return NextResponse.json({ message: "Logged out successfully" }); return NextResponse.json({ message: "Logged out successfully" });
} catch (jwtError) {
return NextResponse.json({ message: "Invalid token", error: jwtError }, { status: 400 });
}
} catch (error) { } catch (error) {
console.error('Logout error:', error);
if (error instanceof Error) if (error instanceof Error)
return NextResponse.json({ message: error.message }, { status: 400 }); return NextResponse.json({ message: error.message }, { status: 400 });
else else

0
src/app/layout.tsx Normal file → Executable file
View File

0
src/app/page.tsx Normal file → Executable file
View File

View File

@ -24,7 +24,7 @@ export default function TestCreateRequestPage() {
}), }),
}); });
const data = await response.json() as {message: string}; const data = await response.json() as {message: string};
setResult(JSON.stringify(data)); setResult(JSON.stringify(data, null, 2));
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
setResult('An error occurred'); setResult('An error occurred');

View File

@ -0,0 +1,61 @@
"use client";
import React, { useState } from 'react';
export default function DeleteRelationshipPage() {
const [relationshipId, setRelationshipId] = useState("");
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/relationships/deleteRelationship', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
relationshipId
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Send Message</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="relationshipId" className="block">Relationship ID:</label>
<input
type="text"
id="relationshipId"
value={relationshipId}
onChange={(e) => setRelationshipId(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Delete Relationship
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,79 @@
"use client";
import React, { useState } from 'react';
type status = 'pending' | 'accepted' | 'rejected';
export default function UpdateRequestPage() {
const [relationshipId, setRelationshipId] = useState("");
const [status, setStatus] = useState("");
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/relationships/updateRequest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
relationshipId,
status
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Send Message</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="relationshipId" className="block">Relationship ID:</label>
<input
type="text"
id="relationshipId"
value={relationshipId}
onChange={(e) => setRelationshipId(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="status" className="block">Status:</label>
<select
id="status"
value={status}
onChange={(e) => setStatus(e.target.value as status)}
className="border p-2 w-full bg-black"
required
>
<option value="pending">Pending</option>
<option value="accepted">Accepted</option>
<option value="rejected">Rejected</option>
</select>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Update Request
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,87 @@
"use client";
import React, { useState } from 'react';
export default function ChangePasswordPage() {
const [userId, setUserId] = useState("");
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/changePassword', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
userId,
oldPassword,
newPassword
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Change Password</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="userId" className="block">User ID:</label>
<input
type="text"
id="userId"
value={userId}
onChange={(e) => setUserId(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="oldPassword" className="block">Old Password:</label>
<input
type="text"
id="oldPassword"
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="newPassword" className="block">New Password:</label>
<input
type="text"
id="newPassword"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Change Password
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,124 @@
"use client";
import React, { useState } from 'react';
export default function CreateUserPage() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [passwordHash, setPasswordHash] = useState("");
const [name, setName] = useState("");
const [pfpURL, setPfpURL] = useState("");
const [pushToken, setPushToken] = useState("");
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/createUser', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
username,
email,
passwordHash,
name,
pfpURL,
pushToken
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Create User</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="username" className="block">Username:</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="email" className="block">Email:</label>
<input
type="text"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="passwordHash" className="block">Password:</label>
<input
type="text"
id="passwordHash"
value={passwordHash}
onChange={(e) => setPasswordHash(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="name" className="block">Name:</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="pfpURL" className="block">Pfp URL:</label>
<input
type="text"
id="pfpURL"
value={pfpURL}
onChange={(e) => setPfpURL(e.target.value)}
className="border p-2 w-full bg-black"
/>
</div>
<div>
<label htmlFor="pushToken" className="block">Push Token:</label>
<input
type="text"
id="pushToken"
value={pushToken}
onChange={(e) => setPushToken(e.target.value)}
className="border p-2 w-full bg-black"
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Create User
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,58 @@
'use client';
import React, { useState } from 'react';
export default function GetUserByIDPage() {
const [userId, setUserId] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch(`/api/users/getUserByID?userId=${userId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Get User By ID</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="userId" className="block">User ID:</label>
<input
type="text"
id="userId"
value={userId}
onChange={(e) => setUserId(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Get User By ID
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,58 @@
'use client';
import React, { useState } from 'react';
export default function GetUserByUsernamePage() {
const [userName, setUserName] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch(`/api/users/getUserByUsername?username=${userName}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Get User By Username</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="userId" className="block">User ID:</label>
<input
type="text"
id="userId"
value={userName}
onChange={(e) => setUserName(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Get User By ID
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,74 @@
'use client';
import React, { useState } from 'react';
export default function LoginTestPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
username,
password
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Login</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="username" className="block">Username:</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="password" className="block">Password:</label>
<input
type="text"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Login
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,60 @@
'use client';
import React, { useState } from 'react';
export default function TestLogout() {
const [refreshToken, setRefreshToken] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
refreshToken
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Logout</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="refreshToken" className="block">Token:</label>
<input
type="text"
id="refreshToken"
value={refreshToken}
onChange={(e) => setRefreshToken(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Logout
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,60 @@
'use client';
import React, { useState } from 'react';
export default function TestRefreshToken() {
const [refreshToken, setRefreshToken] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/refreshToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
refreshToken
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Refresh Token</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="refreshToken" className="block">Token:</label>
<input
type="text"
id="refreshToken"
value={refreshToken}
onChange={(e) => setRefreshToken(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Refresh Token
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

View File

@ -0,0 +1,73 @@
'use client';
import React, { useState } from 'react';
export default function TestUpdatePFP() {
const [userId, setUserId] = useState('');
const [pfpURL, setPfpURL] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/updatePFP', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
userId,
pfpURL
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Update PFP</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="userId" className="block">User ID:</label>
<input
type="text"
id="userId"
value={userId}
onChange={(e) => setUserId(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="pfpURL" className="block">PFP URL:</label>
<input
type="text"
id="pfpURL"
value={pfpURL}
onChange={(e) => setPfpURL(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Update PFP
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
}

View File

@ -0,0 +1,73 @@
'use client'
import React, { useState } from 'react';
export default function TestUpdatePushToken() {
const [userId, setUserId] = useState('');
const [pushToken, setPushToken] = useState('');
const [result, setResult] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setResult(null);
try {
const response = await fetch('/api/users/updatePushToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
},
body: JSON.stringify({
userId,
pushToken
})
});
const data = await response.json() as {message: string}
setResult(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
setResult('An error occurred');
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center
bg-gradient-to-b from-pink-500 to-orange-400 text-white cursor-pointer">
<div className="p-4">
<h1 className="text-2xl mb-4">Test Update Push Token</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="userId" className="block">User ID:</label>
<input
type="text"
id="userId"
value={userId}
onChange={(e) => setUserId(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<div>
<label htmlFor="pushToken" className="block">Push Token:</label>
<input
type="text"
id="pushToken"
value={pushToken}
onChange={(e) => setPushToken(e.target.value)}
className="border p-2 w-full bg-black"
required
/>
</div>
<button type='submit' className='bg-blue-500 p-2 rounded'>
Update Push Token
</button>
</form>
{result && (
<div className='mt-4 p-2 rounded bg-black'>
<pre>{result}</pre>
</div>
)}
</div>
</main>
);
};

33
src/env.js Normal file → Executable file
View File

@ -2,45 +2,32 @@ import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod"; import { z } from "zod";
export const env = createEnv({ export const env = createEnv({
/**
* Specify your server-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars.
*/
server: { server: {
DATABASE_URL: z.string().url(), DATABASE_URL: z.string().url(),
API_KEY: z.string(), API_KEY: z.string(),
NODE_ENV: z NODE_ENV: z
.enum(["development", "test", "production"]) .enum(["development", "test", "production"])
.default("development"), .default("development"),
JWT_SECRET: z.string(),
JWT_REFRESH_SECRET: z.string(),
SKIP_ENV_VALIDATION: z.boolean().optional(),
}, },
/**
* Specify your client-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars. To expose them to the client, prefix them with
* `NEXT_PUBLIC_`.
*/
client: { client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(), NEXT_PUBLIC_API_KEY: z.string(),
}, },
/**
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
* middlewares) or client-side so we need to destruct manually.
*/
runtimeEnv: { runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL, DATABASE_URL: process.env.DATABASE_URL,
API_KEY: process.env.API_KEY, API_KEY: process.env.API_KEY,
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, JWT_SECRET: process.env.JWT_SECRET,
JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET,
NEXT_PUBLIC_API_KEY: process.env.NEXT_PUBLIC_API_KEY,
SKIP_ENV_VALIDATION: process.env.SKIP_ENV_VALIDATION,
}, },
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
* useful for Docker builds.
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION, skipValidation: !!process.env.SKIP_ENV_VALIDATION,
/**
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
* `SOME_VAR=''` will throw an error.
*/
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
}); });

0
src/server/db/index.ts Normal file → Executable file
View File

0
src/server/db/schema.ts Normal file → Executable file
View File

25
src/server/functions.ts Normal file → Executable file
View File

@ -214,6 +214,8 @@ export const changePassword = async (
try { try {
// Ensure all arguments are provided // Ensure all arguments are provided
if (!oldPasswordHash || !newPasswordHash) throw new Error("Password fields are required"); if (!oldPasswordHash || !newPasswordHash) throw new Error("Password fields are required");
if (oldPasswordHash === newPasswordHash)
throw new Error("New password cannot be the same as the old password");
const user = await ensureUserExists(userId); const user = await ensureUserExists(userId);
@ -286,6 +288,10 @@ export const login = async (username: string, passwordHash: string) => {
.set({ lastLogin: new Date() }) .set({ lastLogin: new Date() })
.where(eq(schema.users.id, user.id)); .where(eq(schema.users.id, user.id));
await db.update(schema.users)
.set({ refreshToken: refreshToken })
.where(eq(schema.users.id, user.id));
return { user, accessToken, refreshToken }; return { user, accessToken, refreshToken };
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@ -322,11 +328,21 @@ export const refreshToken = async (refreshToken: string) => {
} }
}; };
export const logout = async (userId: number) => { export const logout = async (refreshToken: string) => {
try { try {
const user = await db.select()
.from(schema.users)
.where(eq(schema.users.refreshToken, refreshToken))
.limit(1);
if (user.length === 0 || !user[0]?.id) {
throw new Error('No user found with this refresh token');
}
await db.update(schema.users) await db.update(schema.users)
.set({ lastLogin: null }) .set({ refreshToken: null })
.where(eq(schema.users.id, userId)); .where(eq(schema.users.id, user[0].id));
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@ -417,6 +433,9 @@ export const updateRelationshipRequest = async (relationshipId: number, status:
export const deleteRelationship = async (relationshipId: number) => { export const deleteRelationship = async (relationshipId: number) => {
try { try {
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
await trx.delete(schema.countdowns)
.where(eq(schema.countdowns.relationshipId, relationshipId));
await trx.delete(schema.userRelationships) await trx.delete(schema.userRelationships)
.where(eq(schema.userRelationships.relationshipId, relationshipId)); .where(eq(schema.userRelationships.relationshipId, relationshipId));

0
src/styles/globals.css Normal file → Executable file
View File

0
tailwind.config.ts Normal file → Executable file
View File

0
tsconfig.json Normal file → Executable file
View File