Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (29)
Showing
with 2218 additions and 41 deletions
run-gitlab-runner:
docker run -d --name ppl-gitlab-runner --restart always \
-v $(shell pwd)/gitlab-runner-config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
launch-gitlab-runner:
docker run --rm -it -v $(shell pwd)/gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner register
run-dockerd:
sudo dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
build-service:
export __CI_REGISTRY_IMAGE__=ppl-six-backend && docker-compose -f docker-compose.yaml build
up: down-service up-service
up-service: build-service
export __CI_REGISTRY_IMAGE__=ppl-six-backend && docker-compose up -d
down-service:
docker-compose down
\ No newline at end of file
FROM node:latest
WORKDIR /usr/src/app/backend
COPY package*.json /usr/src/app/backend/
RUN npm install
ENV NODE_ENV production
EXPOSE 3000
CMD ["npm", "run", "prod"]
\ No newline at end of file
......@@ -152,3 +152,152 @@
"message": "invalid input syntax for type uuid: \"517c210a-f35e-4978-ba9f-e38a6addccb\""
}
```
## Create Course Data
- Endpoint: `/course`
- HTTP Method: `POST`
- Request Body (for S1 Course):
```json
{
"code": "IF5124",
"name": "Kualitas Perangkat Lunak",
"type": "WAJIB",
"credits": 2,
"defaultSemester": 2,
"shortSyllabus": "Review SDLC dan software methodology, agile methodology, OOAD, Design principles, Component design, Configuration management, Continous integration, Service oriented Design, Code Inspection and Code Review",
"completeSyllabus": "Pada kuliah ini mahasiswa dibekali dengan prinsip design perangkat lunak yang “baik”, dan menerapkan proses pembangunan (konstruksi) perangkat lunak dalam team, sesuai dengan praktik yang diterapkan di industri dan tools yang banyak digunakan.",
"outcomes": "Mahasiswa mampu untuk menerapkan prinsip design dalam membangun perangkat lunak, dan membangun perangkat lunak sesuai praktik yang baik (studi kasus).",
"curriculumYear": 2013,
"majorId": "10ee3421-21da-46ca-bb2d-c65c9d76b2db"
}
```
- Response Body (for S1 Course):
```json
{
"id": "72a0fbf7-0782-4f9f-9295-1716ff466577",
"code": "IF5124",
"name": "Kualitas Perangkat Lunak",
"type": "WAJIB",
"credits": 2,
"defaultSemester": 2,
"shortSyllabus": "Review SDLC dan software methodology, agile methodology, OOAD, Design principles, Component design, Configuration management, Continous integration, Service oriented Design, Code Inspection and Code Review",
"completeSyllabus": "Pada kuliah ini mahasiswa dibekali dengan prinsip design perangkat lunak yang “baik”, dan menerapkan proses pembangunan (konstruksi) perangkat lunak dalam team, sesuai dengan praktik yang diterapkan di industri dan tools yang banyak digunakan.",
"outcomes": "Mahasiswa mampu untuk menerapkan prinsip design dalam membangun perangkat lunak, dan membangun perangkat lunak sesuai praktik yang baik (studi kasus).",
"curriculumYear": 2013,
"majorId": "10ee3421-21da-46ca-bb2d-c65c9d76b2db",
"updatedAt": "2021-04-13T18:19:08.273Z",
"createdAt": "2021-04-13T18:19:08.273Z"
}
```
- Request Body (for S2 Course):
```json
{
"code": "IF5124",
"name": "Kualitas Perangkat Lunak",
"type": "WAJIB",
"credits": 2,
"defaultSemester": 2,
"shortSyllabus": "Review SDLC dan software methodology, agile methodology, OOAD, Design principles, Component design, Configuration management, Continous integration, Service oriented Design, Code Inspection and Code Review",
"completeSyllabus": "Pada kuliah ini mahasiswa dibekali dengan prinsip design perangkat lunak yang “baik”, dan menerapkan proses pembangunan (konstruksi) perangkat lunak dalam team, sesuai dengan praktik yang diterapkan di industri dan tools yang banyak digunakan.",
"outcomes": "Mahasiswa mampu untuk menerapkan prinsip design dalam membangun perangkat lunak, dan membangun perangkat lunak sesuai praktik yang baik (studi kasus).",
"curriculumYear": 2013,
"optionId": "cd23bb93-81e6-4870-8365-73dbeb8fcf09"
}
```
- Response Body (for S2 Course):
```json
{
"id": "94867701-bff1-466b-b5a1-fe9b5d3cef97",
"code": "IF5124",
"name": "Kualitas Perangkat Lunak",
"type": "WAJIB",
"credits": 2,
"defaultSemester": 2,
"shortSyllabus": "Review SDLC dan software methodology, agile methodology, OOAD, Design principles, Component design, Configuration management, Continous integration, Service oriented Design, Code Inspection and Code Review",
"completeSyllabus": "Pada kuliah ini mahasiswa dibekali dengan prinsip design perangkat lunak yang “baik”, dan menerapkan proses pembangunan (konstruksi) perangkat lunak dalam team, sesuai dengan praktik yang diterapkan di industri dan tools yang banyak digunakan.",
"outcomes": "Mahasiswa mampu untuk menerapkan prinsip design dalam membangun perangkat lunak, dan membangun perangkat lunak sesuai praktik yang baik (studi kasus).",
"curriculumYear": 2013,
"majorId": "10ee3421-21da-46ca-bb2d-c65c9d76b2db",
"courseS2": [
{
"id": "eb919101-beba-4c50-a84e-459eb9de8dd9",
"optionId": "cd23bb93-81e6-4870-8365-73dbeb8fcf09",
"courseId": "94867701-bff1-466b-b5a1-fe9b5d3cef97",
"updatedAt": "2021-04-13T18:16:59.269Z",
"createdAt": "2021-04-13T18:16:59.269Z"
}
],
"updatedAt": "2021-04-13T18:16:59.242Z",
"createdAt": "2021-04-13T18:16:59.242Z"
}
```
- Response Body (Failure):
```json
{
"name": "SequelizeDatabaseError",
"message": "invalid input value for enum \"enum_Courses_type\": \"W\""
}
```
```json
{
"name": "Error",
"message": "Undefined code,name,type"
}
```
## Update Course Data
- Endpoint: `/course/<id>`
- HTTP Method: `PUT`
- Request Body:
```json
{
"code": "IF2036",
"name": "Rekayasa Perangkat Lunak",
"type": "WAJIB",
"credits": 3,
"defaultSemester": 2,
"shortSyllabus": "Review SDLC dan software methodology, agile methodology, OOAD, Design principles, Component design, Configuration management, Continous integration, Service oriented Design, Code Inspection and Code Review",
"completeSyllabus": "Pada kuliah ini mahasiswa dibekali dengan prinsip design perangkat lunak yang “baik”, dan menerapkan proses pembangunan (konstruksi) perangkat lunak dalam team, sesuai dengan praktik yang diterapkan di industri dan tools yang banyak digunakan.",
"outcomes": "Mahasiswa mampu untuk menerapkan prinsip design dalam membangun perangkat lunak, dan membangun perangkat lunak sesuai praktik yang baik (studi kasus).",
"curriculumYear": 2013
}
```
- Response Body (Success):
```json
{
"id": "e1b53710-b8f2-4564-b326-c7f74de81aa2",
"code": "IF2036",
"name": "Rekayasa Perangkat Lunak",
"type": "WAJIB",
"credits": 3,
"defaultSemester": 2,
"shortSyllabus": "Review SDLC dan software methodology, agile methodology, OOAD, Design principles, Component design, Configuration management, Continous integration, Service oriented Design, Code Inspection and Code Review",
"completeSyllabus": "Pada kuliah ini mahasiswa dibekali dengan prinsip design perangkat lunak yang “baik”, dan menerapkan proses pembangunan (konstruksi) perangkat lunak dalam team, sesuai dengan praktik yang diterapkan di industri dan tools yang banyak digunakan.",
"outcomes": "Mahasiswa mampu untuk menerapkan prinsip design dalam membangun perangkat lunak, dan membangun perangkat lunak sesuai praktik yang baik (studi kasus).",
"curriculumYear": 2013,
"createdAt": "2021-04-09T15:23:13.444Z",
"updatedAt": "2021-04-13T19:16:53.968Z",
"majorId": "10ee3421-21da-46ca-bb2d-c65c9d76b2db"
}
```
- Response Body (Failure):
```json
{
"name": "SequelizeDatabaseError",
"message": "invalid input value for enum \"enum_Courses_type\": \"W\""
}
```
```json
{
"name": "Error",
"message": "Undefined one of these parameters: code, or name, or type, or credits, or defaultSemester, or shortSyllabus, or completeSyllabus, or outcomes, or curriculumYear"
}
```
\ No newline at end of file
# Option API
## Get All Options Data
- Endpoint: `/option`
- HTTP Method: `GET`
- Request Query Param:
- majorId: f1c09304-5c1a-45fe-90bc-f5418f45fa8d (optional, string)
- Response Body (Success):
```json
[
{
"id": "82dd01ef-0217-4c2e-8b15-8ab73297752f",
"name": "Rekayasa Perangkat Lunak",
"createdAt": "2021-04-13T21:07:51.467Z",
"updatedAt": "2021-04-13T21:07:51.467Z",
"majorId": "f1c09304-5c1a-45fe-90bc-f5418f45fa8d"
},
{
"id": "548c8cbe-b4f9-4081-8dd2-bedd49279001",
"name": "Sistem Intelijen",
"createdAt": "2021-04-13T21:07:51.467Z",
"updatedAt": "2021-04-13T21:07:51.467Z",
"majorId": "f1c09304-5c1a-45fe-90bc-f5418f45fa8d"
},
{
"id": "d6b2e5bc-f14d-4849-95e1-0e01684ffe25",
"name": "Sistem Informasi",
"createdAt": "2021-04-13T21:07:51.467Z",
"updatedAt": "2021-04-13T21:07:51.467Z",
"majorId": "f1c09304-5c1a-45fe-90bc-f5418f45fa8d"
},
{
"id": "656a7c88-c023-4473-b184-45deedfef3bd",
"name": "Intelijen Bisnis",
"createdAt": "2021-04-13T21:07:51.467Z",
"updatedAt": "2021-04-13T21:07:51.467Z",
"majorId": "f1c09304-5c1a-45fe-90bc-f5418f45fa8d"
}
]
```
- Response Body (Failed):
```json
{
"name": "Error",
"message": "Object doesn't exist."
}
```
```json
{
"name": "SequelizeDatabaseError",
"message": "invalid input syntax for type uuid: \"f1c09304-5c1a-45fe-90bc-f5418f45fa8\""
}
```
\ No newline at end of file
......@@ -37,3 +37,33 @@
}
}
```
## Update Score Status
- Endpoint: `/score/status/?courseClassId=<courseClassId>`
- HTTP Method: `PUT`
- Request Body:
```json
{
"scoreStatus": "FINAL"
}
```
- Response Body (Success):
```json
[
{
"id": "d4fc6aab-ec9d-426f-a723-9f3d763c60cc",
"attendancePercentage": 0,
"score": "T",
"status": "APPROVED",
"scoreStatus": "FINAL",
"createdAt": "2021-04-09T15:06:53.950Z",
"updatedAt": "2021-04-09T15:07:02.024Z",
"courseClassId": "04e94df8-d605-42c4-812b-286a43fa18b9",
"studyPlanId": "05b04672-423e-4ca4-8390-f4e7829667e5"
}
]
```
......@@ -142,11 +142,19 @@
}
],
"score": {
"nr": 0,
"nr": {
"2021": {
"1": 1,
"2": 0
}
},
"ip": 3.5,
"ipk": 3.5
},
"creditsTotal": 7
"credits": {
"passed": 3,
"scoreT": 2
}
}
```
......
This diff is collapsed.
......@@ -7,7 +7,8 @@
"linux-test": "NODE_ENV=test && ./node_modules/mocha/bin/mocha src/test",
"linux-start": "NODE_ENV=development && node src/index.js",
"windows-start": "set NODE_ENV=development && node src/index.js",
"prettify": "npx prettier --write \"**/*.js\""
"prettify": "npx prettier --write \"**/*.js\"",
"prod": "nodemon src/index.js"
},
"author": "Group B",
"license": "MIT",
......@@ -21,6 +22,7 @@
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mocha": "^8.3.0",
"nodemon": "^2.0.7",
"pg": "^8.5.1",
"pg-hstore": "^2.3.3",
"sequelize": "^6.5.0",
......
......@@ -3,7 +3,7 @@
"username": "postgres",
"password": "postgres",
"database": "ppl_development",
"host": "127.0.0.1",
"host": "database",
"port": "5432",
"dialect": "postgres"
},
......@@ -11,7 +11,7 @@
"username": "postgres",
"password": "postgres",
"database": "ppl_test",
"host": "127.0.0.1",
"host": "database",
"port": "5433",
"dialect": "postgres"
},
......@@ -19,7 +19,7 @@
"username": "postgres",
"password": "postgres",
"database": "ppl_production",
"host": "127.0.0.1",
"host": "database",
"port": "5432",
"dialect": "postgres"
}
......
......@@ -3,10 +3,16 @@ const { Course, Major, Faculty } = require('../models/index');
const {
handleRequestWithInvalidRequestBody,
handleRequestWithResourceItemNotFound,
handleRequestWithInternalServerError
} = require('../util/common');
const { NotExistError } = require('../util/error');
const {
createCourse,
updateCourse
} = require('../util/db/course');
exports.getAllCourseData = async (req, res) => {
let courses = await Course.findAll();
res.json(courses);
......@@ -33,3 +39,28 @@ exports.getCourseData = async (req, res) => {
handleRequestWithResourceItemNotFound(res, new NotExistError());
}
};
exports.createCourseData = async (req, res) => {
try {
const { newCourse } = req.body;
const course = await createCourse(newCourse);
res.json(course);
} catch (error) {
if (error instanceof NotExistError) {
handleRequestWithResourceItemNotFound(res, error);
} else {
handleRequestWithInternalServerError(res, error);
}
}
};
exports.updateCourseData = async (req, res) => {
try {
const { course, changeset } = req.body;
const updatedCourse = await updateCourse(course, changeset);
res.json(updatedCourse);
} catch (error) {
handleRequestWithInternalServerError(res, error);
}
};
'use strict';
const {
handleRequestWithInternalServerError,
handleRequestWithResourceItemNotFound,
} = require('../util/common');
const {
getAllOptions
} = require('../util/db/option');
const {
NotExistError,
} = require('../util/error');
exports.getAllOptions = async (req, res) => {
const { opts } = req;
try {
const options = await getAllOptions(opts);
res.json(options);
} catch (error) {
if (error instanceof NotExistError) {
handleRequestWithResourceItemNotFound(res, error);
} else {
handleRequestWithInternalServerError(res, error);
}
}
};
\ No newline at end of file
......@@ -11,7 +11,7 @@ const {
const {
getStudent,
getCreditsTotal,
getCredits,
getHistoricalTranscript,
getStudentIP,
getStudentIPK,
......@@ -40,7 +40,7 @@ exports.getStudentData = async (req, res) => {
}
if (req.includeCredits.toLowerCase() === 'true') {
student.dataValues.creditsTotal = await getCreditsTotal(student);
student.dataValues.credits = await getCredits(student);
}
res.json(student);
......@@ -92,3 +92,17 @@ exports.updateStudentScore = async (req, res) => {
handleRequestWithInternalServerError(res, error);
}
};
exports.updateStudentScoreStatus = async (req, res) => {
try {
const { studyPlanCourses, changeset } = req.body;
for (let studyPlanCourse of studyPlanCourses) {
studyPlanCourse = await updateStudyPlanCourse(studyPlanCourse, changeset);
}
res.json(studyPlanCourses);
} catch (error) {
handleRequestWithInternalServerError(res, error);
}
};
......@@ -8,8 +8,6 @@ const {
const {
createStudyPlan,
countTotalCredits,
checkClassAvailability,
getStudyPlan,
updateStudyPlan,
} = require('../util/db/study-plan');
......@@ -60,15 +58,10 @@ exports.createStudyPlanData = async (req, res) => {
notes,
};
let creditsTotal = 0;
if (studyPlanCourses) {
await checkClassAvailability(semester, startYear, studyPlanCourses);
creditsTotal = await countTotalCredits(studyPlanCourses);
newStudyPlan['studyPlanCourses'] = studyPlanCourses;
}
newStudyPlan['creditsTotal'] = creditsTotal;
const studyPlans = await createStudyPlan(newStudyPlan);
res.json(studyPlans);
} catch (error) {
......
......@@ -12,6 +12,8 @@ const EndpointEnum = Object.freeze({
STUDY_PLAN: '/study-plan',
COURSE_CLASS_MEETING: '/course-class-meeting',
SCORE: '/score',
SCORE_STATUS: '/score/status',
OPTION: '/option',
},
});
......
const ScoreStatusEnum = Object.freeze({
name: 'ScoreStatusEnum',
enums: {
DRAFT: 'DRAFT',
FINAL: 'FINAL',
},
});
module.exports = ScoreStatusEnum;
......@@ -5,6 +5,7 @@ const { StudyPlanStatusEnum } = require('../enums/index');
const {
calculateTotalCredits,
deleteCurrentMeetingAttendances,
checkClassAvailability,
} = require('../util/hook');
const { generateCourseClassMeetingAttendances } = require('../util/common');
......@@ -20,6 +21,10 @@ StudyPlan.beforeUpdate(async (studyPlan) => {
);
});
StudyPlan.afterValidate(async (studyPlan) => {
await checkClassAvailability(studyPlan.semester, studyPlan.startYear, studyPlan.studyPlanCourses);
});
StudyPlan.afterCreate(async (studyPlan) => {
if (studyPlan.status === StudyPlanStatusEnum.FINAL) {
await generateCourseClassMeetingAttendances(studyPlan);
......
......@@ -18,6 +18,7 @@ const courseRouter = require('./routes/course');
const studyPlanRouter = require('./routes/study-plan');
const courseClassMeetingRouter = require('./routes/course-class-meeting');
const scoreRouter = require('./routes/score');
const optionRouter = require('./routes/option');
require('./hooks/study-plan');
require('./hooks/course-class');
......@@ -36,6 +37,7 @@ app.use(EndpointEnum.COURSE, courseRouter);
app.use(EndpointEnum.STUDY_PLAN, studyPlanRouter);
app.use(EndpointEnum.COURSE_CLASS_MEETING, courseClassMeetingRouter);
app.use(EndpointEnum.SCORE, scoreRouter);
app.use(EndpointEnum.OPTION, optionRouter);
let appServer = app.listen(port, host, () =>
console.log(`SIX backend service listening at http://${host}:${port}`),
......
const {
handleRequestWithInvalidRequestBody,
handleRequestWithInternalServerError,
handleRequestWithResourceItemNotFound,
checkRequiredParameter
} = require('../util/common');
const {
RequiredParameterUndefinedError,
NotExistError
} = require('../util/error');
const { getOption } = require('../util/db/option');
const { getCourse } = require('../util/db/course');
const createCourseMiddleware = async (req, res, next) => {
let newCourse;
let {
code,
name,
type,
credits,
defaultSemester,
shortSyllabus,
completeSyllabus,
outcomes,
curriculumYear,
majorId,
optionId
} = req.body;
try {
checkRequiredParameter({ code, name, type, credits, curriculumYear });
checkRequiredParameter({
oneOf: {
majorId,
optionId
}
});
if (optionId){
const option = await getOption({
where: {
id: optionId
}
});
majorId = option.majorId;
}
newCourse = {
code,
name,
type,
credits,
defaultSemester,
shortSyllabus,
completeSyllabus,
outcomes,
curriculumYear,
majorId,
};
if (optionId) {
newCourse.courseS2 = {
optionId
};
}
req.body.newCourse = newCourse;
next();
} catch (error) {
if (error instanceof RequiredParameterUndefinedError)
handleRequestWithInvalidRequestBody(res, error);
else handleRequestWithInternalServerError(res, error);
}
};
const updateCourseMiddleware = async (req, res, next) => {
const { id } = req.params;
try {
const {
code,
name,
type,
credits,
defaultSemester,
shortSyllabus,
completeSyllabus,
outcomes,
curriculumYear,
} = req.body;
checkRequiredParameter({
oneOf: {
code,
name,
type,
credits,
defaultSemester,
shortSyllabus,
completeSyllabus,
outcomes,
curriculumYear,
},
});
const course = await getCourse({
where: {
id,
}
});
const changeset = {};
if (code) {
changeset.code = code;
}
if (name) {
changeset.name = name;
}
if (type) {
changeset.type = type;
}
if (defaultSemester) {
changeset.defaultSemester = defaultSemester;
}
if (curriculumYear) {
changeset.curriculumYear = curriculumYear;
}
if (credits) {
changeset.credits = credits;
}
if (shortSyllabus) {
changeset.shortSyllabus = shortSyllabus;
}
if (completeSyllabus) {
changeset.completeSyllabus = completeSyllabus;
}
if (outcomes) {
changeset.outcomes = outcomes;
}
req.body.course = course;
req.body.changeset = changeset;
next();
} catch (error) {
if (error instanceof NotExistError) {
handleRequestWithResourceItemNotFound(res, error);
} else if (error instanceof RequiredParameterUndefinedError) {
handleRequestWithInvalidRequestBody(res, error);
} else {
handleRequestWithInternalServerError(res, error);
}
}
};
module.exports = {
createCourseMiddleware,
updateCourseMiddleware
}
\ No newline at end of file
const getAllOptionsMiddleware = async (req, res, next) => {
const {
majorId
} = req.query;
if (majorId){
req.opts = {
where: {
majorId
},
};
}
next();
};
module.exports = {
getAllOptionsMiddleware,
};
......@@ -5,7 +5,12 @@ const {
handleRequestWithInternalServerError,
handleRequestWithResourceItemNotFound,
} = require('../util/common');
const { getStudyPlanCourse } = require('../util/db/study-plan');
const {
getStudyPlanCourse,
getBulkStudyPlanCourses,
} = require('../util/db/study-plan');
const {
RequiredParameterUndefinedError,
NotExistError,
......@@ -57,6 +62,47 @@ const updateScoreMiddleware = async (req, res, next) => {
}
};
const updateScoreStatusMiddleware = async (req, res, next) => {
try {
const { courseClassId } = req.query;
const { scoreStatus } = req.body;
checkRequiredParameter({
courseClassId,
scoreStatus,
});
const studyPlanCourses = await getBulkStudyPlanCourses({
where: {
courseClassId,
},
});
if (studyPlanCourses.length === 0) throw new NotExistError();
const changeset = {
scoreStatus,
};
req.body = {
...req.body,
studyPlanCourses,
changeset,
};
next();
} catch (error) {
if (error instanceof NotExistError) {
handleRequestWithResourceItemNotFound(res, error);
} else if (error instanceof RequiredParameterUndefinedError) {
handleRequestWithInvalidRequestBody(res, error);
} else {
handleRequestWithInternalServerError(res, error);
}
}
};
module.exports = {
updateScoreMiddleware,
updateScoreStatusMiddleware,
};