diff --git a/Gemfile b/Gemfile index 1bd5c98af..ae4a2aeac 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,8 @@ ruby "3.2.1" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.1.3", ">= 7.1.3.2" +gem "devise" + # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem "sprockets-rails" diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d12a..6b4dcfa85 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,3 @@ class ApplicationController < ActionController::Base + before_action :authenticate_user! end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 000000000..ce53f39bc --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,71 @@ +class CommentsController < ApplicationController + before_action :set_comment, only: %i[ show edit update destroy ] + + # GET /comments or /comments.json + def index + @comments = Comment.all + end + + # GET /comments/1 or /comments/1.json + def show + end + + # GET /comments/new + def new + @comment = Comment.new + end + + # GET /comments/1/edit + def edit + end + + # POST /comments or /comments.json + def create + @comment = Comment.new(comment_params) + @comment.author = current_user + + respond_to do |format| + if @comment.save + format.html { redirect_back fallback_location: root_path, notice: "Comment was successfully created." } + format.json { render :show, status: :created, location: @comment } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @comment.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /comments/1 or /comments/1.json + def update + respond_to do |format| + if @comment.update(comment_params) + format.html { redirect_to comment_url(@comment), notice: "Comment was successfully updated." } + format.json { render :show, status: :ok, location: @comment } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @comment.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /comments/1 or /comments/1.json + def destroy + @comment.destroy! + + respond_to do |format| + format.html { redirect_to comments_url, notice: "Comment was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_comment + @comment = Comment.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def comment_params + params.require(:comment).permit(:id, :body, :author_id, :photo_id) + end +end diff --git a/app/controllers/follow_requests_controller.rb b/app/controllers/follow_requests_controller.rb new file mode 100644 index 000000000..528aec1ba --- /dev/null +++ b/app/controllers/follow_requests_controller.rb @@ -0,0 +1,70 @@ +class FollowRequestsController < ApplicationController + before_action :set_follow_request, only: %i[ show edit update destroy ] + + # GET /follow_requests or /follow_requests.json + def index + @follow_requests = FollowRequest.all + end + + # GET /follow_requests/1 or /follow_requests/1.json + def show + end + + # GET /follow_requests/new + def new + @follow_request = FollowRequest.new + end + + # GET /follow_requests/1/edit + def edit + end + + # POST /follow_requests or /follow_requests.json + def create + @follow_request = FollowRequest.new(follow_request_params) + + respond_to do |format| + if @follow_request.save + format.html { redirect_to follow_request_url(@follow_request), notice: "Follow request was successfully created." } + format.json { render :show, status: :created, location: @follow_request } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @follow_request.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /follow_requests/1 or /follow_requests/1.json + def update + respond_to do |format| + if @follow_request.update(follow_request_params) + format.html { redirect_to follow_request_url(@follow_request), notice: "Follow request was successfully updated." } + format.json { render :show, status: :ok, location: @follow_request } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @follow_request.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /follow_requests/1 or /follow_requests/1.json + def destroy + @follow_request.destroy! + + respond_to do |format| + format.html { redirect_to follow_requests_url, notice: "Follow request was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_follow_request + @follow_request = FollowRequest.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def follow_request_params + params.require(:follow_request).permit(:id, :status, :recipient_id, :sender_id) + end +end diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb new file mode 100644 index 000000000..24642c196 --- /dev/null +++ b/app/controllers/likes_controller.rb @@ -0,0 +1,70 @@ +class LikesController < ApplicationController + before_action :set_like, only: %i[ show edit update destroy ] + + # GET /likes or /likes.json + def index + @likes = Like.all + end + + # GET /likes/1 or /likes/1.json + def show + end + + # GET /likes/new + def new + @like = Like.new + end + + # GET /likes/1/edit + def edit + end + + # POST /likes or /likes.json + def create + @like = Like.new(like_params) + + respond_to do |format| + if @like.save + format.html { redirect_to like_url(@like), notice: "Like was successfully created." } + format.json { render :show, status: :created, location: @like } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @like.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /likes/1 or /likes/1.json + def update + respond_to do |format| + if @like.update(like_params) + format.html { redirect_to like_url(@like), notice: "Like was successfully updated." } + format.json { render :show, status: :ok, location: @like } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @like.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /likes/1 or /likes/1.json + def destroy + @like.destroy! + + respond_to do |format| + format.html { redirect_to likes_url, notice: "Like was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_like + @like = Like.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def like_params + params.require(:like).permit(:id, :fan_id, :photo_id) + end +end diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb new file mode 100644 index 000000000..130423d34 --- /dev/null +++ b/app/controllers/photos_controller.rb @@ -0,0 +1,71 @@ +class PhotosController < ApplicationController + before_action :authenticate_user! + before_action :set_photo, only: %i[ show edit update destroy ] + + # GET /photos or /photos.json + def index + @photos = Photo.all + end + + # GET /photos/1 or /photos/1.json + def show + end + + # GET /photos/new + def new + @photo = Photo.new + end + + # GET /photos/1/edit + def edit + end + + # POST /photos or /photos.json + def create + @photo = Photo.new(photo_params) + + respond_to do |format| + if @photo.save + format.html { redirect_to photo_url(@photo), notice: "Photo was successfully created." } + format.json { render :show, status: :created, location: @photo } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @photo.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /photos/1 or /photos/1.json + def update + respond_to do |format| + if @photo.update(photo_params) + format.html { redirect_to photo_url(@photo), notice: "Photo was successfully updated." } + format.json { render :show, status: :ok, location: @photo } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @photo.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /photos/1 or /photos/1.json + def destroy + @photo.destroy! + + respond_to do |format| + format.html { redirect_to photos_url, notice: "Photo was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_photo + @photo = Photo.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def photo_params + params.require(:photo).permit(:image, :comments_count, :likes_count, :caption, :owner_id) + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 000000000..919cce6f2 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,21 @@ +class UsersController < ApplicationController + def show + @user = User.find_by!(username: params.fetch(:username)) + end + + def liked + @user = User.find_by!(username: params.fetch(:username)) + end + + def feed + @user = User.find_by!(username: params.fetch(:username)) + end + + def followers + @user = User.find_by!(username: params.fetch(:username)) + end + + def following + @user = User.find_by!(username: params.fetch(:username)) + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 000000000..89d760150 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,18 @@ +# == Schema Information +# +# Table name: comments +# +# id :bigint not null, primary key +# body :text +# created_at :datetime not null +# updated_at :datetime not null +# author_id :bigint +# photo_id :bigint +# +class Comment < ApplicationRecord + belongs_to :author, class_name: "User", counter_cache: true + belongs_to :photo, counter_cache: true + + + validates :body, presence: true +end diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb new file mode 100644 index 000000000..4083845cf --- /dev/null +++ b/app/models/follow_request.rb @@ -0,0 +1,18 @@ +# == Schema Information +# +# Table name: follow_requests +# +# id :bigint not null, primary key +# status :string +# created_at :datetime not null +# updated_at :datetime not null +# recipient_id :bigint +# sender_id :bigint +# +class FollowRequest < ApplicationRecord + belongs_to :recipient, class_name: "User" + belongs_to :sender, class_name: "User" + + + enum status: { pending: "pending", rejected: "rejected", accepted: "accepted" } +end diff --git a/app/models/like.rb b/app/models/like.rb new file mode 100644 index 000000000..0a9f87f23 --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,16 @@ +# == Schema Information +# +# Table name: likes +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# fan_id :bigint +# photo_id :bigint +# +class Like < ApplicationRecord + belongs_to :fan, class_name: "User", counter_cache: true + belongs_to :photo, counter_cache: true + + +end diff --git a/app/models/photo.rb b/app/models/photo.rb new file mode 100644 index 000000000..6b1a08664 --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,31 @@ +# == Schema Information +# +# Table name: photos +# +# id :bigint not null, primary key +# caption :text +# comments_count :integer +# image :string +# likes_count :integer +# created_at :datetime not null +# updated_at :datetime not null +# owner_id :bigint not null +# +# Indexes +# +# index_photos_on_owner_id (owner_id) +# +# Foreign Keys +# +# fk_rails_... (owner_id => users.id) +# +class Photo < ApplicationRecord + belongs_to :owner, class_name: "User", counter_cache: true + has_many :comments + has_many :likes + has_many :fans, through: :likes + validates :caption, presence: true + validates :image, presence: true + scope :past_week, -> { where(created_at: 1.week.ago...) } + scope :by_likes, -> { order(likes_count: :desc) } +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 000000000..b3eb71128 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,42 @@ +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# comments_count :integer default(0) +# email :string default(""), not null +# encrypted_password :string default(""), not null +# likes_count :integer default(0) +# photos_count :integer +# private :boolean default(TRUE) +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# username :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_username (username) UNIQUE +# +class User < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable + + has_many :accepted_received_follow_requests, -> { accepted }, foreign_key: :recipient_id, class_name: "FollowRequest" + has_many :accepted_sent_follow_requests, -> { accepted }, foreign_key: :sender_id, class_name: "FollowRequest" + has_many :own_photos, foreign_key: :owner_id, class_name: "Photo" + has_many :comments, foreign_key: :author_id + has_many :likes, foreign_key: :fan_id + has_many :sent_follow_requests, foreign_key: :sender_id, class_name: "FollowRequest" + has_many :received_follow_requests, foreign_key: :recipient_id, class_name: "FollowRequest" + has_many :liked_photos, through: :likes, source: :photo + has_many :leaders, through: :accepted_sent_follow_requests, source: :recipient + has_many :followers, through: :accepted_received_follow_requests, source: :sender + has_many :feed, through: :leaders, source: :own_photos + has_many :discover, through: :leaders, source: :liked_photos + validates :username, presence: true, uniqueness: true +end diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb new file mode 100644 index 000000000..4ff37406f --- /dev/null +++ b/app/views/comments/_comment.html.erb @@ -0,0 +1,22 @@ +
+ Id: + <%= comment.id %> +
+ ++ Body: + <%= comment.body %> +
+ ++ Author: + <%= comment.author_id %> +
+ ++ Photo: + <%= comment.photo_id %> +
+ +<%= notice %>
+ +<%= notice %>
+ +<%= render @comment %> + +Welcome <%= @email %>!
+ +You can confirm your account email through the link below:
+ +<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 000000000..32f4ba803 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +Hello <%= @email %>!
+ +<% if @resource.try(:unconfirmed_email?) %> +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/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 000000000..b41daf476 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +Hello <%= @resource.email %>!
+ +We're contacting you to notify you that your password has been changed.
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 000000000..f667dc12f --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +Hello <%= @resource.email %>!
+ +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/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 000000000..41e148bf2 --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +Hello <%= @resource.email %>!
+ +Your account has been locked due to an excessive number of unsuccessful sign in attempts.
+ +Click the link below to unlock your account:
+ +<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 000000000..591cd8c85 --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,27 @@ +Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + <%= f.input :password, + hint: "leave it blank if you don't want to change it", + required: false, + input_html: { autocomplete: "new-password" } %> + <%= f.input :password_confirmation, + required: false, + input_html: { autocomplete: "new-password" } %> + <%= f.input :current_password, + hint: "we need your current password to confirm your changes", + required: true, + input_html: { autocomplete: "current-password" } %> ++ Status: + <%= follow_request.status %> +
+ ++ Recipient: + <%= follow_request.recipient_id %> +
+ ++ Sender: + <%= follow_request.sender_id %> +
+ +<%= notice %>
+ ++ <%= link_to "Show this follow request", follow_request %> +
+ <% end %> +<%= notice %>
+ +<%= render @follow_request %> + +
+ <%= link_to "Show this comment", comment %> +
+ <% end %> +