initial commit
This commit is contained in:
commit
28a1ab8841
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
PORT=
|
||||
DATABASE_URL=
|
130
.gitignore
vendored
Normal file
130
.gitignore
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
5474
package-lock.json
generated
Normal file
5474
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "course-system",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"start": "node src/main.js",
|
||||
"dev": "nodemon src/main.js",
|
||||
"db-push": "prisma db push"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^8.3.0",
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@fastify/jwt": "^6.3.2",
|
||||
"@fastify/multipart": "^7.3.0",
|
||||
"@fastify/reply-from": "^8.3.0",
|
||||
"@fastify/static": "^6.5.0",
|
||||
"@fastify/swagger": "^8.0.0",
|
||||
"@fastify/swagger-ui": "^1.1.0",
|
||||
"@prisma/client": "^4.5.0",
|
||||
"cuid": "^2.1.8",
|
||||
"dotenv": "^16.0.3",
|
||||
"fastify": "^4.9.2",
|
||||
"fastify-keycloak-adapter": "^1.6.2",
|
||||
"minio": "^7.0.32"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.20",
|
||||
"prisma": "^4.5.0"
|
||||
}
|
||||
}
|
74
prisma/schema.prisma
Normal file
74
prisma/schema.prisma
Normal file
@ -0,0 +1,74 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String
|
||||
password String
|
||||
|
||||
organization Organization? @relation(fields: [organizationId], references: [id])
|
||||
organizationId String?
|
||||
|
||||
purchasedCourses Course[]
|
||||
}
|
||||
|
||||
model Teacher {
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
first_name String
|
||||
last_name String
|
||||
email String @unique
|
||||
password String
|
||||
|
||||
organizations Organization[]
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Organization {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
|
||||
teacherId String
|
||||
teacher Teacher @relation(fields: [teacherId], references: [id])
|
||||
|
||||
courses Course[]
|
||||
|
||||
users User[]
|
||||
}
|
||||
|
||||
model Course {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
price Int
|
||||
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
organizationId String
|
||||
|
||||
lessons Lesson[]
|
||||
users User[]
|
||||
|
||||
@@unique([name, organizationId])
|
||||
}
|
||||
|
||||
model Lesson {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
description String
|
||||
type LessonTypes
|
||||
|
||||
course Course @relation(fields: [courseId], references: [id])
|
||||
courseId String
|
||||
}
|
||||
|
||||
enum LessonTypes {
|
||||
VIDEO
|
||||
AUDIO
|
||||
TEXT
|
||||
}
|
87
src/api/controllers/api.auth.cotroller.js
Normal file
87
src/api/controllers/api.auth.cotroller.js
Normal file
@ -0,0 +1,87 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
module.exports = new class ApiController {
|
||||
async login(req, reply) {
|
||||
const user = await prisma.teacher.findFirst({
|
||||
where: {
|
||||
username: req.body.username,
|
||||
password: req.body.password
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return reply
|
||||
.code(401)
|
||||
.send("Incorrect username/password combo");
|
||||
}
|
||||
else {
|
||||
const token = await reply.jwtSign(user)
|
||||
|
||||
reply
|
||||
.setCookie('token', token, {
|
||||
path: '/',
|
||||
secure: false,
|
||||
httpOnly: true,
|
||||
sameSite: 'strict'
|
||||
})
|
||||
.code(200)
|
||||
.send(user);
|
||||
}
|
||||
}
|
||||
|
||||
async register(req, reply) {
|
||||
const userWithSameInfo = await prisma.teacher.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
username: {
|
||||
equals: req.body.username
|
||||
}
|
||||
},
|
||||
{
|
||||
email: {
|
||||
equals: req.body.email
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (userWithSameInfo) {
|
||||
if (userWithSameInfo.username === req.body.username)
|
||||
return reply
|
||||
.code(409)
|
||||
.send({ message: "Username already taken" })
|
||||
if (userWithSameInfo.email === req.body.email)
|
||||
return reply
|
||||
.code(409)
|
||||
.send({ message: "Email already taken" })
|
||||
}
|
||||
|
||||
const data = await prisma.teacher.create({
|
||||
data: {
|
||||
username: req.body.username,
|
||||
first_name: req.body.first_name,
|
||||
last_name: req.body.last_name,
|
||||
email: req.body.email,
|
||||
password: req.body.password
|
||||
}
|
||||
});
|
||||
|
||||
reply
|
||||
.setCookie('token', token, {
|
||||
path: '/',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: true
|
||||
})
|
||||
.code(200)
|
||||
.send(data)
|
||||
return
|
||||
}
|
||||
|
||||
async me(req, reply) {
|
||||
return req.user
|
||||
}
|
||||
}();
|
10
src/api/controllers/api.controller.js
Normal file
10
src/api/controllers/api.controller.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = new class ApiController {
|
||||
async lessons(req, reply) {
|
||||
reply.header('Content-Type', 'application/json');
|
||||
let data = await squidexHelper.contents.query('courses');
|
||||
reply.send(data.items.map(i => {
|
||||
return {id: i.id, ...i.data}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}();
|
73
src/api/controllers/api.courses.controller.js
Normal file
73
src/api/controllers/api.courses.controller.js
Normal file
@ -0,0 +1,73 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
module.exports = new class ApiController {
|
||||
async createCourse(req, reply) {
|
||||
let course = prisma.course.create({
|
||||
data: {
|
||||
name: req.body.name,
|
||||
price: req.body.price,
|
||||
organizationId: req.body.organizationId
|
||||
}
|
||||
});
|
||||
|
||||
return course;
|
||||
}
|
||||
|
||||
async getCourse(req, reply) {
|
||||
let course = prisma.course.findUnique({
|
||||
where: {
|
||||
id: req.params.courseId
|
||||
},
|
||||
include: {
|
||||
organization: true,
|
||||
_count: {
|
||||
select: {
|
||||
lessons: true
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return course;
|
||||
}
|
||||
|
||||
async getCourses(req, reply) {
|
||||
let courses = await prisma.course.findMany({
|
||||
where: {
|
||||
organizationId: req.query.organization,
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
lessons: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let organization = await prisma.organization.findUnique({
|
||||
where: {
|
||||
id: courses[0].organizationId
|
||||
}
|
||||
})
|
||||
|
||||
return { ...organization, courses: courses };
|
||||
}
|
||||
|
||||
async deleteCourse(req, reply) {
|
||||
try {
|
||||
let orgs = await prisma.course.deleteMany({
|
||||
where: {
|
||||
id: req.query.id,
|
||||
organization: { is: { teacherId: req.user.id } }
|
||||
}
|
||||
});
|
||||
|
||||
return reply.send(orgs)
|
||||
}
|
||||
catch (err) {
|
||||
return reply.code(500).send(err);
|
||||
}
|
||||
}
|
||||
}();
|
72
src/api/controllers/api.lesson.controller.js
Normal file
72
src/api/controllers/api.lesson.controller.js
Normal file
@ -0,0 +1,72 @@
|
||||
const { PrismaClient, LessonTypes } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const { getMinio } = require('../helpers/globalData.helper')
|
||||
|
||||
module.exports = new class ApiController {
|
||||
async createLesson(req, reply) {
|
||||
try {
|
||||
let body = req.body;
|
||||
let organization = prisma.course.findFirst({
|
||||
where: {
|
||||
id: body.courseId,
|
||||
course: {
|
||||
id: req.user.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(!organization) {
|
||||
return reply.code(400).send({ message: "Could not find course." })
|
||||
}
|
||||
let lesson = prisma.lesson.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
courseId: body.courseId,
|
||||
type: LessonTypes[body.type.toUpperCase()]
|
||||
}
|
||||
});
|
||||
|
||||
return lesson;
|
||||
}
|
||||
catch (err) {
|
||||
return reply.code(500).send({ error: err })
|
||||
}
|
||||
}
|
||||
|
||||
async getLesson(req, reply) {
|
||||
try {
|
||||
let lesson = prisma.lesson.findFirst({
|
||||
where: {
|
||||
id: req.params.lessonId,
|
||||
course: { is: { organization: { teacherId: req.user.id } }}
|
||||
},
|
||||
include: {
|
||||
course: true
|
||||
}
|
||||
});
|
||||
|
||||
if (lesson) return lesson
|
||||
else return reply.code(404).send({ message: `Lesson with id ${req.params.lessonId} not found.` })
|
||||
}
|
||||
catch (err) {
|
||||
return reply.code(500).send({ error: err })
|
||||
}
|
||||
}
|
||||
|
||||
async getLessons(req, reply) {
|
||||
try {
|
||||
let lessons = prisma.lesson.findMany({
|
||||
where: {
|
||||
course: {is: {id: req.query.courseId, organization: { teacherId: req.user.id } }}
|
||||
}
|
||||
});
|
||||
|
||||
return lessons
|
||||
}
|
||||
catch (err) {
|
||||
return reply.code(500).send({ error: err })
|
||||
}
|
||||
}
|
||||
}();
|
82
src/api/controllers/api.organization.controller.js
Normal file
82
src/api/controllers/api.organization.controller.js
Normal file
@ -0,0 +1,82 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
module.exports = new class ApiController {
|
||||
async createOrganization(req, reply) {
|
||||
let possibleTaken = await prisma.organization.findUnique({
|
||||
where: {
|
||||
name: req.body.name
|
||||
}
|
||||
})
|
||||
|
||||
if (possibleTaken) {
|
||||
return reply
|
||||
.code(409)
|
||||
.send({ message: "Organization name already taken" })
|
||||
}
|
||||
|
||||
let org = prisma.organization.create({
|
||||
data: {
|
||||
name: req.body.name,
|
||||
teacherId: req.user.id
|
||||
}
|
||||
});
|
||||
|
||||
return org;
|
||||
}
|
||||
|
||||
async getOrganizationInfo(req, reply) {
|
||||
let org = await prisma.organization.findUnique({
|
||||
where: {
|
||||
id: req.params.organizationId
|
||||
},
|
||||
include: {
|
||||
courses: req.query.courses || false
|
||||
}
|
||||
});
|
||||
|
||||
if (!org) {
|
||||
return reply
|
||||
.code(404)
|
||||
.send({ message: `Organization with ID ${req.params.id} not found` });
|
||||
}
|
||||
return org;
|
||||
}
|
||||
|
||||
async getAllOrganizations(req, reply) {
|
||||
try {
|
||||
let orgs = await prisma.organization.findMany({
|
||||
where: {
|
||||
teacherId: req.user.id
|
||||
}
|
||||
});
|
||||
|
||||
return reply.send(orgs)
|
||||
}
|
||||
catch (err) {
|
||||
return reply.code(500).send(err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteOrganization(req, reply) {
|
||||
try {
|
||||
let org = await prisma.organization.deleteMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
name: req.query.id,
|
||||
},
|
||||
{
|
||||
teacherId: req.user.id
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
return org
|
||||
}
|
||||
catch (err) {
|
||||
return reply.code(500).send(err)
|
||||
}
|
||||
}
|
||||
}();
|
20
src/api/helpers/globalData.helper.js
Normal file
20
src/api/helpers/globalData.helper.js
Normal file
@ -0,0 +1,20 @@
|
||||
var _redis = null;
|
||||
var _minio = null;
|
||||
|
||||
exports.getRedis = function() {
|
||||
return _redis;
|
||||
};
|
||||
|
||||
exports.setRedis = function(redis) {
|
||||
//validate the data...
|
||||
_redis = redis;
|
||||
};
|
||||
|
||||
exports.getMinio = function() {
|
||||
return _minio;
|
||||
};
|
||||
|
||||
exports.setMinio = function(minio) {
|
||||
//validate the data...
|
||||
_minio = minio;
|
||||
};
|
10
src/api/helpers/password.helper.js
Normal file
10
src/api/helpers/password.helper.js
Normal file
@ -0,0 +1,10 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
module.exports = {
|
||||
encrypt: (password) => {
|
||||
return bcrypt.hashSync(password, bcrypt.genSaltSync(10));
|
||||
},
|
||||
compare: (password, hash) => {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
}
|
||||
}
|
10
src/api/helpers/validator.helper.js
Normal file
10
src/api/helpers/validator.helper.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
email: (email) => {
|
||||
// validate email format
|
||||
return !!email.match(/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
|
||||
},
|
||||
password: (password) => {
|
||||
// validate password format
|
||||
return !!password.match(/^[a-zA-Z\d!@#$&\.]{6,}$/);
|
||||
}
|
||||
}
|
19
src/api/middlewares/auth.middleware.js
Normal file
19
src/api/middlewares/auth.middleware.js
Normal file
@ -0,0 +1,19 @@
|
||||
// const authHelper = require('../helpers/auth.helper');
|
||||
|
||||
module.exports = {
|
||||
auth: async (req, reply) => {
|
||||
try {
|
||||
await req.jwtVerify()
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
return reply.code(401).send({ message: "Not authenticated" })
|
||||
}
|
||||
},
|
||||
|
||||
isAdmin: async (req, res, next) => {
|
||||
if (!req.user.is_admin) {
|
||||
return res.status(403).send({ error: 'You are not allowed to access this resource' });
|
||||
}
|
||||
},
|
||||
};
|
189
src/api/routes/api.auth.routes.js
Normal file
189
src/api/routes/api.auth.routes.js
Normal file
@ -0,0 +1,189 @@
|
||||
const controller = require('../controllers/api.auth.cotroller');
|
||||
const auth = require('../middlewares/auth.middleware')
|
||||
|
||||
function router(fastify, options, next) {
|
||||
fastify.post('/register', {
|
||||
schema: {
|
||||
tags: ['auth'],
|
||||
summary: 'Register account',
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z-_1-9]{6,12}$'
|
||||
},
|
||||
first_name: {
|
||||
type: 'string'
|
||||
},
|
||||
last_name: {
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
format: 'email'
|
||||
},
|
||||
password: {
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
},
|
||||
description: 'Endpoint to register a new user',
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string'
|
||||
},
|
||||
username: {
|
||||
type: 'string'
|
||||
},
|
||||
first_name: {
|
||||
type: 'string'
|
||||
},
|
||||
last_name: {
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
format: 'email'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
409: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
handler: controller.register
|
||||
});
|
||||
|
||||
fastify.post('/login', {
|
||||
schema: {
|
||||
tags: ['auth'],
|
||||
summary: 'Login to existing account',
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z-_.1-9]{6,16}$'
|
||||
},
|
||||
password: {
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
},
|
||||
description: 'Endpoint to register a new user',
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string'
|
||||
},
|
||||
username: {
|
||||
type: 'string'
|
||||
},
|
||||
first_name: {
|
||||
type: 'string'
|
||||
},
|
||||
last_name: {
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
format: 'email'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
}
|
||||
},
|
||||
409: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
handler: controller.login
|
||||
});
|
||||
|
||||
fastify.get('/me', {
|
||||
schema: {
|
||||
tags: ['auth'],
|
||||
summary: 'Get User info',
|
||||
description: 'Endpoint get currently logged in user\'s account info from their jwt',
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string'
|
||||
},
|
||||
username: {
|
||||
type: 'string'
|
||||
},
|
||||
first_name: {
|
||||
type: 'string'
|
||||
},
|
||||
last_name: {
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
format: 'email'
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
iat: {
|
||||
type: 'number'
|
||||
}
|
||||
}
|
||||
},
|
||||
409: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
onRequest: [auth.auth],
|
||||
handler: controller.me
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
function: router,
|
||||
options: {
|
||||
prefix: '/api/auth'
|
||||
}
|
||||
}
|
99
src/api/routes/api.courses.routes.js
Normal file
99
src/api/routes/api.courses.routes.js
Normal file
@ -0,0 +1,99 @@
|
||||
const controller = require('../controllers/api.courses.controller');
|
||||
const auth = require('../middlewares/auth.middleware');
|
||||
|
||||
|
||||
function router(fastify, options, next) {
|
||||
fastify.get('/:courseId', {
|
||||
schema: {
|
||||
tags: ['course'],
|
||||
summary: 'Get a course',
|
||||
description: 'Retrieve course data by ID',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
handler: controller.getCourse
|
||||
})
|
||||
|
||||
fastify.post('/', {
|
||||
schema: {
|
||||
tags: ['course'],
|
||||
summary: 'Create a course',
|
||||
description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
handler: controller.createCourse
|
||||
});
|
||||
|
||||
fastify.get('/', {
|
||||
schema: {
|
||||
tags: ['course'],
|
||||
summary: 'Get all courses from organization',
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
organization: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
handler: controller.getCourses
|
||||
});
|
||||
|
||||
fastify.delete('/', {
|
||||
schema: {
|
||||
tags: ['course'],
|
||||
summary: 'Delete a course by ID',
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [auth.auth],
|
||||
handler: controller.deleteCourse
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
function: router,
|
||||
options: {
|
||||
prefix: '/api/course'
|
||||
}
|
||||
}
|
92
src/api/routes/api.lesson.routes.js
Normal file
92
src/api/routes/api.lesson.routes.js
Normal file
@ -0,0 +1,92 @@
|
||||
const controller = require('../controllers/api.lesson.controller');
|
||||
const { auth } = require('../middlewares/auth.middleware');
|
||||
|
||||
function router(fastify, options, next) {
|
||||
fastify.get('/:lessonId', {
|
||||
schema: {
|
||||
tags: ['lessons'],
|
||||
summary: 'Test endpoint',
|
||||
description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [auth],
|
||||
handler: controller.getLesson
|
||||
});
|
||||
|
||||
fastify.get('/', {
|
||||
schema: {
|
||||
tags: ['lessons'],
|
||||
summary: 'Get all lessons in a course',
|
||||
description: 'Get lessons from a course, whose id is provided in the query',
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
courseId: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [auth],
|
||||
handler: controller.getLessons
|
||||
});
|
||||
|
||||
fastify.post('/', {
|
||||
schema: {
|
||||
tags: ['lessons'],
|
||||
summary: 'Create a lesson',
|
||||
description: 'Endpoint to create a lesson',
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string'
|
||||
},
|
||||
description: {
|
||||
type: 'string'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['video', 'audio', 'text']
|
||||
}
|
||||
}
|
||||
}
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [auth],
|
||||
handler: controller.createLesson
|
||||
})
|
||||
|
||||
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
function: router,
|
||||
options: {
|
||||
prefix: '/api/lesson'
|
||||
}
|
||||
}
|
84
src/api/routes/api.organization.routes.js
Normal file
84
src/api/routes/api.organization.routes.js
Normal file
@ -0,0 +1,84 @@
|
||||
const controller = require('../controllers/api.organization.controller');
|
||||
const authMiddleware = require('../middlewares/auth.middleware')
|
||||
|
||||
function router(fastify, options, next) {
|
||||
fastify.get('/:organizationId', {
|
||||
schema: {
|
||||
tags: ['organization'],
|
||||
summary: 'Test endpoint',
|
||||
description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
handler: controller.getOrganizationInfo
|
||||
});
|
||||
|
||||
fastify.post('/', {
|
||||
schema: {
|
||||
tags: ['organization'],
|
||||
summary: 'Create a new organization',
|
||||
description: 'Endpoint for creating a new organization by providing a name',
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z-_]+$'
|
||||
}
|
||||
}
|
||||
}
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object'
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [authMiddleware.auth],
|
||||
handler: controller.createOrganization
|
||||
});
|
||||
|
||||
fastify.get('/', {
|
||||
schema: {
|
||||
tags: ['organization'],
|
||||
summary: 'Get all organizations',
|
||||
description: 'Get all organizations of currently signed in user',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object'
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [authMiddleware.auth],
|
||||
handler: controller.getAllOrganizations
|
||||
});
|
||||
|
||||
fastify.delete('/', {
|
||||
schema: {
|
||||
tags: ['organization'],
|
||||
summary: 'Get all organizations',
|
||||
description: 'Get all organizations of currently signed in user',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object'
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preHandler: [authMiddleware.auth],
|
||||
handler: controller.deleteOrganization
|
||||
})
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
function: router,
|
||||
options: {
|
||||
prefix: '/api/organization'
|
||||
}
|
||||
}
|
43
src/api/routes/api.routes.js
Normal file
43
src/api/routes/api.routes.js
Normal file
@ -0,0 +1,43 @@
|
||||
const controller = require('../controllers/api.controller');
|
||||
|
||||
function router(fastify, options, next) {
|
||||
// fastify.get('/', {
|
||||
// schema: {
|
||||
// tags: ['test'],
|
||||
// summary: 'Test endpoint',
|
||||
// description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object',
|
||||
// properties: {
|
||||
// message: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// handler: controller.root
|
||||
// });
|
||||
|
||||
fastify.get('/query', {
|
||||
schema: {
|
||||
tags: ['test'],
|
||||
summary: 'Test endpoint',
|
||||
description: 'Endpoint to test the server',
|
||||
// response: {
|
||||
// 200: {
|
||||
// type: 'object'
|
||||
// }
|
||||
// },
|
||||
},
|
||||
handler: controller.lessons
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
function: router,
|
||||
options: {
|
||||
prefix: '/'
|
||||
}
|
||||
}
|
87
src/loaders/fastifyLoader.js
Normal file
87
src/loaders/fastifyLoader.js
Normal file
@ -0,0 +1,87 @@
|
||||
function main() {
|
||||
require('dotenv').config();
|
||||
const globalData = require('../api/helpers/globalData.helper')
|
||||
const minio = require('minio')
|
||||
|
||||
var minioClient = new minio.Client({
|
||||
endPoint: 's3-s3.omersabic.com',
|
||||
useSSL: true,
|
||||
accessKey: 'sedj2oatulc90nDv',
|
||||
secretKey: 'JVoEdWA5gvSP63ENIySv8yW9P9WH5xx5'
|
||||
});
|
||||
|
||||
globalData.setMinio(minioClient)
|
||||
|
||||
const fastify = require('fastify')({
|
||||
logger: false,
|
||||
ignoreTrailingSlash: true,
|
||||
cors: true
|
||||
});
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// Initialize fastify plugins
|
||||
fastify.register(require('@fastify/cors'), {
|
||||
origin: 'http://localhost:3001',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true
|
||||
});
|
||||
|
||||
fastify.register(require('@fastify/swagger'), {
|
||||
swagger: {
|
||||
info: {
|
||||
title: 'API',
|
||||
description: 'API documentation',
|
||||
version: '1.0.0'
|
||||
},
|
||||
externalDocs: {
|
||||
url: 'https://swagger.io',
|
||||
description: 'Find more info here'
|
||||
},
|
||||
host: 'localhost:3000',
|
||||
schemes: ['http'],
|
||||
consumes: ['application/json'],
|
||||
produces: ['application/json']
|
||||
}
|
||||
});
|
||||
|
||||
fastify.register(require('@fastify/swagger-ui'), {
|
||||
routePrefix: '/api/v1/docs',
|
||||
docExpansion: 'full',
|
||||
deepLinking: false
|
||||
})
|
||||
|
||||
fastify.register(require('@fastify/static'), {
|
||||
root: path.join(__dirname, '../api/public'),
|
||||
prefix: '/public/'
|
||||
});
|
||||
|
||||
fastify.register(require('@fastify/jwt'), {
|
||||
// secret: (Math.random() + 1).toString(36).substring(2),
|
||||
secret: 'secret',
|
||||
cookie: {
|
||||
cookieName: 'token',
|
||||
signed: false
|
||||
}
|
||||
});
|
||||
|
||||
fastify.register(require('@fastify/cookie'))
|
||||
|
||||
// fastify.addHook('preValidation', (request, reply, done) => {
|
||||
// request.body = JSON.parse(request.body);
|
||||
// done();
|
||||
// })
|
||||
|
||||
// Load all routes
|
||||
fs.readdirSync(path.join(__dirname, '../api/routes')).forEach(file => {
|
||||
if (!file.endsWith('.routes.js')) return;
|
||||
const route = require(path.join(__dirname, '../api/routes', file));
|
||||
fastify.register(route.function, route.options);
|
||||
});
|
||||
|
||||
|
||||
|
||||
return fastify
|
||||
}
|
||||
|
||||
module.exports = main;
|
19
src/main.js
Normal file
19
src/main.js
Normal file
@ -0,0 +1,19 @@
|
||||
require('dotenv').config()
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Running the fastify loader
|
||||
const fastify = require('./loaders/fastifyLoader')();
|
||||
|
||||
async function start() {
|
||||
fastify.listen({ port: PORT, address: '0.0.0.0' }, (err, address) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.info(`server listening on ${address}`);
|
||||
console.info(`API Docs running on ${address}/api/v1/docs`)
|
||||
});
|
||||
}
|
||||
|
||||
start();
|
Loading…
Reference in New Issue
Block a user