mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Add limits for import size for trial users
This commit is contained in:
parent
71488c9fb1
commit
6708e11ab3
24 changed files with 221 additions and 48 deletions
|
|
@ -4,11 +4,13 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
# [0.30.9] - 2025-08-11
|
# [0.30.9] - 2025-08-13
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
|
- QR code for API key is implemented but hidden under feature flag until the iOS app supports it.
|
||||||
- X-Dawarich-Response and X-Dawarich-Version headers are now returned for all API responses.
|
- X-Dawarich-Response and X-Dawarich-Version headers are now returned for all API responses.
|
||||||
|
- Trial version for cloud users is now available.
|
||||||
|
|
||||||
|
|
||||||
# [0.30.8] - 2025-08-01
|
# [0.30.8] - 2025-08-01
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,11 @@ class StatsController < ApplicationController
|
||||||
|
|
||||||
@stats.each do |year, stats|
|
@stats.each do |year, stats|
|
||||||
stats_by_month = stats.index_by(&:month)
|
stats_by_month = stats.index_by(&:month)
|
||||||
|
|
||||||
year_distances[year] = (1..12).map do |month|
|
year_distances[year] = (1..12).map do |month|
|
||||||
month_name = Date::MONTHNAMES[month]
|
month_name = Date::MONTHNAMES[month]
|
||||||
distance = stats_by_month[month]&.distance || 0
|
distance = stats_by_month[month]&.distance || 0
|
||||||
|
|
||||||
[month_name, distance]
|
[month_name, distance]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { showFlashMessage } from "../maps/helpers"
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["input", "progress", "progressBar", "submit", "form"]
|
static targets = ["input", "progress", "progressBar", "submit", "form"]
|
||||||
static values = {
|
static values = {
|
||||||
url: String
|
url: String,
|
||||||
|
userTrial: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
|
@ -50,6 +51,22 @@ export default class extends Controller {
|
||||||
const files = this.inputTarget.files
|
const files = this.inputTarget.files
|
||||||
if (files.length === 0) return
|
if (files.length === 0) return
|
||||||
|
|
||||||
|
// Check file size limits for trial users
|
||||||
|
if (this.userTrialValue) {
|
||||||
|
const MAX_FILE_SIZE = 11 * 1024 * 1024 // 11MB in bytes
|
||||||
|
const oversizedFiles = Array.from(files).filter(file => file.size > MAX_FILE_SIZE)
|
||||||
|
|
||||||
|
if (oversizedFiles.length > 0) {
|
||||||
|
const fileNames = oversizedFiles.map(f => f.name).join(', ')
|
||||||
|
const message = `File size limit exceeded. Trial users can only upload files up to 10MB. Oversized files: ${fileNames}`
|
||||||
|
showFlashMessage('error', message)
|
||||||
|
|
||||||
|
// Clear the file input
|
||||||
|
this.inputTarget.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Uploading ${files.length} files`)
|
console.log(`Uploading ${files.length} files`)
|
||||||
this.isUploading = true
|
this.isUploading = true
|
||||||
|
|
||||||
|
|
|
||||||
42
app/javascript/controllers/onboarding_modal_controller.js
Normal file
42
app/javascript/controllers/onboarding_modal_controller.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["modal"]
|
||||||
|
static values = { showable: Boolean }
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
if (this.showableValue) {
|
||||||
|
// Listen for Turbo page load events to show modal after navigation completes
|
||||||
|
document.addEventListener('turbo:load', this.handleTurboLoad.bind(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
// Clean up event listener when controller is removed
|
||||||
|
document.removeEventListener('turbo:load', this.handleTurboLoad.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTurboLoad() {
|
||||||
|
if (this.showableValue) {
|
||||||
|
this.checkAndShowModal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndShowModal() {
|
||||||
|
const MODAL_STORAGE_KEY = 'dawarich_onboarding_shown'
|
||||||
|
const hasShownModal = localStorage.getItem(MODAL_STORAGE_KEY)
|
||||||
|
|
||||||
|
if (!hasShownModal && this.hasModalTarget) {
|
||||||
|
// Show the modal
|
||||||
|
this.modalTarget.showModal()
|
||||||
|
|
||||||
|
// Mark as shown in local storage
|
||||||
|
localStorage.setItem(MODAL_STORAGE_KEY, 'true')
|
||||||
|
|
||||||
|
// Add event listener to handle when modal is closed
|
||||||
|
this.modalTarget.addEventListener('close', () => {
|
||||||
|
// Modal closed - state already saved
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,14 @@ class Users::TrialWebhookJob < ApplicationJob
|
||||||
def perform(user_id)
|
def perform(user_id)
|
||||||
user = User.find(user_id)
|
user = User.find(user_id)
|
||||||
|
|
||||||
token = Subscription::EncodeJwtToken.new(
|
payload = {
|
||||||
{ user_id: user.id, email: user.email, action: 'create_user' },
|
user_id: user.id,
|
||||||
ENV['JWT_SECRET_KEY']
|
email: user.email,
|
||||||
).call
|
active_until: user.active_until,
|
||||||
|
action: 'create_user'
|
||||||
|
}
|
||||||
|
|
||||||
|
token = Subscription::EncodeJwtToken.new(payload, ENV['JWT_SECRET_KEY']).call
|
||||||
|
|
||||||
request_url = "#{ENV['MANAGER_URL']}/api/v1/users"
|
request_url = "#{ENV['MANAGER_URL']}/api/v1/users"
|
||||||
headers = {
|
headers = {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class Import < ApplicationRecord
|
||||||
after_commit :remove_attached_file, on: :destroy
|
after_commit :remove_attached_file, on: :destroy
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: { scope: :user_id }
|
validates :name, presence: true, uniqueness: { scope: :user_id }
|
||||||
|
validate :file_size_within_limit, if: -> { user.trial? }
|
||||||
|
|
||||||
enum :status, { created: 0, processing: 1, completed: 2, failed: 3 }
|
enum :status, { created: 0, processing: 1, completed: 2, failed: 3 }
|
||||||
|
|
||||||
|
|
@ -22,6 +23,18 @@ class Import < ApplicationRecord
|
||||||
user_data_archive: 8
|
user_data_archive: 8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def file_size_within_limit
|
||||||
|
return unless file.attached?
|
||||||
|
|
||||||
|
if file.blob.byte_size > 11.megabytes
|
||||||
|
errors.add(:file, 'is too large. Trial users can only upload files up to 10MB.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
def process!
|
def process!
|
||||||
if user_data_archive?
|
if user_data_archive?
|
||||||
process_user_data_archive!
|
process_user_data_archive!
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_subscribe?
|
def can_subscribe?
|
||||||
(active_until.nil? || active_until&.past?) && !DawarichSettings.self_hosted?
|
(trial? || !active_until&.future?) && !DawarichSettings.self_hosted?
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_subscription_token
|
def generate_subscription_token
|
||||||
|
|
@ -130,7 +130,6 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
||||||
end
|
end
|
||||||
|
|
||||||
def activate
|
def activate
|
||||||
# TODO: Remove the `status` column in the future.
|
|
||||||
update(status: :active, active_until: 1000.years.from_now)
|
update(status: :active, active_until: 1000.years.from_now)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ class ImportPolicy < ApplicationPolicy
|
||||||
user.present? && record.user == user
|
user.present? && record.user == user
|
||||||
end
|
end
|
||||||
|
|
||||||
# Users can create new imports if they are active
|
# Users can create new imports if they are active or trial
|
||||||
def new?
|
def new?
|
||||||
create?
|
create?
|
||||||
end
|
end
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
user.present? && user.active?
|
user.present? && (user.active? || user.trial?)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Users can only edit their own imports
|
# Users can only edit their own imports
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,12 @@ class Imports::Create
|
||||||
end
|
end
|
||||||
|
|
||||||
def schedule_visit_suggesting(user_id, import)
|
def schedule_visit_suggesting(user_id, import)
|
||||||
|
return unless user.safe_settings.visits_suggestions_enabled?
|
||||||
|
|
||||||
points = import.points.order(:timestamp)
|
points = import.points.order(:timestamp)
|
||||||
|
|
||||||
|
return if points.none?
|
||||||
|
|
||||||
start_at = Time.zone.at(points.first.timestamp)
|
start_at = Time.zone.at(points.first.timestamp)
|
||||||
end_at = Time.zone.at(points.last.timestamp)
|
end_at = Time.zone.at(points.last.timestamp)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
<p class='py-2'>Use this API key to authenticate your requests.</p>
|
<p class='py-2'>Use this API key to authenticate your requests.</p>
|
||||||
<code><%= current_user.api_key %></code>
|
<code><%= current_user.api_key %></code>
|
||||||
|
|
||||||
<%# if ENV['QR_CODE_ENABLED'] == 'true' %>
|
<% if ENV['QR_CODE_ENABLED'] == 'true' %>
|
||||||
<p class='py-2'>
|
<p class='py-2'>
|
||||||
Or you can scan it in your Dawarich iOS app:
|
Or you can scan it in your Dawarich iOS app:
|
||||||
<%= api_key_qr_code(current_user) %>
|
<%= api_key_qr_code(current_user) %>
|
||||||
</p>
|
</p>
|
||||||
<%# end %>
|
<% end %>
|
||||||
|
|
||||||
<p class='py-2'>
|
<p class='py-2'>
|
||||||
<p>Docs: <%= link_to "API documentation", '/api-docs', class: 'underline hover:no-underline' %></p>
|
<p>Docs: <%= link_to "API documentation", '/api-docs', class: 'underline hover:no-underline' %></p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<%= form_with model: import, class: "contents", data: {
|
<%= form_with model: import, class: "contents", data: {
|
||||||
controller: "direct-upload",
|
controller: "direct-upload",
|
||||||
direct_upload_url_value: rails_direct_uploads_url,
|
direct_upload_url_value: rails_direct_uploads_url,
|
||||||
|
direct_upload_user_trial_value: current_user.trial?,
|
||||||
direct_upload_target: "form"
|
direct_upload_target: "form"
|
||||||
} do |form| %>
|
} do |form| %>
|
||||||
<div class="form-control w-full">
|
<div class="form-control w-full">
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
<dialog id="getting_started" class="modal">
|
<% if user_signed_in? %>
|
||||||
<div class="modal-box">
|
<div data-controller="onboarding-modal"
|
||||||
<h3 class="text-lg font-bold">Start tracking your location!</h3>
|
data-onboarding-modal-showable-value="<%= onboarding_modal_showable?(current_user) %>">
|
||||||
<p class="py-4">
|
<dialog id="getting_started" class="modal" data-onboarding-modal-target="modal">
|
||||||
To start tracking your location and putting it on the map, you need to configure your mobile application.
|
<div class="modal-box">
|
||||||
</p>
|
<h3 class="text-lg font-bold">Start tracking your location!</h3>
|
||||||
<p>
|
<p class="py-4">
|
||||||
To do so, grab the API key from <%= link_to 'here', settings_path, class: 'link' %> and follow the instructions in the <%= link_to 'documentation', 'https://docs.dawarich.com/mobile-apps/android', class: 'link' %>.
|
To start tracking your location and putting it on the map, you need to configure your mobile application.
|
||||||
</p>
|
</p>
|
||||||
<div class="modal-action">
|
<p>
|
||||||
<form method="dialog">
|
To do so, grab the API key from <%= link_to 'here', settings_path, class: 'link' %> and follow the instructions in the <%= link_to 'documentation', 'https://dawarich.app/docs/tutorials/track-your-location?utm_source=app&utm_medium=referral&utm_campaign=onboarding', class: 'link' %>.
|
||||||
<button class="btn">Close</button>
|
</p>
|
||||||
</form>
|
<div class="modal-action">
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="btn">Close</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dialog>
|
||||||
</dialog>
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,7 @@
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<ul class="menu menu-horizontal bg-base-100 rounded-box px-1">
|
<ul class="menu menu-horizontal bg-base-100 rounded-box px-1">
|
||||||
<% if user_signed_in? %>
|
<% if user_signed_in? %>
|
||||||
|
<% if current_user.can_subscribe? %>
|
||||||
<%# if current_user.can_subscribe? %>
|
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<%= link_to "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}" do %>
|
<%= link_to "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}" do %>
|
||||||
<span class="join-item btn btn-sm <%= trial_button_class(current_user) %>">
|
<span class="join-item btn btn-sm <%= trial_button_class(current_user) %>">
|
||||||
|
|
@ -83,7 +82,7 @@
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%# end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="dropdown dropdown-end dropdown-bottom dropdown-hover"
|
<div class="dropdown dropdown-end dropdown-bottom dropdown-hover"
|
||||||
data-controller="notifications"
|
data-controller="notifications"
|
||||||
|
|
@ -159,4 +158,3 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
<p>Export your location data in multiple formats (GPX, GeoJSON) for backup or use with other applications.</p>
|
<p>Export your location data in multiple formats (GPX, GeoJSON) for backup or use with other applications.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="https://my.dawarich.app" class="cta">Continue Exploring</a>
|
<a href="https://my.dawarich.app?utm_source=email&utm_medium=email&utm_campaign=explore_features&utm_content=continue_exploring" class="cta">Continue Exploring</a>
|
||||||
|
|
||||||
<p>You have <strong>5 days</strong> left in your trial. Make the most of it!</p>
|
<p>You have <strong>5 days</strong> left in your trial. Make the most of it!</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
<li>Enjoy beautiful interactive maps</li>
|
<li>Enjoy beautiful interactive maps</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="https://my.dawarich.app" class="cta">Subscribe to Continue</a>
|
<a href="https://my.dawarich.app?utm_source=email&utm_medium=email&utm_campaign=trial_expired&utm_content=subscribe_to_continue" class="cta">Subscribe to Continue</a>
|
||||||
|
|
||||||
<p>Ready to unlock the full power of location insights? Subscribe now and pick up right where you left off!</p>
|
<p>Ready to unlock the full power of location insights? Subscribe now and pick up right where you left off!</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,11 @@
|
||||||
<li>Visit detection and places management</li>
|
<li>Visit detection and places management</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="https://my.dawarich.app" class="cta">Subscribe Now</a>
|
<a href="https://my.dawarich.app?utm_source=email&utm_medium=email&utm_campaign=trial_expires_soon&utm_content=subscribe_now" class="cta">Subscribe Now</a>
|
||||||
|
|
||||||
<p>Don't lose access to your location insights. Subscribe today and continue your journey with Dawarich!</p>
|
<p>Don't lose access to your location insights. Subscribe today and continue your journey with Dawarich!</p>
|
||||||
|
|
||||||
<p>Questions? Drop us a message at hi@dawarich.app</p>
|
<p>Questions? Drop us a message at hi@dawarich.app or just reply to this email.</p>
|
||||||
|
|
||||||
<p>Best regards,<br>
|
<p>Best regards,<br>
|
||||||
Evgenii from Dawarich</p>
|
Evgenii from Dawarich</p>
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@
|
||||||
<li>Export your data in various formats</li>
|
<li>Export your data in various formats</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="https://my.dawarich.app" class="cta">Start Exploring Dawarich</a>
|
<a href="https://my.dawarich.app?utm_source=email&utm_medium=email&utm_campaign=welcome&utm_content=start_exploring" class="cta">Start Exploring Dawarich</a>
|
||||||
|
|
||||||
<p>If you have any questions, feel free to drop us a message at hi@dawarich.app</p>
|
<p>If you have any questions, feel free to drop us a message at hi@dawarich.app or just reply to this email.</p>
|
||||||
|
|
||||||
<p>Happy tracking!<br>
|
<p>Happy tracking!<br>
|
||||||
Evgenii from Dawarich</p>
|
Evgenii from Dawarich</p>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Your 7-day free trial has started. During this time, you can:
|
||||||
|
|
||||||
Start exploring Dawarich: https://my.dawarich.app
|
Start exploring Dawarich: https://my.dawarich.app
|
||||||
|
|
||||||
If you have any questions, feel free to drop us a message at hi@dawarich.app
|
If you have any questions, feel free to drop us a message at hi@dawarich.app or just reply to this email.
|
||||||
|
|
||||||
Happy tracking!
|
Happy tracking!
|
||||||
Evgenii from Dawarich
|
Evgenii from Dawarich
|
||||||
|
|
|
||||||
|
|
@ -36,5 +36,7 @@ module Dawarich
|
||||||
end
|
end
|
||||||
|
|
||||||
config.active_job.queue_adapter = :sidekiq
|
config.active_job.queue_adapter = :sidekiq
|
||||||
|
|
||||||
|
config.action_mailer.preview_paths << "#{Rails.root}/spec/mailers/previews"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
# Preview all emails at http://localhost:3000/rails/mailers/users_mailer
|
# frozen_string_literal: true
|
||||||
class UsersMailerPreview < ActionMailer::Preview
|
|
||||||
|
|
||||||
# Preview this email at http://localhost:3000/rails/mailers/users_mailer/welcome
|
class UsersMailerPreview < ActionMailer::Preview
|
||||||
def welcome
|
def welcome
|
||||||
UsersMailer.welcome
|
UsersMailer.with(user: User.last).welcome
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def explore_features
|
||||||
|
UsersMailer.with(user: User.last).explore_features
|
||||||
|
end
|
||||||
|
|
||||||
|
def trial_expires_soon
|
||||||
|
UsersMailer.with(user: User.last).trial_expires_soon
|
||||||
|
end
|
||||||
|
|
||||||
|
def trial_expired
|
||||||
|
UsersMailer.with(user: User.last).trial_expired
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "rails_helper"
|
require "rails_helper"
|
||||||
|
|
||||||
RSpec.describe UsersMailer, type: :mailer do
|
RSpec.describe UsersMailer, type: :mailer do
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,69 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Import, type: :model do
|
RSpec.describe Import, type: :model do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
subject(:import) { create(:import, user:) }
|
||||||
|
|
||||||
describe 'associations' do
|
describe 'associations' do
|
||||||
it { is_expected.to have_many(:points).dependent(:destroy) }
|
it { is_expected.to have_many(:points).dependent(:destroy) }
|
||||||
it { is_expected.to belong_to(:user) }
|
it 'belongs to a user' do
|
||||||
|
expect(user).to be_present
|
||||||
|
expect(import.user).to eq(user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
subject { build(:import, name: 'test import') }
|
|
||||||
|
|
||||||
it { is_expected.to validate_presence_of(:name) }
|
it { is_expected.to validate_presence_of(:name) }
|
||||||
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:user_id) }
|
|
||||||
|
it 'validates uniqueness of name scoped to user_id' do
|
||||||
|
create(:import, name: 'test_name', user: user)
|
||||||
|
|
||||||
|
duplicate_import = build(:import, name: 'test_name', user: user)
|
||||||
|
expect(duplicate_import).not_to be_valid
|
||||||
|
expect(duplicate_import.errors[:name]).to include('has already been taken')
|
||||||
|
|
||||||
|
other_user = create(:user)
|
||||||
|
different_user_import = build(:import, name: 'test_name', user: other_user)
|
||||||
|
expect(different_user_import).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'file size validation' do
|
||||||
|
context 'when user is a trial user' do
|
||||||
|
let(:user) do
|
||||||
|
user = create(:user)
|
||||||
|
user.update!(status: :trial)
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'validates file size limit for large files' do
|
||||||
|
import = build(:import, user: user)
|
||||||
|
mock_file = double(attached?: true, blob: double(byte_size: 12.megabytes))
|
||||||
|
allow(import).to receive(:file).and_return(mock_file)
|
||||||
|
|
||||||
|
expect(import).not_to be_valid
|
||||||
|
expect(import.errors[:file]).to include('is too large. Trial users can only upload files up to 10MB.')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows files under the size limit' do
|
||||||
|
import = build(:import, user: user)
|
||||||
|
mock_file = double(attached?: true, blob: double(byte_size: 5.megabytes))
|
||||||
|
allow(import).to receive(:file).and_return(mock_file)
|
||||||
|
|
||||||
|
expect(import).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is a paid user' do
|
||||||
|
let(:user) { create(:user, status: :active) }
|
||||||
|
let(:import) { build(:import, user: user) }
|
||||||
|
|
||||||
|
it 'does not validate file size limit' do
|
||||||
|
allow(import).to receive(:file).and_return(double(attached?: true, blob: double(byte_size: 12.megabytes)))
|
||||||
|
|
||||||
|
expect(import).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'enums' do
|
describe 'enums' do
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,8 @@ RSpec.describe User, type: :model do
|
||||||
let(:user) { create(:user, status: :active, active_until: 1000.years.from_now) }
|
let(:user) { create(:user, status: :active, active_until: 1000.years.from_now) }
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
|
user.update(status: :active)
|
||||||
|
|
||||||
expect(user.can_subscribe?).to be_falsey
|
expect(user.can_subscribe?).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -304,6 +306,14 @@ RSpec.describe User, type: :model do
|
||||||
expect(user.can_subscribe?).to be_truthy
|
expect(user.can_subscribe?).to be_truthy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user is on trial' do
|
||||||
|
let(:user) { create(:user, :trial, active_until: 1.week.from_now) }
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
expect(user.can_subscribe?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,16 @@ RSpec.describe 'Imports', type: :request do
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user is a trial user' do
|
||||||
|
let(:user) { create(:user, status: :trial) }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
get new_import_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue