Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ var galleryRouter = require('./routes/gallery/index');
var projectRouter = require('./routes/project/index');
var userRouter = require('./routes/user/index');
var uploadRouter = require('./routes/upload/index');
var groupRouter = require('./routes/group/index');


api.use('/tutorial', tutorialRouter);
api.use('/share', shareRouter);
api.use('/gallery', galleryRouter);
api.use('/project', projectRouter);
api.use('/user', userRouter);
api.use('/upload', uploadRouter);
api.use('/group', groupRouter);


// catch 404 and forward to error handler
api.use(function(req, res, next) {
Expand Down
26 changes: 26 additions & 0 deletions models/group.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { randomUUID } = require("crypto");

const mongoose = require("mongoose");
const { Schema } = mongoose;

const groupSchema = new Schema(
{
_id: { type: String, default: () => randomUUID(), required: true },
name: { type: String, required: true },
accessCode: { type: String, unique: true, required: true },
teacherId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
archived: { type: Boolean, default: false },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
},
{
timestamps: true,
collection: "groups",
},
);

module.exports = mongoose.model("Group", groupSchema);
36 changes: 36 additions & 0 deletions models/groupMembers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const mongoose = require("mongoose");
const { Schema } = mongoose;

const groupMemberSchema = new Schema(
{
_id: { type: String, required: true },
groupId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Group",
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
role: { type: String, enum: ["teacher", "student"], required: true },
name: { type: String },
nickname: { type: String },
onlineStatus: { type: Boolean, default: false },
lastSeen: { type: Date, default: Date.now },
claimed: { type: Boolean, default: false },
sessionToken: { type: String },
joinedAt: { type: Date, default: Date.now },
tutorialId: { type: mongoose.Schema.Types.ObjectId, ref: "Tutorial", default: null }, // NEU
currentStep: { type: Number, default: null },
},
{
timestamps: true,
collection: "groupMembers",
},
);

// sparse:true allows multiple teacher records with null nickname
groupMemberSchema.index({ groupId: 1, nickname: 1 }, { unique: true, sparse: true });

module.exports = mongoose.model("GroupMember", groupMemberSchema);
30 changes: 30 additions & 0 deletions models/groupTutorial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const mongoose = require("mongoose");
const { Schema } = mongoose;

const groupTutorialSchema = new Schema(
{
_id: { type: String },
groupId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Group",
required: true,
},
tutorialId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Tutorial",
required: true,
},
assignedAt: { type: Date, default: Date.now },
assignedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: false,
},
},
{
timestamps: true,
collection: "groupTutorials",
},
);

module.exports = mongoose.model("GroupTutorial", groupTutorialSchema);
22 changes: 22 additions & 0 deletions models/progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const mongoose = require("mongoose");
const { Schema } = mongoose;

const progressSchema = new Schema(
{
_id: { type: String, required: true },
userId: { type: mongoose.Schema.Types.ObjectId, ref: "GroupMember" },
groupId: { type: mongoose.Schema.Types.ObjectId, ref: "Group" },
tutorialId: { type: mongoose.Schema.Types.ObjectId, ref: "Tutorial" },
currentPage: { type: Number },
totalPages: { type: Number },
lastSeen: { type: Date },
updatedAt: { type: Date },
blocklyXml: { type: String }
},
{
timestamps: true,
collection: "progress",
},
);

module.exports = mongoose.model("Progress", progressSchema);
20 changes: 20 additions & 0 deletions models/solution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const mongoose = require("mongoose");
const { Schema } = mongoose;

const solutionSchema = new Schema(
{
_id: { type: String, required: true },
userId: { type: mongoose.Schema.Types.ObjectId, ref: "GroupMember" },
groupId: { type: mongoose.Schema.Types.ObjectId, ref: "Group" },
tutorialId: { type: mongoose.Schema.Types.ObjectId, ref: "Tutorial" },
blocklyXml: { type: String },
publishedAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
},
{
timestamps: true,
collection: "solutions",
},
);

module.exports = mongoose.model("Solution", solutionSchema);
61 changes: 61 additions & 0 deletions routes/group/archiveGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use strict";

const Group = require("../../models/group");

/**
* @api {patch} /groups/:groupId/archive Archive or unarchive a group
* @apiName archiveGroup
* @apiDescription Archive or unarchive a group. Only accessible by the teacher.
* @apiGroup Group
*
* @apiHeader {String} Authorization allows to send a valid JSON Web Token along with this request with `Bearer` prefix.
* @apiParam {String} groupId id of the group (URL param)
* @apiParam {Boolean} archived whether to archive (true) or unarchive (false) the group (body)
*
* @apiSuccess (Success 200) {String} message `Group archived successfully.` or `Group unarchived successfully.`
* @apiSuccess (Success 200) {Object} group The updated group object
* @apiError (On error) {Object} 400 `{"message": "Missing required fields."}`
* @apiError (On error) {Object} 403 `{"message": "No permission to archive this group."}`
* @apiError (On error) {Object} 404 `{"message": "Group not found."}`
* @apiError (On error) {Object} 500 Complications during querying the database.
*/
const archiveGroup = async function (req, res) {
try {
const userId = req.user.id;
const groupId = req.params.groupId;
const { archived } = req.body;

if (!groupId) {
return res.status(400).send({ message: "Missing groupId parameter." });
}

if (typeof archived !== "boolean") {
return res.status(400).send({ message: "Missing or invalid 'archived' field. Must be a boolean." });
}

const group = await Group.findById(groupId);
if (!group) {
return res.status(404).send({ message: "Group not found." });
}

if (group.teacherId.toString() !== userId) {
return res.status(403).send({ message: "No permission to archive this group." });
}

group.archived = archived;
group.updatedAt = new Date();
const result = await group.save();

const action = archived ? "archived": "unarchived";
return res.status(200).send({
message: `Group ${action} successfully.`,
group: result,
});
} catch (err) {
return res.status(500).send({ message: "Server error.", error: err.message });
}
};

