diff --git a/.vscode/settings.json b/.vscode/settings.json index b56efc3fa..8cc3aaad2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,7 +60,7 @@ "erb": "html" }, "[ruby]": { - "editor.defaultFormatter": "mbessey.vscode-rufo", + "editor.defaultFormatter": "jnbt.vscode-rufo", "editor.formatOnSave": false }, "[erb]": { diff --git a/Gemfile b/Gemfile index bc0213aa6..e67ad0b29 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.2.1" +gem "devise" + gem "simple_form" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" diff --git a/Gemfile.lock b/Gemfile.lock index a8196ad6f..46cfec5c4 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) @@ -207,6 +214,8 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.9) + nokogiri (1.15.5-arm64-darwin) + racc (~> 1.4) nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) nokogiri (1.15.5-x86_64-linux) @@ -215,6 +224,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 +288,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) @@ -331,6 +344,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + sqlite3 (1.6.8-arm64-darwin) sqlite3 (1.6.8-x86_64-darwin) sqlite3 (1.6.8-x86_64-linux) stimulus-rails (1.2.2) @@ -357,6 +371,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) @@ -386,6 +402,7 @@ GEM zeitwerk (2.6.12) PLATFORMS + arm64-darwin-22 x86_64-darwin-22 x86_64-linux @@ -398,6 +415,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..cd08b60c2 --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,70 @@ +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) + + respond_to do |format| + if @comment.save + 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_controller.rb b/app/controllers/likes_controller.rb new file mode 100644 index 000000000..f38a1c6e9 --- /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(: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/models/comment.rb b/app/models/comment.rb new file mode 100644 index 000000000..16b80bd2f --- /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 + + 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..1fbd584ea --- /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..001b2df61 --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,26 @@ +# == 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 + + validates :fan_id, uniqueness: { scope: :photo_id, message: "has already liked this photo"} +end diff --git a/app/models/photo.rb b/app/models/photo.rb new file mode 100644 index 000000000..04c0dd88c --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,34 @@ +# == 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 + has_many :own_photos, foreign_key: :owner_id, class_name: "Photo" + + 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..7db2fe370 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,44 @@ +# == 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 :liked_photos, through: :likes, source: :photo + has_many :comments, foreign_key: "author_id" + has_many :sent_follow_requests, foreign_key: :sender_id, class_name: "FollowRequest", dependent: :destroy + has_many :received_follow_requests, foreign_key: :recipient_id, class_name: "FollowRequest", dependent: :destroy + has_many :accepted_sent_follow_requests, -> { accepted }, foreign_key: :sender_id, class_name: "FollowRequest", dependent: :destroy + has_many :accepted_received_follow_requests, -> { accepted }, foreign_key: :recipient_id, class_name: "FollowRequest", dependent: :destroy + has_many :likes, foreign_key: :fan_id + 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 %> +
+ +<%= notice %>
+ +<%= notice %>
+ +<%= render @comment %> + ++ 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 %> +