switch to prisma

This commit is contained in:
Thomas Ruoff
2022-10-11 11:43:32 +02:00
parent 41342475ba
commit 1ef9b14e95
28 changed files with 764 additions and 780 deletions

View File

@@ -1,91 +0,0 @@
import * as mongoose from 'mongoose'
import { BILL_STATUS, MILAGE_TARIFS } from './enums'
import { getBillTotal } from '../helpers/bill'
export type AdditionalCost = {
name: string
value: number
}
export type Bill = {
milageStart: number
milageEnd: number
milage?: number
tarif: MILAGE_TARIFS
status: BILL_STATUS
additionalCosts: AdditionalCost[]
}
export type BillDocument = Bill &
mongoose.SchemaTimestampsConfig &
mongoose.Document
export type BillModel = mongoose.Model<BillDocument>
const BillSchema = new mongoose.Schema<BillDocument>(
{
milageStart: {
type: Number,
required: true,
validate: {
validator: function (v: number): boolean {
const bill = this as BillDocument
return v <= bill.milageEnd
},
message: (props: { value: Number }) =>
`${props.value} is bigger than milageEnd!`,
},
},
milageEnd: {
type: Number,
required: true,
validate: {
validator: function (v: number): boolean {
const bill = this as BillDocument
return v >= bill.milageStart
},
message: (props: { value: Number }) =>
`${props.value} is smaller than milageStart!`,
},
},
tarif: {
type: String,
enum: Object.values(MILAGE_TARIFS),
default: MILAGE_TARIFS.EXTERN,
required: true,
},
additionalCosts: [
{
name: { type: String, required: true },
value: { type: Number, required: true },
},
],
status: {
type: String,
enum: Object.values(BILL_STATUS),
default: BILL_STATUS.UNINVOICED,
},
},
{
timestamps: true,
toJSON: { virtuals: true, getters: true },
toObject: { virtuals: true, getters: true },
}
)
BillSchema.virtual('milage').get(function (): number {
const bill = this as BillDocument
return bill.milageEnd - bill.milageStart
})
BillSchema.virtual('total').get(function (): number {
const bill = this as BillDocument
return getBillTotal(bill)
})
export default (mongoose.models.Bill ||
mongoose.model<BillDocument, BillModel>('Bill', BillSchema)) as BillModel

View File

