diff --git a/Gemfile b/Gemfile index 01f5d3e..436cc39 100644 --- a/Gemfile +++ b/Gemfile @@ -18,7 +18,7 @@ gem "puma", "~> 5.0" # Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails] gem "jsbundling-rails" -gem "hotwire_combobox" +gem "hotwire_combobox", github: "josefarias/hotwire_combobox", branch: "main" # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] gem "turbo-rails", github: "hotwired/turbo-rails", branch: "main" diff --git a/Gemfile.lock b/Gemfile.lock index 09acd44..ebe2c26 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,14 +20,24 @@ GIT GIT remote: https://github.com/hotwired/turbo-rails.git - revision: e8dd34a11b588978534ce9b8f8e0822f2c6e3f2a + revision: e376852bfb273f69f4ebb54cf516b99fcbaa7acb branch: main specs: - turbo-rails (2.0.4) + turbo-rails (2.0.5) actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) +GIT + remote: https://github.com/josefarias/hotwire_combobox.git + revision: 201954ceecf369f4ec70fbd50afaa4e3d0cebedc + branch: main + specs: + hotwire_combobox (0.2.5) + rails (>= 7.0.7.2) + stimulus-rails (>= 1.2) + turbo-rails (>= 1.2) + GIT remote: https://github.com/rspec/rspec-core.git revision: 81589709e88db1ec2537faee3a7f4a43c7d9aac1 @@ -268,10 +278,6 @@ GEM groupdate (6.3.0) activesupport (>= 6.1) hashie (5.0.0) - hotwire_combobox (0.2.1) - rails (>= 7.0.7.2) - stimulus-rails (>= 1.2) - turbo-rails (>= 1.2) http (5.1.1) addressable (~> 2.8) http-cookie (~> 1.0) @@ -340,7 +346,7 @@ GEM json (>= 1.4.6) meta-tags (2.18.0) actionpack (>= 3.2.0, < 7.1) - method_source (1.0.0) + method_source (1.1.0) mini_magick (4.12.0) mini_mime (1.1.5) minitest (5.22.3) @@ -558,7 +564,7 @@ GEM rubocop-performance (~> 1.18.0) standardrb (1.0.1) standard - stimulus-rails (1.2.1) + stimulus-rails (1.3.3) railties (>= 6.0.0) store_attribute (1.1.1) activerecord (>= 6.0) @@ -620,7 +626,7 @@ DEPENDENCIES friendly_id (~> 5.4) geocoder (~> 1.8) groupdate (~> 6.3) - hotwire_combobox + hotwire_combobox! http (~> 5.1) image_processing (~> 1.2) jbuilder diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index 358c665..675465a 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -45,7 +45,7 @@ /* Brand shinanigans -> These will be computed for the users theme at runtime. */ - --rau-brand: #002c63; + --rau-brand: #ea09e3; --rau-brand-emphasis: #002c63; --rau-brand-text: white; } @@ -89,7 +89,7 @@ -> These will be computed for the users theme at runtime. */ - --rau-brand: #002c63; + --rau-brand: #ea09e3; --rau-brand-emphasis: #002c63; --rau-brand-text: white; } @@ -203,6 +203,10 @@ @apply bg-white; } + .scaleX { + @apply hover:scale-125 transform-gpu hover:animate-pulse transition duration-500; + } + textarea { @apply block w-full rounded-md border-0 py-1.5 dark:bg-gray-900 dark:text-gray-100 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-brand-600 sm:text-sm sm:leading-6; } @@ -342,16 +346,16 @@ /* COMBOBOX */ .hw-combobox__main__wrapper { - @apply sm:w-[35rem] rounded-full w-80; + @apply sm:w-full rounded-full w-80 relative; } .hw-combobox__handle { @apply right-3; } .hw-combobox__listbox { - @apply rounded-2xl bg-subtle; + @apply rounded-2xl bg-subtle overflow-auto h-48 absolute w-full z-[500]; } - .hw-combobox__option { + .hw-combobox__listbox .hw-combobox__option { @apply p-2 border-b border-muted hover:bg-emphasis; } } diff --git a/app/controllers/account_connections_controller.rb b/app/controllers/account_connections_controller.rb new file mode 100644 index 0000000..6e5bf90 --- /dev/null +++ b/app/controllers/account_connections_controller.rb @@ -0,0 +1,110 @@ +class AccountConnectionsController < ApplicationController + + + def user_search + @title = "Tracks" + + q = params[:q] + if q.present? + @artists = current_user.find_artists_excluding_children(q) + end + #.with_attached_avatar + #.order("id desc") + @artists = @artists ? @artists.page(params[:page]).per(5) : [] + end + + def new + @collection = [] + @user = FormModels::ArtistForm.new( + request_access: "request", + hide: false, + is_new: params[:kind] == "new" + ) + @users = User.where(role: "artists").page(params[:page]).per(10) + end + + def create + if params[:form_models_artist_form] + resource_params = params.require(:form_models_artist_form).permit( + :password, :username, :hide, :request_access, :email, :search, :first_name, :last_name, :logo + ) + @user = FormModels::ArtistForm.new(resource_params) + @user.is_new = params[:kind] == "new" + @user.inviter = current_user + + unless @user.username.present? + @user.username = User.find_by(id: @user.search)&.username if @user.search.present? + @user.inviter = current_user + end + if @user.valid? + created_user = @user.process_user_interaction + if !created_user + flash.now[:error] = "not invited user" + else + @created = true + end + end + return + end + + if params[:commit] == "Select user" + a = User.find(params[:search]) + @selected_artist = FormModels::ArtistForm.new(username: a.username) + if @selected_artist.valid? + @selected_artist + end + return + end + + if params[:commit] == "Send connect request" + user = User.find(params[:user][:id]) + connected_account = ConnectedAccount.attach_account(inviter: current_user , invited_user: user) if user + + if connected_account + ConnectedAccountMailer.invitation_email(connected_account).deliver_now + + @created = true + end + return + end + end + + def impersonate + if params[:username] + user = User.find_by(username: params[:username]) + if current_user.child_accounts.find(user.id) + session[:parent_user] = current_user.id + Current.label_user = current_user + flash[:notice] = "signed as #{user.username}" + sign_in(:user, user) + redirect_to user_path(user.username) + end + else + if session[:parent_user].present? + user = User.find(session[:parent_user]) + session[:parent_user] = nil + Current.label_user = nil + flash[:notice] = "signed as #{user.username}" + sign_in(:user, user) + redirect_to user_path(user.username) + end + end + end + + def update + + end + + def approve + @connected_account = ConnectedAccount.find_signed(params[:id]) + @label = @connected_account.parent + @artist = @connected_account.user + if request.get? + sign_in(:user, @artist) + elsif request.post? + flash[:notice] = "you are now part of #{@label.username}" + redirect_to user_path(@artist.username) + end + + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4e2013f..cba0733 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,13 +1,23 @@ class ApplicationController < ActionController::Base before_action do ActiveStorage::Current.url_options = {protocol: request.protocol, host: request.host, port: request.port} - ActiveStorage::Current.host = request.url - # ActiveStorage::Current.url_options = { protocol: "http://", host: "localhost", port: "3000" } + Current.label_user = User.find(session[:parent_user]) if session[:parent_user].present? end before_action :set_locale + helper_method :flash_stream + + def flash_stream + turbo_stream.replace("flash", partial: "shared/flash", locals: { flash: flash }) + end + + helper_method :impersonating? + def impersonating?(user) + label_user.present? && current_user&.id == user&.id + end + def set_locale if params[:locale].present? cookies[:locale] = params[:locale] @@ -19,6 +29,12 @@ def set_locale end end + helper_method :label_user + def label_user + return if session[:parent_user].blank? + @label_user ||= User.find session[:parent_user] + end + def become if current_user.is_admin? user = User.find_by(username: params[:id]) diff --git a/app/controllers/label_artists_controller.rb b/app/controllers/label_artists_controller.rb new file mode 100644 index 0000000..f4006ce --- /dev/null +++ b/app/controllers/label_artists_controller.rb @@ -0,0 +1,7 @@ +class LabelArtistsController < ApplicationController + + + def index + @label = User.where(role: ["artist", "admin"], label: true).find_by(username: params[:user_id]) + end +end diff --git a/app/controllers/label_auth_controller.rb b/app/controllers/label_auth_controller.rb new file mode 100644 index 0000000..bf33357 --- /dev/null +++ b/app/controllers/label_auth_controller.rb @@ -0,0 +1,34 @@ +class LabelAuthController < ApplicationController + before_action :authenticate_user! + + # Assuming `User` model and `Accounts` is a service object that contains user related queries + # and `is_child_of?` is a method defined within your User model or an associated service. + + def add + username = params[:username] + user = User.get_user_by_username(username) + + if current_user.is_child_of?(user.id) + sign_in(:user, user) # Devise's sign_in helper + session[:parent_user] = current_user.id + redirect_to "/#{user.username}" + else + flash[:error] = "Not allowed" + redirect_to "/#{current_user.username}" + end + end + + def back + username = params[:username] + user = User.get_user_by_username(username) + + if user.is_child_of?(current_user.id) + sign_in(:user, user) # Devise's sign_in helper + session[:parent_user] = nil + redirect_to "/#{user.username}" + else + flash[:error] = "Not allowed" + redirect_to "/#{current_user.username}" + end + end +end \ No newline at end of file diff --git a/app/controllers/labels_controller.rb b/app/controllers/labels_controller.rb new file mode 100644 index 0000000..fc6eeb1 --- /dev/null +++ b/app/controllers/labels_controller.rb @@ -0,0 +1,6 @@ +class LabelsController < ApplicationController + + before_action :find_user, except: [:index] + before_action :check_user_role, except: [:index] + +end diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index 4d5b04a..99ac617 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -1,9 +1,14 @@ class LikesController < ApplicationController + + before_action :check_user + def create @resource = find_resource @button_class = current_user.toggle_like!(@resource) ? "button-active" : "button" end + private + def find_resource if params[:track_id] @resource = Track.friendly.find(params[:track_id]) @@ -11,4 +16,10 @@ def find_resource @resource = Playlist.friendly.find(params[:playlist_id]) end end + + def check_user + redirect_to new_user_session_path and return if current_user.blank? + end + + end diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index f12e3e3..c476e26 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -4,6 +4,14 @@ class PhotosController < ApplicationController def create end + def show + @photos = User.find(params[:user_id]).photos + @photo = User.find(params[:user_id]).photos.find(params[:id]) + @prev_photo = @photos.where("id < ?", @photo.id).last + @next_photo = @photos.where("id > ?", @photo.id).first + + end + def update current_user.update(resource_params) diff --git a/app/controllers/playlists_controller.rb b/app/controllers/playlists_controller.rb index 375574b..90a0b9a 100644 --- a/app/controllers/playlists_controller.rb +++ b/app/controllers/playlists_controller.rb @@ -43,7 +43,8 @@ def show def edit @tab = params[:tab] || "basic-info-tab" - @playlist = current_user.playlists.friendly.find(params[:id]) + @playlist = find_playlist + @playlist.enable_label = @playlist.label_id.present? end def new @@ -56,19 +57,25 @@ def new def create @tab = params[:tab] || "basic-info-tab" @playlist = current_user.playlists.create(playlist_params) - if @playlist.errors.blank? - flash[:now] = "successfully created" + if @playlist + flash.now[:notice] = "successfully created" + else + flash.now[:error] = "error in creating" end end def update @tab = params[:tab] || "basic-info-tab" - @playlist = current_user.playlists.friendly.find(params[:id]) + @playlist = find_playlist - if !params[:nonpersist] && @playlist.update(playlist_params) - flash[:now] = "successfully updated" - end + @playlist.assign_attributes(playlist_params) + if !params[:nonpersist] && @playlist.save + flash.now[:notice] = "successfully updated" + else + flash.now[:error] = "error updating playlist" + end + if params[:nonpersist] @playlist.assign_attributes(playlist_params) end @@ -83,6 +90,7 @@ def playlist_params :title, :description, :private, :price, :playlist_type, :release_date, :cover, :record_label, :buy_link, + :enable_label, :copyright, :attribution, :noncommercial, :non_derivative_works, :copies, track_playlists_attributes: [ @@ -107,4 +115,10 @@ def sort render "update" end + + def find_playlist + Playlist + .where(user_id: current_user.id).or(Playlist.where(label_id: current_user.id)) + .friendly.find(params[:id]) + end end diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index 443906d..ee30e92 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -23,6 +23,7 @@ def create audios = track_bulk_params["audio"].select { |o| o.is_a?(String) }.reject(&:empty?) # @track = current_user.tracks.new(track_params) @track_form.user = current_user + @track_form.private = track_bulk_params[:private] @track_form.tracks_attributes = audios.map { |o| {audio: o} } @track_form.step = "info" else @@ -44,12 +45,16 @@ def edit def update @track = current_user.tracks.friendly.find(params[:id]) @tab = params[:track][:tab] || "basic-info-tab" + @track.assign_attributes(track_params) if params[:nonpersist] - @track.assign_attributes(track_params) @track.valid? else - flash.now[:notice] = "Track was successfully updated." - @track.update(track_params) + @track.label_id = label_user.id if !label_user.blank? && @track.enable_label + if @track.save + flash.now[:notice] = "Track was successfully updated." + else + flash.now[:error] = @track.errors.full_messages + end end # puts @track.errors.as_json @track.tab = @tab @@ -103,6 +108,7 @@ def get_meta_tags def track_params params.require(:track).permit( :private, + :enable_label, :audio, :title, :step, :description, :tab, :genre, :contains_music, :artist, :publisher, :isrc, :composer, :release_title, :buy_link, :album_title, @@ -124,7 +130,9 @@ def check_activated_account def track_bulk_params params.require(:track_bulk_creator).permit( - :make_playlist, :private, + :make_playlist, + :private, + :enable_label, :step, audio: [], tracks_attributes: [ :audio, :cover, :title, :tags, :description diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 785ec39..3ccd386 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -4,11 +4,14 @@ class UsersController < ApplicationController def index @title = "Tracks" - @artists = User.where(role: "artist") - .where.not(username: nil) + @artists = User.where(role: "artist").where.not(username: nil) + q = params[:q] + if q.present? + @artists = @artists.where("username ILIKE :q OR email ILIKE :q OR first_name ILIKE :q OR last_name ILIKE :q", q: "%#{q}%") + end #.with_attached_avatar #.order("id desc") - .page(params[:page]).per(5) + @artists = @artists.page(params[:page]).per(5) end def show @@ -17,6 +20,8 @@ def show get_meta_tags @as = :track @section = "tracks/track_item" + + # render @user.label ? "labels/show" : "show" end def tracks @@ -25,7 +30,6 @@ def tracks @as = :track @title = "Tracks" @section = "tracks/track_item" - paginated_render end @@ -33,9 +37,10 @@ def playlists_filter @kind = params[:kind].present? ? params[:kind].split(",") : Category.playlist_types - @playlists = @user.playlists + @playlists = Playlist .where(playlist_type: @kind) - .where(user_id: @user.id) + .where(user_id: @user.id).or(Playlist.where(label_id: @user.id)) + .where(private: false) .with_attached_cover .includes(user: {avatar_attachment: :blob}) .includes(tracks: {cover_attachment: :blob}) @@ -54,7 +59,8 @@ def playlists_filter def playlists @title = "Playlists" @section = "playlists" - @collection = @user.playlists + @collection = Playlist + .where(user_id: @user.id).or(Playlist.where(label_id: @user.id)) .where.not(playlist_type: ["album", "ep"]) .with_attached_cover .includes(user: {avatar_attachment: :blob}) @@ -67,33 +73,24 @@ def playlists @collection = @collection.references(:tracks) .page(params[:page]) - #@collection = @collection - #.where( - # playlists: {user_id: @user.id}, - # tracks: {user_id: @user.id} - #) if current_user.present? - - #.or( - # @user.playlists - # .where( - # playlists: {private: true}, - # tracks: {private: true, user_id: @user.id} - # ) - #) - #.or( - # @user.playlists - # .where.not( - # playlists: {user_id: @user.id}, - # tracks: {user_id: @user.id} - # ) - # .where(tracks: {private: true}) - #) - @as = :playlist @section = "playlists/playlist_item" render "show" end + def artists + @label = User.where(role: ["artist", "admin"], label: true).find_by(username: params[:user_id]) + @collection = @label.child_accounts.page(params[:page]).per(5) # connected_accounts.page(params[:page]).per(5) + @as = :artist + @section = "label_artists/artist" + @title = "Artists" + @cta_url = new_account_connection_path + @cta_label = "New account connection" + @collection_class = "mt-6 grid grid-cols-2 gap-x-4 gap-y-10 sm:gap-x-6 md:grid-cols-4 md:gap-y-0 lg:gap-x-8" + @admin = current_user && current_user.id == @label&.id + render "show" + end + def reposts @title = "Reposts" @collection = @user.reposts_preloaded.page(params[:page]).per(5) @@ -105,9 +102,9 @@ def reposts def albums @title = "Albums" @section = "albums" - @collection = @user.playlists + @collection = Playlist + .where(user_id: @user.id).or(Playlist.where(label_id: @user.id)) .where(playlist_type: ["album", "ep"]) - .where(user_id: @user.id) .with_attached_cover .includes(user: {avatar_attachment: :blob}) .includes(tracks: {cover_attachment: :blob}) @@ -148,18 +145,18 @@ def get_meta_tags def get_tracks # @collection = @user.tracks.page(params[:page]).per(2) @collection = if current_user && @user.id == current_user&.id - User.track_preloaded_by_user(current_user&.id) - .where(user_id: @user.id) - .with_attached_cover - .includes(user: {avatar_attachment: :blob}) - .order("id desc") - .page(params[:page]).per(6) + User.track_preloaded_by_user(current_user_id: current_user&.id, user: @user ) + #.where(user_id: @user.id) else - @user.tracks.published - .with_attached_cover - .includes(user: {avatar_attachment: :blob}) - .order("id desc").page(params[:page]).per(6) + User.track_preloaded_by_user_n(user: @user) + #.where(user_id: @user.id) + #@user.tracks.published end + + @collection = @collection + .with_attached_cover + .includes(user: {avatar_attachment: :blob}) + .order("id desc").page(params[:page]).per(6) end def find_user diff --git a/app/helpers/account_connections_helper.rb b/app/helpers/account_connections_helper.rb new file mode 100644 index 0000000..d7a86af --- /dev/null +++ b/app/helpers/account_connections_helper.rb @@ -0,0 +1,2 @@ +module AccountConnectionsHelper +end diff --git a/app/helpers/label_artists_helper.rb b/app/helpers/label_artists_helper.rb new file mode 100644 index 0000000..3c8a95b --- /dev/null +++ b/app/helpers/label_artists_helper.rb @@ -0,0 +1,2 @@ +module LabelArtistsHelper +end diff --git a/app/helpers/label_auth_helper.rb b/app/helpers/label_auth_helper.rb new file mode 100644 index 0000000..ebc5deb --- /dev/null +++ b/app/helpers/label_auth_helper.rb @@ -0,0 +1,2 @@ +module LabelAuthHelper +end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb new file mode 100644 index 0000000..9802e31 --- /dev/null +++ b/app/helpers/labels_helper.rb @@ -0,0 +1,2 @@ +module LabelsHelper +end diff --git a/app/helpers/tailwind_form_builder.rb b/app/helpers/tailwind_form_builder.rb index 54f091d..30131b3 100644 --- a/app/helpers/tailwind_form_builder.rb +++ b/app/helpers/tailwind_form_builder.rb @@ -121,20 +121,25 @@ def div_radio_button(method, tag_value, options = {}) end def radio_button(method, value, options = {}) - info = @template.label_tag( - tr(options[:label] || method), nil, - class: "block font-bold text-md leading-5 text-gray-900 dark:text-white pt-0" - ) + - field_details(method, object, options) - - options[:class] = "self-start mt-1 mr-1 h-4 w-4 text-brand-600 transition duration-150 ease-in-out" - @template.tag.div(class: "flex items-center") do - @template.radio_button( - @object_name, method, value, objectify_options(options) - ) + @template.tag.div(class: "flex-col items-center") { info } + options.merge!(class: "form-radio mr-2 h-4 w-4 text-indigo-600 transition duration-150 ease-in-out") unless options.has_key?(:class) + + label_content = if options[:label] == false + "" + else + @template.label_tag( + tr(options[:label] || method), nil, + class: "block text-sm leading-5 text-muted" + ) + end + + @template.tag.div(class: "inline-flex items-center #{options[:wrapper_class]}") do + super + label_content + + @template.tag.div(class: "text-sm font-normal leading-5 text-muted") do + field_details(method, object, options) + end end end - + def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") info = @template.label_tag( tr(options[:label] || method), nil, diff --git a/app/javascript/controllers/audio_upload_controller.js b/app/javascript/controllers/audio_upload_controller.js index ac1da6d..ab366f4 100644 --- a/app/javascript/controllers/audio_upload_controller.js +++ b/app/javascript/controllers/audio_upload_controller.js @@ -148,7 +148,6 @@ export default class extends Controller { this.preventDefaults(e); let files = e.dataTransfer.files; this.inputTarget.files = files; - debugger this.uploadFile(); } } \ No newline at end of file diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c81..43ae421 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" + default from: ENV['EMAIL_ACCOUNT'] layout "mailer" end diff --git a/app/mailers/connected_account_mailer.rb b/app/mailers/connected_account_mailer.rb new file mode 100644 index 0000000..279a79c --- /dev/null +++ b/app/mailers/connected_account_mailer.rb @@ -0,0 +1,24 @@ +class ConnectedAccountMailer < ApplicationMailer + # default from: 'noreply@labelx.com' + + # Method to send an invitation confirmation email to existing artists + def invitation_email(connected_account) + @artist = connected_account.user + @label = connected_account.parent + @connected_account = connected_account + mail(to: @artist.email, subject: "Welcome to Join Us at Label #{@label.username} on Rauversion.com!") + end + + # Method to notify the label of a new artist account creation + def new_account_notification_to_label(connected_account) + @artist = connected_account.user + @label = connected_account.parent + mail(to: @label.email, subject: "New Artist Account Activation at Label #{@label.username}!") + end + + def new_account_notification_to_artist(connected_account) + @artist = connected_account.user + @label = connected_account.parent + mail(to: @label.email, subject: "New Artist Account Activation at Label #{@label.username}!") + end +end diff --git a/app/models/connected_account.rb b/app/models/connected_account.rb new file mode 100644 index 0000000..a0e30bc --- /dev/null +++ b/app/models/connected_account.rb @@ -0,0 +1,23 @@ +class ConnectedAccount < ApplicationRecord + belongs_to :user + belongs_to :parent, class_name: "User" + + validate :validate_unique_user_for_parent + + private + + def validate_unique_user_for_parent + # Check if any other connected accounts have the same user_id and parent_id + exists = ConnectedAccount.where(parent_id: parent_id, user_id: user_id).exists? + errors.add(:user_id, "is already connected to this parent") if exists && new_record? || persisted? && changed? + end + + def self.attach_account(inviter: , invited_user:, state: "peding") + inviter.connected_accounts.create(user_id: invited_user.id, state: state) + end + + def self.attach_new_account(inviter: , user_params:, state: "pending") + user = User.create(user_params) + self.attach_account(inviter: inviter , invited_user: user, state: state) + end +end diff --git a/app/models/current.rb b/app/models/current.rb new file mode 100644 index 0000000..ffeeb32 --- /dev/null +++ b/app/models/current.rb @@ -0,0 +1,12 @@ +class Current < ActiveSupport::CurrentAttributes + attribute :label_user, :user + attribute :request_id, :user_agent, :ip_address + + resets { Time.zone = nil } + + def user=(user) + super + #self.label_user = user.account + #Time.zone = user.time_zone + end +end diff --git a/app/models/form_models/artist_form.rb b/app/models/form_models/artist_form.rb new file mode 100644 index 0000000..b679d23 --- /dev/null +++ b/app/models/form_models/artist_form.rb @@ -0,0 +1,88 @@ +class FormModels::ArtistForm + include ActiveModel::Model + include ActiveModel::Validations + + attr_accessor :username, :artist_url, :request_access, + :first_name, :last_name, :logo, + :password, :hide, :inviter, :email, :search, :is_new + + validates :username, presence: true + validate :username_must_exist, if: ->{ !self.is_new } + validate :username_must_not_exist, if: ->{ self.is_new } + validates :search, presence: true, if: ->{ !self.is_new } + # validates :request_access, inclusion: { in: [true, false] } + validates :password, presence: true, if: -> { request_access_kind } + validates :hide, inclusion: { in: [true, false, "1", "0"] }, if: ->{ !self.is_new } + validates :request_access, inclusion: { in: ["password", "request"] }, if: ->{ !self.is_new } + # validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }, length: { maximum: 160 } + + def request_access_kind + request_access == "password" + end + # Custom validation to check if the username exists in the User model + def username_must_not_exist + errors.add(:username, "does exist new") if User.exists?(username: username) + end + + def username_must_exist + errors.add(:username, "does not exist") if !User.exists?(username: username) + end + + # Method to process user creation or send an invitation based on request_access + def process_user_interaction + return false unless valid? # Ensure all validations pass before processing + + User.transaction do + if !is_new + send_invitation + else + create_user + end + end + end + + private + + # Create a new user with the provided username and password + def create_user + user = User.create(username: username, + password: password, + email: email, + role: "artist", + first_name: first_name, + last_name: last_name, + password_confirmation: password + ) + user.confirm + + if user + connected_account = ConnectedAccount.attach_account(inviter: inviter , invited_user: user, state: "active") + + ConnectedAccountMailer.new_account_notification_to_label(connected_account).deliver_now + + user + else + error.add(:base, "user not created!") + end + end + + # Send an invitation to the existing user + def send_invitation + + invited_user = User.find_by(username: username) + + if !inviter.child_accounts.exists?(invited_user.id) + + connected_account = ConnectedAccount.attach_account( + inviter: inviter, + invited_user: invited_user, + state: "pending" + ) if invited_user + + ConnectedAccountMailer.invitation_email(connected_account).deliver_now + invited_user + else + nil + end + end +end \ No newline at end of file diff --git a/app/models/playlist.rb b/app/models/playlist.rb index 931c7fc..d115ba7 100644 --- a/app/models/playlist.rb +++ b/app/models/playlist.rb @@ -16,11 +16,14 @@ def self.plain friendly_id :title, use: :slugged belongs_to :user + belongs_to :label, class_name: "User", optional: true + has_many :track_playlists has_many :tracks, through: :track_playlists has_many :listening_events has_many :comments, as: :commentable has_many :likes, as: :likeable + has_one_attached :cover has_one_attached :zip @@ -28,6 +31,14 @@ def self.plain accepts_nested_attributes_for :track_playlists, allow_destroy: true + belongs_to :label, class_name: "User", optional: true + attr_accessor :enable_label + before_save :check_label + + def check_label + self.label_id = Current.label_user.id if enable_label && Current.label_user + end + scope :latests, -> { order("id desc") } scope :published, -> { where(private: false) } diff --git a/app/models/track.rb b/app/models/track.rb index 8c9e8d5..529a536 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -15,6 +15,13 @@ class Track < ApplicationRecord has_many :spotlights, as: :spotlightable # has_many :spotlighted_tracks, through: :spotlight_tracks + belongs_to :label, class_name: "User", optional: true + attr_accessor :enable_label + before_save :check_label + + def check_label + self.label_id = Current.label_user.id if enable_label && Current.label_user + end has_one_attached :cover has_one_attached :audio diff --git a/app/models/track_bulk_creator.rb b/app/models/track_bulk_creator.rb index b8a5016..2087f1f 100644 --- a/app/models/track_bulk_creator.rb +++ b/app/models/track_bulk_creator.rb @@ -10,7 +10,7 @@ class TrackBulkCreator # Initialize the tracks_attributes with an empty array def initialize(attributes = {}) - self.private = true + self.private = attributes[:private] self.tracks_attributes ||= [] end @@ -36,7 +36,7 @@ def tracks @tracks ||= tracks_attributes.map do |attributes| blob = ActiveStorage::Blob.find_signed(attributes[:audio]) t = Track.new(attributes) - t.title = File.basename(blob.filename, File.extname(blob.filename)) + t.title = File.basename(blob.filename.to_s, File.extname(blob.filename.to_s)) t.user = user t.private = private t diff --git a/app/models/user.rb b/app/models/user.rb index 961ab73..d87557f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,6 +20,12 @@ class User < ApplicationRecord has_many :hosted_events, through: :event_hosts has_many :purchases has_many :comments + + + has_many :connected_accounts, foreign_key: :parent_id + + has_many :child_accounts, through: :connected_accounts, source: :user + has_many :spotlights, autosave: true # has_many :spotlighted_tracks, through: :spotlight_tracks, source: :spotlight_tracks has_one_attached :profile_header @@ -54,12 +60,17 @@ class User < ApplicationRecord accepts_nested_attributes_for :photos, allow_destroy: true scope :artists, -> { where(role: "artist").where.not(username: nil) } + + + def full_name + [first_name, last_name].compact.join(" ") + end # Ex:- scope :active, -> {where(:active => true)} def has_invitations_left? true end - def avatar_url(size) + def avatar_url(size = :medium) url = case size when :medium avatar.variant(resize_to_fill: [200, 200]) # &.processed&.url @@ -77,11 +88,11 @@ def avatar_url(size) url || "daniel-schludi-mbGxz7pt0jM-unsplash-sqr-s-bn.png" end - def self.track_preloaded_by_user(id) + def self.track_preloaded_by_user(current_user_id:, user: ) # Track.left_outer_joins(:reposts, :likes) # .where("reposts.user_id = :id OR likes.liker_id = :id OR reposts.user_id IS NULL OR likes.liker_id IS NULL", id: id) # .includes(:audio_blob, :cover_blob, user: :avatar_attachment) - + # user = User.find(id) tracks = Track.arel_table users = User.arel_table reposts_alias = Repost.arel_table.alias("r") @@ -90,27 +101,61 @@ def self.track_preloaded_by_user(id) reposts_join = tracks .join(reposts_alias, Arel::Nodes::OuterJoin) .on(reposts_alias[:track_id].eq(tracks[:id]) - .and(reposts_alias[:user_id].eq(id))) + .and(reposts_alias[:user_id].eq(current_user_id))) .join_sources likes_join = tracks .join(likes_alias, Arel::Nodes::OuterJoin) .on(likes_alias[:likeable_id].eq(tracks[:id]) .and(likes_alias[:likeable_type].eq("Track")) - .and(likes_alias[:liker_id].eq(id)) + .and(likes_alias[:liker_id].eq(current_user_id)) .and(likes_alias[:liker_type].eq("User"))) .join_sources - result = Track.includes(:audio_blob, :cover_blob, user: :avatar_attachment) - .joins(reposts_join, likes_join) - .select("tracks.*, r.id as repost_id, l.id as like_id") - .references(:r, :l) + if !user.label + result = Track.includes(:audio_blob, :cover_blob, user: :avatar_attachment) + .joins(reposts_join, likes_join) + .where(tracks[:user_id].eq(user.id)) + .select("tracks.*, r.id as repost_id, l.id as like_id") + .references(:r, :l) + return result + else + + # Gather child account IDs + # child_ids = User.find(id).child_accounts.pluck(:id) + + # Adjust where clause to include tracks from child accounts + result = Track.includes(:audio_blob, :cover_blob, user: :avatar_attachment) + .joins(reposts_join, likes_join) + .where(tracks[:user_id].eq(user.id).or(tracks[:label_id].in(user.id))) + .select("tracks.*, r.id as repost_id, l.id as like_id") + .references(:r, :l) + end + + end + + def self.track_preloaded_by_user_n(user:) + tracks = Track.arel_table + users = User.arel_table + + if !user.label + result = Track.includes(:audio_blob, :cover_blob, user: :avatar_attachment) + .where(tracks[:user_id].in(user.id)) + return result + else + # Gather child account IDs + # Adjust where clause to include tracks from child accounts + result = Track.includes(:audio_blob, :cover_blob, user: :avatar_attachment) + .where(tracks[:user_id].in(user.id).or(tracks[:label_id].in(user.id))) + end + end - def reposts_preloaded - User.track_preloaded_by_user(id) - .joins(:reposts) - .where("reposts.user_id =?", id) + def reposts_preloaded(current_user: nil) + tracks = current_user.blank? ? + User.track_preloaded_by_user_n(user: self) : + User.track_preloaded_by_user(current_user_id: current_user, user: self) + tracks.joins(:reposts).where("reposts.user_id =?", id) end def is_publisher? @@ -150,7 +195,38 @@ def user_sales_for(kind = "Track") .where(tracks: {user_id: id}) end + + def active_connected_accounts(user) + ConnectedAccount.where(parent_id: user.id, state: 'active').includes(:user) + end + + def is_child_of?(child_user_id) + ConnectedAccount.exists?(parent_id: self.id, state: 'active', user_id: child_user_id) + end + + def to_combobox_display + self.username + end + + def find_artists_excluding_children(q = nil) + # Get IDs of all child accounts for the current user + child_account_ids = self.child_accounts.pluck(:id) + + # Base query adjusted to exclude child account IDs + artists = User.where(role: "artist") + .where.not(username: nil) + #.where.not(label: true) + .where.not(id: child_account_ids) + + # Apply search filter if 'q' is provided + if q.present? + artists = artists.where("username ILIKE :q OR email ILIKE :q", q: "%#{q}%") + end + + artists + end + # def password_required? # false # end -end +end \ No newline at end of file diff --git a/app/views/account_connections/_existing.erb b/app/views/account_connections/_existing.erb new file mode 100644 index 0000000..e0116b1 --- /dev/null +++ b/app/views/account_connections/_existing.erb @@ -0,0 +1,70 @@ + +
Please search for an existing artist.
+ <%= form_with url: account_connections_path(kind: :existing), local: true do |form| %> + <%= form.combobox :search, user_search_account_connections_path, + name_when_new: :search, mobile_at: "0px" + %> +Please search for an existing artist.
+ + <% # f.text_field :username, label: "username", hint: "Artist URL http://#{ ENV["DOMAIN"] }/jjj" %> + +Please specify an account to add.
+ + <% # f.text_field :username, label: "username", hint: "Artist URL http://#{ ENV["DOMAIN"] }/jjj" %> + ++ <%= @label.username %>, has extended an invitation to be part of their catalog. + If you are agree please click on accept button, if not, just ignore the message +
+<%= @user.username %>
+<%= @user.first_name %> <%= @user.last_name%>
+<%= @user.country %> <%= @user.city %>
+<%= t("profile.followings") %>
+ + <%= link_to @user.followees(User).count, user_followees_path(@user.username) %> + +<%= t("profile.followers") %>
+ + <%= link_to @user.followers(User).count, user_followers_path(@user.username) %> + +<%= t("profile.tracks") %>
+ + <%= @user.tracks.size %> + ++ <%= @user.bio %> +
+ +- <%= playlist.title %> -
- -+ <%= playlist.title %> +
+ +