diff --git a/Documentation/Diagram/ERDiagram.png b/Documentation/Diagram/ERDiagram.png
new file mode 100644
index 00000000..ffa9cb55
Binary files /dev/null and b/Documentation/Diagram/ERDiagram.png differ
diff --git a/Documentation/Diagram/NewUMLDiagram.png b/Documentation/Diagram/NewUMLDiagram.png
new file mode 100644
index 00000000..c0c312da
Binary files /dev/null and b/Documentation/Diagram/NewUMLDiagram.png differ
diff --git a/Documentation/PrototypeScreenshots/prototypeMobile.png b/Documentation/PrototypeScreenshots/prototypeMobile.png
new file mode 100644
index 00000000..31ec95b3
Binary files /dev/null and b/Documentation/PrototypeScreenshots/prototypeMobile.png differ
diff --git a/Documentation/README.md b/Documentation/README.md
new file mode 100644
index 00000000..a4f81fc6
--- /dev/null
+++ b/Documentation/README.md
@@ -0,0 +1,195 @@
+# Development Notes
+
+- [Backend Structure](#backend-structure)
+- [Frontend Structure](#frontend-structure)
+- [Administration Structure](#administration-structure)
+
+# Backend Structure
+
+The main components we are going to dive in are:
+- Controllers
+- Extra Fields
+- Filtration
+- Encryption
+
+## Controllers
+
+Each table has a custom controller by which each have their own methods.
+The main controller to look at is the Application Controller.
+
+This controller contains all methods used for the Extra Fields as well as helper methods for other controllers to deny or permit accesd to users and paginate extra fields.
+
+These are explicitly documented inside the code to make it easy to find and follow.
+
+An example of what you can find in the Application Controller:
+
+```ruby
+ # UTILS
+
+ # Returns a ```UserRole``` entry for the currently existing admin role.
+ def get_admin_role
+ return UserRole.where(name: "eduapp-admin").first
+ end
+
+ # Returns the user's respective ```UserRole``` information.
+ def get_user_roles(user_id = @current_user)
+ return UserRole.find(UserInfo.where(user_id: user_id).first.user_role_id)
+ end
+
+ # Serializes each element in an array.
+ def serialize_each(array, iExcept = [], iInclude = [])
+ s = []
+ array.each do |item|
+ s.push(item.serializable_hash(except: iExcept, include: iInclude))
+ end
+ return s
+ end
+
+ # Paginates an ```ActiveRecord``` query.
+ def query_paginate(query, page, limit = 10)
+ page = Integer(page)
+ if page - 1 < 0
+ return { :error => "Page cannot be less than 1" }
+ end
+ return { :current_page => query.limit(limit).offset((page - 1) * limit), :total_pages => (query.count.to_f / limit).ceil, :page => page }
+ end
+
+ # Paginates an array.
+ def array_paginate(array, page, limit = 10)
+ return array.slice(Integer(page) > 0 ? Integer(page) - 1 : 0, limit)
+ end
+
+ # A parser made to correctly return a decoded order filter.
+ def parse_filter_order(order)
+ order = JSON.parse(Base64.decode64(order))
+ return { order["field"] => order["order"] == "asc" ? :asc : :desc }
+ end
+```
+
+## Extra Fields
+
+Extra fields are made to provide access to custom information each Institution needs. Examples of this can be student debts, detention counts and custom notes.
+
+The tables elegible for extra fields are Users, Courses, Sessions, Institutions, Resources and Subjects.
+
+Helper methods for these are located as well in the Application Controller.
+
+## Filtration
+
+Each controller has it's own way of filtrating. Every controller follows a pattern:
+
+- Check if any extra fields exists and try to filter by that
+- If there's any filtered extra fields, filter with that by the other main fields, if not, filter by any value in the database.
+- Order by ascending if no order is specified
+- Return the first page if not page of information is specified
+
+## Encryption
+
+EduApp has it's own encryption utility inside ```lib/edu_app_utils/encrypt_utils.rb```.
+
+Here, you will find different methods used to encrypt and decrypt messages for the messaging part of EduApp.
+
+# Frontend Structure
+
+The main topics we will cover about how the frontend works:
+- It's PWA
+ - How we save information offline
+- It's different utilities
+- Custom hooks
+
+## The PWA
+
+The PWA has two main components necessary:
+- ```service-worker.js```
+- ```serviceWorkerRegistration.js```
+
+These two components are necessary for registering the Service Worker to the browser so that it works correctly.
+
+This all depends on WorkBox to register and save to browser cache the compiled assets necessary to run the app.
+
+To save the user's important information for offline use, we use the ```OfflineManager.js``` utility. This helps us saving and updating any information when needed so that we have always the most updated user information at all types.
+
+## Utilities
+
+The different types of utilities we use are:
+- ```OfflineManager```
+- ```IDBManager```
+- ```MediaFixer```
+- ```FirebaseStorage```
+- ```UrlPrefixer```
+- ActionCable websockets
+
+### OfflineManager
+
+The offline manager contains various functions used to save and load information for further use when in offline mode.
+
+The workflow used is to always store information offline when received from the API, and if the information is different then save it, if not there's no need to save it.
+
+```getOfflineUser()``` is the main function you will want to use for accessing a user's information online or offline. It all depends on the information in it.
+
+### IDBManager
+
+The IDBManager uses a library @jakearchibald called ```idb-keyval``` which converts all IndexedDB functions into asynchronous, for ease of use.
+
+The class contains methods used as a Singleton to load a store and make transactions.
+
+### MediaFixer
+
+MediaFixer is a small function used to correct files from Rails ActiveStorage from not displaying the correct URL. It changes the ```localhost:3000``` domain that ActiveStorage sets to the back domain passed inside the ```.env``` file.
+
+### FirebaseStorage
+
+This is used to upload any files used to a Firebase Storage bucket in case ActiveStorage fails. As of now, only profile images are uploaded to Firebase.
+
+### UrlPrefixer
+
+This is used in case you change your domains base name to a new mount point.
+
+Example: default - eduapp.com
+Changed basename: eduapp.com/app
+
+This functions converts all urls passed to use the new basename you specify. This was previously used for ease of use, but it's not necessary anymore.
+
+### ActionCable Websockets
+
+Rails ActionCable needs a special JS library from the same developers to connect to each other.
+
+To provide an easy interface, we created a class called ```ACManager``` which is an abstract class used to create new Websocket classes to manage the different websocket channels in the backend.
+
+
+# Administration Structure
+
+The main topics we will cover:
+
+- Initialization of the administration panel and database
+- Login
+- Components
+
+## Initialization
+
+When creating and intializing the backend, frontend and administration for the first time, you will be prompted to create a new administrator user. Once done, you may login with your new administration account.
+
+Once in, you will only be able to see to sections:
+- Institutions
+- User Permissions/Roles
+
+To unlock the other administrative sections, you must first create your an Institution. Once created, you may access the other sections to setup your educational environment.
+
+## Login
+
+Only users who have an ```admin-query``` or ```admin``` role are able to have access to the web app by default. You may change this inside the authentication service inside ```services```.
+
+## Components
+
+The administration panel constitutes of one main parent view with rotating child components. This means that ControlPanel is the parent view that never changes, and the only components that change are the inside/body of the main page.
+
+Each component behaves differently since they all manage different sets of data according to their table, but they share common features:
+- Creation
+- Deletion
+- Updates
+- Filtration
+- Extra Fields
+- Extra Fields Filtration
+- Order By
+
+Chat messages are not displayed to manage to keep the users privacy safe.
diff --git a/Documentation/Usability/colorsExample.png b/Documentation/Usability/colorsExample.png
new file mode 100644
index 00000000..86b1a049
Binary files /dev/null and b/Documentation/Usability/colorsExample.png differ
diff --git a/Documentation/Usability/darkModeAfter.png b/Documentation/Usability/darkModeAfter.png
new file mode 100644
index 00000000..4a94e5c0
Binary files /dev/null and b/Documentation/Usability/darkModeAfter.png differ
diff --git a/Documentation/Usability/darkModeBefore.png b/Documentation/Usability/darkModeBefore.png
new file mode 100644
index 00000000..068a3746
Binary files /dev/null and b/Documentation/Usability/darkModeBefore.png differ
diff --git a/Documentation/Usability/darkModeResources.png b/Documentation/Usability/darkModeResources.png
new file mode 100644
index 00000000..b8fea1fc
Binary files /dev/null and b/Documentation/Usability/darkModeResources.png differ
diff --git a/Documentation/Usability/desktopPreview.png b/Documentation/Usability/desktopPreview.png
new file mode 100644
index 00000000..717e6abb
Binary files /dev/null and b/Documentation/Usability/desktopPreview.png differ
diff --git a/Documentation/Usability/loadingAnimationFrame.png b/Documentation/Usability/loadingAnimationFrame.png
new file mode 100644
index 00000000..5597321b
Binary files /dev/null and b/Documentation/Usability/loadingAnimationFrame.png differ
diff --git a/Documentation/Usability/mobilePreview.png b/Documentation/Usability/mobilePreview.png
new file mode 100644
index 00000000..3cdcbc72
Binary files /dev/null and b/Documentation/Usability/mobilePreview.png differ
diff --git a/Documentation/Usability/resourceOpenedMobile.png b/Documentation/Usability/resourceOpenedMobile.png
new file mode 100644
index 00000000..59fdfac6
Binary files /dev/null and b/Documentation/Usability/resourceOpenedMobile.png differ
diff --git a/Documentation/Usability/resourcesForm.png b/Documentation/Usability/resourcesForm.png
new file mode 100644
index 00000000..ad6950ec
Binary files /dev/null and b/Documentation/Usability/resourcesForm.png differ
diff --git a/Documentation/Usability/signUpForm.png b/Documentation/Usability/signUpForm.png
new file mode 100644
index 00000000..d33afef2
Binary files /dev/null and b/Documentation/Usability/signUpForm.png differ
diff --git a/Documentation/Usability/signUpForm2.png b/Documentation/Usability/signUpForm2.png
new file mode 100644
index 00000000..202cb63c
Binary files /dev/null and b/Documentation/Usability/signUpForm2.png differ
diff --git a/Documentation/Usability/signUpForm3.png b/Documentation/Usability/signUpForm3.png
new file mode 100644
index 00000000..cc4cdb3d
Binary files /dev/null and b/Documentation/Usability/signUpForm3.png differ
diff --git a/Documentation/Usability/signUpFormDesktop.png b/Documentation/Usability/signUpFormDesktop.png
new file mode 100644
index 00000000..35a1e4a0
Binary files /dev/null and b/Documentation/Usability/signUpFormDesktop.png differ
diff --git a/Documentation/UseCases.jpg b/Documentation/UseCases.jpg
new file mode 100644
index 00000000..6c6ad5d5
Binary files /dev/null and b/Documentation/UseCases.jpg differ
diff --git a/Documentation/old-readme.txt b/Documentation/old-readme.txt
new file mode 100644
index 00000000..adb3aab8
--- /dev/null
+++ b/Documentation/old-readme.txt
@@ -0,0 +1,313 @@
+
+ Eduapp emerges after the covid 19 pandemic, as the answer to the challenges that this entails.
+
+
+ It's an european project, co-funded by erasmus+ programme
+
+
Partners
+
Fundatia Ecologica Green - Romania, Instituto Politécnico de Santarém - Portugal, Stichting Landstede - Netherlands, SOSU OSTJYLLAND - Denmark and IES El Rincón - Spain
+
Objectives
+
Faciliate and increase the communication between school, students and teachers by developing an application, EduApp, free and open source, customised for each partner school.
+
+
+Eduapp has used postgresQL as database, ruby on rails as server-side web application framework.
+
+
INSTITUTIONS(ID, name)
+
COURSE(ID, name, institution_id*)
+
USERS(ID, email, password)
+
USER_INFOS(ID, user_name, profile_img, user_id*, is_admin, googleId, isLoggedWithGoogle)
+
CHAT_BASES(ID, name, is_group)
+
CHAT_BASE_INFOS(ID, chat_base_id*, chat_img)
+
CHAT_PARTICIPANTS(ID, chat_base_id*, user_id*, is_chat_admin)
+
CHAT_MESSAGES(ID, user_id*, message, send_date)
+
SUBJECTS(ID, name, teacherInCharge, description, color, course_id*)
+
RESOURCES(ID, name, description, firstfile, secondfile, thirdfile, user_id*, course_id*)
+
SESSIONS(ID, name, start_date, end_date, streaming_platform, resources_platform, chat_base_id*, subject_id*)
+
CALENDAR_EVENTS(ID, title, start_date, end_date, description, is_global, user_id*)
+
+
+- A user can enroll into a course, and will access all subjects linked to the course
+ First, you must install the programs. Now you have to clone the project and used this commands.
+After using these commands, you need to look inside the config folder and find the database.yml. There, you must change the database password and put your postgreSQL password, otherwise, the database will not work.
+
+To stop the server you have to use CTRL + C.
+This is how eduapp started but some visual changes were made.
+ First, you must install the programs. Now you have to clone the project and used this commands.
+To stop the server you have to use CTRL + C.
+
+This app is being developed in both platforms, mobile and desktop as a hybrid app.
+If an administrator has signed up an account for you, you must log in, otherwise you won't have access to the application.
+Eduapp has four type of user, depending on it the permissions you will have.
+View your account's calendar, resources, upcoming sessions, and chats.
+They have the same functionality as students, but they have permission to create global events in the calendar, create resources and chats.
+They have control over the users of their school, they manage accounts, the calendar and the sessions.
+They have full access to the management of the app.
+
+
+To see any session you must been enrolled in at least one course, otherwise, this page will be empty.
+Then you must select the course you will like to see the sessions of.
+To see any resource you must been enrolled in at least one course, otherwise, this page will be empty.
+You must select the course to see the resources of that course, you can also filter the resources with the search bar placed at the top of the page.
+If you are a teacher of that course you will see a plus icon, who provide you the access to a form modal, to add a new resource.
+
+
+We have used orange and blue as principal colors , then we use a different gray scales and white.
+After an intensive search we have found the perfect combination with orange and blue as principal colors, combined with a bolder font weight.
+In the sign up form , we have added a advisor in the passwords fields , which gives you feedback if the password it's empty or the confirmation password does not match with the previously written password.
+Before the password its written and the confirmation password matches , you aren't able to sign up the account and the submit button were disabled , after the confirmation matches it will be enabled and you can submit and sign up the account.
+This is the function which checks if the password it is empty or the confirmation password matches with the previously password field.
+
+```bash
+ checkPasswordMatch = () => {
+ //Check first password field is not empty
+ if (this.state.password.length > 0) {
+ this.setState({
+ passwordEmpty: false,
+ });
+ if (this.state.password === this.state.password_confirmation) {
+ this.setState({
+ passwordMatches: true,
+ });
+ document
+ .getElementById("registration__submit")
+ .removeAttribute("disabled");
+ } else {
+ this.setState({
+ passwordMatches: false,
+ });
+ document
+ .getElementById("registration__submit")
+ .setAttribute("disabled", true);
+ }
+ } else {
+ this.setState({
+ passwordEmpty: true,
+ });
+ }
+ //Check if password_confirmation matches
+ };
+```
+Here you can see how the navbar looks , with icon buttons in the bottom of the page becouse it is more easier to users.
+We have decided to change the styles of the navbar buttons, choosing a transparent background, the buttons have a default blue background if you are not in that location, if you click in that button this will change to orange to give you feedback about where you are.
+Here you can see how the navbar looks, placed at the top of the page, the reason why we have decided to change that is becouse in a desktop environment it is more common to see the navbar at the top and text in the buttons instead of icons.
+The reason why we have decided to implement a dark mode it is becouse in the last time it is very common to see in all the apps.
+A dark mode gives you a comfortable experience in situation where the light it is dark, otherwise you force your eyes to see the screen, although this were uncomfortable.
+When page were loading , an animation will be on screen.
+This it's a frame of it , this hole animation was created in pure css.
+This gives you feedback when something is loading.
+In our case , currently we are developing EduApp as a web app , using a full responsiveness , but we like to make it downloadable ,
+making eduapp as a hybrid app.
+It is a pleasure to us be part of this project , as our first project working in with other people,becoming this project a challenge , we found many difficults in the journey, but we also have learned so much through it,although this it is just the beggining of the project and so much things will happen through the journey.
diff --git a/Documentation/screenshots/calendar-page.png b/Documentation/screenshots/calendar-page.png
new file mode 100644
index 00000000..d5ad7e5c
Binary files /dev/null and b/Documentation/screenshots/calendar-page.png differ
diff --git a/Documentation/screenshots/chat-info-page.png b/Documentation/screenshots/chat-info-page.png
new file mode 100644
index 00000000..40443642
Binary files /dev/null and b/Documentation/screenshots/chat-info-page.png differ
diff --git a/Documentation/screenshots/chats-page.png b/Documentation/screenshots/chats-page.png
new file mode 100644
index 00000000..b3c86aea
Binary files /dev/null and b/Documentation/screenshots/chats-page.png differ
diff --git a/Documentation/screenshots/create-direct-chat.png b/Documentation/screenshots/create-direct-chat.png
new file mode 100644
index 00000000..7c4995d5
Binary files /dev/null and b/Documentation/screenshots/create-direct-chat.png differ
diff --git a/Documentation/screenshots/create-group-chat.png b/Documentation/screenshots/create-group-chat.png
new file mode 100644
index 00000000..c34129c8
Binary files /dev/null and b/Documentation/screenshots/create-group-chat.png differ
diff --git a/Documentation/screenshots/create-resource.png b/Documentation/screenshots/create-resource.png
new file mode 100644
index 00000000..507aaab3
Binary files /dev/null and b/Documentation/screenshots/create-resource.png differ
diff --git a/Documentation/screenshots/direct-chat-page.png b/Documentation/screenshots/direct-chat-page.png
new file mode 100644
index 00000000..f9c76eda
Binary files /dev/null and b/Documentation/screenshots/direct-chat-page.png differ
diff --git a/Documentation/screenshots/example-resource.png b/Documentation/screenshots/example-resource.png
new file mode 100644
index 00000000..7754c70e
Binary files /dev/null and b/Documentation/screenshots/example-resource.png differ
diff --git a/Documentation/screenshots/group-chat-page.png b/Documentation/screenshots/group-chat-page.png
new file mode 100644
index 00000000..16ab9be1
Binary files /dev/null and b/Documentation/screenshots/group-chat-page.png differ
diff --git a/Documentation/screenshots/home-page.png b/Documentation/screenshots/home-page.png
new file mode 100644
index 00000000..cac142d0
Binary files /dev/null and b/Documentation/screenshots/home-page.png differ
diff --git a/Documentation/screenshots/login.png b/Documentation/screenshots/login.png
new file mode 100644
index 00000000..7a3a6ce3
Binary files /dev/null and b/Documentation/screenshots/login.png differ
diff --git a/Documentation/screenshots/resources-page.png b/Documentation/screenshots/resources-page.png
new file mode 100644
index 00000000..9de12da7
Binary files /dev/null and b/Documentation/screenshots/resources-page.png differ
diff --git a/Documentation/screenshots/resources-subjects-page.png b/Documentation/screenshots/resources-subjects-page.png
new file mode 100644
index 00000000..35e879b2
Binary files /dev/null and b/Documentation/screenshots/resources-subjects-page.png differ
diff --git a/backend/eduapp_db/.env-example-development b/backend/eduapp_db/.env-example-development
new file mode 100644
index 00000000..70c15e6f
--- /dev/null
+++ b/backend/eduapp_db/.env-example-development
@@ -0,0 +1,38 @@
+# RAILS
+# Do not modify unless you know what you are doing.
+RAILS_SECRET_KEY=599fef69235287bd8c268db895897f236aebf4f63971aa48f48e3ee81c5612006a4857751cb1859546ccde67bb84cb3cc799a6aa9196b036d9a872dcd7765809
+ENCRYPTION_SALT="81IJngXK2FE1+1J2Ugs1VxuK0yTnUyZQCSFbv0SUNRgZpMN7EN8SzoeSDuD+\nOXU0cwQyoFaAgfsckvz0di4SIyxHZs0OWey32Y5VI6Dg8rTNR8q2Z+oIv4tS\n2DOYeQ56uax3nQ/A+JikqvsLAfZYjhe3OpB5GUlgeJFnA30xB68Ock3HSInI\nW1kuZxN8dQu2eGwPT+3iNR9/DHe0Zku3G5MtZNzciHAT04JXVP+YXVraLVv1\nj5ctdDmWx1shNoBidO3q6PS+0peInRJmChVHcfoaBbhQuF2hv5vv5Qcd/c0f\nUDOWqho2DrA3hYm252t5BWTjfNoI3p7M0E1KRHmZDQ==\n"
+ENCRYPTION_PATTERN="#_::_#"
+
+RAILS_ENV=development
+API_VERSION="v1"
+
+# POSTGRES CONFIG
+# This needs to match the 'postgres.docker-example.env' configured variables.
+DEV_DB_USER="eduapp_user"
+DEV_DB_PSWD="custom-password-1234"
+DEV_DB_PORT="5432" # Do not touch.
+
+# HOSTING CONFIG
+# Do not touch. This is internal.
+PORT=3010
+DB_HOST=db # WITH DOCKER
+HOST="localhost" # DEVELOPMENT
+DOMAIN="localhost" # DEVELOPMENT
+
+# FRONTEND ENPOINT FOR CORS
+# It is important for your domain/url not to end with '/', as
+# the CORS policy won't detect it an forbid you from entering
+# Localhost example:
+REACT_APP_FRONTEND_ENDPOINT=http://localhost:8443
+REACT_APP_ADMIN_ENDPOINT=http://localhost:4010
+
+
+# EMAIL
+# Do not touch. This is internal.
+SMTP_PORT=587
+SMTP_ADDRESS=smtp.gmail.com
+# If you wish to use a different email for sending
+# emails to users, change it here.
+GMAIL_USERNAME=youremail@gmail.com
+GMAIL_PASSWORD=yourpassword
\ No newline at end of file
diff --git a/backend/eduapp_db/.env-example-production b/backend/eduapp_db/.env-example-production
new file mode 100644
index 00000000..42f4a362
--- /dev/null
+++ b/backend/eduapp_db/.env-example-production
@@ -0,0 +1,37 @@
+# RAILS
+# Do not modify unless you know what you are doing.
+RAILS_SECRET_KEY=599fef69235287bd8c268db895897f236aebf4f63971aa48f48e3ee81c5612006a4857751cb1859546ccde67bb84cb3cc799a6aa9196b036d9a872dcd7765809
+ENCRYPTION_SALT="81IJngXK2FE1+1J2Ugs1VxuK0yTnUyZQCSFbv0SUNRgZpMN7EN8SzoeSDuD+\nOXU0cwQyoFaAgfsckvz0di4SIyxHZs0OWey32Y5VI6Dg8rTNR8q2Z+oIv4tS\n2DOYeQ56uax3nQ/A+JikqvsLAfZYjhe3OpB5GUlgeJFnA30xB68Ock3HSInI\nW1kuZxN8dQu2eGwPT+3iNR9/DHe0Zku3G5MtZNzciHAT04JXVP+YXVraLVv1\nj5ctdDmWx1shNoBidO3q6PS+0peInRJmChVHcfoaBbhQuF2hv5vv5Qcd/c0f\nUDOWqho2DrA3hYm252t5BWTjfNoI3p7M0E1KRHmZDQ==\n"
+ENCRYPTION_PATTERN="#_::_#"
+
+RAILS_ENV=production
+API_VERSION="v1"
+
+# POSTGRES CONFIG
+# This needs to match the 'postgres.docker-example.env' configured variables.
+PROD_DB_USER="eduapp_user"
+PROD_DB_PSWD="custom-password-1234"
+PROD_DB_PORT="5432" # Do not touch.
+
+# HOSTING CONFIG
+# Do not touch. This is internal.
+PORT=3010
+DB_HOST=db # WITH DOCKER
+
+HOST=your-backend-domain
+DOMAIN=your-backend-domain
+
+# FRONTEND ENPOINT FOR CORS
+# It is important for your domain/url not to end with '/', as
+# the CORS policy won't detect it an forbid you from entering
+REACT_APP_FRONTEND_ENDPOINT=https://your-frontend-domain:port
+REACT_APP_ADMIN_ENDPOINT=https://your-admin-domain:port
+
+# EMAIL
+# Do not touch. This is internal.
+SMTP_PORT=587
+SMTP_ADDRESS=smtp.gmail.com
+# If you wish to use a different email for sending
+# emails to users, change it here.
+GMAIL_USERNAME=youremail@gmail.com
+GMAIL_PASSWORD=yourpassword
\ No newline at end of file
diff --git a/backend/eduapp_db/.gitignore b/backend/eduapp_db/.gitignore
index 8a1b1134..99667ec0 100644
--- a/backend/eduapp_db/.gitignore
+++ b/backend/eduapp_db/.gitignore
@@ -6,6 +6,7 @@
# Ignore bundler config.
/.bundle
+/.cert
# Ignore all logfiles and tempfiles.
/log/*
@@ -22,6 +23,23 @@
/storage/*
!/storage/.keep
.byebug_history
+node_modules
# Ignore master key for decrypting credentials and more.
/config/master.key
+*.env
+!*docker-example.env
+.DS_Store
+
+config-velorcios
+
+.env-real-production
+.env-real-development
+
+dockerfile-real-production
+dockerfile-real-development
+
+dockerfile
+
+#Uploads files
+/public/uploads
diff --git a/backend/eduapp_db/.ruby-version b/backend/eduapp_db/.ruby-version
index 324db8d6..7bde84d0 100644
--- a/backend/eduapp_db/.ruby-version
+++ b/backend/eduapp_db/.ruby-version
@@ -1 +1 @@
-ruby-2.6.8
+ruby-3.1.2
diff --git a/backend/eduapp_db/Gemfile b/backend/eduapp_db/Gemfile
index 92c2dbea..778e0c52 100644
--- a/backend/eduapp_db/Gemfile
+++ b/backend/eduapp_db/Gemfile
@@ -1,40 +1,36 @@
-source 'https://rubygems.org'
+source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
-ruby '2.6.8'
-
-# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
-gem 'rails', '~> 6.1.4', '>= 6.1.4.1'
-# Use postgresql as the database for Active Record
-gem 'pg', '~> 1.1'
-# Use Puma as the app server
-gem 'puma', '~> 5.0'
-# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-# gem 'jbuilder', '~> 2.7'
-# Use Redis adapter to run Action Cable in production
-# gem 'redis', '~> 4.0'
-# Use Active Model has_secure_password
-gem 'bcrypt', '~> 3.1.7'
-gem 'devise' #login
-gem 'rack-cors'
-
-
-# Use Active Storage variant
-# gem 'image_processing', '~> 1.2'
-
-# Reduces boot times through caching; required in config/boot.rb
-gem 'bootsnap', '>= 1.4.4', require: false
-
-# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
-# gem 'rack-cors'
+ruby "3.1.2"
+
+gem "rails", "~> 6.1.4", ">= 6.1.4.1"
+gem "pg", "~> 1.1"
+gem "puma", "~> 5.0"
+gem 'carrierwave'
+gem "devise"
+gem "devise-jwt"
+gem "jwt"
+gem "openssl"
+gem "useragent"
+gem "dotenv-rails", :group => [:production, :development, :test]
+gem "rack-cors", :require => "rack/cors"
+gem "ransack"
+gem 'rmagick'
+gem "image_processing", "~> 1.2"
+gem "active_model_serializers"
+gem "bootsnap", ">= 1.4.4", require: false
+gem "omniauth-rails_csrf_protection"
+gem "omniauth-google-oauth2"
+gem "omniauth"
+gem "net-smtp"
+gem "webpush"
group :development, :test do
- # Call 'byebug' anywhere in the code to stop execution and get a debugger console
- gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+ gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
end
-group :development do
+group :test do
+ gem "rspec-rails"
end
-# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
diff --git a/backend/eduapp_db/Gemfile.lock b/backend/eduapp_db/Gemfile.lock
index 53cdf986..3c43b4b6 100644
--- a/backend/eduapp_db/Gemfile.lock
+++ b/backend/eduapp_db/Gemfile.lock
@@ -1,171 +1,313 @@
GEM
remote: https://rubygems.org/
specs:
- actioncable (6.1.4.1)
- actionpack (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actioncable (6.1.7.3)
+ actionpack (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.1.4.1)
- actionpack (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionmailbox (6.1.7.3)
+ actionpack (= 6.1.7.3)
+ activejob (= 6.1.7.3)
+ activerecord (= 6.1.7.3)
+ activestorage (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
mail (>= 2.7.1)
- actionmailer (6.1.4.1)
- actionpack (= 6.1.4.1)
- actionview (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionmailer (6.1.7.3)
+ actionpack (= 6.1.7.3)
+ actionview (= 6.1.7.3)
+ activejob (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.1.4.1)
- actionview (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionpack (6.1.7.3)
+ actionview (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.1.4.1)
- actionpack (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actiontext (6.1.7.3)
+ actionpack (= 6.1.7.3)
+ activerecord (= 6.1.7.3)
+ activestorage (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
nokogiri (>= 1.8.5)
- actionview (6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionview (6.1.7.3)
+ activesupport (= 6.1.7.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
- activejob (6.1.4.1)
- activesupport (= 6.1.4.1)
+ active_model_serializers (0.10.13)
+ actionpack (>= 4.1, < 7.1)
+ activemodel (>= 4.1, < 7.1)
+ case_transform (>= 0.2)
+ jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
+ activejob (6.1.7.3)
+ activesupport (= 6.1.7.3)
globalid (>= 0.3.6)
- activemodel (6.1.4.1)
- activesupport (= 6.1.4.1)
- activerecord (6.1.4.1)
- activemodel (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- activestorage (6.1.4.1)
- actionpack (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- marcel (~> 1.0.0)
+ activemodel (6.1.7.3)
+ activesupport (= 6.1.7.3)
+ activerecord (6.1.7.3)
+ activemodel (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
+ activestorage (6.1.7.3)
+ actionpack (= 6.1.7.3)
+ activejob (= 6.1.7.3)
+ activerecord (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
+ marcel (~> 1.0)
mini_mime (>= 1.1.0)
- activesupport (6.1.4.1)
+ activesupport (6.1.7.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
- bcrypt (3.1.16)
- bootsnap (1.9.1)
- msgpack (~> 1.0)
+ addressable (2.8.1)
+ public_suffix (>= 2.0.2, < 6.0)
+ bcrypt (3.1.18)
+ bootsnap (1.16.0)
+ msgpack (~> 1.2)
builder (3.2.4)
byebug (11.1.3)
- concurrent-ruby (1.1.9)
+ carrierwave (2.2.3)
+ activemodel (>= 5.0.0)
+ activesupport (>= 5.0.0)
+ addressable (~> 2.6)
+ image_processing (~> 1.1)
+ marcel (~> 1.0.0)
+ mini_mime (>= 0.1.3)
+ ssrf_filter (~> 1.0)
+ case_transform (0.2)
+ activesupport
+ concurrent-ruby (1.2.2)
crass (1.0.6)
- devise (4.8.0)
+ date (3.3.3)
+ devise (4.9.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
- erubi (1.10.0)
- globalid (0.5.2)
+ devise-jwt (0.10.0)
+ devise (~> 4.0)
+ warden-jwt_auth (~> 0.6)
+ diff-lcs (1.5.0)
+ dotenv (2.8.1)
+ dotenv-rails (2.8.1)
+ dotenv (= 2.8.1)
+ railties (>= 3.2)
+ dry-auto_inject (1.0.1)
+ dry-core (~> 1.0)
+ zeitwerk (~> 2.6)
+ dry-configurable (1.0.1)
+ dry-core (~> 1.0, < 2)
+ zeitwerk (~> 2.6)
+ dry-core (1.0.0)
+ concurrent-ruby (~> 1.0)
+ zeitwerk (~> 2.6)
+ erubi (1.12.0)
+ faraday (2.7.4)
+ faraday-net_http (>= 2.0, < 3.1)
+ ruby2_keywords (>= 0.0.4)
+ faraday-net_http (3.0.2)
+ ffi (1.15.5)
+ globalid (1.1.0)
activesupport (>= 5.0)
- i18n (1.8.11)
+ hashie (5.0.0)
+ hkdf (0.3.0)
+ i18n (1.12.0)
concurrent-ruby (~> 1.0)
- loofah (2.12.0)
+ image_processing (1.12.2)
+ mini_magick (>= 4.9.5, < 5)
+ ruby-vips (>= 2.0.17, < 3)
+ jsonapi-renderer (0.2.2)
+ jwt (2.7.0)
+ loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
- mail (2.7.1)
+ mail (2.8.1)
mini_mime (>= 0.1.1)
+ net-imap
+ net-pop
+ net-smtp
marcel (1.0.2)
method_source (1.0.0)
+ mini_magick (4.12.0)
mini_mime (1.1.2)
- minitest (5.14.4)
- msgpack (1.4.2)
+ minitest (5.18.0)
+ msgpack (1.6.1)
+ multi_xml (0.6.0)
+ net-imap (0.3.4)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.2.1)
+ timeout
+ net-smtp (0.3.3)
+ net-protocol
nio4r (2.5.8)
- nokogiri (1.12.5-x64-mingw32)
+ nokogiri (1.14.2-x86_64-linux)
racc (~> 1.4)
+ oauth2 (2.0.9)
+ faraday (>= 0.17.3, < 3.0)
+ jwt (>= 1.0, < 3.0)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 4)
+ snaky_hash (~> 2.0)
+ version_gem (~> 1.1)
+ omniauth (2.1.1)
+ hashie (>= 3.4.6)
+ rack (>= 2.2.3)
+ rack-protection
+ omniauth-google-oauth2 (1.1.1)
+ jwt (>= 2.0)
+ oauth2 (~> 2.0.6)
+ omniauth (~> 2.0)
+ omniauth-oauth2 (~> 1.8.0)
+ omniauth-oauth2 (1.8.0)
+ oauth2 (>= 1.4, < 3)
+ omniauth (~> 2.0)
+ omniauth-rails_csrf_protection (1.0.1)
+ actionpack (>= 4.2)
+ omniauth (~> 2.0)
+ openssl (3.1.0)
orm_adapter (0.5.0)
- pg (1.2.3-x64-mingw32)
- puma (5.5.2)
+ pg (1.4.6)
+ pkg-config (1.5.1)
+ public_suffix (5.0.1)
+ puma (5.6.5)
nio4r (~> 2.0)
- racc (1.6.0)
- rack (2.2.3)
- rack-cors (1.1.1)
+ racc (1.6.2)
+ rack (2.2.6.4)
+ rack-cors (2.0.1)
rack (>= 2.0.0)
- rack-test (1.1.0)
- rack (>= 1.0, < 3)
- rails (6.1.4.1)
- actioncable (= 6.1.4.1)
- actionmailbox (= 6.1.4.1)
- actionmailer (= 6.1.4.1)
- actionpack (= 6.1.4.1)
- actiontext (= 6.1.4.1)
- actionview (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activemodel (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ rack-protection (3.0.5)
+ rack
+ rack-test (2.1.0)
+ rack (>= 1.3)
+ rails (6.1.7.3)
+ actioncable (= 6.1.7.3)
+ actionmailbox (= 6.1.7.3)
+ actionmailer (= 6.1.7.3)
+ actionpack (= 6.1.7.3)
+ actiontext (= 6.1.7.3)
+ actionview (= 6.1.7.3)
+ activejob (= 6.1.7.3)
+ activemodel (= 6.1.7.3)
+ activerecord (= 6.1.7.3)
+ activestorage (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
bundler (>= 1.15.0)
- railties (= 6.1.4.1)
+ railties (= 6.1.7.3)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
- rails-html-sanitizer (1.4.2)
- loofah (~> 2.3)
- railties (6.1.4.1)
- actionpack (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ rails-html-sanitizer (1.5.0)
+ loofah (~> 2.19, >= 2.19.1)
+ railties (6.1.7.3)
+ actionpack (= 6.1.7.3)
+ activesupport (= 6.1.7.3)
method_source
- rake (>= 0.13)
+ rake (>= 12.2)
thor (~> 1.0)
rake (13.0.6)
- responders (3.0.1)
- actionpack (>= 5.0)
- railties (>= 5.0)
- sprockets (4.0.2)
+ ransack (4.0.0)
+ activerecord (>= 6.1.5)
+ activesupport (>= 6.1.5)
+ i18n
+ responders (3.1.0)
+ actionpack (>= 5.2)
+ railties (>= 5.2)
+ rmagick (5.2.0)
+ pkg-config (~> 1.4)
+ rspec-core (3.12.1)
+ rspec-support (~> 3.12.0)
+ rspec-expectations (3.12.2)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.12.0)
+ rspec-mocks (3.12.4)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.12.0)
+ rspec-rails (6.0.1)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ railties (>= 6.1)
+ rspec-core (~> 3.11)
+ rspec-expectations (~> 3.11)
+ rspec-mocks (~> 3.11)
+ rspec-support (~> 3.11)
+ rspec-support (3.12.0)
+ ruby-vips (2.1.4)
+ ffi (~> 1.12)
+ ruby2_keywords (0.0.5)
+ snaky_hash (2.0.1)
+ hashie
+ version_gem (~> 1.1, >= 1.1.1)
+ sprockets (4.2.0)
concurrent-ruby (~> 1.0)
- rack (> 1, < 3)
- sprockets-rails (3.2.2)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
+ rack (>= 2.2.4, < 4)
+ sprockets-rails (3.4.2)
+ actionpack (>= 5.2)
+ activesupport (>= 5.2)
sprockets (>= 3.0.0)
- thor (1.1.0)
- tzinfo (2.0.4)
+ ssrf_filter (1.1.1)
+ thor (1.2.1)
+ timeout (0.3.2)
+ tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
- tzinfo-data (1.2021.5)
- tzinfo (>= 1.0.0)
+ useragent (0.16.10)
+ version_gem (1.1.2)
warden (1.2.9)
rack (>= 2.0.9)
+ warden-jwt_auth (0.8.0)
+ dry-auto_inject (>= 0.8, < 2)
+ dry-configurable (>= 0.13, < 2)
+ jwt (~> 2.1)
+ warden (~> 1.2)
+ webpush (1.1.0)
+ hkdf (~> 0.2)
+ jwt (~> 2.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
- zeitwerk (2.5.1)
+ zeitwerk (2.6.7)
PLATFORMS
- x64-mingw32
+ x86_64-linux
DEPENDENCIES
- bcrypt (~> 3.1.7)
+ active_model_serializers
bootsnap (>= 1.4.4)
byebug
+ carrierwave
devise
+ devise-jwt
+ dotenv-rails
+ image_processing (~> 1.2)
+ jwt
+ net-smtp
+ omniauth
+ omniauth-google-oauth2
+ omniauth-rails_csrf_protection
+ openssl
pg (~> 1.1)
puma (~> 5.0)
rack-cors
rails (~> 6.1.4, >= 6.1.4.1)
+ ransack
+ rmagick
+ rspec-rails
tzinfo-data
+ useragent
+ webpush
RUBY VERSION
- ruby 2.6.8p205
+ ruby 3.1.2p20
BUNDLED WITH
- 1.17.2
+ 2.4.9
diff --git a/backend/eduapp_db/Procfile b/backend/eduapp_db/Procfile
new file mode 100644
index 00000000..3136daab
--- /dev/null
+++ b/backend/eduapp_db/Procfile
@@ -0,0 +1 @@
+web: rails s -b 0.0.0.0
\ No newline at end of file
diff --git a/backend/eduapp_db/README.md b/backend/eduapp_db/README.md
deleted file mode 100644
index 7db80e4c..00000000
--- a/backend/eduapp_db/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# README
-
-This README would normally document whatever steps are necessary to get the
-application up and running.
-
-Things you may want to cover:
-
-* Ruby version
-
-* System dependencies
-
-* Configuration
-
-* Database creation
-
-* Database initialization
-
-* How to run the test suite
-
-* Services (job queues, cache servers, search engines, etc.)
-
-* Deployment instructions
-
-* ...
diff --git a/backend/eduapp_db/app/channels/chat_channel.rb b/backend/eduapp_db/app/channels/chat_channel.rb
new file mode 100644
index 00000000..8222ee60
--- /dev/null
+++ b/backend/eduapp_db/app/channels/chat_channel.rb
@@ -0,0 +1,105 @@
+class ChatChannel < ApplicationCable::Channel
+ require "json"
+ require "edu_app_utils/encrypt_utils"
+
+ def subscribed
+ reject and return unless check_chat_user(params[:chat_room][1..-1], params[:connection_requester])
+ @chat_name = "eduapp.channel.#{params[:chat_room]}"
+ stream_from @chat_name
+ end
+
+ def receive(data)
+ case data["command"]
+ when "message"
+ instance = ChatMessage.new(
+ chat_base_id: params[:chat_room][1..-1],
+ user_id: data["author"],
+ message: data["message"],
+ send_date: data["send_date"],
+ )
+
+ if instance.save
+ newMsg = format_msg(instance)
+ newMsg.merge!(JSON.parse("{\"command\": \"new_message\"}"))
+
+ ActionCable.server.broadcast @chat_name, newMsg
+ else
+ ActionCable.server.broadcast @chat_name, { "command" => "error", "message" => "Error saving message" }
+ end
+
+ current_chat = ChatBase.find(params[:chat_room][1..-1])
+ ChatParticipant.where(chat_base_id: current_chat.id).each do |participant|
+ if participant.user_id != data["author"]
+ UserNotifsChannel.broadcast_to(
+ participant.user_id,
+ command: "new_msg",
+ author_name: UserInfo.find_by(user_id: data["author"]).user_name,
+ author_pic: UserInfo.find_by(user_id: data["author"]).profile_image,
+ msg: data["message"],
+ key: current_chat.private_key,
+ chat_url: "#{ENV.fetch("REACT_APP_FRONTEND_ENDPOINT")}/chat/#{current_chat.isGroup ? "g" : "p"}#{current_chat.id}",
+ )
+
+ subcriptions = PushNotification.where(user_id: participant.user_id)
+ user = UserInfo.find_by(user_id: data["author"])
+ message = {
+ title: "Nuevo mensaje",
+ body: data["message"],
+ user: user.user_name,
+ icon: user.profile_image,
+ privKey: current_chat.private_key
+ }
+ subcriptions.each do |subcription|
+ begin
+ Webpush.payload_send(
+ endpoint: subcription.endpoint,
+ message: JSON.generate(message),
+ p256dh: subcription.p256dh,
+ auth: subcription.auth,
+ vapid: {
+ subject: "mailto:email@example.com",
+ public_key: ENV.fetch('VAPID_PUBLIC_KEY'),
+ private_key: ENV.fetch('VAPID_PRIVATE_KEY')
+ }
+ )
+ rescue Webpush::ExpiredSubscription => e
+ subcription.destroy
+ rescue Exception => e
+ e
+ end
+ end
+ end
+ end
+ else
+ puts "DEFAULT"
+ end
+ end
+
+ def unsubscribed
+ end
+
+ protected
+
+ def format_msg(queryMsg)
+ mainMsg = JSON.parse(queryMsg.to_json)
+ mainMsg.merge!(JSON.parse("{\"chat_base\": #{queryMsg.chat_base.to_json}}"))
+ mainMsg.merge!(JSON.parse("{\"user\": #{queryMsg.user.to_json}}"))
+ mainMsg.delete("chat_base_id")
+ mainMsg.delete("user_id")
+ mainMsg["chat_base"].delete("private_key")
+ mainMsg["chat_base"].delete("public_key")
+
+ return mainMsg
+ end
+
+ # Checks if user belongs to that chat.
+ def check_chat_user(chat_base_id, user_id)
+ chat_participants = ChatParticipant.where(chat_base_id: chat_base_id)
+ chat_participants.each do |participant|
+ if participant.user_id == user_id
+ return true
+ end
+ end
+ return false
+ end
+end
diff --git a/backend/eduapp_db/app/channels/user_notifs_channel.rb b/backend/eduapp_db/app/channels/user_notifs_channel.rb
new file mode 100644
index 00000000..8ca1e99c
--- /dev/null
+++ b/backend/eduapp_db/app/channels/user_notifs_channel.rb
@@ -0,0 +1,67 @@
+class UserNotifsChannel < ApplicationCable::Channel
+ def subscribed
+ @user_count = 0 unless @user_count.instance_of?(Integer)
+ reject unless check_auth_token(params[:token]) && check_user && @user_count == 0
+ @chat_name = "eduapp.notifs.user.#{params[:user_id]}"
+ @user_count = 1
+ stream_from @chat_name
+ stream_for @notifs_user
+ end
+
+ def receive(cmd)
+ case cmd["command"]
+ when "new_msgs"
+ puts "hi"
+ else
+ end
+ end
+
+ def unsubscribed
+ @user_count = 0
+ @notifs_user = nil
+ end
+
+ private
+
+ def check_auth_token(token)
+ if token.nil?
+ return false
+ end
+
+ token = token.split("Bearer ").last
+ jwt_payload = User.unlock_token(token)
+ if jwt_payload.instance_of? Array
+ if Time.now.to_i > Integer(jwt_payload[0]["exp"])
+ return false
+ end
+ jtiMatch = JtiMatchList.where(user_id: jwt_payload[0]["sub"], jti: jwt_payload[0]["jti"])
+ if !jtiMatch.present?
+ return false
+ end
+ @notifs_user = jwt_payload[0]["sub"]
+ else
+ return false
+ end
+ end
+
+ # If token check succeeds, checks if there is already a user in the websocket.
+ def check_user
+ if @notifs_user.nil?
+ return false
+ else
+ user = User.find(@notifs_user)
+ if user.nil?
+ return false
+ end
+
+ if user.id != params[:user_id]
+ return false
+ end
+ return true
+ end
+ end
+
+ def set_user_count
+ @user_count = 0
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/application_controller.rb b/backend/eduapp_db/app/controllers/application_controller.rb
index 4ac8823b..3b2e5e97 100644
--- a/backend/eduapp_db/app/controllers/application_controller.rb
+++ b/backend/eduapp_db/app/controllers/application_controller.rb
@@ -1,2 +1,332 @@
class ApplicationController < ActionController::API
+ before_action :configure_permitted_parameters, if: :devise_controller?
+
+
+ # Renders the desired table's ```extra_fields```.
+ def get_extrafields
+ authenticate_user!
+ table = return_table(params[:table])
+ render json: table.extra_fields and return
+ end
+
+ # Creates new ```extra_fields``` for the requested table.
+ def push_extrafields
+ authenticate_user!
+ table = return_table(params[:table])
+ body = request.body.read.to_s
+
+ extrafields_body = table.extra_fields
+ extrafields_body.push(body)
+
+ if table.update(extra_fields: extrafields_body)
+ table.save
+ render json: table.extra_fields and return
+ else
+ render json: { error: "Error updating extra fields" }, status: 422 and return
+ end
+ end
+
+ # Updates the desired ```extra_fields``` from the given table.
+ def update_extrafield
+ authenticate_user!
+ table = return_table(params[:table])
+ body = JSON.parse(request.body.read)
+ extrafields = table.extra_fields
+
+ extrafields_updated = []
+ extrafields.each do |extrafield|
+ extrafield = JSON.parse(extrafield)
+ if extrafield["name"] === body["name"]
+ extrafield["value"] = body["value"]
+ end
+ extrafields_updated.push(JSON.generate(extrafield))
+ end
+
+ if table.update(extra_fields: extrafields_updated)
+ render json: table.extra_fields and return
+ else
+ render json: { error: "Error updating extra fields" }, status: 422 and return
+ end
+ end
+
+ # Deletes any ```extra_fields``` from the given table.
+ def delete_extrafield
+ authenticate_user!
+ table = return_table(params[:table])
+ name = params[:field] || params[:name]
+
+ extrafields_updated = []
+ extrafields = table.extra_fields
+ extrafields.each do |extrafield|
+ extrafield = JSON.parse(extrafield)
+ if extrafield["name"] != name
+ extrafields_updated.push(JSON.generate(extrafield))
+ end
+ end
+
+ if table.update(extra_fields: extrafields_updated)
+ render json: table.extra_fields and return true
+ else
+ render json: { error: "Error deleting extra fields" }, status: 422 and return
+ end
+ end
+
+
+ protected
+
+ def configure_permitted_parameters
+ added_attrs = [:username, :email, :password, :password_confirmation]
+ devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
+ devise_parameter_sanitizer.permit(:sign_in) do |user_params|
+ user_params.permit(:username, :email)
+ end
+ end
+
+ # Returns the ID's information from the requested table.
+ def return_table(table)
+ case table
+ when "users"
+ return User.find(params[:id])
+ when "courses"
+ return Course.find(params[:id])
+ when "sessions"
+ return EduappUserSession.find(params[:id])
+ when "institutions"
+ return Institution.find(params[:id])
+ when "resources"
+ return Resource.find(params[:id])
+ when "subjects"
+ return Subject.find(params[:id])
+ end
+ end
+
+
+ # Filters the desired table's ```extra_fields``` for further filtration.
+ def filter_extrafields(extras, table)
+ check_extra_fields(table)
+ extras = JSON.parse(Base64.decode64(extras))
+ valuable = table.where.not(extra_fields: [])
+
+ return nil if extras.nil?
+
+ ids = []
+ valuable.each do |entry|
+ next if ids.include? entry.id
+ entry.extra_fields.each do |field|
+ break if ids.include? entry.id
+ extras.each do |extra_field|
+ break if ids.include? entry.id
+ extra_field = { extra_field[0] => extra_field[1] }
+ field = field.instance_of?(String) ? JSON.parse(field) : field
+
+ ids.push(entry.id) if field["value"] =~ /^#{extra_field[field["name"]]}.*$/ && !extra_field[field["name"]].nil?
+ end
+ end
+ end
+
+ return ids.length > 0 ? table.where(id: ids) : nil
+ end
+
+ # PERMISSIONS
+
+ # Deny access due to permissions.
+ def deny_perms_access!
+ render json: { error: "You do not have permission to access this endpoint." }, status: :forbidden and return false
+ end
+
+ # Deny action due to permissions.
+ def deny_perms_action!
+ render json: { error: "You do not have permission to perform this action." }, status: 405 and return false
+ end
+
+
+ private
+
+ # AUTH
+
+ # Raises an exception if the table is not elegible for extra fields.
+ def check_extra_fields(table)
+ raise Exception.new "Table does is not elegible for extra fields." unless table.column_names.include?("extra_fields")
+ end
+
+ # Checks a JWT token from the request and returns an appropiate response.
+ def authenticate_user!(options = {})
+ if request.headers["eduauth"].present?
+ token = request.headers["eduauth"].split("Bearer ").last
+ jwt_payload = User.unlock_token(token)
+ if jwt_payload.instance_of? Array
+ if Time.now.to_i > Integer(jwt_payload[0]["exp"])
+ render json: { error: "Token has expired." }, status: 428 and return
+ end
+ jtiMatch = JtiMatchList.where(user_id: jwt_payload[0]["sub"], jti: jwt_payload[0]["jti"])
+ if jtiMatch.count === 0
+ render json: { error: "Token Mismatch." }, status: 406 and return
+ end
+ @current_user = jwt_payload[0]["sub"]
+ else
+ render json: { error: "Invalid token." }, status: :forbidden and return
+ end
+ else
+ render json: { error: "No auth provided." }, status: :unauthorized and return
+ end
+ end
+
+ # Checks the incoming role of a user provided by its JWT Token.
+ def check_role!
+ if request.headers["eduauth"].present?
+ token = request.headers["eduauth"].split("Bearer ").last
+ jwt_payload = User.unlock_token(token)
+ if jwt_payload.instance_of? Array
+ valid_role = false
+ UserRole.all.each do |role|
+ if role.name === jwt_payload[0]["aud"]
+ valid_role = true
+ break
+ end
+ end
+
+ if !valid_role
+ render json: { error: "Invalid role." }, status: :forbidden and return
+ end
+ end
+ end
+ end
+
+ def current_user
+ @current_user ||= super || User.where(id: @current_user)
+ end
+
+ def signed_in?
+ @current_user.present?
+ end
+
+ # UTILS
+
+ # Returns a ```UserRole``` entry for the currently existing admin role.
+ def get_admin_role
+ return UserRole.where(name: "eduapp-admin").first
+ end
+
+ # Returns the user's respective ```UserRole``` information.
+ def get_user_roles(user_id = @current_user)
+ return UserRole.find(UserInfo.where(user_id: user_id).first.user_role_id)
+ end
+
+ # Serializes each element in an array.
+ def serialize_each(array, iExcept = [], iInclude = [])
+ s = []
+ array.each do |item|
+ s.push(item.serializable_hash(except: iExcept, include: iInclude))
+ end
+ return s
+ end
+
+ # Paginates an ```ActiveRecord``` query.
+ def query_paginate(query, page, limit = 10)
+ page = begin Integer(page) rescue 1 end
+ if page - 1 < 0
+ return { :error => "Page cannot be less than 1" }
+ end
+ return { :current_page => query.limit(limit).offset((page - 1) * limit), :total_pages => (query.count.to_f / limit).ceil, :page => page }
+ end
+
+ # Paginates an array.
+ def array_paginate(array, page, limit = 10)
+ return array.slice(Integer(page) > 0 ? Integer(page) - 1 : 0, limit)
+ end
+
+ # A parser made to correctly return a decoded order filter.
+ def parse_filter_order(order, relational_order = {'.' => ''})
+ order = order.is_a?(String) ? JSON.parse(Base64.decode64(order)) : order
+ field = relational_order[order["field"]] || order["field"] || nil
+
+ return { field => order['order'] == "asc" ? :asc : :desc, id: :asc }
+ end
+
+ # PERMISSIONS
+
+ # Deny access due to permissions.
+ def deny_perms_access!
+ render json: { error: "You do not have permission to access this endpoint." }, status: :forbidden and return false
+ end
+
+ # Deny action due to permissions.
+ def deny_perms_action!
+ render json: { error: "You do not have permission to perform this action." }, status: 405 and return false
+ end
+
+ # Checks the owner that executed the desired action.
+ def check_action_owner!(requested_id, render_error = true)
+ if requested_id === @current_user
+ return true
+ else
+ return render_error ? (deny_perms_action! unless get_user_roles.name === get_admin_role.name) : false
+ end
+ end
+
+ # Checks ```UserRole``` permissions for querying everything.
+ def check_perms_all!(user_roles)
+ if user_roles[0]
+ return true
+ else
+ return deny_perms_access!
+ end
+ end
+
+ # Checks ```UserRole``` permissions for performing specific queries.
+ def check_perms_query!(user_roles, render_error= true)
+ if user_roles[1]
+ return true
+ else
+ return render_error ? deny_perms_access! : false
+ end
+ end
+
+ # Checks ```UserRole``` permissions for querying information about itself..
+ def check_perms_query_self!(user_roles, query_user_id)
+ if user_roles[2]
+ if query_user_id == @current_user
+ return true
+ else
+ return check_perms_query!(user_roles)
+ end
+ else
+ return deny_perms_access!
+ end
+ end
+
+ # Checks ```UserRole``` permissions for creating.
+ def check_perms_write!(user_roles)
+ if user_roles[3]
+ return true
+ else
+ return deny_perms_action!
+ end
+ end
+
+ # Checks ```UserRole``` permissions for updating.
+ def check_perms_update!(user_roles, needs_owner_check = false, requested_id = nil, render_error = true)
+ if user_roles[4]
+ return true
+ else
+ if needs_owner_check
+ return check_action_owner!(requested_id, render_error)
+ else
+ return render_error ? deny_perms_action! : false
+ end
+ end
+ end
+
+ # Checks ```UserRole``` permissions for deleting.
+ def check_perms_delete!(user_roles, needs_owner_check, requested_id)
+ if user_roles[5]
+ return true
+ else
+ if needs_owner_check
+ return check_action_owner!(requested_id)
+ else
+ return deny_perms_action!
+ end
+ end
+ end
end
diff --git a/backend/eduapp_db/app/controllers/calendar_annotations_controller.rb b/backend/eduapp_db/app/controllers/calendar_annotations_controller.rb
new file mode 100644
index 00000000..9034a466
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/calendar_annotations_controller.rb
@@ -0,0 +1,218 @@
+class CalendarAnnotationsController < ApplicationController
+ before_action :set_calendar_annotation, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /calendar_annotations
+ def index
+ wants_event_for_calendar = false
+
+ # Returns events in a way for it to be readable by the App's calendar.
+ if params[:user_id]
+ wants_event_for_calendar = true
+ if !check_perms_query_self!(get_user_roles.perms_events, params[:user_id])
+ return
+ end
+ # TODO: Possible refactorization:
+ # tuitions = Tuition.where(user_id: params[:user_id]).pluck(:course_id)
+ # @subjects = Subject.where(course_id: tuitions)
+ # @sessions = CalendarAnnotation.where(subject_id: @subjects)
+ # @calendarEvents = @sessions.where(isGlobal: false)
+ # @colorEvents = @subjects.pluck(:id, :color)
+
+ # @TuitionsUserId = SubjectsUser.where(user_id: params[:user_id]).pluck(:subject_id)
+ @calendar_isGlobal = CalendarAnnotation.where(isGlobal: true)
+ @calendarEvents = []
+ @sessions = []
+ user = User.find(params[:user_id])
+ if user.user_info.user_role.name == 'eduapp-teacher'
+ @subjects = user.user_info.teaching_list
+ else
+ @subjects = user.subjects.pluck(:id)
+ end
+
+ for subject in @subjects
+ @calendarEvents += CalendarAnnotation.where(isGlobal: false, subject_id: subject)
+ @colorEvents = Subject.where(id: @TuitionsUserId).pluck(:id, :color)
+ @sessions += EduappUserSession.where(subject_id: subject)
+ end
+ @calendar_annotations = { :globalEvents => @calendar_isGlobal, :calendarEvents => @calendarEvents, :sessions => @sessions, :colorEvents => @colorEvents }
+ else
+ if !check_perms_all!(get_user_roles.perms_events)
+ return
+ end
+ @calendar_annotations = CalendarAnnotation.all
+ end
+
+ if !wants_event_for_calendar
+ if !params[:order].nil? && Base64.decode64(params[:order]) != "null"
+ @calendar_annotations = @calendar_annotations.order(parse_filter_order(params[:order]))
+ else
+ @calendar_annotations = @calendar_annotations.order(annotation_title: :asc)
+ end
+ end
+
+ if !wants_event_for_calendar
+ if params[:page]
+ @calendar_annotations = query_paginate(@calendar_annotations, params[:page])
+ @calendar_annotations[:current_page] = serialize_each(@calendar_annotations[:current_page], [:created_at, :updated_at, :user_id, :subject_id], [:subject, :user])
+ end
+ end
+
+ render json: @calendar_annotations
+ end
+
+ # TODO: revisar
+ def calendar_info
+ if !check_perms_query_self!(get_user_roles.perms_events, params[:user_id])
+ return
+ end
+ annotation = CalendarAnnotation.where(isGlobal: true, isPop: true).order(:created_at).pluck(:annotation_start_date, :annotation_end_date)
+ now = Time.now.to_i
+ event = []
+ for date in annotation
+ stime = date[0].to_time.to_i
+ etime = date[1].to_time.to_i
+ if stime > now or now < etime
+ event += CalendarAnnotation.where(annotation_start_date: date[0], annotation_end_date: date[1])
+ end
+ end
+ render json: event
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ events_query = {}
+ subject_query = {}
+ params.each do |param|
+ next if param[0] == "controller" || param[0] == "action" || param[0] == "extras" || param[0] == "calendar_annotations"
+ next unless param[1] != "null" && param[1].length > 0
+
+ query = { param[0] => param[1] }
+ case param[0]
+ when "id", "annotation_title", "annotation_description", "event_author"
+ events_query.merge!(query)
+ when "subject_name"
+ subject_query.merge!(query)
+ end
+ end
+
+ final_query = nil
+
+ if !events_query.empty?
+ query = nil
+
+ if events_query["id"]
+ ids = []
+ CalendarAnnotation.all.each do |e|
+ ids << e.id if e.id.to_s =~ /^#{events_query["id"]}.*$/
+ end
+ query = CalendarAnnotation.where(id: ids)
+ end
+
+ if events_query["annotation_title"]
+ if !query.nil?
+ query = query.where("annotation_title LIKE ?", "%#{events_query["annotation_title"]}%")
+ else
+ query = CalendarAnnotation.where("annotation_title LIKE ?", "%#{events_query["annotation_title"]}%")
+ end
+ end
+
+ if events_query["annotation_description"]
+ if !query.nil?
+ query = query.where("annotation_description LIKE ?", "%#{events_query["annotation_description"]}%")
+ else
+ query = CalendarAnnotation.where("annotation_description LIKE ?", "%#{events_query["annotation_description"]}%")
+ end
+ end
+
+ if events_query["event_author"]
+ if !query.nil?
+ query = query.where(user_id: User.where("email LIKE ?", "%#{events_query["event_author"]}%"))
+ else
+ query = CalendarAnnotation.where(user_id: User.where("email LIKE ?", "%#{events_query["event_author"]}%"))
+ end
+ end
+
+ final_query = query
+ end
+
+ if !final_query.nil? && !subject_query.empty?
+ final_query = final_query.where(subject_id: Subject.where("name LIKE ?", "%#{subject_query["subject_name"]}%"))
+ elsif !subject_query.empty?
+ final_query = CalendarAnnotation.where(subject_id: Subject.where("name LIKE ?", "%#{subject_query["subject_name"]}%"))
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :subject_id], [:subject])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /calendar_annotations/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_events)
+ return
+ end
+ render json: @calendar_annotation
+ end
+
+ def show_calendar_event
+ if !check_perms_query!(get_user_roles.perms_events)
+ return
+ end
+ calendar_annotation = CalendarAnnotation.where(isPop: true).last
+ render json: calendar_annotation
+ end
+
+ # POST /calendar_annotations
+ def create
+ if !check_perms_write!(get_user_roles.perms_events)
+ return
+ end
+ @calendar_annotation = CalendarAnnotation.new(calendar_annotation_params)
+
+ if @calendar_annotation.save
+ render json: @calendar_annotation, status: :created, location: @calendar_annotation
+ else
+ render json: @calendar_annotation.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /calendar_annotations/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_events, false, :null)
+ return
+ end
+ puts "A: #{calendar_annotation_params}"
+ if @calendar_annotation.update(calendar_annotation_params)
+ render json: @calendar_annotation
+ else
+ render json: @calendar_annotation.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /calendar_annotations/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_events, false, :null)
+ return
+ end
+ @calendar_annotation.destroy
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_calendar_annotation
+ @calendar_annotation = CalendarAnnotation.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def calendar_annotation_params
+ params.require(:calendar_annotation).permit(:annotation_start_date, :annotation_end_date, :annotation_title, :annotation_description, :isGlobal, :isPop, :user_id, :subject_id)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/chat_bases_controller.rb b/backend/eduapp_db/app/controllers/chat_bases_controller.rb
new file mode 100644
index 00000000..da27eb86
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/chat_bases_controller.rb
@@ -0,0 +1,220 @@
+class ChatBasesController < ApplicationController
+ before_action :set_chat_basis, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ require "edu_app_utils/encrypt_utils"
+
+ # GET /chat_bases
+ def index
+ wants_complete_chat = false
+
+ # Made to return all necessary information for a chat.
+ if params[:complete_chat_for]
+ wants_complete_chat = true
+ if !check_user_in_chat(params[:complete_chat_for])
+ return
+ end
+ chatBase = ChatBase.find(params[:complete_chat_for])
+ chat = chatBase.serializable_hash(:except => [:created_at, :updated_at])
+ other_participants = ChatParticipant.where(chat_base_id: params[:complete_chat_for])
+
+ # Not the best way, but a way to track readed chats
+ chatBase.chat_participants.where(user_id: @current_user).first.update_attribute(:last_seen, DateTime.now)
+
+ participants = []
+ other_participants.each do |participant|
+ p = UserInfo.where(user_id: participant.user_id).first.serializable_hash(:only => [:profile_image, :user_name], :include => [:user])
+ p.merge!({ isChatAdmin: participant.isChatAdmin })
+ participants.push(p)
+ end
+
+ # Be able to gather the counterpart info even if they left the chat
+ if chat["chat_name"].include?("private_chat_")
+ if participants.length < 2
+ name_disect = chat["chat_name"].split("_")
+ name_disect.delete("private")
+ name_disect.delete("chat")
+
+ chat_counterpart_id = name_disect[0] == participants[0]["user"]["id"] ? name_disect[1] : name_disect[0]
+ chat_counterpart = UserInfo.where(user_id: chat_counterpart_id).first.serializable_hash(:only => [:profile_image, :user_name], :include => [:user])
+ participants.push(chat_counterpart)
+ end
+ end
+
+ @chat_bases = { chat: chat, participants: participants }
+ else
+ if !check_perms_all!(get_user_roles.perms_chat)
+ return
+ end
+ @chat_bases = ChatBase.all
+ end
+
+ if !wants_complete_chat
+ if !params[:order].nil? && Base64.decode64(params[:order]) != "null"
+ order = JSON.parse(Base64.decode64(params[:order]))
+ order["field"] = order["field"] == "name" ? "chat_name" : order["field"]
+ @chat_bases = @chat_bases.order({ order["field"] => order["order"] == "asc" ? :asc : :desc })
+ else
+ @chat_bases = @chat_bases.order(chat_name: :asc)
+ end
+ end
+
+ if params[:page]
+ @chat_bases = query_paginate(@chat_bases, params[:page])
+ end
+
+ render json: @chat_bases
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ chat_query = {}
+ params.each do |param|
+ next unless param[0] == "chat_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ chat_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = nil
+
+ if chat_query["chat_name"]
+ final_query = ChatBase.where("chat_name LIKE ?", "%#{chat_query["chat_name"]}%")
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at], [])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /chat_bases/1
+ def show
+ if !check_user_in_chat(@chat_basis.id)
+ if !check_perms_query!(get_user_roles.perms_chat)
+ return
+ end
+ end
+ render json: @chat_basis
+ end
+
+ # Checks if a user has an open ```ChatBase``` with the system user.
+ def has_system_notifs
+ if !check_perms_query!(get_user_roles.perms_chat)
+ return
+ end
+ notifs = ChatBase.where(chat_name: "private_chat_system_#{params[:user_id]}")
+
+ if notifs.count > 0
+ render json: notifs.first, status: :ok and return
+ end
+
+ render json: { :error => "User has no system chat." }, status: 404
+ end
+
+ # POST /chat_bases
+ def create
+ if !check_perms_write!(get_user_roles.perms_chat)
+ return
+ end
+ if params[:chat_name].include?("private_chat_")
+ nameDisect = params[:chat_name].split("_")
+ inverted_name = "private_chat_#{nameDisect[3]}_#{nameDisect[2]}"
+ nameExists = ChatBase.where(chat_name: params[:chat_name])
+ invertedExists = ChatBase.where(chat_name: inverted_name)
+
+ if nameExists.count > 0 || invertedExists.count > 0
+ return render json: { error: "Chat already exists" }, status: :unprocessable_entity
+ end
+ @chat_basis = ChatBase.new(
+ chat_name: params[:chat_name],
+ isGroup: params[:isGroup],
+ isReadOnly: params[:isReadOnly].nil? ? false : params[:isReadOnly],
+ )
+ pri_key, pub_key = EduAppUtils::EncryptUtils::gen_key_pair(@chat_basis.id)
+ @chat_basis.private_key = pri_key
+ @chat_basis.public_key = pub_key
+
+ if @chat_basis.save
+ render json: @chat_basis, status: :created, location: @chat_basis
+ else
+ render json: @chat_basis.errors, status: :unprocessable_entity
+ end
+ else
+ @chat_basis = ChatBase.new(
+ chat_name: params[:chat_name],
+ isGroup: params[:isGroup],
+ isReadOnly: params[:isReadOnly].nil? ? false : params[:isReadOnly],
+ )
+ pri_key, pub_key = EduAppUtils::EncryptUtils::gen_key_pair(@chat_basis.id)
+ @chat_basis.private_key = pri_key
+ @chat_basis.public_key = pub_key
+
+ if @chat_basis.save
+ render json: @chat_basis, status: :created, location: @chat_basis
+ else
+ render json: @chat_basis.errors, status: :unprocessable_entity
+ end
+ end
+ end
+
+ # PUT /chat_bases/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_chat, false, :null)
+ return
+ end
+ if @chat_basis.update(chat_basis_params)
+ render json: @chat_basis
+ else
+ render json: @chat_basis.errors, status: :unprocessable_entity
+ end
+ end
+
+ # # PUT /chat_bases/1/read
+ # def read
+ # if !check_perms_update!(get_user_roles.perms_chat, false, :null)
+ # return
+ # end
+ # @chat_basis.participants.where(user_id: @current_user).first.update_attribute(:last_seen, DateTime.now())
+ # render json: {read: DateTime.now()}
+ # end
+
+ # DELETE /chat_bases/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_chat, false, :null)
+ return
+ end
+ @subject = Subject.find_by(chat_link: @chat_basis.id)
+ if @subject.present?
+ @subject.update(chat_link: nil)
+ @subject.save
+ end
+
+ @chat_basis.destroy
+ end
+
+ private
+
+ # Checks if a user is in a certain ```ChatBase```.
+ def check_user_in_chat(chat_base_id)
+ if ChatParticipant.where(user_id: @current_user, chat_base_id: chat_base_id).count > 0 || get_user_roles.name === "eduapp_admin"
+ return true
+ end
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_chat_basis
+ @chat_basis = ChatBase.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def chat_basis_params
+ params.require(:chat_basis).permit(:chat_name, :isGroup)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/chat_messages_controller.rb b/backend/eduapp_db/app/controllers/chat_messages_controller.rb
new file mode 100644
index 00000000..f3f953e0
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/chat_messages_controller.rb
@@ -0,0 +1,91 @@
+class ChatMessagesController < ApplicationController
+ before_action :set_chat_message, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /chat_messages
+ def index
+ if params[:chat_base_id]
+ if !check_user_in_chat(params[:chat_base_id])
+ return deny_perms_access!
+ end
+ @chat_messages = ChatMessage.order(send_date: :asc).where(chat_base_id: params[:chat_base_id])
+ else
+ if !check_perms_all!(get_user_roles.perms_message)
+ return
+ end
+ @chat_messages = ChatMessage.all
+ end
+
+ if params[:page]
+ @chat_messages = query_paginate(@chat_messages, params[:page])
+ end
+
+ render json: @chat_messages
+ end
+
+ # GET /chat_messages/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_message)
+ return
+ end
+ render json: @chat_message
+ end
+
+ # POST /chat_messages
+ def create
+ if !check_user_in_chat(params[:chat_base_id])
+ if !check_perms_write!(get_user_roles.perms_message)
+ return
+ end
+ end
+ @chat_message = ChatMessage.new(chat_message_params)
+
+ if @chat_message.save
+ render json: @chat_message, status: :created, location: @chat_message
+ else
+ render json: @chat_message.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /chat_messages/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_message, false, :null) && !check_action_owner!(@chat_message.user_id)
+ return
+ end
+
+ if @chat_message.update(chat_message_params)
+ render json: @chat_message
+ else
+ render json: @chat_message.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /chat_messages/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_roles, false, :null) && !check_user_in_chat(params[:id])
+ return
+ end
+ @chat_message.destroy
+ end
+
+ private
+
+ # Checks if a user is in a certain ```ChatBase```.
+ def check_user_in_chat(chat_base_id)
+ if ChatParticipant.where(user_id: @current_user, chat_base_id: chat_base_id).count > 0 || get_user_roles.name == "eduapp_admin"
+ return true
+ end
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_chat_message
+ @chat_message = ChatMessage.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def chat_message_params
+ params.require(:chat_message).permit(:chat_base_id, :user_id, :message, :send_date)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/chat_participants_controller.rb b/backend/eduapp_db/app/controllers/chat_participants_controller.rb
new file mode 100644
index 00000000..4ab4d600
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/chat_participants_controller.rb
@@ -0,0 +1,183 @@
+class ChatParticipantsController < ApplicationController
+ before_action :set_chat_participant, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /chat_participants
+ def index
+ if params[:user_id]
+ if !check_perms_query_self!(get_user_roles.perms_chat_participants, params[:user_id])
+ return
+ end
+ @participants = ChatParticipant.where(user_id: params[:user_id])
+ elsif params[:chats_for]
+ if !check_perms_query_self!(get_user_roles.perms_chat_participants, params[:chats_for])
+ return
+ end
+ user_chats = ChatParticipant.where(user_id: params[:chats_for])
+
+ final_chats = []
+ user_chats.each do |chatp|
+ chatBase = ChatBase.find(chatp.chat_base_id)
+ lastMessage = chatBase.chat_messages.last || ChatMessage.new(send_date: chatBase.created_at)
+ chatSelfCounterpart = chatBase.chat_participants.where({user_id: params[:chats_for]}).first
+ chat = chatBase.serializable_hash(:except => [:private_key, :public_key, :created_at, :updated_at]).merge({
+ last_message: lastMessage.serializable_hash,
+ self_counterpart: chatSelfCounterpart.serializable_hash
+ })
+ if chat["chat_name"].include?("private_chat_")
+ chat_counterpart = ChatParticipant.where(chat_base_id: chat["id"]).where.not(user_id: params[:chats_for]).first
+
+ # Be able to gather the counterpart info even if they left the chat
+ if chat_counterpart == nil
+ name_disect = chat["chat_name"].split("_")
+ name_disect.delete("private")
+ name_disect.delete("chat")
+
+ chat_counterpart_id = name_disect[0] == params[:chat_for] ? name_disect[1] : name_disect[0]
+ chat_counterpart = UserInfo.where(user_id: chat_counterpart_id).first
+ end
+
+ final_chats.push({
+ chat_info: chat,
+ chat_participant: UserInfo.where(user_id: chat_counterpart.user_id).first.serializable_hash(:except => [:created_at, :updated_at, :user_role_id, :googleid]), # .merge(chat_counterpart.serializable_hash),
+ })
+ else
+ final_chats.push({ chat_info: chat })
+ end
+ end
+ @participants = { personal_chats: final_chats }
+ elsif params[:chat_id]
+ if !check_perms_query!(get_user_roles.perms_chat_participants)
+ return
+ end
+ @participants = ChatParticipant.where(chat_base_id: params[:chat_id])
+ else
+ if !check_perms_all!(get_user_roles.perms_chat_participants)
+ return
+ end
+ @participants = ChatParticipant.all
+ end
+
+ if params[:page]
+ @participants = query_paginate(@participants, params[:page])
+ @participants[:current_page] = serialize_each(@participants[:current_page], [:created_at, :updated_at, :user_id, :chat_base_id], [:user, :chat_base])
+ end
+
+
+ render json: @participants
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ parts_query = {}
+ params.each do |param|
+ next unless param[0] == "email" || param[0] == "chat_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ parts_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = nil
+
+ if parts_query["email"]
+ final_query = ChatParticipant.where(user_id: User.where("email LIKE ?", "%#{parts_query["email"]}%"))
+ end
+
+ if parts_query["chat_name"]
+ if !final_query.nil?
+ final_query = final_query.where(chat_base_id: ChatBase.where("chat_name LIKE ?", "%#{parts_query["chat_name"]}%"))
+ else
+ final_query = ChatParticipant.where(chat_base_id: ChatBase.where("chat_name LIKE ?", "%#{parts_query["chat_name"]}%"))
+ end
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :user_id, :chat_base_base], [:chat_base, :user])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /chat_participants/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_chat_participants)
+ return
+ end
+ render json: @chat_participant
+ end
+
+ # POST /chat_participants
+ def create
+ if !check_perms_write!(get_user_roles.perms_chat_participants)
+ return
+ end
+ @chat_participant = ChatParticipant.new(chat_participant_params)
+ if @chat_participant.save
+ render json: @chat_participant, status: :created, location: @chat_participant
+ else
+ render json: @chat_participant.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /chat_participants/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_chat_participants, false, :null)
+ return
+ end
+
+ if @chat_participant.update(chat_participant_params)
+ render json: @chat_participant
+ else
+ render json: @chat_participant.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /chat_participants/1
+ def destroy
+ return if !check_perms_delete!(get_user_roles.perms_chat_participants, false, :null)
+
+ @chat_participant.destroy
+ end
+
+ # Removes a user from a chat, and deletes the ```ChatBase``` if
+ # no more ```ChatParticipant``` are linked to it.
+ def remove_participant
+ @chat_participant = ChatParticipant.where(user_id: params[:user_id], chat_base_id: params[:chat_base_id])
+ if @chat_participant.length > 0
+ @chat_participant.first.destroy
+
+ if ChatParticipant.where(chat_base_id: params[:chat_base_id]).length < 1
+ ChatMessage.where(chat_base_id: params[:chat_base_id]).each do |m|
+ m.destroy
+ end
+ ChatBase.find(params[:chat_base_id]).destroy
+ end
+ return
+ end
+ return render json: { error: "Participant not found." }, status: 404
+ end
+
+ private
+
+ # Checks if a user is in a certain ```ChatBase```.
+ def check_user_in_chat(chat_base_id)
+ if ChatParticipant.where(user_id: @current_user, chat_base_id: chat_base_id).count > 0 || get_user_roles.name == "eduapp_admin"
+ return true
+ end
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_chat_participant
+ @chat_participant = ChatParticipant.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def chat_participant_params
+ params.require(:chat_participant).permit(:chat_base_id, :user_id, :isChatAdmin)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/concerns/current_user_concern.rb b/backend/eduapp_db/app/controllers/concerns/current_user_concern.rb
new file mode 100644
index 00000000..393688d3
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/concerns/current_user_concern.rb
@@ -0,0 +1,13 @@
+module CurrentUserConcern
+ extend ActiveSupport::Concern
+ included do
+ before_action :set_current_user
+ end
+
+ # Devise set user for session management.
+ def set_current_user
+ if session[:user_id]
+ @current_user = User.find(session[:user_id])
+ end
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/courses_controller.rb b/backend/eduapp_db/app/controllers/courses_controller.rb
new file mode 100644
index 00000000..48f40dc4
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/courses_controller.rb
@@ -0,0 +1,135 @@
+class CoursesController < ApplicationController
+ before_action :set_course, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /courses
+ def index
+ is_name = false
+ if params[:user_id]
+ if !check_perms_query_self!(get_user_roles.perms_course, params[:user_id])
+ return
+ end
+ @courses = Course.where(id: Tuition.where(user_id: params[:user_id]))
+ elsif params[:name]
+ # TODO: HANDLE PERMISSIONS FOR NAME QUERIES
+ is_name = true
+ @courses = Course.where('name ilike ?', "%#{params[:name]}%")
+ elsif params[:id]
+ # TODO: HANDLE PERMISSIONS FOR NAME QUERIES
+ @courses = Course.where('id::text ilike ?', "%#{params[:id]}%")
+ else
+ if !check_perms_all!(get_user_roles.perms_course)
+ return
+ end
+ @courses = Course.all
+ end
+
+ if !is_name
+ if !params[:order].nil? && Base64.decode64(params[:order]) != "null"
+ @courses = @courses.order(parse_filter_order(params[:order]))
+ else
+ @courses = @courses.order(name: :asc)
+ end
+ end
+
+ if params[:page]
+ @courses = query_paginate(@courses, params[:page])
+ @courses[:current_page] = serialize_each(@courses[:current_page], [:created_at, :updated_at, :institution_id], [:institution])
+ end
+
+ render json: @courses
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ course_query = {}
+ params.each do |param|
+ next unless param[0] == "id" || param[0] == "name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ course_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = params[:extras] ? filter_extrafields(params[:extras], Course) : nil
+
+ if course_query["id"]
+ ids = []
+ Course.all.each do |c|
+ ids << c.id if c.id.to_s =~ /^#{course_query["id"]}.*$/
+ end
+ final_query = !final_query.nil? ? final_query.where(id: ids) : Course.where(id: ids)
+ end
+
+ if course_query["name"]
+ if !final_query.nil?
+ final_query = final_query.where("name LIKE ?", "%#{course_query["name"]}%")
+ else
+ final_query = Course.where("name LIKE ?", "%#{course_query["name"]}%")
+ end
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :institution_id], [:institution])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /courses/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_course)
+ return
+ end
+ render json: @course
+ end
+
+ # POST /courses
+ def create
+ if !check_perms_write!(get_user_roles.perms_course)
+ return
+ end
+ @course = Course.new(course_params)
+
+ if @course.save
+ render json: @course, status: :created, location: @course
+ else
+ render json: @course.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /courses/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_course, false, :null)
+ return
+ end
+ if @course.update(course_params)
+ render json: @course
+ else
+ render json: @course.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /courses/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_course, false, :null)
+ return
+ end
+ @course.destroy
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_course
+ @course = Course.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def course_params
+ params.permit(:institution_id, :name)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/eduapp_user_sessions_controller.rb b/backend/eduapp_db/app/controllers/eduapp_user_sessions_controller.rb
new file mode 100644
index 00000000..06709a7e
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/eduapp_user_sessions_controller.rb
@@ -0,0 +1,322 @@
+class EduappUserSessionsController < ApplicationController
+ before_action :set_eduapp_user_session, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ require "date"
+ require "securerandom"
+
+ # GET /eduapp_user_sessions
+ def index
+ if params[:subject_id]
+ if !subject_in_user_course(params[:subject_id])
+ return deny_perms_access!
+ end
+ @eduapp_user_sessions = EduappUserSession.where(subject_id: params[:subject_id])
+ elsif params[:session_name]
+ # if !subject_in_user_course(params[:subject_id])
+ # return deny_perms_access!
+ # end
+ @eduapp_user_sessions = EduappUserSession.where('session_name ilike ?', "%#{params[:session_name]}%")
+ elsif params[:id]
+ # TODO: HANDLE PERMISSIONS FOR NAME QUERIES
+ @eduapp_user_sessions = EduappUserSession.where('id::text ilike ?', "%#{params[:id]}%")
+ elsif params[:streaming_platform]
+ # if !subject_in_user_course(params[:subject_id])
+ # return deny_perms_access!
+ # end
+ @eduapp_user_sessions = EduappUserSession.where('streaming_platform ilike ?', "%#{params[:streaming_platform]}%")
+ elsif params[:session_chat_id]
+ # if !subject_in_user_course(params[:subject_id])
+ # return deny_perms_access!
+ # end
+ @eduapp_user_sessions = EduappUserSession.where('session_chat_id::text ilike ?', "%#{params[:session_chat_id]}%")
+ elsif params[:subject_name]
+ # TODO: HANDLE PERMISSIONS FOR CHAINED SUBJECT QUERIES
+ @eduapp_user_sessions = EduappUserSession.joins(:subject).where('subjects.name ilike ?', "%#{params[:subject_name]}%")
+ else
+ if !check_perms_all!(get_user_roles.perms_sessions)
+ return
+ end
+ @eduapp_user_sessions = EduappUserSession.all
+ end
+
+ order = !params[:order].nil? && JSON.parse(Base64.decode64(params[:order]))
+ if order && order["field"] != ""
+ if order["field"] == 'subject_name'
+ @eduapp_user_sessions = @eduapp_user_sessions.joins(:subject)
+ end
+ @eduapp_user_sessions = @eduapp_user_sessions.order(parse_filter_order(order, {'subject_name' => 'subjects.name'}))
+
+ else
+ @eduapp_user_sessions = @eduapp_user_sessions.order(session_name: :asc)
+ end
+
+ if params[:page]
+ @eduapp_user_sessions = query_paginate(@eduapp_user_sessions, params[:page])
+ @eduapp_user_sessions[:current_page] = serialize_each(@eduapp_user_sessions[:current_page], [:created_at, :updated_at, :subject_id], [:subject])
+ end
+
+ render json: @eduapp_user_sessions
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ sessions_query = {}
+ subject_query = {}
+ params.each do |param|
+ next if param[0] == "controller" || param[0] == "action" || param[0] == "extras" || param[0] == "eduapp_user_session"
+ next unless param[1] != "null" && param[1].length > 0
+
+ query = { param[0] => param[1] }
+ case param[0]
+ when "id", "session_name", "streaming_platform", "resources_platform", "session_chat_id"
+ sessions_query.merge!(query)
+ when "subject_name"
+ subject_query.merge!(query)
+ end
+ end
+
+ final_query = params[:extras] ? filter_extrafields(params[:extras], EduappUserSession) : nil
+
+ if !sessions_query.empty?
+ query = !final_query.nil? ? final_query : nil
+
+ if sessions_query["id"]
+ ids = []
+ EduappUserSession.all.each do |s|
+ ids << s.id if s.id.to_s =~ /^#{sessions_query["id"]}.*$/
+ end
+ query = EduappUserSession.where(id: ids)
+ end
+
+ if sessions_query["session_name"]
+ if !query.nil?
+ query = query.where("session_name LIKE ?", "%#{sessions_query["session_name"]}%")
+ else
+ query = EduappUserSession.where("session_name LIKE ?", "%#{sessions_query["session_name"]}%")
+ end
+ end
+
+ if sessions_query["streaming_platform"]
+ if !query.nil?
+ query = query.where("streaming_platform LIKE ?", "%#{sessions_query["streaming_platform"]}%")
+ else
+ query = EduappUserSession.where("streaming_platform LIKE ?", "%#{sessions_query["streaming_platform"]}%")
+ end
+ end
+
+ if sessions_query["resources_platform"]
+ if !query.nil?
+ query = query.where("resources_platform LIKE ?", "%#{sessions_query["resources_platform"]}%")
+ else
+ query = EduappUserSession.where("resources_platform LIKE ?", "%#{sessions_query["resources_platform"]}%")
+ end
+ end
+
+ if sessions_query["session_chat_id"]
+ if !query.nil?
+ query = query.where("session_chat_id LIKE ?", "%#{sessions_query["session_chat_id"]}%")
+ else
+ query = EduappUserSession.where("session_chat_id LIKE ?", "%#{sessions_query["session_chat_id"]}%")
+ end
+ end
+
+ final_query = query
+ end
+
+ if !final_query.nil? && !subject_query.empty?
+ final_query = final_query.where(subject_id: Subject.where("name LIKE ?", "%#{subject_query["subject_name"]}%"))
+ elsif !subject_query.empty?
+ final_query = EduappUserSession.where(subject_id: Subject.where("name LIKE ?", "%#{subject_query["subject_name"]}%"))
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :subject_id], [:subject])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /eduapp_user_sessions/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_sessions, false) && !check_perms_query_self!(get_user_roles.perms_subjects, current_user)
+ deny_perms_access!
+ return
+ end
+ render json: @eduapp_user_session
+ end
+
+ # POST /eduapp_user_sessions
+ def create
+ if !check_perms_write!(get_user_roles.perms_sessions)
+ return
+ end
+ @eduapp_user_session = EduappUserSession.new(eduapp_user_session_params)
+ if @eduapp_user_session.save
+ render json: @eduapp_user_session, status: :created, location: @eduapp_user_session
+ else
+ render json: @eduapp_user_session.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /eduapp_user_sessions/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_sessions, false, :null)
+ return
+ end
+
+ if @eduapp_user_session.update(session_name: params[:session_name],
+ session_start_date: params[:session_start_date],
+ session_end_date: params[:session_end_date],
+ streaming_platform: params[:streaming_platform],
+ resources_platform: params[:resources_platform],
+ session_chat_id: params[:session_chat_id],
+ subject_id: params[:subject_id], batch_id: nil)
+ render json: @eduapp_user_session
+ else
+ render json: @eduapp_user_session.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /eduapp_user_sessions/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_roles, false, :null)
+ return
+ end
+ @eduapp_user_session.destroy
+ end
+
+ # Destroys a group of ```EduappUserSession``` created by the
+ # batch loader.
+ def destroy_batch
+ EduappUserSession.where(batch_id: params[:batch_id]).destroy_all
+ end
+
+ # Creates a group of ```EduappUserSession``` based on many entries
+ # and generates a new ```EduappUserSession``` entry for each.
+ def session_batch_load
+ days_added = 0
+ weeks_passed = 0
+ week_days_passed = 1
+ first_week = true
+
+ new_session_days = []
+ cursor_date = params[:session_start_date].split("T")[0].to_date - 1
+ last_cursor_date = cursor_date
+
+ while days_added <= params[:diff_days]
+ if !first_week
+ if params[:week_repeat] > 0
+ if cursor_date.strftime("%U").to_i != last_cursor_date.strftime("%U").to_i
+ weeks_passed = 0 if weeks_passed >= params[:week_repeat]
+ weeks_passed += 1
+ if weeks_passed < params[:week_repeat]
+ cursor_date = cursor_date + 7
+ if cursor_date > params[:session_end_date].split("T")[0].to_date
+ break
+ end
+ next
+ end
+ end
+ end
+ end
+
+ if (params[:check_week_days][0] && cursor_date.monday?)
+ new_session_days.append(cursor_date)
+ end
+ if (params[:check_week_days][1] && cursor_date.tuesday?)
+ new_session_days.append(cursor_date)
+ end
+ if (params[:check_week_days][2] && cursor_date.wednesday?)
+ new_session_days.append(cursor_date)
+ end
+ if (params[:check_week_days][3] && cursor_date.thursday?)
+ new_session_days.append(cursor_date)
+ end
+ if (params[:check_week_days][4] && cursor_date.friday?)
+ new_session_days.append(cursor_date)
+ end
+ if (params[:check_week_days][5] && cursor_date.saturday?)
+ new_session_days.append(cursor_date)
+ end
+ if (params[:check_week_days][6] && cursor_date.sunday?)
+ new_session_days.append(cursor_date)
+ end
+
+ days_added += 1
+ last_cursor_date = cursor_date
+ cursor_date = cursor_date + 1
+ week_days_passed += 1
+ if week_days_passed > 7
+ first_week = false
+ week_days_passed = 0
+ end
+ end
+
+ session_start_time = params[:session_start_date].split("T")[1]
+ session_end_time = params[:session_end_date].split("T")[1]
+ batch_id = SecureRandom.uuid
+ subjectId = Subject.where(id: params[:subject_id]).first.id
+
+ new_session_days.each do |day|
+ @eduapp_user_session = EduappUserSession.new(
+ session_name: params[:session_name],
+ session_start_date: day.to_s + "T" + session_start_time,
+ session_end_date: day.to_s + "T" + session_end_time,
+ resources_platform: params[:resources_platform],
+ streaming_platform: params[:streaming_platform],
+ session_chat_id: params[:session_chat_id],
+ subject_id: subjectId,
+ batch_id: batch_id,
+ )
+
+ if !@eduapp_user_session.save
+ render json: @eduapp_user_session.errors, status: :unprocessable_entity and return
+ end
+ end
+ render json: @eduapp_user_session
+ end
+
+ # Updates a group of ```EduappUserSession``` based on its batch identifier.
+ def update_batch
+ render json: { error: "No id provided" }, status: :unprocessable_entity and return if params[:batch_id].nil?
+
+ sessionsToBeUpdated = EduappUserSession.where(batch_id: params[:batch_id])
+ if sessionsToBeUpdated.update(
+ session_name: params[:session_name],
+ resources_platform: params[:resources_platform],
+ streaming_platform: params[:streaming_platform],
+ session_chat_id: params[:session_chat_id],
+ subject_id: params[:subject_id],
+ )
+ render json: { message: "Successfully updated sessions" } and return
+ else
+ render json: { error: "Failed to update sessions" }, status: :unprocessable_entity and return
+ end
+ end
+
+ private
+
+ # Checks if ```Subject``` is present in the user's ```Course```.
+ def subject_in_user_course(s_id)
+ c_id = Subject.find(s_id).course_id
+ if Tuition.where(user_id: @current_user, course_id: c_id).count > 0
+ return true
+ end
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_eduapp_user_session
+ @eduapp_user_session = EduappUserSession.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def eduapp_user_session_params
+ params.require(:eduapp_user_session).permit(:session_name, :session_start_date, :session_end_date, :streaming_platform, :resources_platform, :session_chat_id, :subject_id, :number_repeat, :check_week_days, :week_repeat, :diff_days)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/institutions_controller.rb b/backend/eduapp_db/app/controllers/institutions_controller.rb
new file mode 100644
index 00000000..7d54b63a
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/institutions_controller.rb
@@ -0,0 +1,70 @@
+class InstitutionsController < ApplicationController
+ before_action :set_institution, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /institutions
+ def index
+ if !check_perms_all!(get_user_roles.perms_institution)
+ return
+ end
+ @institutions = Institution.all
+
+ if params[:page]
+ @institutions = query_paginate(@institutions, params[:page])
+ end
+
+ render json: @institutions
+ end
+
+ # GET /institutions/1
+ def show
+ render json: @institution
+ end
+
+ # POST /institutions
+ def create
+ if !check_perms_query!(get_user_roles.perms_institution)
+ return
+ end
+ @institution = Institution.new(institution_params)
+
+ if @institution.save
+ render json: @institution, status: :created, location: @institution
+ else
+ render json: @institution.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /institutions/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_institution, false, :null)
+ return
+ end
+ if @institution.update(institution_params)
+ render json: @institution
+ else
+ render json: @institution.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /institutions/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_institution, false, :null)
+ return
+ end
+ @institution.destroy
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_institution
+ @institution = Institution.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def institution_params
+ params.permit(:name)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/push_notifications_controller.rb b/backend/eduapp_db/app/controllers/push_notifications_controller.rb
new file mode 100644
index 00000000..d83d2f2e
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/push_notifications_controller.rb
@@ -0,0 +1,50 @@
+class PushNotificationsController < ApplicationController
+ before_action :authenticate_user!, except: [:key, :subscribe]
+ before_action :check_role!, except: [:key, :subscribe]
+
+ def key
+ render json: { public_key: Base64.urlsafe_decode64(ENV.fetch("VAPID_PUBLIC_KEY")).bytes }
+ end
+
+ def subscribe
+ push_notification = PushNotification.find_by(auth: permit_params[:auth])
+ if !push_notification
+ push_notification = PushNotification.new(permit_params)
+ end
+
+ if push_notification.save
+ render json: push_notification, status: :created
+ else
+ render json: push_notification.errors, status: :unprocessable_entity
+ end
+ end
+
+ # def push
+ # push_notifications = PushNotification.where(user_id: @current_user)
+ # message = {
+ # title: "title",
+ # body: "body",
+ # icon: "http://example.com/icon.pn"
+ # }
+ # push_notifications.each do |push_noficification|
+ # Webpush.payload_send(
+ # endpoint: push_noficification.endpoint,
+ # message: JSON.generate(message),
+ # p256dh: push_noficification.p256dh,
+ # auth: push_noficification.auth,
+ # vapid: {
+ # subject: "mailto:alejandro.vera1988@example.com",
+ # public_key: ENV.fetch('VAPID_PUBLIC_KEY'),
+ # private_key: ENV.fetch('VAPID_PRIVATE_KEY')
+ # }
+ # )
+ # end
+ # end
+
+ private
+
+ def permit_params
+ params.permit(:endpoint, :user_id, :p256dh, :auth)
+ end
+
+end
diff --git a/backend/eduapp_db/app/controllers/resources_controller.rb b/backend/eduapp_db/app/controllers/resources_controller.rb
index f3cd0de1..e2e41ce3 100644
--- a/backend/eduapp_db/app/controllers/resources_controller.rb
+++ b/backend/eduapp_db/app/controllers/resources_controller.rb
@@ -1,32 +1,160 @@
class ResourcesController < ApplicationController
before_action :set_resource, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
# GET /resources
def index
- @resources = Resource.all
+ if params[:subject_id]
+ @resources = Resource.order(created_at: :desc).where(subject_id: params[:subject_id])
+ else
+ if !check_perms_all!(get_user_roles.perms_resources)
+ return
+ end
+ @resources = Resource.all
+ end
+
+ if !params[:order].nil? && Base64.decode64(params[:order]) != "null"
+ @resources = @resources.order(parse_filter_order(params[:order]))
+ else
+ @resources = @resources.order(name: :asc)
+ end
+
+ if params[:page]
+ @resources = query_paginate(@resources, params[:page])
+ @resources[:current_page] = serialize_each(@resources[:current_page], [:created_at, :updated_at, :user_id, :subject_id], [:user, :subject])
+ end
render json: @resources
end
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ resources_query = {}
+ subject_query = {}
+ params.each do |param|
+ next if param[0] == "controller" || param[0] == "action" || param[0] == "extras" || param[0] == "resource"
+ next unless param[1] != "null" && param[1].length > 0
+
+ query = { param[0] => param[1] }
+ case param[0]
+ when "id", "name", "author"
+ resources_query.merge!(query)
+ when "subject_name"
+ subject_query.merge!(query)
+ end
+ end
+
+ final_query = params[:extras] ? filter_extrafields(params[:extras], Resource) : nil
+
+ if !resources_query.empty?
+ query = !final_query.nil? ? final_query : nil
+
+ if resources_query["id"]
+ ids = []
+ Resource.all.each do |r|
+ ids << r.id if r.id.to_s =~ /^#{resources_query["id"]}.*$/
+ end
+ query = Resource.where(id: ids)
+ end
+
+ if resources_query["name"]
+ if !query.nil?
+ query = query.where("name LIKE ?", "%#{resources_query["name"]}%")
+ else
+ query = Resource.where("name LIKE ?", "%#{resources_query["name"]}%")
+ end
+ end
+
+ if resources_query["author"]
+ if !query.nil?
+ query = query.where(user_id: User.where("email LIKE ?", "%#{resources_query["author"]}%"))
+ else
+ query = Resource.where(user_id: User.where("email LIKE ?", "%#{resources_query["author"]}%"))
+ end
+ end
+
+ final_query = query
+ end
+
+ if !final_query.nil? && !subject_query.empty?
+ final_query = final_query.where(subject_id: Subject.where("name LIKE ?", "%#{subject_query["subject_name"]}%"))
+ elsif !subject_query.empty?
+ final_query = Resource.where(subject_id: Subject.where("name LIKE ?", "%#{subject_query["subject_name"]}%"))
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :user_id, :subject_id], [:user, :subject])
+ end
+
+ render json: { filtration: final_query }
+ end
+
# GET /resources/1
def show
+ if !resource_in_user_course && get_user_roles.name != get_admin_role.name
+ return deny_perms_access!
+ end
render json: @resource
end
# POST /resources
def create
- @resource = Resource.new(resource_params)
+ if !check_perms_write!(get_user_roles.perms_resources)
+ return
+ end
+ @resource = Resource.new(
+ name: params[:name],
+ description: params[:description],
+ user_id: params[:user_id],
+ subject_id: params[:subject_id],
+ )
if @resource.save
+ files = []
+ x = 0
+ while x < 9
+ files.append(resource_params["file_#{x}"]) if !resource_params["file_#{x}"].nil?
+ x += 1
+ end
+
+ if files.count > 0
+ @resource.files.attach(files)
+ if @resource.files.attached?
+ sFiles = []
+ json = []
+ for f in @resource.files.attachments
+ sFiles.append(url_for(f))
+ json.append(f.to_json)
+ end
+ @resource.resource_files = sFiles
+ @resource.resource_files_json = json
+ if @resource.save
+ render json: @resource, status: :created, location: @resource and return
+ else
+ render json: @resource.errors, status: :unprocessable_entity and return
+ end
+ end
+ end
render json: @resource, status: :created, location: @resource
else
render json: @resource.errors, status: :unprocessable_entity
end
end
- # PATCH/PUT /resources/1
+ # PUT /resources/1
def update
- if @resource.update(resource_params)
+ if !check_perms_update!(get_user_roles.perms_resources, true, @resource.user_id)
+ return
+ end
+
+ if @resource.update(name: params[:name], description: params[:description], resource_files: params[:resource_files], resource_files_json: params[:resource_files_json], blob_id_delete: params[:blob_id_delete], subject_id: params[:subject_id])
+ for idDelete in params[:blob_id_delete]
+ @resource.files.find(idDelete).purge
+ end
render json: @resource
else
render json: @resource.errors, status: :unprocessable_entity
@@ -35,17 +163,51 @@ def update
# DELETE /resources/1
def destroy
+ if !check_perms_delete!(get_user_roles.perms_resources, true, @resource.user_id)
+ return
+ end
+ if @resource.files.attached?
+ @resource.files.attachments.each do |attachment|
+ attachment.purge
+ end
+ end
@resource.destroy
end
private
- # Use callbacks to share common setup or constraints between actions.
- def set_resource
- @resource = Resource.find(params[:id])
+
+ # Only allow a list of trusted parameters through.
+ def resource_params
+ permits = [:id, :name, :description, :blob_id_delete, :user_id, :subject_id]
+
+ x = 0
+ while x < 10
+ permits.append("file_#{x}")
+ x += 1
+ end
+ params.permit(permits)
+ end
+
+ # Checks if ```Subject``` is present in the user's ```Course```.
+ def subject_in_user_course(s_id)
+ c_id = Subject.find(s_id).course_id
+ if Tuition.where(user_id: @current_user, course_id: c_id).count > 0
+ return true
end
+ return false
+ end
- # Only allow a list of trusted parameters through.
- def resource_params
- params.require(:resource).permit(:name, :description)
+ # Checks if ```Resource``` is present in the user's ```Course```.
+ def resource_in_user_course
+ c_id = Subject.find(@resource.subject_id).course_id
+ if Tuition.where(user_id: @current_user, course_id: c_id).count > 0
+ return true
end
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_resource
+ @resource = Resource.find(params[:id])
+ end
end
diff --git a/backend/eduapp_db/app/controllers/static_controller.rb b/backend/eduapp_db/app/controllers/static_controller.rb
new file mode 100644
index 00000000..822f1f7f
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/static_controller.rb
@@ -0,0 +1,16 @@
+class StaticController < ApplicationController
+ # Checks if the server is alive.
+ def ping
+ render json: { status: "Pong!" }
+ end
+
+ # Tests if there is an existing administrator.
+ def admin
+ render json: { created: UserInfo.where(user_role_id: UserRole.find_by(name: "eduapp-admin").id).count > 0 } and return
+ end
+
+ # Checks for at least 1 ```Institution``` entry.
+ def created
+ render json: { created: Institution.all.count > 0 } and return
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/subjects_controller.rb b/backend/eduapp_db/app/controllers/subjects_controller.rb
new file mode 100644
index 00000000..999b6379
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/subjects_controller.rb
@@ -0,0 +1,243 @@
+class SubjectsController < ApplicationController
+ before_action :set_subject, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /subjects
+ def index
+ wants_info_for_calendar = false || params[:wants_info_for_calendar]
+
+ if params[:user_id]
+ wants_info_for_calendar = true
+ if !check_perms_query_self!(get_user_roles.perms_subjects, params[:user_id])
+ return
+ end
+ # TODO: Possible refactorization:
+ # tuitions = Tuition.where(user_id: params[:user_id]).pluck(:course_id)
+ # @Subjects = Subject.where(course_id: tuitions)
+ # @todaySessions = EduappUserSession.where(subject_id: @Subjects).pluck(:session_start_date)
+ # @Sessions = []
+
+ # for hour in @todaySessions
+ # if (hour.split("T")[1].split(":")[0] == @TodayHourNow or hour.split("T")[1].split(":")[0] >= @TodayHourNow and hour.split("T")[0] == @Today)
+ # @Sessions += EduappUserSession.where(subject_id: @Subjects, session_start_date: hour)
+ # end
+ # end
+ # @subjects = @Sessions
+
+ @subjects = User.find(params[:user_id]).subjects
+ elsif params[:all_sessions]
+ #TODO: cambiar esta lógica pasarla a eventos, calendario(analizar)
+ # Tiene que devolver todos los evenos que hay hoy, sin tener que preguntar por la hora
+ @sessions = []
+ @today = Time.now.strftime("%F")
+ @todayHourNow = Time.now.strftime("%H")
+ user = User.find(current_user)
+ if user.user_info.user_role.name == 'eduapp-teacher'
+ @user_subjects = user.user_info.teaching_list
+ else
+ @user_subjects = user.subjects.pluck(:id)
+ end
+ @todaySessions = []
+ for subject in @user_subjects
+ @todaySessions += EduappUserSession.where(subject_id: subject).pluck(:session_start_date)
+ end
+
+ for hour in @todaySessions
+ if (hour.split("T")[1].split(":")[0] == @todayHourNow or hour.split("T")[1].split(":")[0] >= @todayHourNow and hour.split("T")[0] == @today)
+ @sessions += EduappUserSession.where(subject_id: @user_subjects, session_start_date: hour)
+ end
+ end
+ @subjects = @sessions
+ elsif params[:subject_id]
+ @subjects = Subject.where(id: params[:subject_id])
+ elsif params[:name]
+ # TODO: HANDLE PERMISSIONS FOR CHAINED SUBJECT QUERIES
+ @subjects = Subject.where('name ilike ?', "%#{params[:name]}%")
+ elsif params[:subject_code]
+ # TODO: HANDLE PERMISSIONS FOR CHAINED SUBJECT QUERIES
+ @subjects = Subject.where('subject_code ilike ?', "%#{params[:subject_code]}%")
+ elsif params[:id]
+ # TODO: HANDLE PERMISSIONS FOR NAME QUERIES
+ @subjects = Subject.where('id::text ilike ?', "%#{params[:id]}%")
+ elsif params[:user]
+ # TODO: HANDLE PERMISSIONS FOR CHAINED SUBJECT QUERIES
+ @subjects = Subject.where(course_id: Tuition.where(user_id: params[:user]).pluck(:course_id))
+ elsif params[:course_name]
+ # TODO: HANDLE PERMISSIONS FOR CHAINED SUBJECT QUERIES
+ @subjects = Subject.joins(:course).where('courses.name ilike ?', "%#{params[:course_name]}%")
+ else
+ if !check_perms_all!(get_user_roles.perms_subjects)
+ return
+ end
+ @subjects = Subject.all
+ end
+
+ if wants_info_for_calendar
+ order = !params[:order].nil? && JSON.parse(Base64.decode64(params[:order]))
+ if order && order["field"] != ""
+ if order["field"] == 'course_name'
+ @subjects = @subjects.joins(:course)
+ end
+ @subjects = @subjects.order(parse_filter_order(order,{'course_name' => 'courses.name'}))
+ else
+ @subjects = @subjects.order(name: :asc)
+ end
+ end
+
+ if params[:page]
+ @subjects = query_paginate(@subjects, params[:page])
+ @subjects[:current_page] = serialize_each(@subjects[:current_page], [:created_at, :updated_at], [:course, :users])
+ end
+
+ render json: @subjects
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ subjects_query = {}
+ course_query = {}
+ params.each do |param|
+ next if param[0] == "controller" || param[0] == "action" || param[0] == "extras" || param[0] == "subject"
+ next unless param[1] != "null" && param[1].length > 0
+
+ query = { param[0] => param[1] }
+ case param[0]
+ when "id", "name", "subject_code"
+ subjects_query.merge!(query)
+ when "course_name"
+ course_query.merge!(query)
+ end
+ end
+
+ final_query = params[:extras] ? filter_extrafields(params[:extras], Subject) : nil
+
+ if !subjects_query.empty?
+ query = !final_query.nil? ? final_query : nil
+
+ if subjects_query["id"]
+ ids = []
+ Subject.all.each do |s|
+ ids << s.id if s.id.to_s =~ /^#{subjects_query["id"]}.*$/
+ end
+ query = Subject.where(id: ids)
+ end
+
+ if subjects_query["name"]
+ if !query.nil?
+ query = query.where("name LIKE ?", "%#{subjects_query["name"]}%")
+ else
+ query = Subject.where("name LIKE ?", "%#{subjects_query["name"]}%")
+ end
+ end
+
+ if subjects_query["subject_code"]
+ if !query.nil?
+ query = query.where("subject_code LIKE ?", "%#{subjects_query["subject_code"]}%")
+ else
+ query = Subject.where("subject_code LIKE ?", "%#{subjects_query["subject_code"]}%")
+ end
+ end
+
+ final_query = query
+ end
+
+ if !final_query.nil? && !course_query.empty?
+ final_query = final_query.where(course_id: Course.where("name LIKE ?", "%#{course_query["course_name"]}%"))
+ elsif !course_query.empty?
+ final_query = Subject.where(course_id: Course.where("name LIKE ?", "%#{course_query["course_name"]}%"))
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :course_id], [:course])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /subjects/1
+ #TODO: probar pasar un parametro render(boolean) para controlar los render anidados
+ def show
+ if !check_perms_query!(get_user_roles.perms_subjects, false) && !check_perms_query_self!(get_user_roles.perms_subjects, current_user)
+ deny_perms_access!
+ return
+ end
+ render json: @subject
+ end
+
+ # POST /subjects
+ def create
+ if !check_perms_write!(get_user_roles.perms_subjects)
+ return
+ end
+
+ if Subject.where(subject_code: params[:subject_code]).count > 0
+ render json: @Subject, status: :unprocessable_entity
+ elsif params[:enrollment] # TODO: refactor crear una acción
+ @subject = Subject.find(params[:subject_id])
+ @subject.users << User.find(params[:user_id])
+ @subject.save
+ else
+ @subject = Subject.new(subject_code: params[:subject_code], name: params[:name],
+ description: params[:description], color: params[:color],
+ course_id: params[:course_id], chat_link: params[:chat_link])
+ if @subject.save
+ render json: @subject, status: :created, location: @subject
+ else
+ render json: @subject.errors, status: :unprocessable_entity
+ end
+ end
+ end
+
+ # PUT /subjects/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_subjects, true, current_user, false)
+ return
+ end
+ if @subject.update(subject_params)
+ render json: @subject
+ else
+ render json: @subject.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /subjects/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_subjects, false, :null)
+ return
+ end
+ @subject.destroy
+ end
+
+ # DELETE /subjects/1/users/1
+ def destroy_user
+ if !check_perms_delete!(get_user_roles.perms_subjects, false, :null)
+ return
+ end
+ @subject = Subject.find(params[:subject_id])
+ @subject.users.destroy(params[:user_id])
+ end
+
+ private
+
+ # Checks if ```Subject``` is present in the user's ```Course```.
+ def subject_in_user_course
+ if Tuition.where(user_id: @current_user, course_id: @subject.course_id).count > 0
+ return true
+ end
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_subject
+ @subject = Subject.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def subject_params
+ params.require(:subject).permit(:subject_code, :name, :description, :color, :chat_link)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/subjects_users_controller.rb b/backend/eduapp_db/app/controllers/subjects_users_controller.rb
new file mode 100644
index 00000000..428636e4
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/subjects_users_controller.rb
@@ -0,0 +1,126 @@
+class SubjectsUsersController < ApplicationController
+ before_action :set_subject_user, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ def index
+ if !check_perms_all!(get_user_roles.perms_tuitions)
+ return
+ end
+ enroll_query = {}
+ params.each do |param|
+ next unless param[0] == "user_email" || param[0] == "subject_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ enroll_query.merge!({ param[0] => param[1] })
+ end
+
+ @subjects_users = SubjectsUser
+
+ if params[:user_id]
+ @subjects_users = SubjectsUser.where(user_id: params[:user_id])
+ end
+
+ if enroll_query["user_email"]
+ @subjects_users = SubjectsUser.where(user_id: User.where("email LIKE ?", "%#{enroll_query["user_email"]}%"))
+ end
+
+ if enroll_query["subject_name"]
+ @subjects_users = SubjectsUser.where(subject_id: Subject.where("name LIKE ?", "%#{enroll_query["subject_name"]}%"))
+ end
+
+ if params[:page]
+ @subjects_users = query_paginate(@subjects_users, params[:page] || 1)
+ @subjects_users[:current_page] = serialize_each(@subjects_users[:current_page], [:created_at, :updated_at, :subject, :user_id], [:subject, :user])
+ end
+
+ render json: @subjects_users
+ end
+
+ def filter
+ enroll_query = {}
+ params.each do |param|
+ next unless param[0] == "user_email" || param[0] == "subject_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ enroll_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = nil
+
+ if enroll_query["user_email"]
+ final_query = SubjectsUser.where(user_id: User.where("email LIKE ?", "%#{enroll_query["user_email"]}%"))
+ end
+
+ if enroll_query["subject_name"]
+ if !final_query.nil?
+ final_query = final_query.where(subject_id: Subject.where("name LIKE ?", "%#{enroll_query["subject_name"]}%"))
+ else
+ final_query = SubjectsUser.where(subject_id: Subject.where("name LIKE ?", "%#{enroll_query["subject_name"]}%"))
+ end
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :user_id, :course_id], [:user, :course])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ def show
+ if !check_perms_query!(get_user_roles.perms_tuitions)
+ return
+ end
+ render json: @subject_user
+ end
+
+ def create
+ if !check_perms_write!(get_user_roles.perms_tuitions)
+ return
+ end
+
+ @subject_user = SubjectsUser.new(subject_id: params[:subject_id], user_id: params[:user_id])
+ if SubjectsUser.where(user_id: params[:user_id], subject_id: params[:subject_id]).count > 0
+ render json: @subject_user.errors, status: :unprocessable_entity
+ else
+ if @subject_user.save
+ render json: @subject_user, status: :created, location: @subject_user
+ else
+ render json: @subject_user.errors, status: :unprocessable_entity
+ end
+ end
+ end
+
+ def update
+ if !check_perms_update!(get_user_roles.perms_tuitions, false, :null)
+ return
+ end
+
+ if @subject_user.update(subject_id: params[:subject_id], user_id: params[:user_id], id: params[:id])
+ render json: @subject_user
+ else
+ render json: @subject_user.errors, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_tuitions, false, :null)
+ return
+ end
+
+ @subject_user.destroy
+ end
+
+ private
+
+ def set_subject_user
+ @subject_user = SubjectsUser.find(params[:id])
+ end
+
+ def subject_user_params
+ params.require(:subject_user).permit(:subject_id, :user_id, :id)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/tuitions_controller.rb b/backend/eduapp_db/app/controllers/tuitions_controller.rb
new file mode 100644
index 00000000..680951df
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/tuitions_controller.rb
@@ -0,0 +1,128 @@
+class TuitionsController < ApplicationController
+ before_action :set_tuition, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /tuitions
+ def index
+ if !check_perms_all!(get_user_roles.perms_tuitions)
+ return
+ end
+ enroll_query = {}
+ params.each do |param|
+ next unless param[0] == "user_email" || param[0] == "course_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ enroll_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = Tuition
+
+ if enroll_query["user_email"]
+ final_query = final_query.where(user_id: User.where("email LIKE ?", "%#{enroll_query["user_email"]}%"))
+ end
+
+ if enroll_query["course_name"]
+ final_query = final_query.where(course_id: Course.where("name LIKE ?", "%#{enroll_query["course_name"]}%"))
+ end
+
+ @tuitions = query_paginate(final_query, params[:page] || 1)
+ @tuitions[:current_page] = serialize_each(@tuitions[:current_page], [:created_at, :updated_at, :course, :user_id], [:course, :user])
+
+ render json: @tuitions
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ enroll_query = {}
+ params.each do |param|
+ next unless param[0] == "user_email" || param[0] == "course_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ enroll_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = nil
+
+ if enroll_query["user_email"]
+ final_query = Tuition.where(user_id: User.where("email LIKE ?", "%#{enroll_query["user_email"]}%"))
+ end
+
+ if enroll_query["course_name"]
+ if !final_query.nil?
+ final_query = final_query.where(course_id: Course.where("name LIKE ?", "%#{enroll_query["course_name"]}%"))
+ else
+ final_query = Tuition.where(course_id: Course.where("name LIKE ?", "%#{enroll_query["course_name"]}%"))
+ end
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :user_id, :course_id], [:user, :course])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /tuitions/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_tuitions)
+ return
+ end
+ render json: @tuition
+ end
+
+ # POST /tuitions
+ def create
+ if !check_perms_write!(get_user_roles.perms_tuitions)
+ return
+ end
+
+ @tuition = Tuition.new(course_id: params[:course_id], user_id: params[:user_id])
+ if Tuition.where(user_id: params[:user_id], course_id: params[:course_id]).count > 0
+ render json: @tuition.errors, status: :unprocessable_entity
+ else
+ if @tuition.save
+ render json: @tuition, status: :created, location: @tuition
+ else
+ render json: @tuition.errors, status: :unprocessable_entity
+ end
+ end
+ end
+
+ # PATCH/PUT /tuitions/1
+ def update
+ if !check_perms_update!(get_user_roles.perms_tuitions, false, :null)
+ return
+ end
+
+ if @tuition.update(course_id: params[:course_id], user_id: params[:user_id], id: params[:id])
+ render json: @tuition
+ else
+ render json: @tuition.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /tuitions/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_tuitions, false, :null)
+ return
+ end
+
+ @tuition.destroy
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_tuition
+ @tuition = Tuition.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def tuition_params
+ params.require(:tuition).permit(:course_id, :user_id, :id)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/user_infos_controller.rb b/backend/eduapp_db/app/controllers/user_infos_controller.rb
new file mode 100644
index 00000000..c8bfa209
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/user_infos_controller.rb
@@ -0,0 +1,321 @@
+class UserInfosController < ApplicationController
+ before_action :set_user_info, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /user_infos
+ def index
+ @user_infos = UserInfo
+ if params[:user_id]
+ if !check_perms_query_self!(get_user_roles.perms_users, params[:user_id])
+ return
+ end
+ @user_infos = @user_infos.where(user_id: params[:user_id])
+ elsif params[:name]
+ # TODO: CHECK IF USER CAN SEARCH BY NAME
+ @user_infos = @user_infos.search_name(params[:name]).take(3)
+ elsif params[:user_name]
+ # TODO: CHECK IF USER CAN SEARCH BY USER NAME
+ @user_infos = @user_infos.search_name(params[:user_name]) #.take(3)
+ elsif params[:email]
+ # TODO: CHECK IF USER CAN SEARCH BY EMAIL
+ @user_infos = @user_infos.search_email(params[:email]) #.take(3)
+ else
+ if !check_perms_all!(get_user_roles.perms_users)
+ return
+ end
+ end
+
+ if !params[:order].nil? && Base64.decode64(params[:order]) != "null"
+ @user_infos = @user_infos.order(parse_filter_order(params[:order]))
+ else
+ @user_infos = params[:name] ? @user_infos : @user_infos.order(user_name: :asc)
+ end
+
+ if params[:extras]
+ extras = filter_extrafields(params[:extras], User)
+ @user_infos = @user_infos.where(user_id: extras)
+ end
+
+ if params[:page]
+ @user_infos = query_paginate(@user_infos, params[:page])
+ @user_infos[:current_page] = serialize_each(@user_infos[:current_page], [:created_at, :googleid, :updated_at, :user_id, :user_role_id], [:user, :user_role])
+ @user_infos[:current_page].each do |user_info|
+ user_info["user"]["last_sign_in_at"] = User.find(user_info["user"]["id"]).last_sign_in_at
+ end
+ end
+
+ render json: @user_infos
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ infos_query = {}
+ user_query = {}
+ role_query = {}
+ params.each do |param|
+ next if param[0] == "controller" || param[0] == "action" || param[0] == "extras" || param[0] == "user_info"
+ next unless param[1] != "null" && param[1].length > 0
+
+ query = { param[0] => param[1] }
+ case param[0]
+ when "user_id", "user_name"
+ infos_query.merge!(query)
+ when "email"
+ user_query.merge!(query)
+ when "role"
+ role_query.merge!(query)
+ end
+ end
+
+ extras = filter_extrafields(params[:extras], User)
+ final_query = params[:extras] ? (!extras.nil? ? UserInfo.where(user_id: extras) : nil) : nil
+
+ if !infos_query.empty?
+ query = !final_query.nil? ? final_query : nil
+
+ if infos_query["user_id"]
+ user_ids = []
+ UserInfo.all.each do |u|
+ user_ids << u.user_id if u.user_id.to_s =~ /^#{infos_query["user_id"]}.*$/
+ end
+ query = !query.nil? ? final_query.where(user_id: user_ids) : UserInfo.where(user_id: user_ids)
+ end
+
+ if infos_query["user_name"]
+ if !query.nil?
+ query = query.where("user_name LIKE ?", "%#{infos_query["user_name"]}%")
+ else
+ query = UserInfo.where("user_name LIKE ?", "%#{infos_query["user_name"]}%")
+ end
+ end
+
+ final_query = query
+ end
+
+ if !final_query.nil? && !user_query.empty?
+ final_query = final_query.where(user_id: User.where("email LIKE ?", "%#{user_query["email"]}%"))
+ elsif !user_query.empty?
+ final_query = UserInfo.where(user_id: User.where("email LIKE ?", "%#{user_query["email"]}%"))
+ end
+
+ if !final_query.nil? && !role_query.empty?
+ final_query = final_query.where(user_role_id: UserRole.where("name LIKE ?", "%#{role_query["role"]}%"))
+ elsif !role_query.empty?
+ final_query = UserInfo.where(user_role_id: UserRole.where("name LIKE ?", "%#{role_query["role"]}%"))
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page] && !final_query.instance_of?(Array)
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at, :user_id, :user_role_id, :googleid], [:user, :user_role])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # Returns a filtered query based on the parameters passed for teachers.
+ def teacher_filter
+ teacher_query = {}
+ params.each do |param|
+ next unless param[0] == "teacher_name" || param[0] == "subject_name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ teacher_query.merge!({ param[0] => param[1] })
+ end
+
+ teachers = UserInfo.where(user_role_id: UserRole.where(name: ["eduapp-teacher", "eduapp-admin-query", "eduapp-admin"]))
+ final_query = nil
+ filtered_users = nil
+ filtered_subjects = nil
+
+ if teacher_query["teacher_name"]
+ filtered_users = teachers.where("user_name LIKE ?", "%#{teacher_query["teacher_name"]}%")
+ end
+
+ if teacher_query["subject_name"]
+ filtered_subjects = Subject.where("name LIKE ?", "%#{teacher_query["subject_name"]}%")
+ end
+
+ if !filtered_users.nil? || !filtered_subjects.nil?
+ users = !filtered_users.nil? ? filtered_users : teachers
+ subjects = !filtered_subjects.nil? ? filtered_subjects : Subject.all
+
+ final_query = []
+ users.each do |u|
+ subjects.each do |s|
+ next if final_query.include?({ user: u, subject: s })
+ final_query << { user: u, subject: s } if u.teaching_list.include? s.id
+ end
+ end
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = array_paginate(final_query, params[:page])
+ final_query.each do |t|
+ t[:user] = UserInfo.find(t[:user][:id]).serializable_hash(except: [:created_at, :updated_at, :googleid, :calendar_event, :isLoggedWithGoogle, :profile_image, :user_role_id, :user_id], include: [:user])
+ t[:subject] = Subject.find(t[:subject][:id]).serializable_hash(except: [:created_at, :updated_at, :course_id, :color, :description], include: [:course])
+ end if !final_query.nil?
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /user_infos/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_users)
+ return
+ end
+ render json: @user_info
+ end
+
+ def system_user
+ render json: UserInfo.where(user_name: "eduapp_system", user_role_id: get_admin_role.id).first, status: :ok and return
+ end
+
+ # POST /user_infos
+ def create
+ if !check_perms_write!(get_user_roles.perms_users)
+ return
+ end
+ @user_info = UserInfo.new(user_info_params)
+ if @user_info.save
+ render json: @user_info, status: :created, location: @user_info
+ else
+ render json: @user_info.errors, status: :unprocessable_entity
+ end
+ end
+
+ # Adds a ```Subject``` ID to the ```UserInfo``` ```:teaching_list``` array.
+ def add_subject
+ if !check_perms_write!(get_user_roles.perms_users)
+ return
+ end
+ @user_info = UserInfo.where(user_id: params[:user_id]).first
+ if @user_info.teaching_list.include?(params[:subject_id])
+ render json: { message: "Subject already added" }, status: :unprocessable_entity and return
+ end
+ @user_info.teaching_list << Subject.find(params[:subject_id]).id
+ @user_info.save
+ render json: @user_info
+ end
+
+ # Adds a ```CalendarAnnotation``` ID to the ```UserInfo``` ```:calendar_event``` array.
+ def add_events
+ if !check_perms_write!(get_user_roles.perms_users)
+ return
+ end
+ @user_info = UserInfo.all
+ calendar_annotation = CalendarAnnotation.where(isPop: true)
+
+ @user_info.each do |user_info|
+ calendar_annotation.each do |annotation|
+ if !user_info.calendar_event.include?(annotation.id.to_s)
+ user_info.calendar_event << annotation.id
+ end
+ end
+ user_info.save
+ end
+
+ render json: @user_info
+ end
+
+ # Removes a ```CalendarAnnotation``` ID of the ```UserInfo``` ```:calendar_event``` array.
+ def remove_event
+ if !check_perms_write!(get_user_roles.perms_users)
+ return
+ end
+ @user_info = UserInfo.where(user_id: params[:user_id]).first
+ @user_info.calendar_event.delete(CalendarAnnotation.find(params[:calendar_event]).id)
+ @user_info.save
+ render json: @user_info
+ end
+
+ # Removes a ```Subject``` ID of the ```UserInfo``` ```:teaching_list``` array.
+ def remove_subject
+ if !check_perms_write!(get_user_roles.perms_users)
+ return
+ end
+ @user_info = UserInfo.where(user_id: params[:user_id]).first
+ @user_info.teaching_list.delete(Subject.find(params[:subject_id]).id)
+ @user_info.save
+ render json: @user_info
+ end
+
+ # PUT /user_infos/1
+ def update
+ if !can_update_self(@user_info.user_id)
+ if !check_perms_update!(get_user_roles.perms_users)
+ return
+ end
+ end
+
+ if @user_info.update(user_info_params)
+ render json: @user_info
+ else
+ render json: @user_info.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /user_infos/1
+ def destroy
+ if !check_perms_delete!(get_user_roles.perms_users, true, UserInfo.find(params[:id]).user_id)
+ return
+ end
+ @user_info.destroy
+ end
+
+ # Completely removes a ```User``` with it's respective
+ # ```Tuition```, ```JtiMatchList``` and ```UserInfo``` linked entries.
+ def destroyuser
+ if !check_perms_delete!(get_user_roles.perms_users, true, params[:id])
+ return
+ end
+ user = User.find(params[:id])
+ user_i = UserInfo.find_by(user_id: params[:id])
+
+ # Cannot delete a user if he's the last administrator.
+ if UserInfo.where(user_role_id: get_admin_role.id).count === 1 && user_i.user_role_id === get_admin_role.id
+ render json: { message: "Cannot delete the last admin." }, status: 403 and return
+ end
+
+ JtiMatchList.where(user_id: params[:id]).each do |jti|
+ jti.destroy
+ end
+
+ Tuition.where(user_id: params[:id]).each do |tuition|
+ tuition.destroy
+ end
+
+ if user_i.destroy
+ if user.destroy
+ render json: { message: "Deleted user successfully." }, status: :ok
+ else
+ render json: { message: "Couldn't delete user" }, status: 500
+ end
+ else
+ render json: { message: "Couldn't delete user info" }, status: 500
+ end
+ end
+
+ private
+
+ # Tests if the requester id and the current ```User``` IDs are the same.
+ def can_update_self(requester_id)
+ return true if requester_id === @current_user
+ return false
+ end
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_user_info
+ @user_info = UserInfo.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def user_info_params
+ params.permit(:extras, :user_name, :user_id, :user_role, :calendar_event, :profile_image, :teaching_list, :googleid, :isLoggedWithGoogle)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/user_roles_controller.rb b/backend/eduapp_db/app/controllers/user_roles_controller.rb
new file mode 100644
index 00000000..ada4098d
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/user_roles_controller.rb
@@ -0,0 +1,153 @@
+class UserRolesController < ApplicationController
+ before_action :set_user_role, only: [:show, :update, :destroy]
+ before_action :authenticate_user!
+ before_action :check_role!
+
+ # GET /user_roles
+ def index
+ if params[:role_name]
+ if !check_perms_query!(get_user_roles.perms_roles)
+ return
+ end
+ @user_roles = UserRole.where(name: params[:role_name])
+ elsif params[:user_id]
+ if !check_perms_query_self!(get_user_roles.perms_roles, params[:user_id])
+ return
+ end
+ @user_roles = UserRole.find(UserInfo.where(user_id: params[:user_id]).first.user_roles_id)
+ else
+ if !check_perms_all!(get_user_roles.perms_roles)
+ return
+ end
+ @user_roles = UserRole.all
+ end
+
+ if !params[:order].nil? && Base64.decode64(params[:order]) != "null"
+ @user_roles = @user_roles.order(parse_filter_order(params[:order]))
+ else
+ @user_roles = @user_roles.order(name: :asc)
+ end
+
+ if params[:page]
+ @user_roles = query_paginate(@user_roles, params[:page])
+ end
+
+ render json: @user_roles
+ end
+
+ # Returns a filtered query based on the parameters passed.
+ def filter
+ role_query = {}
+ params.each do |param|
+ next unless param[0] == "name"
+ next unless param[1] != "null" && param[1].length > 0
+
+ role_query.merge!({ param[0] => param[1] })
+ end
+
+ final_query = nil
+
+ if role_query["name"]
+ final_query = UserRole.where("name LIKE ?", "%#{role_query["name"]}%")
+ end
+
+ final_query = [] if final_query.nil?
+
+ if params[:page]
+ final_query = query_paginate(final_query, params[:page])
+ final_query = serialize_each(final_query[:current_page], [:created_at, :updated_at], [])
+ end
+
+ render json: { filtration: final_query }
+ end
+
+ # GET /user_roles/1
+ def show
+ if !check_perms_query!(get_user_roles.perms_roles,false) && !check_perms_query_self!(get_user_roles.perms_roles, current_user)
+ deny_perms_access!
+ return
+ end
+ render json: @user_role
+ end
+
+ # POST /user_roles
+ def create
+ return if !check_perms_write!(get_user_roles.perms_roles)
+
+ @user_role = UserRole.new({
+ name: params[:user_role][:name],
+ description: params[:user_role][:description],
+ perms_institution: params[:user_role][:perms_institution],
+ perms_course: params[:user_role][:perms_course],
+ perms_subjects: params[:user_role][:perms_subjects],
+ perms_resources: params[:user_role][:perms_resources],
+ perms_sessions: params[:user_role][:perms_sessions],
+ perms_session_chats: params[:user_role][:perms_session_chats],
+ perms_events: params[:user_role][:perms_events],
+ perms_teachers: params[:user_role][:perms_teachers],
+ perms_users: params[:user_role][:perms_users],
+ perms_roles: params[:user_role][:perms_roles],
+ perms_tuitions: params[:user_role][:perms_tuitions],
+ perms_jti_matchlist: params[:user_role][:perms_jti_matchlist],
+ perms_chat: params[:user_role][:perms_chat],
+ perms_chat_participants: params[:user_role][:perms_chat_participants],
+ perms_message: params[:user_role][:perms_message],
+ perms_app_views: params[:user_role][:perms_app_views],
+ })
+
+ if @user_role.save
+ render json: @user_role, status: :created, location: @user_role
+ else
+ render json: @user_role.errors, status: :unprocessable_entity
+ end
+ end
+
+ # PUT /user_roles/1
+ def update
+ return if !check_perms_update!(get_user_roles.perms_roles, false, :null)
+
+ if @user_role.update({
+ name: params[:user_role][:name],
+ description: params[:user_role][:description],
+ perms_institution: params[:user_role][:perms_institution],
+ perms_course: params[:user_role][:perms_course],
+ perms_subjects: params[:user_role][:perms_subjects],
+ perms_resources: params[:user_role][:perms_resources],
+ perms_sessions: params[:user_role][:perms_sessions],
+ perms_session_chats: params[:user_role][:perms_session_chats],
+ perms_events: params[:user_role][:perms_events],
+ perms_teachers: params[:user_role][:perms_teachers],
+ perms_users: params[:user_role][:perms_users],
+ perms_roles: params[:user_role][:perms_roles],
+ perms_tuitions: params[:user_role][:perms_tuitions],
+ perms_jti_matchlist: params[:user_role][:perms_jti_matchlist],
+ perms_chat: params[:user_role][:perms_chat],
+ perms_chat_participants: params[:user_role][:perms_chat_participants],
+ perms_message: params[:user_role][:perms_message],
+ perms_app_views: params[:user_role][:perms_app_views],
+ })
+ render json: @user_role
+ else
+ render json: @user_role.errors, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /user_roles/1
+ def destroy
+ return if !check_perms_delete!(get_user_roles.perms_roles, false, :null)
+
+ @user_role.destroy
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_user_role
+ @user_role = UserRole.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def user_role_params
+ params.require(:user_role).permit(:name, :description, :perms_institution, :perms_course, :perms_subjects, :perms_resources, :perms_sessions, :perms_session_chats, :perms_events, :perms_teachers, :perms_users, :perms_roles, :perms_tuitions, :perms_jti_matchlist, :perms_chat, :perms_chat_participants, :perms_message, :perms_app_views)
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/users/confirmations_controller.rb b/backend/eduapp_db/app/controllers/users/confirmations_controller.rb
new file mode 100644
index 00000000..fa535c0a
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/users/confirmations_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Users::ConfirmationsController < Devise::ConfirmationsController
+ # GET /resource/confirmation/new
+ # def new
+ # super
+ # end
+
+ # POST /resource/confirmation
+ # def create
+ # super
+ # end
+
+ # GET /resource/confirmation?confirmation_token=abcdef
+ # def show
+ # super
+ # end
+
+ # protected
+
+ # The path used after resending confirmation instructions.
+ # def after_resending_confirmation_instructions_path_for(resource_name)
+ # super(resource_name)
+ # end
+
+ # The path used after confirmation.
+ # def after_confirmation_path_for(resource_name, resource)
+ # super(resource_name, resource)
+ # end
+end
diff --git a/backend/eduapp_db/app/controllers/users/passwords_controller.rb b/backend/eduapp_db/app/controllers/users/passwords_controller.rb
new file mode 100644
index 00000000..2f0650c8
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/users/passwords_controller.rb
@@ -0,0 +1,77 @@
+class Users::PasswordsController < Devise::PasswordsController
+ # When the auth is implemented use this
+ # before_action :authenticate_user! , only: [:get_reset_password_token,:do_reset_password]
+
+ # Sends a confirmation code via email to the user.
+ def send_change_password_instructions
+ old_password = params[:old_password]
+ @user = User.find_by(email: params[:email])
+ # When the auth is implemented use this
+ # @user = current_user
+ if @user.present?
+ if @user.valid_password?(old_password)
+ PasswordMailer.with(user: @user).send_confirmation_email.deliver_now
+ render json: { status: "success", message: "email sent to " + @user.email }
+ else
+ render json: { status: "failure", message: "Invalid old password" }
+ end
+ else
+ render json: { status: "failure", message: "User not found" }
+ end
+ end
+
+ # Changes the user's password via a confirmation code method.
+ def change_password
+ # Use below when the auth is implemented
+ # user = current_user
+ # Instead use this, but you should pass the user email in the params
+ user = User.find_by(email: params[:email])
+ new_password = Base64.decode64(params[:new_password])
+ confirm_password = Base64.decode64(params[:confirm_password])
+ confirmation_code = params[:confirmation_code]
+ if user.present?
+ if new_password == confirm_password && user.confirmation_code == confirmation_code
+ if user.confirmation_code_exp_time > DateTime.now
+ user.password = new_password
+ user.save
+ render json: { status: "success", message: "Password changed successfully." }
+ else
+ render json: { status: "failure", message: "Expired code" }
+ end
+ else
+ render json: { message: "Password change failed." }, status: :unprocessable_entity
+ end
+ else
+ render json: { message: "User not found." }, status: :unprocessable_entity
+ end
+ end
+
+ # Resets a user's password.
+ def do_reset_password
+ user = User.find_by(email: params[:email])
+ if user.reset_password_token == params[:token]
+ user.reset_password(params[:password], params[:password_confirmation])
+ render json: { status: "success", message: "Password changed successfully." }
+ else
+ render json: { status: "failure", message: "Invalid token" }
+ end
+ end
+
+ # Sends a password reset link to the user's email.
+ def send_reset_password_link
+ user = User.find_by(email: params[:email])
+ if user.present?
+ user.send(:set_reset_password_token)
+ user.save
+ @token = user.reset_password_token
+ if @token.present?
+ PasswordMailer.with(user: user).send_reset_email.deliver_now
+ render json: { message: "email sent to " + user.email, status: "success", expires_in: 15.minutes.from_now } and return
+ else
+ render json: { message: "email not sent", status: "failure, no token" } and return
+ end
+ else
+ render json: { status: "failure", message: "User not found" } and return
+ end
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/users/registrations_controller.rb b/backend/eduapp_db/app/controllers/users/registrations_controller.rb
new file mode 100644
index 00000000..c6d27237
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/users/registrations_controller.rb
@@ -0,0 +1,111 @@
+class Users::RegistrationsController < Devise::RegistrationsController
+ respond_to :json
+
+ def new
+ return render json: { error: "Method not allowed" }, status: :method_not_allowed
+ end
+
+ def create
+ if UserInfo.all.where(user_role_id: UserRole.where(name: "eduapp-admin").first.id).count > 0
+ if !check_perms_write!(get_user_roles(params[:requester_id]).perms_users) || params[:requester_id].nil?
+ return
+ end
+ end
+
+ puts "email: #{params[:email]} ,password: #{params[:password]}"
+
+ build_resource({ email: params[:email], password: params[:password] })
+
+ begin
+ resource.save
+ rescue ActiveRecord::RecordNotUnique => ex
+ render json: { errors: ex.message }, status: :unprocessable_entity
+ return
+ end
+
+ yield resource if block_given?
+ if resource.persisted?
+ if resource.active_for_authentication?
+ set_flash_message! :notice, :signed_up
+ sign_up(resource_name, resource)
+ else
+ set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
+ expire_data_after_sign_in!
+ end
+ else
+ clean_up_passwords resource
+ set_minimum_password_length
+ end
+
+ if User.generate_jti(resource, request.remote_ip)
+ user_info = create_info(params, resource)
+ if user_info[1] == 200
+ render json: user_info[0], status: :created, location: user_info[0]
+ else
+ render json: user_info[0], status: :unprocessable_entity
+ end
+ else
+ render json: { errors: "Couldn't generate jti." }, status: :unprocessable_entity
+ end
+ end
+
+ def edit_user
+ begin
+ user = User.find(params[:id])
+ payload = params.select {|k,v| ['name','surname','username'].include?(k) }
+ payload.each do |p|
+ user.update_attribute(p[0],p[1])
+ end
+ user.user_info.update_attribute(:user_name, user.username);
+ user.user_info.update_attribute(:profile_image, user.profile_image);
+
+ rescue => error
+ render json: { errors: error}, status: :unprocessable_entity
+ return
+ else
+ render json: {status: 'Updated'}, status: 200
+ return
+ end
+ end
+
+ private
+
+ # Creates a ```UserInfo``` entry for the provided ```User```
+ def create_info(createParams, resource)
+ if UserRole.where(name: createParams[:user_role]).count > 0
+ respective_userinfo = UserInfo.new(
+ user_id: resource.id,
+ user_name: resource.email.split("@")[0],
+ user_role_id: UserRole.where(name: createParams[:user_role]).first.id,
+ )
+
+ calendar_annotation = CalendarAnnotation.where(isPop: true)
+
+ if calendar_annotation.count > 0
+ respective_userinfo.calendar_event = calendar_annotation.pluck(:id)
+ end
+
+ if respective_userinfo.save
+ return [respective_userinfo, 200]
+ else
+ return [respective_userinfo.errors, 422]
+ end
+ else
+ return [{ errors: "User role doesn't exist." }, 422]
+ end
+ end
+
+ def respond_with(resource, _opts = {})
+ register_success && return if resource.persisted?
+
+ register_failed
+ end
+
+ def register_success
+ render json: { message: resource }
+ end
+
+ def register_failed
+ render json: { message: "Coudln't register user." }
+ end
+end
diff --git a/backend/eduapp_db/app/controllers/users/sessions_controller.rb b/backend/eduapp_db/app/controllers/users/sessions_controller.rb
new file mode 100644
index 00000000..ba9e8eef
--- /dev/null
+++ b/backend/eduapp_db/app/controllers/users/sessions_controller.rb
@@ -0,0 +1,107 @@
+class Users::SessionsController < Devise::SessionsController
+ skip_before_action :verify_signed_out_user, only: :destroy
+
+ respond_to :json
+
+ def new
+ render json: { error: "Method not used" }, status: 405 and return
+ end
+
+ def create
+ self.resource = warden.authenticate!(auth_options)
+ set_flash_message!(:notice, :signed_in)
+ sign_in(resource_name, resource)
+ yield resource if block_given?
+
+ @user_info = UserInfo.where(user_id: resource.id).first.serializable_hash(:include => [:user, :user_role, :profile_image], :except => [:user_role_id, :googleid, :created_at, :updated_at])
+ respond_with @user_info
+ end
+
+ # Revokes the logged out user's token with a new one.
+ def destroy
+ if !request.headers["eduauth"].present?
+ render json: { error: "No auth provided." }, status: :unauthorized and return
+ end
+
+ unlockedToken = User.unlock_token(request.headers["eduauth"].split("Bearer ").last)
+ if unlockedToken.instance_of? Array
+ jtiOwner = JtiMatchList.where(jti: unlockedToken[0]["jti"]).first
+ if jtiOwner.present?
+ newToken = User.revoke_token(jtiOwner.user_id, request.remote_ip)
+ if newToken === true
+ signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
+ set_flash_message! :notice, :signed_out if signed_out
+ yield if block_given?
+ respond_to_on_destroy
+ else
+ render json: { errors: "Couldn't revoke token." }, status: :unprocessable_entity
+ end
+ else
+ render json: { errors: "Couldn't find token." }, status: 401
+ end
+ else
+ render json: { errors: "Invalid token." }, status: :forbidden
+ end
+ end
+
+ def glogin_link
+ gid= "#{params['gid']}_eduapp_gid"
+ user = User.find(params['user_id'])
+
+ user.valid?
+ user.encrypted_googleid = Base64.encode64(gid)
+ user.save
+ render json: {status: 200, message: "Successfully link"}
+ return
+ end
+
+ def glogin_unlink
+ user = User.find(params['user_id'])
+ user.valid?
+ puts user.encrypted_googleid
+ user.encrypted_googleid = nil
+ user.save
+ render json: {status: 200, message: "Successfully unlinked"}
+ return
+
+ end
+
+ def glogin_login
+ google_id = "#{params['gid']}_eduapp_gid"
+ user = User.find_by encrypted_googleid: Base64.encode64(google_id)
+ sign_in user, event: :authentication
+ set_flash_message!(:notice, :signed_in)
+ yield user if block_given?
+
+ @user_info = UserInfo.where(user_id: user.id).first.serializable_hash(:include => [:user, :user_role], :except => [:user_role_id, :googleid, :created_at, :updated_at])
+ respond_with @user_info
+
+ end
+
+ private
+
+ def respond_with(resource, _opts = {})
+ token = User.generate_token(resource, request.remote_ip)
+ headers["EduAuth"] = "Bearer #{token}"
+ headers["Access-Control-Allow-Origin"] = "*"
+ headers["Access-Control-Allow-Methods"] = "POST, PUT, DELETE, GET, OPTIONS"
+ headers["Access-Control-Request-Method"] = "*"
+ headers["Access-Control-Expose-Headers"] = "*"
+ headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Authorization"
+ render json: { message: resource, headers: response.headers }, status: :ok
+ end
+
+ def respond_to_on_destroy
+ log_out_success && return if current_user
+
+ log_out_failure
+ end
+
+ def log_out_success
+ render json: { message: "You have logged out." }, status: :ok
+ end
+
+ def log_out_failure
+ render json: { message: "Couldn't logout the user." }, status: :unauthorized
+ end
+end
diff --git a/backend/eduapp_db/app/mailers/application_mailer.rb b/backend/eduapp_db/app/mailers/application_mailer.rb
index 286b2239..e511d10a 100644
--- a/backend/eduapp_db/app/mailers/application_mailer.rb
+++ b/backend/eduapp_db/app/mailers/application_mailer.rb
@@ -1,4 +1,9 @@
class ApplicationMailer < ActionMailer::Base
- default from: 'from@example.com'
+ default from: 'eduappdevelopment@gmail.com'
layout 'mailer'
+
+ def configure_permitted_parameters
+ devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation) }
+ devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:email, :password, :password_confirmation, :current_password)}
+ end
end
diff --git a/backend/eduapp_db/app/mailers/password_mailer.rb b/backend/eduapp_db/app/mailers/password_mailer.rb
new file mode 100644
index 00000000..9eaeb3a6
--- /dev/null
+++ b/backend/eduapp_db/app/mailers/password_mailer.rb
@@ -0,0 +1,29 @@
+class PasswordMailer < ApplicationMailer
+ layout "mailer"
+
+ def send_reset_email
+ @user = params[:user]
+ if @user.present?
+ token = @user.reset_password_token
+ @url = ENV.fetch("REACT_APP_FRONTEND_ENDPOINT") + "/password/reset?email=" + @user.email + "&token=" + token
+ mail(to: @user.email, subject: "EduApp Password Reset", url: @url)
+ else
+ render json: { status: "failure" }
+ end
+ end
+
+ def send_confirmation_email
+ @user = params[:user]
+ @confirmation_code = SecureRandom.hex
+ @expireCodeDate = DateTime.now + 20.minutes
+ if @user.present?
+ @user.confirmation_code = @confirmation_code
+ @user.confirmation_code_exp_time = @expireCodeDate
+ @user.save
+ mail(to: @user.email, subject: "EduApp Confirmation", confirmation_code: @confirmation_code)
+ render json: { status: "success", expires_in: @expireCodeDate, message: "code sent to " + @user.email }
+ else
+ render json: { status: "failure", message: "User not found in send_confirmation_email" }
+ end
+ end
+end
\ No newline at end of file
diff --git a/backend/eduapp_db/app/models/calendar_annotation.rb b/backend/eduapp_db/app/models/calendar_annotation.rb
new file mode 100644
index 00000000..90d0701b
--- /dev/null
+++ b/backend/eduapp_db/app/models/calendar_annotation.rb
@@ -0,0 +1,5 @@
+class CalendarAnnotation < ApplicationRecord
+ # belongs_to :course
+ belongs_to :user
+ belongs_to :subject
+end
diff --git a/backend/eduapp_db/app/models/chat_base.rb b/backend/eduapp_db/app/models/chat_base.rb
new file mode 100644
index 00000000..eb063f7b
--- /dev/null
+++ b/backend/eduapp_db/app/models/chat_base.rb
@@ -0,0 +1,5 @@
+class ChatBase < ApplicationRecord
+ has_one_attached :chat_image
+ has_many :chat_participants, dependent: :destroy
+ has_many :chat_messages, dependent: :destroy
+end
diff --git a/backend/eduapp_db/app/models/chat_base_info.rb b/backend/eduapp_db/app/models/chat_base_info.rb
new file mode 100644
index 00000000..df083d85
--- /dev/null
+++ b/backend/eduapp_db/app/models/chat_base_info.rb
@@ -0,0 +1,3 @@
+class ChatBaseInfo < ApplicationRecord
+ belongs_to :chat_base
+end
diff --git a/backend/eduapp_db/app/models/chat_message.rb b/backend/eduapp_db/app/models/chat_message.rb
new file mode 100644
index 00000000..1ea2b5f0
--- /dev/null
+++ b/backend/eduapp_db/app/models/chat_message.rb
@@ -0,0 +1,4 @@
+class ChatMessage < ApplicationRecord
+ belongs_to :chat_base
+ belongs_to :user
+end
diff --git a/backend/eduapp_db/app/models/chat_participant.rb b/backend/eduapp_db/app/models/chat_participant.rb
new file mode 100644
index 00000000..c7d965cc
--- /dev/null
+++ b/backend/eduapp_db/app/models/chat_participant.rb
@@ -0,0 +1,9 @@
+class ChatParticipant < ApplicationRecord
+ belongs_to :chat_base
+ belongs_to :user
+
+ enum status: {
+ "Offline" => 0,
+ "Online" => 1,
+ }
+end
diff --git a/backend/eduapp_db/app/models/course.rb b/backend/eduapp_db/app/models/course.rb
new file mode 100644
index 00000000..dedc0c26
--- /dev/null
+++ b/backend/eduapp_db/app/models/course.rb
@@ -0,0 +1,6 @@
+class Course < ApplicationRecord
+ belongs_to :institution
+ has_many :eduapp_user_session
+ has_many :resource
+ has_one :calendar_annotation
+end
diff --git a/backend/eduapp_db/app/models/eduapp_user_session.rb b/backend/eduapp_db/app/models/eduapp_user_session.rb
new file mode 100644
index 00000000..fe978cb9
--- /dev/null
+++ b/backend/eduapp_db/app/models/eduapp_user_session.rb
@@ -0,0 +1,3 @@
+class EduappUserSession < ApplicationRecord
+ belongs_to :subject
+end
diff --git a/backend/eduapp_db/app/models/institution.rb b/backend/eduapp_db/app/models/institution.rb
new file mode 100644
index 00000000..df72d5a0
--- /dev/null
+++ b/backend/eduapp_db/app/models/institution.rb
@@ -0,0 +1,3 @@
+class Institution < ApplicationRecord
+ has_many :course
+end
diff --git a/backend/eduapp_db/app/models/jti_match_list.rb b/backend/eduapp_db/app/models/jti_match_list.rb
new file mode 100644
index 00000000..066a3de9
--- /dev/null
+++ b/backend/eduapp_db/app/models/jti_match_list.rb
@@ -0,0 +1,3 @@
+class JtiMatchList < ApplicationRecord
+ belongs_to :user
+end
diff --git a/backend/eduapp_db/app/models/push_notification.rb b/backend/eduapp_db/app/models/push_notification.rb
new file mode 100644
index 00000000..0bf67ce1
--- /dev/null
+++ b/backend/eduapp_db/app/models/push_notification.rb
@@ -0,0 +1,4 @@
+class PushNotification < ApplicationRecord
+ belongs_to :user
+
+end
diff --git a/backend/eduapp_db/app/models/resource.rb b/backend/eduapp_db/app/models/resource.rb
index 4b4a5c41..c732b42a 100644
--- a/backend/eduapp_db/app/models/resource.rb
+++ b/backend/eduapp_db/app/models/resource.rb
@@ -1,2 +1,6 @@
class Resource < ApplicationRecord
+ belongs_to :subject
+ belongs_to :user
+
+ has_many_attached :files
end
diff --git a/backend/eduapp_db/app/models/subject.rb b/backend/eduapp_db/app/models/subject.rb
new file mode 100644
index 00000000..b245abb4
--- /dev/null
+++ b/backend/eduapp_db/app/models/subject.rb
@@ -0,0 +1,5 @@
+class Subject < ApplicationRecord
+ belongs_to :course
+ has_many :subjects_user
+ has_many :users, through: :subjects_user
+end
diff --git a/backend/eduapp_db/app/models/subjects_user.rb b/backend/eduapp_db/app/models/subjects_user.rb
new file mode 100644
index 00000000..5cbb574b
--- /dev/null
+++ b/backend/eduapp_db/app/models/subjects_user.rb
@@ -0,0 +1,4 @@
+class SubjectsUser < ApplicationRecord
+ belongs_to :subject
+ belongs_to :user
+end
diff --git a/backend/eduapp_db/app/models/tuition.rb b/backend/eduapp_db/app/models/tuition.rb
new file mode 100644
index 00000000..6529ecba
--- /dev/null
+++ b/backend/eduapp_db/app/models/tuition.rb
@@ -0,0 +1,4 @@
+class Tuition < ApplicationRecord
+ belongs_to :course
+ belongs_to :user
+end
diff --git a/backend/eduapp_db/app/models/user.rb b/backend/eduapp_db/app/models/user.rb
index 47567994..83732cc0 100644
--- a/backend/eduapp_db/app/models/user.rb
+++ b/backend/eduapp_db/app/models/user.rb
@@ -1,6 +1,123 @@
class User < ApplicationRecord
- # Include default devise modules. Others available are:
- # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
- devise :database_authenticatable, :registerable,
- :recoverable, :rememberable, :validatable
+ require "jwt"
+
+ devise :database_authenticatable,
+ :jwt_authenticatable,
+ :registerable,
+ :trackable,
+ :recoverable,
+ jwt_revocation_strategy: self
+
+ has_one :user_info
+ has_many :tuitions
+ has_many :subjects_user
+ has_many :subjects, through: :subjects_user
+ has_many :push_notifications
+
+ # Allow user to login either with username and email
+
+ attr_writer :login
+
+ def login
+ @login || self.username || self.email
+ end
+
+ def self.find_for_database_authentication(warden_conditions)
+ conditions = warden_conditions.dup
+ login = conditions.delete(:login)
+ where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.strip.downcase }]).first
+ end
+
+ # JWT Management
+
+ @secret = ENV.fetch("RAILS_SECRET_KEY")
+
+ # Generates a new ```JtiMatchList``` entry for the user.
+ def self.generate_jti(user, user_ip)
+ iat, exp = self.gen_exp
+ jti = self.gen_jti iat
+
+ jwtEntry = JtiMatchList.new(
+ user_id: user["user_id"],
+ jti: jti,
+ exp: exp,
+ access_ip: user_ip,
+ )
+ if jwtEntry.save
+ return true
+ else
+ return { error: jwtEntry.errors.messages }
+ end
+ end
+
+ # Generates a JWT Token for a user.
+ def self.generate_token(user, user_ip)
+ iat, exp = self.gen_exp
+
+ userTotalJti = JtiMatchList.where(user_id: user["user_id"]).order(created_at: :desc)
+ userTotalJti.last.destroy if userTotalJti.count === 4 # Logs out the last connection's IP for a new connection.
+
+ existingUserJti = JtiMatchList.where(user_id: user["user_id"], access_ip: user_ip)
+ if existingUserJti.count > 0
+ jti = existingUserJti.first.jti
+ else
+ if self.generate_jti(user, user_ip) === true
+ existingUserJti = JtiMatchList.where(user_id: user["user_id"], access_ip: user_ip)
+ else
+ return { error: "Failed to generate token." }
+ end
+ end
+
+ user_role = UserRole.find(UserInfo.where(user_id: existingUserJti[0].user_id).first.user_role_id)
+ return { error: "Failed to find user role." } if !user_role
+
+ payload = {
+ iat: iat,
+ exp: exp,
+ aud: user_role.name,
+ jti: jti.nil? ? existingUserJti[0].jti : jti,
+ sub: existingUserJti[0].user_id,
+ }
+
+ if existingUserJti.update(exp: exp)
+ return JWT.encode payload, @secret, "HS256"
+ else
+ return { error: jwtEntry.errors.messages }
+ end
+ end
+
+ # Decodes a JWT Token.
+ def self.unlock_token(token)
+ begin
+ return JWT.decode token, @secret, true, { verify_jti: true, aud: "user", algorithm: "HS256" }
+ rescue JWT::ExpiredSignature => expired
+ return { error: "Token has expired: #{expired}" }
+ rescue JWT::VerificationError, JWT::DecodeError, JWT::ImmatureSignature => err
+ return { error: "Verify Token is invalid: #{err}" }
+ end
+ end
+
+ # Revokes a JWT Token from a user.
+ def self.revoke_token(user, user_ip)
+ revokedUserJti = JtiMatchList.where(user_id: user, access_ip: user_ip)
+
+ iat, exp = self.gen_exp
+ if revokedUserJti.update(exp: exp, jti: self.gen_jti(iat))
+ return true
+ else
+ return { error: revokedUserJti.errors.messages }
+ end
+ end
+
+ private
+
+ # Generates an expiration for an hour in time.
+ def self.gen_exp
+ return Time.now.to_i, 1.hour.from_now.to_i # [iat, exp]
+ end
+
+ # Returnes a newly signed JTI.
+ def self.gen_jti(issued_at)
+ return Digest::SHA256.hexdigest([@secret, issued_at].join(":").to_s)
+ end
end
diff --git a/backend/eduapp_db/app/models/user_info.rb b/backend/eduapp_db/app/models/user_info.rb
new file mode 100644
index 00000000..f9bd3f23
--- /dev/null
+++ b/backend/eduapp_db/app/models/user_info.rb
@@ -0,0 +1,21 @@
+class UserInfo < ApplicationRecord
+ belongs_to :user
+ belongs_to :user_role
+
+ mount_uploader :profile_image, ProfileImageUploader
+ # Mainly used for the name searcher when creating a chat in the App.
+ def self.search_name(pattern)
+ if pattern.blank?
+ self
+ else
+ order(user_name: :asc).where("user_name LIKE ?", "%#{pattern}%")
+ end
+ end
+ def self.search_email(pattern)
+ if pattern.blank?
+ self
+ else
+ order("users.email" => :asc).joins(:user).where("users.email LIKE ?", "%#{pattern}%")
+ end
+ end
+end
diff --git a/backend/eduapp_db/app/models/user_role.rb b/backend/eduapp_db/app/models/user_role.rb
new file mode 100644
index 00000000..e1897f56
--- /dev/null
+++ b/backend/eduapp_db/app/models/user_role.rb
@@ -0,0 +1,3 @@
+class UserRole < ApplicationRecord
+ has_one :user_info
+end
diff --git a/backend/eduapp_db/app/serializers/calendar_annotation_serializer.rb b/backend/eduapp_db/app/serializers/calendar_annotation_serializer.rb
new file mode 100644
index 00000000..bbdef0fa
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/calendar_annotation_serializer.rb
@@ -0,0 +1,5 @@
+class CalendarAnnotationSerializer < ActiveModel::Serializer
+ attributes :id, :annotation_start_date,:annotation_end_date, :annotation_title, :annotation_description, :isGlobal, :isPop, :user_id, :subject_id
+ has_one :user
+ has_one :subject
+end
diff --git a/backend/eduapp_db/app/serializers/chat_base_serializer.rb b/backend/eduapp_db/app/serializers/chat_base_serializer.rb
new file mode 100644
index 00000000..779a444d
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/chat_base_serializer.rb
@@ -0,0 +1,3 @@
+class ChatBaseSerializer < ActiveModel::Serializer
+ attributes :id, :chat_name, :isGroup, :isReadOnly, :private_key, :public_key
+end
diff --git a/backend/eduapp_db/app/serializers/chat_message_serializer.rb b/backend/eduapp_db/app/serializers/chat_message_serializer.rb
new file mode 100644
index 00000000..58fe1beb
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/chat_message_serializer.rb
@@ -0,0 +1,5 @@
+class ChatMessageSerializer < ActiveModel::Serializer
+ attributes :id, :message, :send_date
+ has_one :chat_base
+ has_one :user
+end
diff --git a/backend/eduapp_db/app/serializers/chat_participant_serializer.rb b/backend/eduapp_db/app/serializers/chat_participant_serializer.rb
new file mode 100644
index 00000000..66eaea96
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/chat_participant_serializer.rb
@@ -0,0 +1,5 @@
+class ChatParticipantSerializer < ActiveModel::Serializer
+ attributes :id, :isChatAdmin
+ has_one :chat_base
+ has_one :user
+end
diff --git a/backend/eduapp_db/app/serializers/course_serializer.rb b/backend/eduapp_db/app/serializers/course_serializer.rb
new file mode 100644
index 00000000..c3ed3e17
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/course_serializer.rb
@@ -0,0 +1,4 @@
+class CourseSerializer < ActiveModel::Serializer
+ attributes :id, :name, :institution_id
+ has_one :institution
+end
diff --git a/backend/eduapp_db/app/serializers/eduapp_user_session_serializer.rb b/backend/eduapp_db/app/serializers/eduapp_user_session_serializer.rb
new file mode 100644
index 00000000..4c19f6bc
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/eduapp_user_session_serializer.rb
@@ -0,0 +1,4 @@
+class EduappUserSessionSerializer < ActiveModel::Serializer
+ attributes :id, :session_name, :session_start_date, :session_end_date, :streaming_platform, :resources_platform, :session_chat_id, :batch_id
+ has_one :subject
+end
diff --git a/backend/eduapp_db/app/serializers/institution_serializer.rb b/backend/eduapp_db/app/serializers/institution_serializer.rb
new file mode 100644
index 00000000..87c71093
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/institution_serializer.rb
@@ -0,0 +1,3 @@
+class InstitutionSerializer < ActiveModel::Serializer
+ attributes :id, :name
+end
diff --git a/backend/eduapp_db/app/serializers/push_notification_serializer.rb b/backend/eduapp_db/app/serializers/push_notification_serializer.rb
new file mode 100644
index 00000000..4b360968
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/push_notification_serializer.rb
@@ -0,0 +1,3 @@
+class PushNoticationSerializer < ActiveModel::Serializer
+ attributes :id, :endpoint, :user_id, :p256dh, :auth
+end
diff --git a/backend/eduapp_db/app/serializers/resource_serializer.rb b/backend/eduapp_db/app/serializers/resource_serializer.rb
new file mode 100644
index 00000000..b6e4ccb1
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/resource_serializer.rb
@@ -0,0 +1,5 @@
+class ResourceSerializer < ActiveModel::Serializer
+ attributes :id, :name, :description, :resource_files, :resource_files_json, :created_at, :updated_at
+ has_one :subject
+ has_one :user
+end
diff --git a/backend/eduapp_db/app/serializers/subject_serializer.rb b/backend/eduapp_db/app/serializers/subject_serializer.rb
new file mode 100644
index 00000000..1d779264
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/subject_serializer.rb
@@ -0,0 +1,5 @@
+class SubjectSerializer < ActiveModel::Serializer
+ attributes :id,:subject_code, :name, :description, :color, :course_id, :chat_link, :users
+ has_one :course
+ has_many :users
+end
diff --git a/backend/eduapp_db/app/serializers/tuition_serializer.rb b/backend/eduapp_db/app/serializers/tuition_serializer.rb
new file mode 100644
index 00000000..d8470a06
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/tuition_serializer.rb
@@ -0,0 +1,5 @@
+class TuitionSerializer < ActiveModel::Serializer
+ attributes :id, :course_id, :user_id
+ has_one :course
+ has_one :user
+end
diff --git a/backend/eduapp_db/app/serializers/user_info_serializer.rb b/backend/eduapp_db/app/serializers/user_info_serializer.rb
new file mode 100644
index 00000000..1d653709
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/user_info_serializer.rb
@@ -0,0 +1,5 @@
+class UserInfoSerializer < ActiveModel::Serializer
+ attributes :id, :user_name, :profile_image, :teaching_list,:calendar_event, :googleid, :isLoggedWithGoogle
+ has_one :user_role
+ has_one :user
+end
diff --git a/backend/eduapp_db/app/serializers/user_role_serializer.rb b/backend/eduapp_db/app/serializers/user_role_serializer.rb
new file mode 100644
index 00000000..bffcd56f
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/user_role_serializer.rb
@@ -0,0 +1,3 @@
+class UserRoleSerializer < ActiveModel::Serializer
+ attributes :id, :name, :description, :perms_institution, :perms_course, :perms_subjects, :perms_resources, :perms_sessions, :perms_events, :perms_teachers, :perms_users, :perms_roles, :perms_tuitions, :perms_jti_matchlist, :perms_chat, :perms_chat_participants, :perms_message, :perms_app_views
+end
diff --git a/backend/eduapp_db/app/serializers/user_serializer.rb b/backend/eduapp_db/app/serializers/user_serializer.rb
new file mode 100644
index 00000000..400f88a7
--- /dev/null
+++ b/backend/eduapp_db/app/serializers/user_serializer.rb
@@ -0,0 +1,5 @@
+class UserSerializer < ActiveModel::Serializer
+ attributes :confirmation_code, :confirmation_code_exp_time, :created_at, :email, :encrypted_googleid, :extra_fields, :id, :name, :surname, :updated_at, :username, :user_info
+ has_one :user_info
+ has_many :subjects
+end
diff --git a/backend/eduapp_db/app/uploaders/profile_image_uploader.rb b/backend/eduapp_db/app/uploaders/profile_image_uploader.rb
new file mode 100644
index 00000000..f1b6d7d8
--- /dev/null
+++ b/backend/eduapp_db/app/uploaders/profile_image_uploader.rb
@@ -0,0 +1,36 @@
+class ProfileImageUploader < CarrierWave::Uploader::Base
+ include CarrierWave::RMagick
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ def store_dir
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ end
+
+ # Process files as they are uploaded:
+ # process scale: [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+ # Create different versions of your uploaded files:
+ version :thumb do
+ process resize_to_fit: [50, 50]
+ end
+
+ # Add an allowlist of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ def extension_allowlist
+ %w(jpg jpeg gif png)
+ end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # "something.jpg" if original_filename
+ # end
+end
diff --git a/backend/eduapp_db/app/views/devise/confirmations/new.html.erb b/backend/eduapp_db/app/views/devise/confirmations/new.html.erb
new file mode 100644
index 00000000..b12dd0cb
--- /dev/null
+++ b/backend/eduapp_db/app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,16 @@
+<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/backend/eduapp_db/app/views/devise/mailer/email_changed.html.erb b/backend/eduapp_db/app/views/devise/mailer/email_changed.html.erb
new file mode 100644
index 00000000..32f4ba80
--- /dev/null
+++ b/backend/eduapp_db/app/views/devise/mailer/email_changed.html.erb
@@ -0,0 +1,7 @@
+We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
+<% else %>
+ We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
+<% end %>
diff --git a/backend/eduapp_db/app/views/devise/mailer/password_change.html.erb b/backend/eduapp_db/app/views/devise/mailer/password_change.html.erb
new file mode 100644
index 00000000..b41daf47
--- /dev/null
+++ b/backend/eduapp_db/app/views/devise/mailer/password_change.html.erb
@@ -0,0 +1,3 @@
+We're contacting you to notify you that your password has been changed.
diff --git a/backend/eduapp_db/app/views/devise/mailer/reset_password_instructions.html.erb b/backend/eduapp_db/app/views/devise/mailer/reset_password_instructions.html.erb
new file mode 100644
index 00000000..f667dc12
--- /dev/null
+++ b/backend/eduapp_db/app/views/devise/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
+Someone has requested a link to change your password. You can do this through the link below.
+
+<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
+
+If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/backend/eduapp_db/app/views/devise/mailer/unlock_instructions.html.erb b/backend/eduapp_db/app/views/devise/mailer/unlock_instructions.html.erb
new file mode 100644
index 00000000..41e148bf
--- /dev/null
+++ b/backend/eduapp_db/app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
+Your account has been locked due to an excessive number of unsuccessful sign in attempts.
+
+Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>
+
+<%= link_to "Back", :back %>
diff --git a/backend/eduapp_db/app/views/devise/registrations/new.html.erb b/backend/eduapp_db/app/views/devise/registrations/new.html.erb
new file mode 100644
index 00000000..d655b66f
--- /dev/null
+++ b/backend/eduapp_db/app/views/devise/registrations/new.html.erb
@@ -0,0 +1,29 @@
+