feat: add db models, setup prisma + utils
This commit is contained in:
parent
d799f3270a
commit
a10816e43c
|
|
@ -1,13 +1,13 @@
|
||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaClient } from '@prisma/client';
|
||||||
import invariant from 'tiny-invariant'
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
invariant(process.env.DATABASE_URL, 'DATABASE_URL must be set')
|
invariant(process.env.DATABASE_URL, 'DATABASE_URL must be set');
|
||||||
const DATABASE_URL = process.env.DATABASE_URL
|
const DATABASE_URL = process.env.DATABASE_URL;
|
||||||
|
|
||||||
let prisma: PrismaClient
|
let prisma: PrismaClient;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var __db__: PrismaClient
|
var __db__: PrismaClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is needed because in development we don't want to restart
|
// this is needed because in development we don't want to restart
|
||||||
|
|
@ -15,20 +15,20 @@ declare global {
|
||||||
// create a new connection to the DB with every change either.
|
// create a new connection to the DB with every change either.
|
||||||
// in production we'll have a single connection to the DB.
|
// in production we'll have a single connection to the DB.
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
prisma = getClient()
|
prisma = getClient();
|
||||||
} else {
|
} else {
|
||||||
if (!global.__db__) {
|
if (!global.__db__) {
|
||||||
global.__db__ = getClient()
|
global.__db__ = getClient();
|
||||||
}
|
}
|
||||||
prisma = global.__db__
|
prisma = global.__db__;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClient() {
|
function getClient() {
|
||||||
invariant(typeof DATABASE_URL === 'string', 'DATABASE_URL env var not set')
|
invariant(typeof DATABASE_URL === 'string', 'DATABASE_URL env var not set');
|
||||||
|
|
||||||
const databaseUrl = new URL(DATABASE_URL)
|
const databaseUrl = new URL(DATABASE_URL);
|
||||||
|
|
||||||
console.log(`🔌 setting up prisma client to ${databaseUrl.host}`)
|
console.log(`🔌 setting up prisma client to ${databaseUrl.host}`);
|
||||||
// NOTE: during development if you change anything in this function, remember
|
// NOTE: during development if you change anything in this function, remember
|
||||||
// that this only runs once per server restart and won't automatically be
|
// that this only runs once per server restart and won't automatically be
|
||||||
// re-run per request like everything else is. So if you need to change
|
// re-run per request like everything else is. So if you need to change
|
||||||
|
|
@ -39,11 +39,11 @@ function getClient() {
|
||||||
url: databaseUrl.toString()
|
url: databaseUrl.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// connect eagerly
|
// connect eagerly
|
||||||
client.$connect()
|
client.$connect();
|
||||||
|
|
||||||
return client
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { prisma }
|
export { prisma };
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,55 @@
|
||||||
import type { User, Project } from '@prisma/client'
|
import type { User, Project } from '@prisma/client';
|
||||||
|
|
||||||
import { prisma } from '~/db.server'
|
import { prisma } from '~/db.server';
|
||||||
|
|
||||||
export type { Project } from '@prisma/client'
|
export type { Project } from '@prisma/client';
|
||||||
|
|
||||||
export function getProject({
|
export function getProject({
|
||||||
id,
|
id,
|
||||||
userId
|
userId
|
||||||
}: Pick<Project, 'id'> & {
|
}: Pick<Project, 'id'> & {
|
||||||
userId: User['id']
|
userId: User['id'];
|
||||||
}) {
|
}) {
|
||||||
return prisma.project.findFirst({
|
return prisma.project.findFirst({
|
||||||
where: { id, userId }
|
where: { id, userId }
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProjects({
|
export async function getProjects({
|
||||||
userId,
|
userId,
|
||||||
page,
|
page,
|
||||||
offset,
|
size,
|
||||||
orderBy
|
orderBy,
|
||||||
|
order
|
||||||
}: {
|
}: {
|
||||||
userId: User['id']
|
userId: User['id'];
|
||||||
page?: number
|
page?: number;
|
||||||
offset?: number
|
size?: number;
|
||||||
orderBy?: { [key in keyof Project]?: 'asc' | 'desc' }
|
orderBy?: string;
|
||||||
|
order?: 'asc' | 'desc';
|
||||||
}) {
|
}) {
|
||||||
return prisma.project.findMany({
|
const totalProjects = await prisma.project.count({
|
||||||
|
where: { userId }
|
||||||
|
});
|
||||||
|
const paginatedProjects = await prisma.project.findMany({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
orderBy: orderBy || { updatedAt: 'desc' },
|
orderBy: {
|
||||||
skip: page && offset ? page * offset : 0,
|
[orderBy || 'createdAt']: order || 'desc'
|
||||||
take: offset
|
},
|
||||||
})
|
skip: page && size ? (page - 1) * size : 0,
|
||||||
|
take: size
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextPage =
|
||||||
|
page && size && totalProjects > page * size ? page + 1 : null;
|
||||||
|
const previousPage = page && page > 2 ? page - 1 : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: totalProjects,
|
||||||
|
projects: paginatedProjects,
|
||||||
|
nextPage,
|
||||||
|
previousPage
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createProject({
|
export function createProject({
|
||||||
|
|
@ -40,7 +58,7 @@ export function createProject({
|
||||||
color,
|
color,
|
||||||
userId
|
userId
|
||||||
}: Pick<Project, 'name' | 'description' | 'color'> & {
|
}: Pick<Project, 'name' | 'description' | 'color'> & {
|
||||||
userId: User['id']
|
userId: User['id'];
|
||||||
}) {
|
}) {
|
||||||
return prisma.project.create({
|
return prisma.project.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -49,7 +67,7 @@ export function createProject({
|
||||||
color,
|
color,
|
||||||
userId
|
userId
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateProject({
|
export function updateProject({
|
||||||
|
|
@ -58,7 +76,7 @@ export function updateProject({
|
||||||
description,
|
description,
|
||||||
color
|
color
|
||||||
}: Partial<Pick<Project, 'name' | 'description' | 'color'>> & {
|
}: Partial<Pick<Project, 'name' | 'description' | 'color'>> & {
|
||||||
projectId: Project['id']
|
projectId: Project['id'];
|
||||||
}) {
|
}) {
|
||||||
return prisma.project.update({
|
return prisma.project.update({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -69,11 +87,14 @@ export function updateProject({
|
||||||
where: {
|
where: {
|
||||||
id: projectId
|
id: projectId
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteProject({ id, userId }: Pick<Project, 'id'> & { userId: User['id'] }) {
|
export function deleteProject({
|
||||||
|
id,
|
||||||
|
userId
|
||||||
|
}: Pick<Project, 'id'> & { userId: User['id'] }) {
|
||||||
return prisma.project.deleteMany({
|
return prisma.project.deleteMany({
|
||||||
where: { id, userId }
|
where: { id, userId }
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,104 @@
|
||||||
import type { User, TimeEntry, Project } from '@prisma/client'
|
import type { User, TimeEntry, Project } from '@prisma/client';
|
||||||
|
|
||||||
import { prisma } from '~/db.server'
|
import { prisma } from '~/db.server';
|
||||||
|
|
||||||
export type { TimeEntry } from '@prisma/client'
|
export type { TimeEntry } from '@prisma/client';
|
||||||
|
|
||||||
export function getTimeEntry({
|
export function getTimeEntry({
|
||||||
id,
|
id,
|
||||||
userId
|
userId
|
||||||
}: Pick<TimeEntry, 'id'> & {
|
}: Pick<TimeEntry, 'id'> & {
|
||||||
userId: User['id']
|
userId: User['id'];
|
||||||
}) {
|
}) {
|
||||||
return prisma.timeEntry.findFirst({
|
return prisma.timeEntry.findFirst({
|
||||||
where: { id, userId }
|
where: { id, userId },
|
||||||
})
|
include: {
|
||||||
|
project: true
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTimeEntries({
|
export async function getTimeEntries({
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
page,
|
page,
|
||||||
offset,
|
size,
|
||||||
orderBy
|
orderBy,
|
||||||
|
order
|
||||||
}: {
|
}: {
|
||||||
userId: User['id']
|
userId: User['id'];
|
||||||
projectId?: Project['id']
|
projectId?: Project['id'];
|
||||||
page?: number
|
page?: number;
|
||||||
offset?: number
|
size?: number;
|
||||||
orderBy?: { [key in keyof TimeEntry]?: 'asc' | 'desc' }
|
orderBy?: string;
|
||||||
|
order?: 'asc' | 'desc';
|
||||||
}) {
|
}) {
|
||||||
return prisma.timeEntry.findMany({
|
const totalTimeEntries = await prisma.timeEntry.count({
|
||||||
|
where: { userId, projectId }
|
||||||
|
});
|
||||||
|
const paginatedEntries = await prisma.timeEntry.findMany({
|
||||||
where: { userId, projectId },
|
where: { userId, projectId },
|
||||||
orderBy: orderBy || { updatedAt: 'desc' },
|
include: {
|
||||||
skip: page && offset ? page * offset : 0,
|
project: true
|
||||||
take: offset
|
},
|
||||||
})
|
orderBy: {
|
||||||
|
[orderBy || 'startTime']: order || 'desc'
|
||||||
|
},
|
||||||
|
skip: page && size ? (page - 1) * size : 0,
|
||||||
|
take: size
|
||||||
|
});
|
||||||
|
|
||||||
|
const monthAgo = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
|
||||||
|
const weekAgo = new Date(
|
||||||
|
new Date().getFullYear(),
|
||||||
|
new Date().getMonth(),
|
||||||
|
new Date().getDate() - 7
|
||||||
|
);
|
||||||
|
const monthEntries = await prisma.timeEntry.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
projectId,
|
||||||
|
startTime: { gte: monthAgo },
|
||||||
|
endTime: { lte: new Date() }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const monthTotalHours =
|
||||||
|
monthEntries.reduce(
|
||||||
|
(acc, entry) =>
|
||||||
|
acc +
|
||||||
|
((entry.endTime || new Date(Date.now())).getTime() -
|
||||||
|
entry.startTime.getTime()),
|
||||||
|
0
|
||||||
|
) /
|
||||||
|
1000 /
|
||||||
|
60 /
|
||||||
|
60;
|
||||||
|
const weekTotalHours =
|
||||||
|
monthEntries
|
||||||
|
.filter((e) => e.startTime >= weekAgo)
|
||||||
|
.reduce(
|
||||||
|
(acc, entry) =>
|
||||||
|
acc +
|
||||||
|
((entry.endTime || new Date(Date.now())).getTime() -
|
||||||
|
entry.startTime.getTime()),
|
||||||
|
0
|
||||||
|
) /
|
||||||
|
1000 /
|
||||||
|
60 /
|
||||||
|
60;
|
||||||
|
|
||||||
|
const nextPage =
|
||||||
|
page && size && totalTimeEntries > page * size ? page + 1 : null;
|
||||||
|
const previousPage = page && page > 2 ? page - 1 : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: totalTimeEntries,
|
||||||
|
monthTotalHours,
|
||||||
|
weekTotalHours,
|
||||||
|
timeEntries: paginatedEntries,
|
||||||
|
nextPage,
|
||||||
|
previousPage
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTimeEntry({
|
export function createTimeEntry({
|
||||||
|
|
@ -43,8 +108,8 @@ export function createTimeEntry({
|
||||||
userId,
|
userId,
|
||||||
projectId
|
projectId
|
||||||
}: Pick<TimeEntry, 'description' | 'startTime' | 'endTime'> & {
|
}: Pick<TimeEntry, 'description' | 'startTime' | 'endTime'> & {
|
||||||
userId: User['id']
|
userId: User['id'];
|
||||||
projectId: Project['id']
|
projectId: Project['id'];
|
||||||
}) {
|
}) {
|
||||||
return prisma.timeEntry.create({
|
return prisma.timeEntry.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -54,7 +119,14 @@ export function createTimeEntry({
|
||||||
projectId,
|
projectId,
|
||||||
userId
|
userId
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopAllTimeEntries(userId: User['id']) {
|
||||||
|
return prisma.timeEntry.updateMany({
|
||||||
|
where: { userId, endTime: null },
|
||||||
|
data: { endTime: new Date() }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTimeEntry({
|
export function updateTimeEntry({
|
||||||
|
|
@ -63,8 +135,10 @@ export function updateTimeEntry({
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
projectId
|
projectId
|
||||||
}: Partial<Pick<TimeEntry, 'description' | 'startTime' | 'endTime' | 'projectId'>> & {
|
}: Partial<
|
||||||
timeEntryId: TimeEntry['id']
|
Pick<TimeEntry, 'description' | 'startTime' | 'endTime' | 'projectId'>
|
||||||
|
> & {
|
||||||
|
timeEntryId: TimeEntry['id'];
|
||||||
}) {
|
}) {
|
||||||
return prisma.timeEntry.update({
|
return prisma.timeEntry.update({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -76,11 +150,14 @@ export function updateTimeEntry({
|
||||||
where: {
|
where: {
|
||||||
id: timeEntryId
|
id: timeEntryId
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteNote({ id, userId }: Pick<TimeEntry, 'id'> & { userId: User['id'] }) {
|
export function deleteTimeEntry({
|
||||||
|
id,
|
||||||
|
userId
|
||||||
|
}: Pick<TimeEntry, 'id'> & { userId: User['id'] }) {
|
||||||
return prisma.timeEntry.deleteMany({
|
return prisma.timeEntry.deleteMany({
|
||||||
where: { id, userId }
|
where: { id, userId }
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import type { Password, User } from "@prisma/client";
|
import type { Password, User } from '@prisma/client';
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
import { prisma } from "~/db.server";
|
import { prisma } from '~/db.server';
|
||||||
|
|
||||||
export type { User } from "@prisma/client";
|
export type { User } from '@prisma/client';
|
||||||
|
|
||||||
export async function getUserById(id: User["id"]) {
|
export async function getUserById(id: User['id']) {
|
||||||
return prisma.user.findUnique({ where: { id } });
|
return prisma.user.findUnique({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserByEmail(email: User["email"]) {
|
export async function getUserByEmail(email: User['email']) {
|
||||||
return prisma.user.findUnique({ where: { email } });
|
return prisma.user.findUnique({ where: { email } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(email: User["email"], password: string) {
|
export async function createUser(email: User['email'], password: string) {
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
return prisma.user.create({
|
return prisma.user.create({
|
||||||
|
|
@ -21,27 +21,28 @@ export async function createUser(email: User["email"], password: string) {
|
||||||
email,
|
email,
|
||||||
password: {
|
password: {
|
||||||
create: {
|
create: {
|
||||||
hash: hashedPassword,
|
hash: hashedPassword
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteUserByEmail(email: User["email"]) {
|
export async function deleteUserByEmail(email: User['email']) {
|
||||||
return prisma.user.delete({ where: { email } });
|
return prisma.user.delete({ where: { email } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyLogin(
|
export async function verifyLogin(
|
||||||
email: User["email"],
|
email: User['email'],
|
||||||
password: Password["hash"]
|
password: Password['hash']
|
||||||
) {
|
) {
|
||||||
const userWithPassword = await prisma.user.findUnique({
|
const userWithPassword = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
include: {
|
include: {
|
||||||
password: true,
|
password: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
console.log(userWithPassword);
|
||||||
|
|
||||||
if (!userWithPassword || !userWithPassword.password) {
|
if (!userWithPassword || !userWithPassword.password) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -51,6 +52,7 @@ export async function verifyLogin(
|
||||||
password,
|
password,
|
||||||
userWithPassword.password.hash
|
userWithPassword.password.hash
|
||||||
);
|
);
|
||||||
|
console.log(isValid, password, userWithPassword.password.hash);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,67 @@
|
||||||
import { createCookieSessionStorage, redirect } from '@remix-run/node'
|
import { createCookieSessionStorage, redirect } from '@remix-run/node';
|
||||||
import invariant from 'tiny-invariant'
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
import type { User } from '~/models/user.server'
|
import type { User } from '~/models/user.server';
|
||||||
import { getUserById } from '~/models/user.server'
|
import { getUserById } from '~/models/user.server';
|
||||||
|
|
||||||
invariant(process.env.SESSION_SECRET, 'SESSION_SECRET must be set')
|
invariant(process.env.SESSION_SECRET, 'SESSION_SECRET must be set');
|
||||||
const SESSION_SECRET = process.env.SESSION_SECRET
|
const SESSION_SECRET = process.env.SESSION_SECRET;
|
||||||
|
|
||||||
export const sessionStorage = createCookieSessionStorage({
|
export const sessionStorage = createCookieSessionStorage({
|
||||||
cookie: {
|
cookie: {
|
||||||
name: '__session',
|
name: 'wta__session',
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
secrets: [SESSION_SECRET],
|
secrets: [SESSION_SECRET],
|
||||||
secure: process.env.NODE_ENV === 'production'
|
secure: process.env.NODE_ENV === 'production'
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const USER_SESSION_KEY = 'userId'
|
const USER_SESSION_KEY = 'userId';
|
||||||
|
|
||||||
export async function getSession(request: Request) {
|
export async function getSession(request: Request) {
|
||||||
const cookie = request.headers.get('Cookie')
|
const cookie = request.headers.get('Cookie');
|
||||||
return sessionStorage.getSession(cookie)
|
return sessionStorage.getSession(cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserId(request: Request): Promise<User['id'] | undefined> {
|
export async function getUserId(
|
||||||
const session = await getSession(request)
|
request: Request
|
||||||
const userId = session.get(USER_SESSION_KEY)
|
): Promise<User['id'] | undefined> {
|
||||||
return userId
|
const session = await getSession(request);
|
||||||
|
const userId = session.get(USER_SESSION_KEY);
|
||||||
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUser(request: Request) {
|
export async function getUser(request: Request) {
|
||||||
const userId = await getUserId(request)
|
const userId = await getUserId(request);
|
||||||
if (userId === undefined) return null
|
if (userId === undefined) return null;
|
||||||
|
|
||||||
const user = await getUserById(userId)
|
const user = await getUserById(userId);
|
||||||
if (user) return user
|
if (user) return user;
|
||||||
|
|
||||||
throw await logout(request)
|
throw await logout(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requireUserId(request: Request, redirectTo: string = new URL(request.url).pathname) {
|
export async function requireUserId(
|
||||||
const userId = await getUserId(request)
|
request: Request,
|
||||||
|
redirectTo: string = new URL(request.url).pathname
|
||||||
|
) {
|
||||||
|
const userId = await getUserId(request);
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
const searchParams = new URLSearchParams([['redirectTo', redirectTo]])
|
const searchParams = new URLSearchParams([['redirectTo', redirectTo]]);
|
||||||
throw redirect(`/login?${searchParams}`)
|
throw redirect(`/login?${searchParams}`);
|
||||||
}
|
}
|
||||||
return userId
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requireUser(request: Request) {
|
export async function requireUser(request: Request) {
|
||||||
const userId = await requireUserId(request)
|
const userId = await requireUserId(request);
|
||||||
|
|
||||||
const user = await getUserById(userId)
|
const user = await getUserById(userId);
|
||||||
if (user) return user
|
if (user) return user;
|
||||||
|
|
||||||
throw await logout(request)
|
throw await logout(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUserSession({
|
export async function createUserSession({
|
||||||
|
|
@ -65,13 +70,13 @@ export async function createUserSession({
|
||||||
remember,
|
remember,
|
||||||
redirectTo
|
redirectTo
|
||||||
}: {
|
}: {
|
||||||
request: Request
|
request: Request;
|
||||||
userId: string
|
userId: string;
|
||||||
remember: boolean
|
remember: boolean;
|
||||||
redirectTo: string
|
redirectTo: string;
|
||||||
}) {
|
}) {
|
||||||
const session = await getSession(request)
|
const session = await getSession(request);
|
||||||
session.set(USER_SESSION_KEY, userId)
|
session.set(USER_SESSION_KEY, userId);
|
||||||
return redirect(redirectTo, {
|
return redirect(redirectTo, {
|
||||||
headers: {
|
headers: {
|
||||||
'Set-Cookie': await sessionStorage.commitSession(session, {
|
'Set-Cookie': await sessionStorage.commitSession(session, {
|
||||||
|
|
@ -80,14 +85,14 @@ export async function createUserSession({
|
||||||
: undefined
|
: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout(request: Request) {
|
export async function logout(request: Request) {
|
||||||
const session = await getSession(request)
|
const session = await getSession(request);
|
||||||
return redirect('/', {
|
return redirect('/', {
|
||||||
headers: {
|
headers: {
|
||||||
'Set-Cookie': await sessionStorage.destroySession(session)
|
'Set-Cookie': await sessionStorage.destroySession(session)
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
app/utils.ts
20
app/utils.ts
|
|
@ -1,9 +1,9 @@
|
||||||
import { useMatches } from "@remix-run/react";
|
import { useMatches } from '@remix-run/react';
|
||||||
import { useMemo } from "react";
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import type { User } from "~/models/user.server";
|
import type { User } from '~/models/user.server';
|
||||||
|
|
||||||
const DEFAULT_REDIRECT = "/";
|
const DEFAULT_REDIRECT = '/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be used any time the redirect path is user-provided
|
* This should be used any time the redirect path is user-provided
|
||||||
|
|
@ -16,11 +16,11 @@ export function safeRedirect(
|
||||||
to: FormDataEntryValue | string | null | undefined,
|
to: FormDataEntryValue | string | null | undefined,
|
||||||
defaultRedirect: string = DEFAULT_REDIRECT
|
defaultRedirect: string = DEFAULT_REDIRECT
|
||||||
) {
|
) {
|
||||||
if (!to || typeof to !== "string") {
|
if (!to || typeof to !== 'string') {
|
||||||
return defaultRedirect;
|
return defaultRedirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!to.startsWith("/") || to.startsWith("//")) {
|
if (!to.startsWith('/') || to.startsWith('//')) {
|
||||||
return defaultRedirect;
|
return defaultRedirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,11 +45,11 @@ export function useMatchesData(
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUser(user: any): user is User {
|
function isUser(user: any): user is User {
|
||||||
return user && typeof user === "object" && typeof user.email === "string";
|
return user && typeof user === 'object' && typeof user.email === 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useOptionalUser(): User | undefined {
|
export function useOptionalUser(): User | undefined {
|
||||||
const data = useMatchesData("root");
|
const data = useMatchesData('root');
|
||||||
if (!data || !isUser(data.user)) {
|
if (!data || !isUser(data.user)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -60,12 +60,12 @@ export function useUser(): User {
|
||||||
const maybeUser = useOptionalUser();
|
const maybeUser = useOptionalUser();
|
||||||
if (!maybeUser) {
|
if (!maybeUser) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"No user found in root loader, but user is required by useUser. If user is optional, try useOptionalUser instead."
|
'No user found in root loader, but user is required by useUser. If user is optional, try useOptionalUser instead.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return maybeUser;
|
return maybeUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateEmail(email: unknown): email is string {
|
export function validateEmail(email: unknown): email is string {
|
||||||
return typeof email === "string" && email.length > 3 && email.includes("@");
|
return typeof email === 'string' && email.length > 3 && email.includes('@');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaClient } from '@prisma/client';
|
||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
async function seed() {
|
async function seed() {
|
||||||
const email = 'admin@rawmaterial.it'
|
const email = 'admin@rawmaterial.it';
|
||||||
|
|
||||||
// cleanup the existing database
|
// cleanup the existing database
|
||||||
await prisma.user.delete({ where: { email } }).catch(() => {
|
await prisma.user.delete({ where: { email } }).catch(() => {
|
||||||
// no worries if it doesn't exist yet
|
// no worries if it doesn't exist yet
|
||||||
})
|
});
|
||||||
|
|
||||||
const hashedPassword = await bcrypt.hash('admin', 10)
|
const hashedPassword = await bcrypt.hash('rawmaterial', 10);
|
||||||
|
|
||||||
const user = await prisma.user.create({
|
const user = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -22,16 +22,36 @@ async function seed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const project = await prisma.project.create({
|
const project = await prisma.project.create({
|
||||||
data: {
|
data: {
|
||||||
name: 'RawMaterial',
|
name: 'RawMaterial',
|
||||||
description: 'Raw Material is a web app for managing your projects and tasks.',
|
description:
|
||||||
color: '#333',
|
'Raw Material is a web app for managing your projects and tasks.',
|
||||||
|
color: 'green',
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
const otherProject = await prisma.project.create({
|
||||||
|
data: {
|
||||||
|
name: 'Memori',
|
||||||
|
description: 'Memori is a web app for managing your memories.',
|
||||||
|
color: 'violet',
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new Array(10).fill(0).forEach(async (_, index) => {
|
||||||
|
await prisma.project.create({
|
||||||
|
data: {
|
||||||
|
name: `Project ${index}`,
|
||||||
|
description: `Project ${index} description`,
|
||||||
|
color: 'red',
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await prisma.timeEntry.create({
|
await prisma.timeEntry.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -41,25 +61,42 @@ async function seed() {
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
await prisma.timeEntry.create({
|
await prisma.timeEntry.create({
|
||||||
data: {
|
data: {
|
||||||
description: 'Database setup',
|
description: 'Database setup same day',
|
||||||
startTime: new Date('2021-01-01T13:00:00.000Z'),
|
startTime: new Date('2021-01-01T13:00:00.000Z'),
|
||||||
endTime: new Date('2021-01-01T19:00:00.000Z'),
|
endTime: new Date('2021-01-01T19:00:00.000Z'),
|
||||||
|
projectId: otherProject.id,
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await prisma.timeEntry.create({
|
||||||
|
data: {
|
||||||
|
description: 'Database setup next day',
|
||||||
|
startTime: new Date('2021-01-02T13:00:00.000Z'),
|
||||||
|
endTime: new Date('2021-01-02T19:00:00.000Z'),
|
||||||
|
projectId: otherProject.id,
|
||||||
|
userId: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await prisma.timeEntry.create({
|
||||||
|
data: {
|
||||||
|
description: 'Ongoing activity',
|
||||||
|
startTime: new Date('2021-01-02T13:00:00.000Z'),
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log(`Database has been seeded. 🌱`)
|
console.log(`Database has been seeded. 🌱`);
|
||||||
}
|
}
|
||||||
|
|
||||||
seed()
|
seed()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
process.exit(1)
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
await prisma.$disconnect()
|
await prisma.$disconnect();
|
||||||
})
|
});
|
||||||
|
|
|
||||||
4
remix.env.d.ts
vendored
4
remix.env.d.ts
vendored
|
|
@ -1,2 +1,6 @@
|
||||||
/// <reference types="@remix-run/dev" />
|
/// <reference types="@remix-run/dev" />
|
||||||
/// <reference types="@remix-run/node" />
|
/// <reference types="@remix-run/node" />
|
||||||
|
|
||||||
|
declare type Prettify<T> = {
|
||||||
|
[K in keyof T]: T[K]
|
||||||
|
} & {}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue