Skip to content

Commit

Permalink
modif: slot_id has been added to lessons and interviews tables.
Browse files Browse the repository at this point in the history
  • Loading branch information
mmoehabb committed Jan 7, 2025
1 parent 6f044f9 commit 8d62046
Show file tree
Hide file tree
Showing 25 changed files with 303 additions and 73 deletions.
3 changes: 3 additions & 0 deletions packages/atlas/src/atlas.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { User } from "@/user";
import { Auth } from "@/auth";
import { AvailabilitySlot } from "@/availabilitySlot";
import { Backend } from "@litespace/types";
import { Plan } from "@/plan";
import { Coupon } from "@/coupon";
Expand All @@ -22,6 +23,7 @@ import { Session } from "@/session";
export class Atlas {
public readonly user: User;
public readonly auth: Auth;
public readonly availabilitySlot: AvailabilitySlot;
public readonly plan: Plan;
public readonly coupon: Coupon;
public readonly invite: Invite;
Expand All @@ -42,6 +44,7 @@ export class Atlas {
constructor(backend: Backend, token: AuthToken | null) {
this.user = new User(backend, token);
this.auth = new Auth(backend, token);
this.availabilitySlot = new AvailabilitySlot(backend, token);
this.plan = new Plan(backend, token);
this.coupon = new Coupon(backend, token);
this.invite = new Invite(backend, token);
Expand Down
3 changes: 3 additions & 0 deletions packages/headless/src/lessons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,21 @@ export function useCreateLesson({
async ({
tutorId,
ruleId,
slotId,
start,
duration,
}: {
tutorId: number;
ruleId: number;
slotId: number;
start: string;
duration: ILesson.Duration;
}) => {
return await atlas.lesson.create({
tutorId,
duration,
ruleId,
slotId,
start,
});
},
Expand Down
22 changes: 20 additions & 2 deletions packages/models/fixtures/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ import {
IRating,
IMessage,
ISession,
IAvailabilitySlot,
} from "@litespace/types";
import { faker } from "@faker-js/faker/locale/ar";
import { entries, range, sample } from "lodash";
import { Knex } from "knex";
import dayjs from "@/lib/dayjs";
import { Time } from "@litespace/sol/time";
import { availabilitySlots } from "@/availabilitySlot";
import { availabilitySlots } from "@/availabilitySlots";
import { randomUUID } from "crypto";

export async function flush() {
await knex.transaction(async (tx) => {
await availabilitySlots.builder(tx).del();
await topics.builder(tx).userTopics.del();
await topics.builder(tx).topics.del();
await messages.builder(tx).del();
Expand All @@ -44,6 +44,7 @@ export async function flush() {
await rules.builder(tx).del();
await ratings.builder(tx).del();
await tutors.builder(tx).del();
await availabilitySlots.builder(tx).del();
await users.builder(tx).del();
});
}
Expand Down Expand Up @@ -102,6 +103,17 @@ export async function rule(payload?: Partial<IRule.CreatePayload>) {
});
}

export async function slot(payload?: Partial<IAvailabilitySlot.CreatePayload>) {
const start = dayjs.utc(payload?.start || faker.date.future());
const end = start.add(faker.number.int(8), "hours");

return (await availabilitySlots.create([{
userId: await or.tutorId(payload?.userId),
start: start.toISOString(),
end: end.toISOString(),
}]))[0];
}

const or = {
async tutorId(id?: number): Promise<number> {
if (!id) return await tutor().then((tutor) => tutor.id);
Expand All @@ -123,6 +135,10 @@ const or = {
if (!id) return await rule().then((rule) => rule.id);
return id;
},
async slotId(id?: number): Promise<number> {
if (!id) return await slot().then((slot) => slot.id);
return id;
},
start(start?: string): string {
if (!start) return faker.date.soon().toISOString();
return start;
Expand Down Expand Up @@ -154,6 +170,7 @@ export async function lesson(
duration: payload?.duration || sample([15, 30]),
price: payload?.price || faker.number.int(500),
rule: await or.ruleId(payload?.rule),
slot: await or.slotId(payload?.slot),
student,
tutor,
tx,
Expand All @@ -172,6 +189,7 @@ export async function interview(payload: Partial<IInterview.CreatePayload>) {
interviewee: await or.tutorId(payload.interviewee),
session: await or.sessionId("interview"),
rule: await or.ruleId(payload.rule),
slot: await or.slotId(payload.slot),
start: or.start(payload.start),
});
}
Expand Down
6 changes: 4 additions & 2 deletions packages/models/migrations/1716146586880_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ exports.up = (pgm) => {
duration: { type: "SMALLINT", notNull: true },
price: { type: "INT", notNull: true },
rule_id: { type: "SERIAL", references: "rules(id)", notNull: true },
slot_id: { type: "SERIAL", references: "availability_slots(id)", notNull: true },
session_id: { type: "VARCHAR(50)", notNull: true, primaryKey: true },
canceled_by: { type: "INT", references: "users(id)", default: null },
canceled_at: { type: "TIMESTAMP", default: null },
Expand All @@ -125,6 +126,7 @@ exports.up = (pgm) => {
interviewee_feedback: { type: "TEXT", default: null },
session_id: { type: "TEXT", notNull: true, primaryKey: true },
rule_id: { type: "SERIAL", references: "rules(id)", notNull: true },
slot_id: { type: "SERIAL", references: "availability_slots(id)", notNull: true },
note: { type: "TEXT", default: null },
level: { type: "INT", default: null },
status: { type: "interview_status", default: "pending" },
Expand Down Expand Up @@ -349,7 +351,6 @@ exports.up = (pgm) => {
*/
exports.down = (pgm) => {
// indexes
pgm.dropIndex("availability_slots", "id", { ifExists: true });
pgm.dropIndex("invoices", "id", { ifExists: true });
pgm.dropIndex("messages", "id", { ifExists: true });
pgm.dropIndex("rooms", "id", { ifExists: true });
Expand All @@ -364,10 +365,10 @@ exports.down = (pgm) => {
pgm.dropIndex("lessons", "id", { ifExists: true });
pgm.dropIndex("rules", "id", { ifExists: true });
pgm.dropIndex("tutors", "id", { ifExists: true });
pgm.dropIndex("availability_slots", "id", { ifExists: true });
pgm.dropIndex("users", "id", { ifExists: true });

// tables
pgm.dropTable("availability_slots", { ifExists: true });
pgm.dropTable("user_topics", { ifExists: true });
pgm.dropTable("topics", { ifExists: true });
pgm.dropTable("withdraw_methods", { ifExists: true });
Expand All @@ -388,6 +389,7 @@ exports.down = (pgm) => {
pgm.dropTable("lessons", { ifExists: true });
pgm.dropTable("rules", { ifExists: true });
pgm.dropTable("tutors", { ifExists: true });
pgm.dropTable("availability_slots", { ifExists: true });
pgm.dropTable("users", { ifExists: true, cascade: true });

// types
Expand Down
29 changes: 25 additions & 4 deletions packages/models/scripts/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
invoices,
hashPassword,
topics,
availabilitySlots,
} from "@litespace/models";
import { IInterview, ILesson, IUser, IWithdrawMethod } from "@litespace/types";
import dayjs from "dayjs";
Expand All @@ -25,7 +26,7 @@ import { calculateLessonPrice } from "@litespace/sol/lesson";
import { logger } from "@litespace/sol/log";
import { price } from "@litespace/sol/value";
import { IDate, IRule } from "@litespace/types";
import { first, random, range, sample } from "lodash";
import { first, range, sample } from "lodash";
import { Knex } from "knex";
import utc from "dayjs/plugin/utc";
import { faker } from "@faker-js/faker/locale/ar";
Expand Down Expand Up @@ -116,7 +117,8 @@ async function main(): Promise<void> {
)
);

const studio = await users.create({
// seeding studio user
await users.create({
role: IUser.Role.Studio,
email: "[email protected]",
name: faker.person.fullName(),
Expand Down Expand Up @@ -258,7 +260,7 @@ async function main(): Promise<void> {
// seeding topics data
stdout.info(`Inserting at most 50 random topics in the database.`);
await Promise.all(
range(100).map(async (idx) => {
range(100).map(async () => {
try {
const ar = faker.lorem.words(2);
const en = fakerEn.lorem.words(2);
Expand All @@ -285,6 +287,15 @@ async function main(): Promise<void> {
monthday: sample(range(1, 31)),
});

// seeding slots
await availabilitySlots.create(addedTutors.map(tutor =>
({
userId: tutor.id,
start: dayjs.utc().startOf("day").toISOString(),
end: dayjs.utc().startOf("day").add(30, "days").toISOString(),
})
));

const times = range(0, 24).map((hour) =>
[hour.toString().padStart(2, "0"), "00"].join(":")
);
Expand Down Expand Up @@ -331,6 +342,7 @@ async function main(): Promise<void> {
IWithdrawMethod.Type.Instapay,
];

/* Draft
function randomWithdrawMethod() {
const method = sample(methods)!;
return {
Expand All @@ -348,6 +360,7 @@ async function main(): Promise<void> {
: Math.random().toString(36).slice(2),
};
}
*/

async function createRandomLesson({
tutorId,
Expand All @@ -367,6 +380,7 @@ async function main(): Promise<void> {
start,
duration,
rule: 1,
slot: 1,
tx,
});

Expand All @@ -387,7 +401,7 @@ async function main(): Promise<void> {
await rooms.create([tutor.id, student.id]);

for (const _ of range(1, 100)) {
const lesson = await createRandomLesson({
await createRandomLesson({
tutorId: tutor.id,
start: start.toISOString(),
});
Expand All @@ -400,6 +414,12 @@ async function main(): Promise<void> {
);
}

const slot = (await availabilitySlots.create([{
userId: tutorManager.id,
start: dayjs.utc().startOf("day").toISOString(),
end: dayjs.utc().startOf("day").add(1, "days").toISOString(),
}]))[0];

for (const tutor of addedTutors) {
await knex.transaction(async (tx: Knex.Transaction) => {
const interview = await interviews.create({
Expand All @@ -408,6 +428,7 @@ async function main(): Promise<void> {
interviewer: tutorManager.id,
start: randomStart(),
rule: rule.id,
slot: slot.id,
tx,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { IAvailabilitySlot } from "@litespace/types";
import { IAvailabilitySlot, IFilter, Paginated } from "@litespace/types";
import dayjs from "@/lib/dayjs";
import { Knex } from "knex";
import { first, isEmpty } from "lodash";
import { knex, column, WithOptionalTx } from "@/query";
import {
knex,
column,
countRows,
WithOptionalTx,
withSkippablePagination,
} from "@/query";

type SearchFilter = {
/**
* Slot ids to be included in the search query.
*/
slots?: number[],
/**
* User ids to be included in the search query.
*/
Expand All @@ -19,6 +29,7 @@ type SearchFilter = {
* All slots before (or the same as) this date will be included.
*/
before?: string;
pagination?: IFilter.SkippablePagination,
};

export class AvailabilitySlots {
Expand Down Expand Up @@ -82,23 +93,43 @@ export class AvailabilitySlots {

async find({
tx,
slots,
users,
after,
before,
}: WithOptionalTx<SearchFilter>): Promise<IAvailabilitySlot.Self[]> {
pagination,
}: WithOptionalTx<SearchFilter>): Promise<Paginated<IAvailabilitySlot.Self>> {
const baseBuilder = this.applySearchFilter(this.builder(tx), {
slots,
users,
after,
before,
});
const rows = await baseBuilder.clone().select();
return rows.map((row) => this.from(row));
const total = await countRows(baseBuilder.clone());
const rows = await withSkippablePagination(baseBuilder.clone(), pagination);
return {
list: rows.map((row) => this.from(row)),
total,
};
}

async findById(id: number, tx?: Knex.Transaction): Promise<IAvailabilitySlot.Self | null> {
const { list } = await this.find({ slots: [id], tx });
return first(list) || null;
}

applySearchFilter<R extends object, T>(
builder: Knex.QueryBuilder<R, T>,
{ users, after, before }: SearchFilter
{
slots,
users,
after,
before
}: SearchFilter
): Knex.QueryBuilder<R, T> {
if (slots && !isEmpty(slots))
builder.whereIn(this.column("id"), slots);

if (users && !isEmpty(users))
builder.whereIn(this.column("user_id"), users);

Expand Down
1 change: 1 addition & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { availabilitySlots } from "@/availabilitySlots";
export { coupons } from "@/coupons";
export { interviews } from "@/interviews";
export { invites } from "@/invites";
Expand Down
Loading

0 comments on commit 8d62046

Please sign in to comment.