module.exports = {
archiveGroup,
};
46 changes: 46 additions & 0 deletions routes/group/getAllGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";

const Group = require("../../models/group");
const mongoose = require("mongoose");

/**
* @api {get} /group Get groups
* @apiName getAllGroup
* @apiDescription Get all groups of the user.
* @apiGroup Group
*
* @apiHeader {String} Authorization allows to send a valid JSON Web Token along with this request with `Bearer` prefix.
* @apiHeaderExample {String} Authorization Header Example
* Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlMTk5OTEwY2QxMDgyMjA3Y2Y1ZGM2ZiIsImlhdCI6MTU3ODg0NDEwOSwiZXhwIjoxNTc4ODUwMTA5fQ.D4NKx6uT3J329j7JrPst6p02d311u7AsXVCUEyvoiTo
*
* @apiSuccess (Success 200) {String} message `Groups found successfully.`
* @apiSuccess (Success 200) {Object} groups `[
{
"_id": "5fd89de648ccd57688c77d3b",
"name": "Gruppe 1",
"accessCode": "HDBW134",
"teacherId": "5fd89de648ccd57688c77d3a",
"archived": "false",
"createdAt": "2020-12-15T11:28:38.300Z",
"updatedAt": "2020-12-15T11:28:38.300Z",
"__v": 0
}
]`
*
* @apiError (On error) {Obejct} 500 Complications during querying the database.
*/
const getAllGroup = async function (req, res) {
try {
const result = await Group.find({});
return res.status(200).send({
message: "Get All Groups found successfully.",
groups: result,
});
} catch (err) {
return res.status(500).send({ message: "Server error.", error: err.message });
}
};
module.exports = {
getAllGroup,
};

46 changes: 46 additions & 0 deletions routes/group/getGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";

const Group = require("../../models/group");

/**
* @api {get} /group Get groups
* @apiName getGroup
* @apiDescription Get all groups of the user.
* @apiGroup Group
*
* @apiHeader {String} Authorization allows to send a valid JSON Web Token along with this request with `Bearer` prefix.
* @apiHeaderExample {String} Authorization Header Example
* Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlMTk5OTEwY2QxMDgyMjA3Y2Y1ZGM2ZiIsImlhdCI6MTU3ODg0NDEwOSwiZXhwIjoxNTc4ODUwMTA5fQ.D4NKx6uT3J329j7JrPst6p02d311u7AsXVCUEyvoiTo
*
* @apiSuccess (Success 200) {String} message `Groups found successfully.`
* @apiSuccess (Success 200) {Object} groups `[
{
"_id": "5fd89de648ccd57688c77d3b",
"name": "Gruppe 1",
"accessCode": "HDBW134",
"teacherId": "5fd89de648ccd57688c77d3a",
"archived": "false",
"createdAt": "2020-12-15T11:28:38.300Z",
"updatedAt": "2020-12-15T11:28:38.300Z",
"__v": 0
}
]`
*
* @apiError (On error) {Obejct} 500 Complications during querying the database.
*/
const getGroup = async function (req, res) {
try {
const result = await Group.find({ teacherId: req.user.id });
return res.status(200).send({
message: "Group one",
groups: result,
});
} catch (err) {
return res.status(500).send({ message: "Server error.", error: err.message });
}
};

module.exports = {
getGroup,
};

35 changes: 35 additions & 0 deletions routes/group/getGroupById.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use strict";

const group = require("../../models/group");
const Group = require("../../models/group");
const GroupMember = require("../../models/groupMembers");

/**
* @api {get} /groups/:groupId Get group details
* @apiName getGroupById
* @apiDescription Get details of a specific group. Available to members and teachers of the group.
* @apiGroup Group
*
* @apiHeader {String} Authorization allows to send a valid JSON Web Token along with this request with `Bearer` prefix.
* @apiParam {String} groupId id of the group (URL param)
*
* @apiSuccess (Success 200) {String} message `Group found successfully.`
* @apiSuccess (Success 200) {Object} group The group object
* @apiSuccess (Success 200) {Boolean} isTeacher Whether the user is the teacher of this group
* @apiError (On error) {Object} 500 `{"message": "Server error."}`
*/
const getGroupById = async function (req, res) {
try {
const result = await Group.findById(req.params.groupId);
return res.status(200).send({
message: "Group by Id ",
groups: result,
});
} catch (err) {
return res.status(500).send({ message: "Server error.", error: err.message });
}
};

module.exports = {
getGroupById,
};
Loading