Skip to content

Commit

Permalink
Implementing search for different entities (#31)
Browse files Browse the repository at this point in the history
* Implementing search at `GET /<entitiy>` with query parameters
* No written tests, just manual ones.
  • Loading branch information
G0maa authored Dec 31, 2022
1 parent 3bb3354 commit a8971d8
Show file tree
Hide file tree
Showing 19 changed files with 147 additions and 51 deletions.
15 changes: 7 additions & 8 deletions src/controllers/activeSubject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
getActiveSubjects,
} from '../services/activeSubject.service';
import { setAuthorizedRoles, isAuthenticated } from '../utils/middleware';
import { ZActiveSubject } from '../validator/activeSubject.validator';
import {
ZActiveSubject,
ZActiveSubjectQuery,
} from '../validator/activeSubject.validator';
import { ZRole } from '../validator/general.validator';

const activeSubjectRouter = express.Router();
Expand All @@ -18,17 +21,13 @@ activeSubjectRouter.get(
'/',
setAuthorizedRoles([ZRole.enum.Admin]),
isAuthenticated,
async (_req, res) => {
const query = await getActiveSubjects();
async (req, res) => {
const searchQuery = ZActiveSubjectQuery.parse(req.query);
const query = await getActiveSubjects(searchQuery);
return res.status(200).json(query).end();
}
);

// Lots of variations in this one.
// To-Do
// /api/activeSubjects/:id?query=teacher i.e. all classes & subjects for this teacher id
// /api/activeSubjects/:id?query=studyClass i.e. all subjects & teachers for this class
// /api/activeSubjects/:id?query=subject i.e. all classes & teachers for this subject
activeSubjectRouter.get('/:id', isAuthenticated, async (req, res) => {
const query = await getActiveSubject(req.params.id);
return res.status(200).json(query).end();
Expand Down
10 changes: 6 additions & 4 deletions src/controllers/student.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
} from '../services/student.service';
import { setAuthorizedRoles, isAuthenticated } from '../utils/middleware';
import { ZRole, ZUuid } from '../validator/general.validator';
import { ZStudent } from '../validator/student.validator';
import { ZUser } from '../validator/user.validator';
import { ZStudent, ZStudentQuery } from '../validator/student.validator';
import { ZUser, ZUserQuery } from '../validator/user.validator';

const studentRouter = express.Router();

Expand All @@ -19,8 +19,10 @@ studentRouter.get(
'/',
setAuthorizedRoles([ZRole.enum.Admin]),
isAuthenticated,
async (_req, res) => {
const allStudents = await getStudents();
async (req, res) => {
const searchQueryUser = ZUserQuery.parse(req.query);
const searchQueryStudent = ZStudentQuery.parse(req.query);
const allStudents = await getStudents(searchQueryUser, searchQueryStudent);
return res.status(200).json(allStudents).end();
}
);
Expand Down
10 changes: 7 additions & 3 deletions src/controllers/studyClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
} from '../services/studyClass.service';
import { setAuthorizedRoles, isAuthenticated } from '../utils/middleware';
import { ZRole } from '../validator/general.validator';
import { ZStudyClass } from '../validator/studyClass.validator';
import {
ZStudyClass,
ZStudyClassQuery,
} from '../validator/studyClass.validator';

const studyClassRouter = express.Router();

Expand All @@ -18,8 +21,9 @@ studyClassRouter.get(
'/',
setAuthorizedRoles([ZRole.enum.Admin, ZRole.enum.Student]),
isAuthenticated,
async (_req, res) => {
const query = await getStudyClasses();
async (req, res) => {
const searchQuery = ZStudyClassQuery.parse(req.query);
const query = await getStudyClasses(searchQuery);
return res.status(200).json(query).end();
}
);
Expand Down
9 changes: 6 additions & 3 deletions src/controllers/subject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import {
} from '../services/subject.service';
import { setAuthorizedRoles, isAuthenticated } from '../utils/middleware';
import { ZRole } from '../validator/general.validator';
import { ZSubject } from '../validator/subject.validator';
import { ZSubject, ZSubjectQuery } from '../validator/subject.validator';

const subjectRouter = express.Router();

// #17 very WET CRUD operations.
// Searching not tested,
subjectRouter.get(
'/',
setAuthorizedRoles([ZRole.enum.Admin, ZRole.enum.Student]),
isAuthenticated,
async (_req, res) => {
const query = await getSubjects();
async (req, res) => {
const searchQuery = ZSubjectQuery.parse(req.query);

const query = await getSubjects(searchQuery);
return res.status(200).json(query).end();
}
);
Expand Down
11 changes: 7 additions & 4 deletions src/controllers/teacher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
} from '../services/teacher.service';
import { setAuthorizedRoles, isAuthenticated } from '../utils/middleware';
import { ZRole, ZUuid } from '../validator/general.validator';
import { ZTeacher } from '../validator/teacher.validator';
import { ZUser } from '../validator/user.validator';
import { ZTeacher, ZTeacherQuery } from '../validator/teacher.validator';
import { ZUser, ZUserQuery } from '../validator/user.validator';

const teacherRouter = express.Router();

Expand All @@ -19,8 +19,11 @@ teacherRouter.get(
'/',
setAuthorizedRoles([ZRole.enum.Admin]),
isAuthenticated,
async (_req, res) => {
const query = await getTeachers();
async (req, res) => {
const searchQueryUser = ZUserQuery.parse(req.query);
const searchQueryTeacher = ZTeacherQuery.parse(req.query);

const query = await getTeachers(searchQueryUser, searchQueryTeacher);
return res.status(200).json(query).end();
}
);
Expand Down
16 changes: 8 additions & 8 deletions src/services/activeSubject.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-misused-promises */
import ActiveSubject from '../models/activeSubject';
import { ZActiveSubject } from '../validator/activeSubject.validator';
import {
ZActiveSubject,
ZActiveSubjectQuery,
} from '../validator/activeSubject.validator';

// #17 very WET CRUD operations.
const getActiveSubjects = async () => {
const query = await ActiveSubject.findAll();
// Should I incldie Teacher, studyClass, & Subject?
// Searching not tested
const getActiveSubjects = async (searchQuery: ZActiveSubjectQuery) => {
const query = await ActiveSubject.findAll({ where: { ...searchQuery } });
return query;
};

// Lots of variations in this one.
// To-Do
// /api/activeSubjects/:id?query=teacher i.e. all classes & subjects for this teacher id
// /api/activeSubjects/:id?query=studyClass i.e. all subjects & teachers for this class
// /api/activeSubjects/:id?query=subject i.e. all classes & teachers for this subject
const getActiveSubject = async (serial: string) => {
const query = await ActiveSubject.findOne({
where: { serial },
Expand Down
15 changes: 11 additions & 4 deletions src/services/student.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-misused-promises */
import { Student, User } from '../models';
import { ZStudent } from '../validator/student.validator';
import { ZUser } from '../validator/user.validator';
import { ZStudent, ZStudentQuery } from '../validator/student.validator';
import { ZUser, ZUserQuery } from '../validator/user.validator';
import { deleteUser } from './user.service';

const getStudents = async () => {
const query = await Student.findAll();
// Searching not tested
const getStudents = async (
searchQueryUser: ZUserQuery,
searchQueryStudent: ZStudentQuery
) => {
const query = await Student.findAll({
include: { model: User, where: { ...searchQueryUser } },
where: { ...searchQueryStudent },
});
return query;
};

Expand Down
10 changes: 7 additions & 3 deletions src/services/studyClass.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-misused-promises */
import StudyClass from '../models/class';
import { ZStudyClass } from '../validator/studyClass.validator';
import {
ZStudyClass,
ZStudyClassQuery,
} from '../validator/studyClass.validator';

const getStudyClasses = async () => {
const query = await StudyClass.findAll();
// Searching not well tested
const getStudyClasses = async (searchQuery: ZStudyClassQuery) => {
const query = await StudyClass.findAll({ where: { ...searchQuery } });
return query;
};

Expand Down
10 changes: 5 additions & 5 deletions src/services/subject.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-misused-promises */
import Subject from '../models/subject';
import { ZSubject } from '../validator/subject.validator';
import { ZSubject, ZSubjectQuery } from '../validator/subject.validator';

const getSubjects = async () => {
const query = await Subject.findAll();
const getSubjects = async (searchQuery: ZSubjectQuery) => {
const query = await Subject.findAll({
where: { ...searchQuery },
});
return query;
};

Expand Down
14 changes: 10 additions & 4 deletions src/services/teacher.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-misused-promises */
import { Teacher, User } from '../models';
import { ZTeacher } from '../validator/teacher.validator';
import { ZUser } from '../validator/user.validator';
import { ZTeacher, ZTeacherQuery } from '../validator/teacher.validator';
import { ZUser, ZUserQuery } from '../validator/user.validator';
import { deleteUser } from './user.service';

const getTeachers = async () => {
const query = await Teacher.findAll();
const getTeachers = async (
searchQueryUser: ZUserQuery,
searchQueryTeacher: ZTeacherQuery
) => {
const query = await Teacher.findAll({
include: { model: User, where: { ...searchQueryUser } },
where: { ...searchQueryTeacher },
});
return query;
};

Expand Down
1 change: 1 addition & 0 deletions src/tests/teacher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const teacherRoute = '/api/teacher/';

let sessionId: string;
beforeAll(async () => {
// jest.setTimeout(20000);
sessionId = (await loginAdmin(api)) as string;
// Can be moved to its own function
await api.get('/testAuth').set('Cookie', [sessionId]).expect(200);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const sequelize = new Sequelize(config.DATABASE_URL, {
}
: null,
},
logging: config.NODE_ENV === 'DEV' ? true : false,
logging: config.NODE_ENV === 'DEV' ? console.log : false,
});

sequelize.addModels([
Expand Down
10 changes: 10 additions & 0 deletions src/validator/activeSubject.validator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { ToLikeQuery } from './general.validator';
import { ZStudyClass } from './studyClass.validator';
import { ZSubject } from './subject.validator';
import { ZTeacher } from './teacher.validator';
Expand All @@ -8,11 +9,20 @@ import { ZTeacher } from './teacher.validator';
// i.e. some client sends this object with the 'serial' attribute
// which is an auto incremented value in the DB,
// sequelize has to omit this value.
// P.S, this is inconsitent with DB design, all IDs are optional.
// P.P.S: This allows searching for non-existent IDs
export const ZActiveSubject = z.object({
serial: z.number().positive().optional(),
subjectId: ZSubject.shape.subjectId,
classId: ZStudyClass.shape.classId,
teacherId: ZTeacher.shape.userId,
subjectSchedule: z.string().max(6).optional(),
});

export const ZActiveSubjectQuery = ZActiveSubject.extend({
subjectSchedule: ZActiveSubject.shape.subjectSchedule.transform((attribute) =>
ToLikeQuery(attribute)
),
}).partial();
export type ZActiveSubjectQuery = z.infer<typeof ZActiveSubjectQuery>;
export type ZActiveSubject = z.infer<typeof ZActiveSubject>;
5 changes: 5 additions & 0 deletions src/validator/general.validator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Op } from 'sequelize';
import { z } from 'zod';
import { Bug } from '../types';
// type CreateStudent = z.infer<typeof CreateStudent>; I forget how to do this a lot.
Expand All @@ -17,6 +18,10 @@ export const ZBloodGroup = z.enum([
'AB-',
]);
export const ZEducationType = z.enum(['Sceiences', 'Literature', 'Other']);
export const ToLikeQuery = (attribute: string | undefined) => {
if (!attribute) return;
return { [Op.like]: `%${attribute}%` };
};

// Inferred Types
export type StudyYear = z.infer<typeof ZStudyYear>;
Expand Down
18 changes: 17 additions & 1 deletion src/validator/student.validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { ZEducationType, ZStudyYear } from './general.validator';
import { ToLikeQuery, ZEducationType, ZStudyYear } from './general.validator';
import { ZStudyClass } from './studyClass.validator';

export const ZStudent = z
Expand All @@ -15,6 +15,22 @@ export const ZStudent = z
.required({ studyYear: true });
export type ZStudent = z.infer<typeof ZStudent>;

export const ZStudentQuery = ZStudent.partial()
.extend({
classId: ZStudent.shape.classId.transform((attribute) =>
ToLikeQuery(attribute)
), // duplicate use Z
parentName: ZStudent.shape.parentName.transform((attribute) =>
ToLikeQuery(attribute)
),
parentPhonenumber: ZStudent.shape.parentPhonenumber.transform((attribute) =>
ToLikeQuery(attribute)
),
})
.partial();
export type ZStudentQuery = z.infer<typeof ZStudentQuery>;

// What is this doing here?
export const ValidateRawQuery = z.tuple([
z.object({
lastValue: z.string().nullable().optional(),
Expand Down
10 changes: 9 additions & 1 deletion src/validator/studyClass.validator.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { z } from 'zod';
import { ZEducationType, ZStudyYear } from './general.validator';
import { ToLikeQuery, ZEducationType, ZStudyYear } from './general.validator';

export const ZStudyClass = z.object({
classId: z.string().max(6),
studyYear: ZStudyYear.optional(),
educationType: ZEducationType.optional(),
});

export const ZStudyClassQuery = ZStudyClass.extend({
classId: ZStudyClass.shape.classId.transform((attrbiute) =>
ToLikeQuery(attrbiute)
),
}).partial();

export type ZStudyClassQuery = z.infer<typeof ZStudyClassQuery>;
export type ZStudyClass = z.infer<typeof ZStudyClass>;
10 changes: 9 additions & 1 deletion src/validator/subject.validator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { z } from 'zod';
import { ZEducationType, ZStudyYear } from './general.validator';
import { ToLikeQuery, ZEducationType, ZStudyYear } from './general.validator';

export const ZSubject = z.object({
subjectId: z.string().max(6),
name: z.string().max(64).optional(),
studyYear: ZStudyYear,
educationType: ZEducationType,
});

export const ZSubjectQuery = ZSubject.extend({
subjectId: ZSubject.shape.studyYear.transform((attribute) =>
ToLikeQuery(attribute)
),
name: ZSubject.shape.name.transform((attribute) => ToLikeQuery(attribute)),
}).partial();
export type ZSubject = z.infer<typeof ZSubject>;
export type ZSubjectQuery = z.infer<typeof ZSubjectQuery>;
9 changes: 9 additions & 0 deletions src/validator/teacher.validator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { ToLikeQuery } from './general.validator';

// Caveat of this re-work,
// you're not veryfing referrentail integrtity here,
Expand All @@ -10,4 +11,12 @@ export const ZTeacher = z
education: z.string().max(64),
})
.partial();

export const ZTeacherQuery = ZTeacher.extend({
department: ZTeacher.shape.department.transform((attribute) =>
ToLikeQuery(attribute)
),
}).partial();
export type ZTeacherQuery = z.infer<typeof ZTeacherQuery>;

export type ZTeacher = z.infer<typeof ZTeacher>;
Loading

0 comments on commit a8971d8

Please sign in to comment.