diff --git a/sources/apollo/courses/course.datasources.js b/sources/apollo/courses/course.datasources.js new file mode 100644 index 00000000..1b5891a5 --- /dev/null +++ b/sources/apollo/courses/course.datasources.js @@ -0,0 +1,38 @@ +/* eslint no-useless-constructor: "off", class-methods-use-this: "off" */ + +const CourseModel = require('./course.model.js') +const { DataSource } = require('apollo-datasource'); + +class CourseAPI extends DataSource { + constructor() { + super(); + } + // eslint-disable-next-line no-empty-function + initialize(_) {} + + getCourseById(id) { + return CourseModel.findById(id); + } + + getCourseBySubjectCode(subjectCode) { + return CourseModel.findOne({subjectCode}); + } + + addCourse(course){ + const ltp={ + lecture: course.ltp[0], + tutorial: course.ltp[1], + // eslint-disable-next-line no-magic-numbers + practical: course.ltp[2] + } + const courseObject = new CourseModel({ + subjectCode: course.subjectCode, + name: course.name, + ltp, + credits: course.credits + }); + return courseObject.save(); + } +} + +module.exports = CourseAPI; \ No newline at end of file diff --git a/sources/apollo/courses/course.model.js b/sources/apollo/courses/course.model.js new file mode 100644 index 00000000..22357f96 --- /dev/null +++ b/sources/apollo/courses/course.model.js @@ -0,0 +1,65 @@ +const { Schema, model } = require('mongoose'); + +const courseSchema = new Schema( + { + subjectCode:{ + type: String, + required: true, + trim: true, + unique: true, + uppercase: true, + }, + name:{ + type: String, + required: true, + trim: true, + }, + ltp:{ + lecture:{ + type: Number, + required: true, + min: 0, + }, + tutorial:{ + type: Number, + required: true, + min: 0, + }, + practical:{ + type: Number, + required: true, + min: 0, + } + }, + credits:{ + type: Number, + required: true, + min: 0, + }, + schemaVersion: { + type: Number, + required: true, + default: 1, + min: 1, + }, + createdBy: { + type: Schema.Types.ObjectId, + ref: 'User', + required: false, + default: null, //TODo: Remove after making helper function for this field + }, + updatedBy: { + type: Schema.Types.ObjectId, + ref: 'User', + required: false, + default: null, + }, + + }, + { + timestamps: true, + }, +); +courseSchema.path('subjectCode').index({ unique: true }); + +module.exports = model('Course', courseSchema); diff --git a/sources/apollo/courses/course.resolver.js b/sources/apollo/courses/course.resolver.js new file mode 100644 index 00000000..b242ab6b --- /dev/null +++ b/sources/apollo/courses/course.resolver.js @@ -0,0 +1,13 @@ +/** @format */ +/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ +const queries = { + courseById: (_, args, { dataSources }, __) => dataSources.CourseAPI.getCourseById(args.id), + + courseBySubjectCode: (_, args, { dataSources }, __) => dataSources.CourseAPI.getCourseBySubjectCode(args.subjectCode) +}; + +const mutations = { + addCourse: (_, {course}, { dataSources }, __) => dataSources.CourseAPI.addCourse(course), +} + +module.exports = { queries, mutations }; diff --git a/sources/apollo/courses/course.schema.js b/sources/apollo/courses/course.schema.js new file mode 100644 index 00000000..5e3fa254 --- /dev/null +++ b/sources/apollo/courses/course.schema.js @@ -0,0 +1,37 @@ +/** @format */ + +const types = ` + type LTP{ + lecture: Int + tutorial: Int + practical: Int + } + type Course{ + id:ID + subjectCode : String + name : String + ltp : LTP + credits : Int + } + input CourseInputType{ + subjectCode : String! + name : String! + ltp : [Int!] + credits : Int! + } +`; + +const queries = ` + courseBySubjectCode(subjectCode:String!):Course + courseById(id:ID!):Course +`; + +const mutations = ` +addCourse(course:CourseInputType!):Course +`; + +module.exports = { types, queries, mutations }; + +// updateCourse(id:ID!,course:CourseInputType!):Course +// deleteCourse(id:ID!):Course +// List courses by search | Use Atlas Search \ No newline at end of file diff --git a/sources/apollo/resolvers.js b/sources/apollo/resolvers.js index d1118205..d6581c5e 100644 --- a/sources/apollo/resolvers.js +++ b/sources/apollo/resolvers.js @@ -3,14 +3,16 @@ const Event = require('./events/event.resolver.js'); const Club = require('./clubs/club.resolver.js'); const AccessLevel = require('./accessLevels/accessLevel.resolver.js'); const Story = require('./stories/story.resolver.js'); +const Course = require('./courses/course.resolver.js'); +const Timetable = require('./timetables/timetable.resolver.js'); const { GraphQLDateTime } =require ("graphql-iso-date"); const FieldResolver = {}; const Query = {}; const Mutation = {}; -const schemas = [User, Event, Club, AccessLevel,Story]; -schemas.forEach(s => { +const schemas = [User, Event, Club, AccessLevel,Story, Course, Timetable]; +schemas.forEach((s) => { Object.assign(FieldResolver, s.fieldResolvers); Object.assign(Query, s.queries); Object.assign(Mutation, s.mutations); diff --git a/sources/apollo/schema.js b/sources/apollo/schema.js index b6532a82..e08d17e7 100644 --- a/sources/apollo/schema.js +++ b/sources/apollo/schema.js @@ -7,13 +7,16 @@ const Venue = require('./venues/venue.schema.js'); const Story = require('./stories/story.schema.js'); const Access = require('./accessLevels/accessLevel.schema.js'); const ErrorClass = require('./errorClass/error.schema.js'); +const Course = require('./courses/course.schema.js'); +const Timetable = require('./timetables/timetable.schema.js'); +const {GraphQLDateTime} = require('graphql-iso-date'); const types = []; const queries = []; const mutations = []; -const schemas = [User, Event, Club, Venue, Access, ErrorClass,Story]; -schemas.forEach(s => { +const schemas = [User, Event, Club, Venue, Access, ErrorClass,Story, Course, Timetable]; +schemas.forEach((s) => { types.push(s.types); queries.push(s.queries); mutations.push(s.mutations); diff --git a/sources/apollo/server.js b/sources/apollo/server.js index 7b7d67e4..c6865009 100644 --- a/sources/apollo/server.js +++ b/sources/apollo/server.js @@ -9,6 +9,8 @@ const EventAPI = require('./events/event.datasources.js'); const VenueAPI = require('./venues/venue.datasources.js'); const AccessLevelAPI = require('./accessLevels/accessLevel.datasources.js'); const StoryAPI = require('./stories/story.datasources.js'); +const CourseAPI = require('./courses/course.datasources.js'); +const TimetableAPI = require('./timetables/timetable.datasources.js'); const typeDefs = require('./schema.js'); const resolvers = require('./resolvers.js'); const {firebaseApp}=require("../helpers/firebase"); @@ -22,7 +24,9 @@ const dataSources = () => ({ EventAPI: new EventAPI(), VenueAPI: new VenueAPI(), AccessLevelAPI: new AccessLevelAPI(), - StoryAPI:new StoryAPI() + StoryAPI:new StoryAPI(), + CourseAPI: new CourseAPI(), + TimetableAPI: new TimetableAPI(), }); const server = new ApolloServer({ diff --git a/sources/apollo/timetables/timetable.datasources.js b/sources/apollo/timetables/timetable.datasources.js new file mode 100644 index 00000000..dc0a7d3f --- /dev/null +++ b/sources/apollo/timetables/timetable.datasources.js @@ -0,0 +1,36 @@ +/* eslint no-useless-constructor: "off", class-methods-use-this: "off" */ + +const TimetableModel = require('./timetable.model.js') +const { DataSource } = require('apollo-datasource'); + +class TimetableAPI extends DataSource { + constructor() { + super(); + } + // eslint-disable-next-line no-empty-function + initialize(_) {} + + getTimetableById(id) { + return TimetableModel.findById(id); + } + + getTimetableByIdentifier(identifier) { + const regex = new RegExp(identifier,'u') + return TimetableModel.find({ "identifier" : { $regex: regex, $options: 'i' } }); + } + + addTimetable(timetable, userId){ + let slotInfo =[] + for (const slotInfoElement of timetable.slotInfo){ + slotInfo.push(slotInfoElement); + } + const timetableObject = new TimetableModel({ + user: userId, + identifier:timetable.identifier, + slotInfo, + }); + return timetableObject.save(); + } +} + +module.exports = TimetableAPI; \ No newline at end of file diff --git a/sources/apollo/timetables/timetable.model.js b/sources/apollo/timetables/timetable.model.js new file mode 100644 index 00000000..97d6bb74 --- /dev/null +++ b/sources/apollo/timetables/timetable.model.js @@ -0,0 +1,85 @@ +// TODO: Update CBy and UBy to make it non-nullabe by design +// TODO: Add Professor + +const { Schema, model } = require('mongoose'); + +const timetableSchema = new Schema( + { + user: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true + }, + identifier: { + type: String, + required: true, + trim: true, + }, + slotInfo: [{ + name: { + type: String, // Put slot name TA1 TA2 etc here. Not directly used now. May be useful later + required: true, + trim: true, + }, + course: { + type: Schema.Types.ObjectId, + ref: 'Course', + required: true, + }, + startTime: { + hours:{ + type: Number, + required: true, + min: 0, + max: 23 + }, + minutes:{ + type: Number, + required: true, + min: 0, + max: 59 + }, + }, + duration:{ + type: Number, // Units in minutes + required: true, + min: 0, + }, + day: { + type: Number, // 1-Monday, 5-Friday + required: true, + max: 5, + min: 1 + } + }], + expiry: { + type: Boolean, + required: true, + default: false, + }, + schemaVersion: { + type: Number, + required: true, + default: 1, + min: 1, + }, + createdBy: { + type: Schema.Types.ObjectId, + ref: 'User', + required: false, + default: null, + }, + updatedBy: { + type: Schema.Types.ObjectId, + ref: 'User', + required: false, + default: null, + }, + + }, + { + timestamps: true, + }, +); + +module.exports = model('Timetable', timetableSchema); diff --git a/sources/apollo/timetables/timetable.resolver.js b/sources/apollo/timetables/timetable.resolver.js new file mode 100644 index 00000000..520f32ee --- /dev/null +++ b/sources/apollo/timetables/timetable.resolver.js @@ -0,0 +1,28 @@ +/** @format */ +/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ +const queries = { + timetableById: (_, args, { dataSources }, __) => dataSources.TimetableAPI.getTimetableById(args.id), + + timetableByIdentifier: (_, args, { dataSources }, __) => dataSources.TimetableAPI.getTimetableByIdentifier(args.identifier) +}; + +const mutations = { + addTimetable: (_, {timetable}, { dataSources }, __) => dataSources.TimetableAPI.addTimetable(timetable, "6009a59c3dae9456bce4a8f9"), +}; + +const fieldResolvers = { + Timetable: { + slotInfo: (parent, _, { dataSources }, __) => { + const {slotInfo}=parent; + const ResolvedCourses=slotInfo.map(async slot => { + // eslint-disable-next-line require-atomic-updates + slot.course= await dataSources.CourseAPI.getCourseById(slot.course) + return slot + }) + return ResolvedCourses + }, + user: (parent, _, { dataSources }, __) => dataSources.UserAPI.getUserById(parent.user) + } +}; +// +module.exports = { queries, mutations, fieldResolvers}; diff --git a/sources/apollo/timetables/timetable.schema.js b/sources/apollo/timetables/timetable.schema.js new file mode 100644 index 00000000..c4ccbe3b --- /dev/null +++ b/sources/apollo/timetables/timetable.schema.js @@ -0,0 +1,58 @@ +/** @format */ + +// TODO: Unified Date Time format across the project +// TODo: Change time to unified UTC format in Model and Schema + +const types = ` + type StartTime{ + hours: Int + minutes: Int + } + input StartTimeInput{ + hours: Int! + minutes: Int! + } + type SlotInfo{ + name: String + course: Course + startTime: StartTime + duration: Int + day: Int + } + input SlotInfoInput{ + name: String! + course: ID! + startTime: StartTimeInput! + duration: Int! + day: Int! + } + type Timetable{ + id:ID + user : User + identifier : String + slotInfo : [SlotInfo] + expiry : Boolean + } + input TimetableInputType{ + identifier : String! + slotInfo : [SlotInfoInput!] + } +`; + +const queries = ` + timetableByIdentifier(identifier:String!):[Timetable] + timetableById(id:ID!):Timetable +`; + +const mutations = ` +addTimetable(timetable:TimetableInputType!):Timetable +`; + +module.exports = { types, queries, mutations }; + +// Add Timetable +// Delete Timetable +// Move a course to new slot +// Delete Course from timetable +// Add Course course to timetable +// Make as timetable as expired \ No newline at end of file