@@ -1,168 +0,0 @@
import * as mongoose from 'mongoose'
import { v4 as uuidv4 } from 'uuid'
import {
dateFormatBackend,
getDays,
nowInTz,
dateParseBackend,
} from '../helpers/date'
import { createCalendarEvent, deleteCalendarEvent } from '../lib/googlecalendar'
import { Bill } from './bill'
import { BOOKING_STATUS, VALIDATION_ERRORS } from './enums'
export type Booking = {
uuid: string
name: string
email: string
phone: string
street: string
zip: string
city: string
bill?: Bill
// format YYYY-MM-DD
startDate: string
// format YYYY-MM-DD
endDate: string
status?: BOOKING_STATUS
purpose?: string
org?: string
destination?: string
days?: string[]
calendarEventId: string
}
export type BookingDocument = Booking &
mongoose.Document &
mongoose.SchemaTimestampsConfig
export type BookingModel = mongoose.Model<BookingDocument> & {
findBookedDays(uuidsToIngore?: string[]): Promise<string[]>
}
const BookingSchema = new mongoose.Schema<BookingDocument>(
{
// need a seperate uuid to be able to target a booking anonimously
uuid: {
type: String,
default: uuidv4,
index: true,
},
name: { type: String, required: true },
email: { type: String, required: true, minlength: 5 },
phone: { type: String, required: false },
street: { type: String, required: true },
zip: { type: String, required: true },
city: { type: String, required: true },
bill: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Bill',
required: false,
},
startDate: {
type: String,
required: true,
validator: function (value: string): boolean {
return !!dateParseBackend(value)
},
},
endDate: {
type: String,
required: true,
validator: function (value: string): boolean {
return !!dateParseBackend(value)
},
},
days: {
type: [String],
required: true,
validate: {
validator: async function (days: string[]): Promise<boolean> {
const booking = this as Booking
const uuid = booking.uuid && [booking.uuid]
const bookedDays = await BookingModel.findBookedDays(uuid)
const doubleBookedDays = days.filter((day: string): boolean =>
bookedDays.includes(day)
)
return doubleBookedDays.length === 0
},
message: (props: { value: string[] }): string =>
`At least one day is of ${props.value.join(',')} is already booked`,
type: VALIDATION_ERRORS.AT_LEAST_ONE_DAY_BOOKED,
},
},
status: {
type: String,
enum: Object.values(BOOKING_STATUS),
required: true,
default: BOOKING_STATUS.REQUESTED,
},
purpose: { type: String, required: false },
org: { type: String, required: false },
destination: { type: String, required: false },
calendarEventId: { type: String, required: false },
},
{
timestamps: true,
toJSON: { virtuals: true, getters: true },
toObject: { virtuals: true, getters: true },
}
)
BookingSchema.pre('validate', function (next: () => void): void {
const booking = this as BookingDocument
booking.days = getDays({
startDate: new Date(booking.startDate),
endDate: new Date(booking.endDate),
})
next()
})
BookingSchema.pre('save', async function (next: () => void): Promise<void> {
const booking = this as BookingDocument
if (!booking.calendarEventId) {
// create calendar event before saving to database
await createCalendarEvent(booking)
} else if (
[BOOKING_STATUS.CANCELED, BOOKING_STATUS.REJECTED].includes(booking.status)
) {
// event has been canceled or rejected, delete calendar event again to free up the slot
await deleteCalendarEvent(booking)
}
next()
})
BookingSchema.static(
'findBookedDays',
async function (uuidsToIngore: string[] = []): Promise<string[]> {
const model = this as BookingModel
const now = nowInTz()
const bookedDays = await model
.find(
{
status: { $in: [BOOKING_STATUS.REQUESTED, BOOKING_STATUS.CONFIRMED] },
uuid: { $nin: uuidsToIngore },
// dateFormatBackend uses YYYY-MM-DD, which is startOfDay anyway
endDate: { $gt: dateFormatBackend(now) },
},
'days'
)
.exec()
return bookedDays
.map((b) => b.days)
.flat()
.sort()
}
)
const BookingModel = (mongoose.models.Booking ||
mongoose.model<BookingDocument, BookingModel>(
'Booking',
BookingSchema
)) as BookingModel
export default BookingModel

View File

@@ -1,16 +1,3 @@
export enum BOOKING_STATUS {
REQUESTED = 'requested',
CONFIRMED = 'confirmed',
REJECTED = 'rejected',
CANCELED = 'canceled',
}
export enum BILL_STATUS {
UNINVOICED = 'uninvoiced',
INVOICED = 'invoiced',
PAID = 'paid',
}
export enum MILAGE_TARIFS {
INTERN = 'intern',
EXTERN = 'extern',

View File

@@ -1,129 +1,121 @@
import * as mongoose from 'mongoose'
import BookingModel, { Booking, BookingDocument } from './booking'
import BillModel, { Bill } from './bill'
import { BookingStatus, Booking, PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()
import { getBookedDays as calendarGetBookedDays } from '../lib/googlecalendar'
import { BOOKING_STATUS } from './enums'
import { uniqueFilter } from '../helpers/array'
export const MONGO_URI = process.env.MONGO_URI
mongoose.connect(process.env.MONGO_URI, {
serverSelectionTimeoutMS: 3000,
})
import { dateFormatBackend, getDays, nowInTz } from '../helpers/date'
export async function getBookedDays(
uuidsToIngore?: string[]
): Promise<string[]> {
const [bookedInDatabase, bookedInCalendar] = await Promise.all([
BookingModel.findBookedDays(uuidsToIngore),
) {
const [bookingsInDbRaw, bookingsInCalendar] = await Promise.all([
prisma.booking.findMany({
where: {
uuid: { notIn: uuidsToIngore },
startDate: { gte: dateFormatBackend(nowInTz()) },
status: { notIn: [BookingStatus.REJECTED, BookingStatus.CANCELED] }
},
select: {
startDate: true,
endDate: true,
},
}),
calendarGetBookedDays(),
])
return [...bookedInDatabase, ...bookedInCalendar].filter(uniqueFilter).sort()
const bookingsInDb = bookingsInDbRaw.map(booking => getDays(booking)).flat();
return [...bookingsInDb, ...bookingsInCalendar].filter(uniqueFilter).sort()
}
export async function getBookingByUUID(uuid: string): Promise<BookingDocument> {
return BookingModel.findOne({ uuid })
export function getBookingByUUID(uuid: string) {
// TODO: can we ignore canceled and rejected ones ?
return prisma.booking.findUniqueOrThrow({
where: {
uuid,
},
include: {
bill: true
}
})
}
export async function getBookings({
status = [BOOKING_STATUS.CONFIRMED, BOOKING_STATUS.REQUESTED],
export function getBookings({
status = [
BookingStatus.REQUESTED,
BookingStatus.CONFIRMED,
],
startDateGreaterThan = '2000-01-01T00:00:00Z',
}: { status?: BOOKING_STATUS[]; startDateGreaterThan?: string } = {}): Promise<
BookingDocument[]
> {
return await BookingModel.find({
status: { $in: status },
startDate: { $gte: startDateGreaterThan },
})
.sort({ startDate: -1 })
.exec()
}
: { status?: BookingStatus[]; startDateGreaterThan?: string } = {}) {
return prisma.booking.findMany({
where: {
startDate: { gte: startDateGreaterThan },
status: { notIn: status }
},
});
}
export async function createBooking({
startDate,
endDate,
purpose,
org,
destination,
name,
email,
phone,
street,
zip,
city,
}: Booking): Promise<Booking> {
const booking = new BookingModel({
startDate,
endDate,
purpose,
org,
destination,
name,
email,
phone,
street,
zip,
city,
})
await booking.save()
return booking.toJSON<Booking>()
export function createBooking(data: Booking) {
return prisma.booking.create({
data
});
}
export async function patchBooking(
uuid: string,
data: Prisma.BookingCreateInput
) {
const current = await prisma.booking.update({
where: { uuid },
data
});
const previous = { ...current, ...data }
return { current, previous }
}
export function createBill(
bookingUUID: string,
bookingData: Booking
): Promise<{ current: Booking; previous: Booking }> {
const booking = await getBookingByUUID(bookingUUID)
const oldBooking = booking.toJSON<Booking>()
booking.set(bookingData)
await booking.save()
return { current: booking.toJSON<Booking>(), previous: oldBooking }
data: Prisma.BillCreateInput,
) {
return prisma.bill.create({
data: {
...data,
booking: {
connect: { uuid: bookingUUID }
}
}
});
}
export async function createBill(
export function patchBill(
bookingUUID: string,
billData: Bill
): Promise<Bill> {
const booking = await getBookingByUUID(bookingUUID)
const bill = new BillModel()
bill.set(billData)
await bill.save()
booking.bill = bill._id
await booking.save()
return bill.toJSON<Bill>()
data: Prisma.BillUncheckedUpdateInput,
) {
const { id, ...rest } = data;
return prisma.bill.update({
where: {
},
data: {
...rest,
booking: {
connect: { uuid: bookingUUID }
}
}
});
}
export async function patchBill(
bookingUUID: string,
billData: Bill
): Promise<Bill> {
const booking = await getBookingByUUID(bookingUUID)
const bill =
(booking.bill && (await BillModel.findById(booking.bill))) ||
(await BillModel.create({}))
export async function getMilageMax() {
const { milageEnd } = await prisma.bill.findFirst({
select: { milageEnd: true },
orderBy: [
{
milageEnd: 'desc',
},
],
});
bill.set(billData)
await bill.save()
if (booking.bill !== bill._id) {
booking.bill = bill._id
await booking.save()
}
return bill.toJSON<Bill>()
}
export async function getMilageMax(): Promise<number> {
const billMaxMilageEnd = await BillModel.findOne({})
.sort('-milageEnd')
.select('milageEnd')
.exec()
return billMaxMilageEnd?.milageEnd || 0
return milageEnd || 0;
}