diff --git a/Gemfile b/Gemfile index bc0213aa6..3e56edea8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,12 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } +gem "devise" + ruby "3.2.1" +gem "faker" + gem "simple_form" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" diff --git a/Gemfile.lock b/Gemfile.lock index a8196ad6f..21187bdf1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,6 +76,7 @@ GEM tabulo awesome_print (1.9.2) base64 (0.1.1) + bcrypt (3.1.20) better_errors (2.9.1) coderay (>= 1.0.0) erubi (>= 1.0.0) @@ -109,6 +110,12 @@ GEM irb (>= 1.5.0) reline (>= 0.3.1) debug_inspector (1.1.0) + devise (4.9.3) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) diff-lcs (1.5.0) diffy (3.4.2) domain_name (0.5.20190701) @@ -215,6 +222,7 @@ GEM faraday (>= 1, < 3) sawyer (~> 0.9) oj (3.13.23) + orm_adapter (0.5.0) pg (1.4.6) pry (0.14.2) coderay (~> 1.1) @@ -278,6 +286,9 @@ GEM regexp_parser (2.8.2) reline (0.3.9) io-console (~> 0.5) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) rexml (3.2.6) rspec (3.12.0) rspec-core (~> 3.12.0) @@ -357,6 +368,8 @@ GEM unf_ext unf_ext (0.0.9.1) unicode-display_width (2.4.2) + warden (1.2.9) + rack (>= 2.0.9) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -398,6 +411,7 @@ DEPENDENCIES bootsnap capybara debug + devise dotenv-rails draft_matchers faker 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..ffe47c480 --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,72 @@ +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.html { redirect_to comment_url(@comment), 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(:author_id, :photo_id, :body) + end +end diff --git a/app/controllers/follow_requests_controller.rb b/app/controllers/follow_requests_controller.rb new file mode 100644 index 000000000..47074cf0f --- /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(:recipient_id, :sender_id, :status) + end +end diff --git a/app/controllers/likes_controllerr.rb b/app/controllers/likes_controllerr.rb new file mode 100644 index 000000000..f38a1c6e9 --- /dev/null +++ b/app/controllers/likes_controllerr.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(: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..86ba2de13 --- /dev/null +++ b/app/controllers/photos_controller.rb @@ -0,0 +1,70 @@ +class PhotosController < ApplicationController + 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..157773a78 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,24 @@ +# app/controllers/users_controller.rb + +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..a1739b5a8 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,26 @@ +# == Schema Information +# +# Table name: comments +# +# id :bigint not null, primary key +# body :text not null +# created_at :datetime not null +# updated_at :datetime not null +# author_id :bigint not null +# photo_id :bigint not null +# +# Indexes +# +# index_comments_on_photo_id (photo_id) +# +# Foreign Keys +# +# fk_rails_... (author_id => users.id) +# fk_rails_... (photo_id => photos.id) +# +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..24824b700 --- /dev/null +++ b/app/models/follow_request.rb @@ -0,0 +1,27 @@ +# == Schema Information +# +# Table name: follow_requests +# +# id :bigint not null, primary key +# status :string default("pending") +# created_at :datetime not null +# updated_at :datetime not null +# recipient_id :bigint not null +# sender_id :bigint not null +# +# Indexes +# +# index_follow_requests_on_recipient_id (recipient_id) +# index_follow_requests_on_sender_id (sender_id) +# +# Foreign Keys +# +# fk_rails_... (recipient_id => users.id) +# fk_rails_... (sender_id => users.id) +# +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..1cb48625c --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: likes +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# fan_id :bigint not null +# photo_id :bigint not null +# +# Indexes +# +# index_likes_on_fan_id (fan_id) +# index_likes_on_photo_id (photo_id) +# +# Foreign Keys +# +# fk_rails_... (fan_id => users.id) +# fk_rails_... (photo_id => photos.id) +# + +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..42cb6e452 --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,36 @@ +# == Schema Information +# +# Table name: photos +# +# id :bigint not null, primary key +# caption :text +# comments_count :integer default(0) +# image :string +# likes_count :integer default(0) +# 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..d8ca68308 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,55 @@ +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# comments_count :integer default(0) +# email :citext default(""), not null +# encrypted_password :string default(""), not null +# likes_count :integer default(0) +# photos_count :integer default(0) +# private :boolean default(TRUE) +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# username :citext +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_email (email) UNIQUE +# index_users_on_reset_password_token (reset_password_token) UNIQUE +# 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 :own_photos, class_name: "Photo", foreign_key: "owner_id" + has_many :comments, foreign_key: "author_id" + + has_many :sent_follow_requests, foreign_key: :sender_id, class_name: "FollowRequest" + has_many :accepted_sent_follow_requests, -> { accepted }, foreign_key: :sender_id, class_name: "FollowRequest" + + has_many :received_follow_requests, foreign_key: :recipient_id, class_name: "FollowRequest" + has_many :accepted_received_follow_requests, -> { accepted }, foreign_key: :recipient_id, class_name: "FollowRequest" + + has_many :likes, foreign_key: :fan_id + has_many :own_photos, foreign_key: :owner_id, class_name: "Photo" + + 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..e5aac46fe --- /dev/null +++ b/app/views/comments/_comment.html.erb @@ -0,0 +1,17 @@ +
+ Author: + <%= comment.author_id %> +
+ ++ Photo: + <%= comment.photo_id %> +
+ ++ Body: + <%= comment.body %> +
+ ++ Recipient: + <%= follow_request.recipient_id %> +
+ ++ Sender: + <%= follow_request.sender_id %> +
+ ++ Status: + <%= follow_request.status %> +
+ +<%= notice %>
+ ++ <%= link_to "Show this follow request", follow_request %> +
+ <% end %> +<%= notice %>
+ +<%= render @follow_request %> + +
+ <%= link_to "Show this comment", comment %> +
+ <% end %> +