diff --git a/.gitignore b/.gitignore index 8838121..21ff5ca 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ # Ignore master key for decrypting credentials and more. /config/master.key + +.env \ No newline at end of file diff --git a/AI/app.py b/AI/app.py new file mode 100644 index 0000000..b719b44 --- /dev/null +++ b/AI/app.py @@ -0,0 +1,16 @@ +from flask import Flask, request, jsonify + +app = Flask(__name) + +# Add chatbot routes + +@app.route('/match', methods=['POST']) +def match_users(): + user_data = request.json + # Implement your AI matching logic here + matched_mentor = {} # Replace this with actual matching results + return jsonify(matched_mentor) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/Gemfile b/Gemfile index b1e47fd..5b8bee3 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ gem "puma", "~> 5.0" # gem "kredis" # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" +gem "bcrypt", "~> 3.1.7" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] @@ -34,7 +34,7 @@ gem "bootsnap", require: false # gem "image_processing", "~> 1.2" # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible -# gem "rack-cors" +gem "rack-cors" group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem @@ -48,3 +48,7 @@ end gem "active_model_serializers", "~> 0.10.14" + +gem 'dotenv-rails' + +gem 'faker' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index f9b34e3..0d8ca2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,6 +71,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + bcrypt (3.1.19) bootsnap (1.16.0) msgpack (~> 1.2) builder (3.2.4) @@ -82,7 +83,13 @@ GEM debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) + railties (>= 3.2) erubi (1.12.0) + faker (3.2.1) + i18n (>= 1.8.11, < 2) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.1) @@ -124,6 +131,8 @@ GEM nio4r (~> 2.0) racc (1.7.1) rack (2.2.8) + rack-cors (2.0.1) + rack (>= 2.0.0) rack-test (2.1.0) rack (>= 1.3) rails (7.0.8) @@ -174,10 +183,14 @@ PLATFORMS DEPENDENCIES active_model_serializers (~> 0.10.14) + bcrypt (~> 3.1.7) bootsnap debug + dotenv-rails + faker pg (~> 1.1) puma (~> 5.0) + rack-cors rails (~> 7.0.8) tzinfo-data diff --git a/app/controllers/ai_controller.rb b/app/controllers/ai_controller.rb new file mode 100644 index 0000000..acbe09f --- /dev/null +++ b/app/controllers/ai_controller.rb @@ -0,0 +1,60 @@ +class AiController < ApplicationController + skip_before_action :authorized_user + + def chat_prompt_data + + render json: current_user, serializer: UserSerializer, status: :ok + end + + def match_prompt_data + + # current user (id, match criteria only) + # all mentor records (id, match criteria only) + + # mentors = Mentor.select(:id).includes(:skills, :interests, :genders, :races) + + @mentors = Mentor.all + + mentors = [] + + @mentors.each do |mentor| + user = mentor.user + mentor_obj = { + skills: user.skills, + interests: user.interests, + genders: user.genders, + races: user.races + } + + mentors << mentor_obj + end + + match_data = { + mentee_id: current_user.id, + skills: current_user.skills, + interests: current_user.interests, + mentors: mentors + } + + render json: match_data, status: :ok + end + + def get_cards + # render array of cards (max 5) + end + + private + + def gender_prompt(user) + gender = user.genders[0] + prompt = "I would prefer to be matched with a mentor that has experience as a #{gender} person." + prompt + end + + def race_prompt(user) + race = user.races[0] + prompt = "I would prefer to be matched with a mentor that has experience as a #{race} person." + prompt + end + +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cf7d2fd..5f76cee 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,33 +1,64 @@ + + class ApplicationController < ActionController::API - # rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response - # rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response - # rescue_from ActiveRecord::ConnectionNotEstablished, with: :render_connection_not_established_response - # include ActionController::Cookies + rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response + rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response + rescue_from ActiveRecord::ConnectionNotEstablished, with: :render_connection_not_established_response + include ActionController::Cookies + + before_action :authorized_user + + # PassageClient = Passage::Client.new( + # app_id: Rails.application.config.passage_app_id + # api_key: Rails.application.config.passage_api_key + # ) + + def current_user + user = User.find_by(id: session[:user_id]) + + if !user && session[:psg_user_id] + passage_user = PassageClient.user.get(user_id: @user_id) + @email = passage_user[:email] + user = User.find_by(email: @email) + + User.create!(email: @email) unless user + user + end + + user + end + + def authorized_user + render json: { error: "Not Authorized" }, status: :unauthorized unless current_user + end - # before_action :authorized_user + def authorize! + begin + request.to_hash() + @user_id = Passage.auth.authenticate_request(request) + session[:psg_user_id] = @user_id - # def current_user - # user = User.find_by(id: session[:user_id]) - # user - # end + render json: current_user, status: :ok + rescue Exception => e + # unauthorized + redirect_to "/unauthorized" + end + end - # def authorized_user - # render json: { error: "Not Authorized" }, status: :unauthorized unless current_user - # end - # private + private - # def render_not_found_response(error) - # render json: { error: "#{error.model} not found."}, status: :not_found - # end + def render_not_found_response(error) + render json: { error: "#{error.model} not found."}, status: :not_found + end - # def render_unprocessable_entity_response(error) - # render json: { error: invalid.record.errors.full_messages }, status: :unprocessable_entity - # end + def render_unprocessable_entity_response(error) + render json: { error: invalid.record.errors.full_messages }, status: :unprocessable_entity + end - # def render_connection_not_established_response(error) - # render json: { error: error}, status: :service_unavailable - # end + def render_connection_not_established_response(error) + render json: { error: error}, status: :service_unavailable + end end diff --git a/app/controllers/career_fields_controller.rb b/app/controllers/career_fields_controller.rb new file mode 100644 index 0000000..c5465cd --- /dev/null +++ b/app/controllers/career_fields_controller.rb @@ -0,0 +1,7 @@ +class CareerFieldsController < ApplicationController + skip_before_action :authorized_user + + def index + render json: CareerField.all, status: :ok + end +end diff --git a/app/controllers/career_titles_controller.rb b/app/controllers/career_titles_controller.rb new file mode 100644 index 0000000..85e67d5 --- /dev/null +++ b/app/controllers/career_titles_controller.rb @@ -0,0 +1,8 @@ +class CareerTitlesController < ApplicationController + skip_before_action :authorized_user + + def index + render json: CareerTitle.all, status: :ok + end + +end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000..2365579 --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,7 @@ +class CategoriesController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Category.all, status: :ok + end +end diff --git a/app/controllers/category_interests_controller.rb b/app/controllers/category_interests_controller.rb new file mode 100644 index 0000000..c018e25 --- /dev/null +++ b/app/controllers/category_interests_controller.rb @@ -0,0 +1,7 @@ +class CategoryInterestsController < ApplicationController + skip_before_action :authorized_user + + def index + render json: CategoryInterest.all, status: :ok + end +end diff --git a/app/controllers/category_skills_controller.rb b/app/controllers/category_skills_controller.rb new file mode 100644 index 0000000..b20e057 --- /dev/null +++ b/app/controllers/category_skills_controller.rb @@ -0,0 +1,7 @@ +class CategorySkillsController < ApplicationController + skip_before_action :authorized_user + + def index + render json: CategorySkill.all, status: :ok + end +end diff --git a/app/controllers/genders_controller.rb b/app/controllers/genders_controller.rb new file mode 100644 index 0000000..ab190d7 --- /dev/null +++ b/app/controllers/genders_controller.rb @@ -0,0 +1,7 @@ +class GendersController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Gender.all, status: :ok + end +end diff --git a/app/controllers/interests_controller.rb b/app/controllers/interests_controller.rb new file mode 100644 index 0000000..81dbd8c --- /dev/null +++ b/app/controllers/interests_controller.rb @@ -0,0 +1,8 @@ +class InterestsController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Interest.all, status: :ok + end + +end diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb new file mode 100644 index 0000000..a2ef55f --- /dev/null +++ b/app/controllers/matches_controller.rb @@ -0,0 +1,7 @@ +class MatchesController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Match.all, status: :ok + end +end diff --git a/app/controllers/mentees_controller.rb b/app/controllers/mentees_controller.rb new file mode 100644 index 0000000..ca862f5 --- /dev/null +++ b/app/controllers/mentees_controller.rb @@ -0,0 +1,7 @@ +class MenteesController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Mentee.all, status: :ok + end +end diff --git a/app/controllers/mentors_controller.rb b/app/controllers/mentors_controller.rb new file mode 100644 index 0000000..8044d86 --- /dev/null +++ b/app/controllers/mentors_controller.rb @@ -0,0 +1,8 @@ +class MentorsController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Mentor.all, status: :ok + end + +end diff --git a/app/controllers/races_controller.rb b/app/controllers/races_controller.rb new file mode 100644 index 0000000..2bb87db --- /dev/null +++ b/app/controllers/races_controller.rb @@ -0,0 +1,7 @@ +class RacesController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Race.all, status: :ok + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..12cd79b --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,49 @@ +class SessionsController < ApplicationController + + skip_before_action :authorized_user, only: [:create, :destroy] + + def show + render json: current_user, status: :ok + end + + # This is the standard password login without Passage + def create + + puts "Requested email: " + user = User.find_by(email: params[:email]) + + # Handle successful login with the password + # if user&.authenticate(params[:password]) + if user + puts "Actually logging in the user" + session[:user_id] = user.id + render json: user, status: :created + + # # Handle no password set due to user originally registering with Passage + # elsif !user.password + # render json: { error: "No password was set for this account", email: params[:email]}, status: :unprocessable_entity + + # Handle incorrect username or password + else + + # Temporary code while the auth issue is being fixed + mock_user = User.first + + if mock_user + puts "Pretending to log in the user" + session[:user_id] = mock_user.id + render json: mock_user, status: :ok + else + render json:{ error: "Invalid username or password", }, status: :unauthorized + end + # End temporary code + end + end + + # This is the logout regardless of the auth method + def destroy + session.delete :user_id + session.delete :psg_user_id + head :no_content + end +end diff --git a/app/controllers/skills_controller.rb b/app/controllers/skills_controller.rb new file mode 100644 index 0000000..abe042c --- /dev/null +++ b/app/controllers/skills_controller.rb @@ -0,0 +1,7 @@ +class SkillsController < ApplicationController + skip_before_action :authorized_user + + def index + render json: Skill.all, status: :ok + end +end diff --git a/app/controllers/user_careers_controller.rb b/app/controllers/user_careers_controller.rb new file mode 100644 index 0000000..750af38 --- /dev/null +++ b/app/controllers/user_careers_controller.rb @@ -0,0 +1,9 @@ +class UserCareersController < ApplicationController + skip_before_action :authorized_user + + def index + @user = User.find(params[:id]) + + render json: UserCareer.where!(user: @user), status: :ok + end +end diff --git a/app/controllers/user_genders_controller.rb b/app/controllers/user_genders_controller.rb new file mode 100644 index 0000000..0d810c8 --- /dev/null +++ b/app/controllers/user_genders_controller.rb @@ -0,0 +1,9 @@ +class UserGendersController < ApplicationController + skip_before_action :authorized_user + + def index + @user = User.find(params[:id]) + + render json: UserGender.where!(user: @user), status: :ok + end +end diff --git a/app/controllers/user_interests_controller.rb b/app/controllers/user_interests_controller.rb new file mode 100644 index 0000000..361d71d --- /dev/null +++ b/app/controllers/user_interests_controller.rb @@ -0,0 +1,9 @@ +class UserInterestsController < ApplicationController + skip_before_action :authorized_user + + def index + @user = User.find(params[:id]) + + render json: UserInterest.where!(user: @user), status: :ok + end +end diff --git a/app/controllers/user_races_controller.rb b/app/controllers/user_races_controller.rb new file mode 100644 index 0000000..3cbec01 --- /dev/null +++ b/app/controllers/user_races_controller.rb @@ -0,0 +1,9 @@ +class UserRacesController < ApplicationController + skip_before_action :authorized_user + + def index + @user = User.find(params[:id]) + + render json: UserRace.where!(user: @user), status: :ok + end +end diff --git a/app/controllers/user_skills_controller.rb b/app/controllers/user_skills_controller.rb new file mode 100644 index 0000000..276b8c7 --- /dev/null +++ b/app/controllers/user_skills_controller.rb @@ -0,0 +1,9 @@ +class UserSkillsController < ApplicationController + skip_before_action :authorized_user + + def index + @user = User.find(params[:id]) + + render json: UserSkill.where!(user: @user), status: :ok + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..3ca42b1 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,40 @@ +class UsersController < ApplicationController + skip_before_action :authorized_user, only: [:create] + + def index + render json: User.all, status: :ok + end + + def show + render json: User.find(params[:id]), status: :ok + end + + # This is the login route using traditional password registration + # Passage registration and PostgreSQL entity creations are currently handled by application#current_user + # Will improve dual registration handling in a future version + def create + render json: User.create!(user_params), status: :created + end + + def update + render json: User.update!(user_params), status: :accepted + end + + def destroy + user = User.find_by(id: params[:id]) + if user&.authenticate(params[:password]) + user.destroy + head :no_content + else + render json:{ errors: "Invalid password"}, status: :unauthorized + end + end + + private + + def user_params + params.require(:user).permit(:username, :password, :email, :phone_number, :first_name, :middle_name, :last_name, :suffix, :date_of_birth, :city, :state, :country, :zip_code, :timezone) + end + + +end diff --git a/app/models/ai.rb b/app/models/ai.rb new file mode 100644 index 0000000..ae100f4 --- /dev/null +++ b/app/models/ai.rb @@ -0,0 +1,8 @@ +class AI < ApplicationRecord + + def ai_mentor_info + user = current_user.id + + render json: user, serializer: CurrentUserSerializer, status: :ok + end +end diff --git a/app/models/career_field.rb b/app/models/career_field.rb new file mode 100644 index 0000000..73fb419 --- /dev/null +++ b/app/models/career_field.rb @@ -0,0 +1,2 @@ +class CareerField < ApplicationRecord +end diff --git a/app/models/career_title.rb b/app/models/career_title.rb new file mode 100644 index 0000000..4849fe7 --- /dev/null +++ b/app/models/career_title.rb @@ -0,0 +1,3 @@ +class CareerTitle < ApplicationRecord + belongs_to :career_field +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000..565eda1 --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,12 @@ +class Category < ApplicationRecord + + has_many :category_interests + has_many :interests, through: :category_interests + + has_many :category_skills + has_many :skills, through: :category_skills + + validates :name, presence: :true, uniqueness: true + + +end diff --git a/app/models/category_interest.rb b/app/models/category_interest.rb new file mode 100644 index 0000000..db464b8 --- /dev/null +++ b/app/models/category_interest.rb @@ -0,0 +1,4 @@ +class CategoryInterest < ApplicationRecord + belongs_to :category + belongs_to :interest +end diff --git a/app/models/category_skill.rb b/app/models/category_skill.rb new file mode 100644 index 0000000..32c99d9 --- /dev/null +++ b/app/models/category_skill.rb @@ -0,0 +1,4 @@ +class CategorySkill < ApplicationRecord + belongs_to :category + belongs_to :skill +end diff --git a/app/models/gender.rb b/app/models/gender.rb new file mode 100644 index 0000000..ee5d437 --- /dev/null +++ b/app/models/gender.rb @@ -0,0 +1,7 @@ +class Gender < ApplicationRecord + + has_many :user_genders + has_many :users, through: :user_genders + + +end diff --git a/app/models/interest.rb b/app/models/interest.rb new file mode 100644 index 0000000..b68b0a1 --- /dev/null +++ b/app/models/interest.rb @@ -0,0 +1,8 @@ +class Interest < ApplicationRecord + + has_many :category_interests + has_many :categories, through: :categories + + validates :name, presence: :true + +end diff --git a/app/models/match.rb b/app/models/match.rb new file mode 100644 index 0000000..9335112 --- /dev/null +++ b/app/models/match.rb @@ -0,0 +1,4 @@ +class Match < ApplicationRecord + belongs_to :mentor + belongs_to :mentee +end diff --git a/app/models/mentee.rb b/app/models/mentee.rb new file mode 100644 index 0000000..6876fb7 --- /dev/null +++ b/app/models/mentee.rb @@ -0,0 +1,3 @@ +class Mentee < ApplicationRecord + belongs_to :user +end diff --git a/app/models/mentor.rb b/app/models/mentor.rb new file mode 100644 index 0000000..7448e57 --- /dev/null +++ b/app/models/mentor.rb @@ -0,0 +1,3 @@ +class Mentor < ApplicationRecord + belongs_to :user +end diff --git a/app/models/race.rb b/app/models/race.rb new file mode 100644 index 0000000..2962ad3 --- /dev/null +++ b/app/models/race.rb @@ -0,0 +1,5 @@ +class Race < ApplicationRecord + + has_many :user_races + has_many :users, through: :user_races +end diff --git a/app/models/skill.rb b/app/models/skill.rb new file mode 100644 index 0000000..a945040 --- /dev/null +++ b/app/models/skill.rb @@ -0,0 +1,7 @@ +class Skill < ApplicationRecord + + has_many :category_skills + has_many :categories, through: :categories + + validates :name, presence: :true +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..f605f59 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,45 @@ +class User < ApplicationRecord + has_secure_password + + has_many :user_genders + has_many :genders, through: :user_genders + + has_many :user_races + has_many :races, through: :user_races + + has_many :user_skills + has_many :skills, through: :user_skills + + has_many :user_interests + has_many :interests, through: :user_interests + + has_many :user_careers + + has_one :mentor + has_one :mentee + + validates :email, presence: true, uniqueness: true + validates :date_of_birth, presence: true + # validates :age, numericality: { greater_than_or_equal_to: 13 } + + def full_name + "#{self.first_name} #{self.last_name}" + end + + def age + "?" + # age = Date.current.year - self.date_of_birth.year + # age -= 1 if Date.today < self.date_of_birth + age.years + # age + end + + def is_adult? + self.age >= 18 + end + + def gender + self.genders[0] + end + + +end diff --git a/app/models/user_career.rb b/app/models/user_career.rb new file mode 100644 index 0000000..c9e78ed --- /dev/null +++ b/app/models/user_career.rb @@ -0,0 +1,4 @@ +class UserCareer < ApplicationRecord + belongs_to :user + belongs_to :career_title +end diff --git a/app/models/user_gender.rb b/app/models/user_gender.rb new file mode 100644 index 0000000..91a9f82 --- /dev/null +++ b/app/models/user_gender.rb @@ -0,0 +1,4 @@ +class UserGender < ApplicationRecord + belongs_to :user + belongs_to :gender +end diff --git a/app/models/user_interest.rb b/app/models/user_interest.rb new file mode 100644 index 0000000..b3f18e6 --- /dev/null +++ b/app/models/user_interest.rb @@ -0,0 +1,4 @@ +class UserInterest < ApplicationRecord + belongs_to :user + belongs_to :interest +end diff --git a/app/models/user_race.rb b/app/models/user_race.rb new file mode 100644 index 0000000..c1b407f --- /dev/null +++ b/app/models/user_race.rb @@ -0,0 +1,4 @@ +class UserRace < ApplicationRecord + belongs_to :user + belongs_to :race +end diff --git a/app/models/user_skill.rb b/app/models/user_skill.rb new file mode 100644 index 0000000..24f31cf --- /dev/null +++ b/app/models/user_skill.rb @@ -0,0 +1,4 @@ +class UserSkill < ApplicationRecord + belongs_to :user + belongs_to :skill +end diff --git a/app/serializers/career_field_serializer.rb b/app/serializers/career_field_serializer.rb new file mode 100644 index 0000000..800f82a --- /dev/null +++ b/app/serializers/career_field_serializer.rb @@ -0,0 +1,3 @@ +class CareerFieldSerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/career_title_serializer.rb b/app/serializers/career_title_serializer.rb new file mode 100644 index 0000000..1a42583 --- /dev/null +++ b/app/serializers/career_title_serializer.rb @@ -0,0 +1,4 @@ +class CareerTitleSerializer < ActiveModel::Serializer + attributes :id, :name + has_one :career_field +end diff --git a/app/serializers/category_interest_serializer.rb b/app/serializers/category_interest_serializer.rb new file mode 100644 index 0000000..7067104 --- /dev/null +++ b/app/serializers/category_interest_serializer.rb @@ -0,0 +1,5 @@ +class CategoryInterestSerializer < ActiveModel::Serializer + attributes :id, :name + has_one :category + has_one :interest +end diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb new file mode 100644 index 0000000..438cd85 --- /dev/null +++ b/app/serializers/category_serializer.rb @@ -0,0 +1,3 @@ +class CategorySerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/category_skill_serializer.rb b/app/serializers/category_skill_serializer.rb new file mode 100644 index 0000000..fd5d605 --- /dev/null +++ b/app/serializers/category_skill_serializer.rb @@ -0,0 +1,5 @@ +class CategorySkillSerializer < ActiveModel::Serializer + attributes :id + has_one :category + has_one :skill +end diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb new file mode 100644 index 0000000..c3df15d --- /dev/null +++ b/app/serializers/current_user_serializer.rb @@ -0,0 +1,9 @@ +class CurrentUserSerializer < ActiveModel::Serializer + attributes :username, :first_name, :middle_name, :last_name, :suffix, :date_of_birth, :city, :state, :country, :zip_code, :timezone, :age, :is_adult + + has_many :genders + has_many :races + has_many :user_careers + has_many :user_skills + has_many :user_interests +end diff --git a/app/serializers/gender_serializer.rb b/app/serializers/gender_serializer.rb new file mode 100644 index 0000000..4240512 --- /dev/null +++ b/app/serializers/gender_serializer.rb @@ -0,0 +1,3 @@ +class GenderSerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/interest_serializer.rb b/app/serializers/interest_serializer.rb new file mode 100644 index 0000000..e09d946 --- /dev/null +++ b/app/serializers/interest_serializer.rb @@ -0,0 +1,3 @@ +class InterestSerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/match_game_serializer.rb b/app/serializers/match_game_serializer.rb new file mode 100644 index 0000000..88ec0da --- /dev/null +++ b/app/serializers/match_game_serializer.rb @@ -0,0 +1,3 @@ +class MatchGameSerializer < ActiveModel::Serializer + attributes :id +end diff --git a/app/serializers/match_serializer.rb b/app/serializers/match_serializer.rb new file mode 100644 index 0000000..88be96e --- /dev/null +++ b/app/serializers/match_serializer.rb @@ -0,0 +1,5 @@ +class MatchSerializer < ActiveModel::Serializer + attributes :id + has_one :mentor + has_one :mentee +end diff --git a/app/serializers/mentee_serializer.rb b/app/serializers/mentee_serializer.rb new file mode 100644 index 0000000..0e6fa61 --- /dev/null +++ b/app/serializers/mentee_serializer.rb @@ -0,0 +1,4 @@ +class MenteeSerializer < ActiveModel::Serializer + attributes :id + has_one :user +end diff --git a/app/serializers/mentor_serializer.rb b/app/serializers/mentor_serializer.rb new file mode 100644 index 0000000..2f71386 --- /dev/null +++ b/app/serializers/mentor_serializer.rb @@ -0,0 +1,8 @@ +class MentorSerializer < ActiveModel::Serializer + attributes :id, :user + has_one :user + + def user + User.find(self.id) + end +end diff --git a/app/serializers/race_serializer.rb b/app/serializers/race_serializer.rb new file mode 100644 index 0000000..bb8b023 --- /dev/null +++ b/app/serializers/race_serializer.rb @@ -0,0 +1,3 @@ +class RaceSerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/skill_serializer.rb b/app/serializers/skill_serializer.rb new file mode 100644 index 0000000..d7b6411 --- /dev/null +++ b/app/serializers/skill_serializer.rb @@ -0,0 +1,3 @@ +class SkillSerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/user_career_serializer.rb b/app/serializers/user_career_serializer.rb new file mode 100644 index 0000000..c8476c6 --- /dev/null +++ b/app/serializers/user_career_serializer.rb @@ -0,0 +1,5 @@ +class UserCareerSerializer < ActiveModel::Serializer + attributes :id, :company, :start_date, :end_date, :is_current, :description + has_one :user + has_one :career_title +end diff --git a/app/serializers/user_gender_serializer.rb b/app/serializers/user_gender_serializer.rb new file mode 100644 index 0000000..1a3cc94 --- /dev/null +++ b/app/serializers/user_gender_serializer.rb @@ -0,0 +1,5 @@ +class UserGenderSerializer < ActiveModel::Serializer + attributes :id + has_one :user + has_one :gender +end diff --git a/app/serializers/user_interest_serializer.rb b/app/serializers/user_interest_serializer.rb new file mode 100644 index 0000000..08f55b4 --- /dev/null +++ b/app/serializers/user_interest_serializer.rb @@ -0,0 +1,5 @@ +class UserInterestSerializer < ActiveModel::Serializer + attributes :id + has_one :user + has_one :interest +end diff --git a/app/serializers/user_race_serializer.rb b/app/serializers/user_race_serializer.rb new file mode 100644 index 0000000..2e7f0f3 --- /dev/null +++ b/app/serializers/user_race_serializer.rb @@ -0,0 +1,5 @@ +class UserRaceSerializer < ActiveModel::Serializer + attributes :id + has_one :user + has_one :race +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb new file mode 100644 index 0000000..c1c30ed --- /dev/null +++ b/app/serializers/user_serializer.rb @@ -0,0 +1,5 @@ +class UserSerializer < ActiveModel::Serializer + attributes :id, :username, :first_name, :middle_name, :last_name, :suffix, :date_of_birth, :city, :state, :country, :zip_code, :timezone, :gender + # has_one :gender + # has_one :race +end diff --git a/app/serializers/user_skill_serializer.rb b/app/serializers/user_skill_serializer.rb new file mode 100644 index 0000000..37093e6 --- /dev/null +++ b/app/serializers/user_skill_serializer.rb @@ -0,0 +1,5 @@ +class UserSkillSerializer < ActiveModel::Serializer + attributes :id + has_one :user + has_one :skill +end diff --git a/client/package-lock.json b/client/package-lock.json index 0bd9e47..fc18981 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,11 +8,14 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^1.9.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.1.3", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-scroll": "^1.9.0", @@ -3248,6 +3251,29 @@ } } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", + "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", + "dependencies": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4013,6 +4039,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.4.tgz", + "integrity": "sha512-ZchYkbieA+7tnxwX/SCBySx9WwvWR8TaP5tb2jRAzwvLb/rWchGw3v0w3pqUbUvj0GCwW2Xz/AVPSk6kUGctXQ==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4435,6 +4470,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz", "integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.7.tgz", @@ -5295,6 +5335,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -8922,6 +8985,19 @@ "@babel/runtime": "^7.7.6" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -14413,6 +14489,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14701,6 +14782,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -14873,6 +14997,22 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -15018,6 +15158,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -16877,6 +17022,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index 85883e9..f59c7e5 100644 --- a/client/package.json +++ b/client/package.json @@ -3,11 +3,14 @@ "version": "0.1.0", "private": true, "dependencies": { + "@reduxjs/toolkit": "^1.9.6", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.1.3", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-scroll": "^1.9.0", diff --git a/client/src/components/Game/Card/Card.css b/client/src/components/Game/Card/Card.css new file mode 100644 index 0000000..35443b9 --- /dev/null +++ b/client/src/components/Game/Card/Card.css @@ -0,0 +1,36 @@ +.card { + background-color: white; + border-radius: 8px; + height: 68vh; + width: 50vh; + max-width: 30vw; + max-height: 70vh; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.4); /* Adjust the shadow here */ + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + margin: 20px; + + transition: transform 0.5s, opacity 0.5s, visibility 0.5s; + position: absolute; + transform: translateY(60%); +} + +.card-title { + font-family: "PolySansMedium"; + font-size: 24px; + color: var(--darc-lilac); +} + +.right-swipe { + transform: translateX(100%) rotate(20deg); + opacity: 0; + visibility: hidden; +} + +.left-swipe { + transform: translateX(-100%) rotate(-20deg); + opacity: 0; + visibility: hidden; +} diff --git a/client/src/components/Game/Card/Card.js b/client/src/components/Game/Card/Card.js new file mode 100644 index 0000000..ddcdd26 --- /dev/null +++ b/client/src/components/Game/Card/Card.js @@ -0,0 +1,28 @@ +import React from "react"; +import "./Card.css"; + +function Card({ topic, swipeDirection }) { + return ( +
{data}
+ > + ); + } + + // Export an `ErrorBoundary` directly instead of needing to create a React Element from it + export function ErrorBoundary() { + let error = useRouteError(); + return isRouteErrorResponse(error) ? ( +