moduler 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/moduler/tailwind.css +105 -0
- data/app/components/moduler/form/file_component.html.erb +14 -0
- data/app/components/moduler/form/file_component.rb +11 -0
- data/app/controllers/concerns/moduler/authentication.rb +1 -1
- data/app/controllers/concerns/moduler/require_document_acceptance.rb +31 -0
- data/app/controllers/moduler/administration/application_controller.rb +2 -0
- data/app/controllers/moduler/administration/documents_controller.rb +56 -0
- data/app/controllers/moduler/administration/users_controller.rb +2 -2
- data/app/controllers/moduler/application_controller.rb +1 -0
- data/app/controllers/moduler/articles_controller.rb +1 -0
- data/app/controllers/moduler/document_acceptances_controller.rb +25 -0
- data/app/controllers/moduler/profile/sessions/revocation_controller.rb +2 -0
- data/app/controllers/moduler/profile/sessions_controller.rb +2 -0
- data/app/controllers/moduler/profile_controller.rb +2 -0
- data/app/controllers/moduler/sessions_controller.rb +15 -6
- data/app/controllers/moduler/users_controller.rb +1 -0
- data/app/models/moduler/document.rb +17 -0
- data/app/models/moduler/user.rb +8 -0
- data/app/models/moduler/user_document.rb +8 -0
- data/app/policies/moduler/administration/document_policy.rb +25 -0
- data/app/services/moduler/document_acceptance.rb +43 -0
- data/app/views/moduler/administration/documents/_document.html.erb +19 -0
- data/app/views/moduler/administration/documents/_form.html.erb +23 -0
- data/app/views/moduler/administration/documents/edit.html.erb +1 -0
- data/app/views/moduler/administration/documents/index.html.erb +13 -0
- data/app/views/moduler/administration/documents/new.html.erb +1 -0
- data/app/views/moduler/administration/users/edit.html.erb +1 -0
- data/app/views/moduler/administration/users/new.html.erb +1 -0
- data/app/views/moduler/common/administration/_sidebar.html.erb +1 -0
- data/app/views/moduler/document_acceptances/new.html.erb +36 -0
- data/config/locales/moduler/administration/documents/en.yml +35 -0
- data/config/locales/moduler/administration/users/en.yml +6 -0
- data/config/locales/moduler/common/en.yml +1 -0
- data/config/locales/moduler/document_acceptances/en.yml +17 -0
- data/config/locales/moduler/sessions/en.yml +1 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20261104120000_add_status_to_moduler_users.rb +6 -0
- data/db/migrate/20261104205837_create_moduler_documents.rb +10 -0
- data/db/migrate/20261104205950_create_moduler_user_documents.rb +12 -0
- data/lib/moduler/version.rb +1 -1
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ec81c1092462c6513cd56e7953a2101eae358249f501d32993f1666bfd8867a
|
|
4
|
+
data.tar.gz: c92cde6155a2bc5fe17193155426ac2e593d6979092dae58e2ec64f0940bb9e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd6b3fb551373a67a76faeb79ffb8d49fa50740e2c9c3fb11838d9b99199e0bfa3f77f42e8dc847a86fb3b32f37371caef0dea2734b0d1c5ec75032abae574eb
|
|
7
|
+
data.tar.gz: 854b1562ccdaf04ccaa17c7d96418100374b7cb7294f06e3120babb7098e94c7605a18a531e31166a310df47abbd1ed311202ec0d3b7213fcc8cd70522337ff6
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
--color-slate-600: oklch(44.6% 0.043 257.281);
|
|
33
33
|
--color-slate-700: oklch(37.2% 0.044 257.287);
|
|
34
34
|
--color-slate-900: oklch(20.8% 0.042 265.755);
|
|
35
|
+
--color-gray-50: oklch(98.5% 0.002 247.839);
|
|
35
36
|
--color-gray-200: oklch(92.8% 0.006 264.531);
|
|
36
37
|
--color-gray-300: oklch(87.2% 0.01 258.338);
|
|
37
38
|
--color-gray-400: oklch(70.7% 0.022 261.325);
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
--spacing: 0.25rem;
|
|
42
43
|
--container-sm: 24rem;
|
|
43
44
|
--container-lg: 32rem;
|
|
45
|
+
--container-2xl: 42rem;
|
|
44
46
|
--container-3xl: 48rem;
|
|
45
47
|
--container-5xl: 64rem;
|
|
46
48
|
--text-xs: 0.75rem;
|
|
@@ -872,6 +874,9 @@
|
|
|
872
874
|
margin-bottom: 0;
|
|
873
875
|
}
|
|
874
876
|
}
|
|
877
|
+
.mt-0\.5 {
|
|
878
|
+
margin-top: calc(var(--spacing) * 0.5);
|
|
879
|
+
}
|
|
875
880
|
.mt-1 {
|
|
876
881
|
margin-top: calc(var(--spacing) * 1);
|
|
877
882
|
}
|
|
@@ -955,6 +960,9 @@
|
|
|
955
960
|
.w-full {
|
|
956
961
|
width: 100%;
|
|
957
962
|
}
|
|
963
|
+
.max-w-2xl {
|
|
964
|
+
max-width: var(--container-2xl);
|
|
965
|
+
}
|
|
958
966
|
.max-w-3xl {
|
|
959
967
|
max-width: var(--container-3xl);
|
|
960
968
|
}
|
|
@@ -1052,6 +1060,11 @@
|
|
|
1052
1060
|
border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
|
1053
1061
|
}
|
|
1054
1062
|
}
|
|
1063
|
+
.divide-gray-200 {
|
|
1064
|
+
:where(& > :not(:last-child)) {
|
|
1065
|
+
border-color: var(--color-gray-200);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1055
1068
|
.divide-slate-100 {
|
|
1056
1069
|
:where(& > :not(:last-child)) {
|
|
1057
1070
|
border-color: var(--color-slate-100);
|
|
@@ -1118,6 +1131,9 @@
|
|
|
1118
1131
|
.border-gray-200 {
|
|
1119
1132
|
border-color: var(--color-gray-200);
|
|
1120
1133
|
}
|
|
1134
|
+
.border-gray-300 {
|
|
1135
|
+
border-color: var(--color-gray-300);
|
|
1136
|
+
}
|
|
1121
1137
|
.border-red-200 {
|
|
1122
1138
|
border-color: var(--color-red-200);
|
|
1123
1139
|
}
|
|
@@ -1333,6 +1349,9 @@
|
|
|
1333
1349
|
.italic {
|
|
1334
1350
|
font-style: italic;
|
|
1335
1351
|
}
|
|
1352
|
+
.accent-custom {
|
|
1353
|
+
accent-color: var(--color-custom);
|
|
1354
|
+
}
|
|
1336
1355
|
.opacity-0 {
|
|
1337
1356
|
opacity: 0%;
|
|
1338
1357
|
}
|
|
@@ -1440,6 +1459,76 @@
|
|
|
1440
1459
|
}
|
|
1441
1460
|
}
|
|
1442
1461
|
}
|
|
1462
|
+
.file\:mr-3 {
|
|
1463
|
+
&::file-selector-button {
|
|
1464
|
+
margin-right: calc(var(--spacing) * 3);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
.file\:rounded-md {
|
|
1468
|
+
&::file-selector-button {
|
|
1469
|
+
border-radius: var(--radius-md);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
.file\:border-0 {
|
|
1473
|
+
&::file-selector-button {
|
|
1474
|
+
border-style: var(--tw-border-style);
|
|
1475
|
+
border-width: 0px;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
.file\:bg-white {
|
|
1479
|
+
&::file-selector-button {
|
|
1480
|
+
background-color: var(--color-white);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
.file\:px-3 {
|
|
1484
|
+
&::file-selector-button {
|
|
1485
|
+
padding-inline: calc(var(--spacing) * 3);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
.file\:py-1\.5 {
|
|
1489
|
+
&::file-selector-button {
|
|
1490
|
+
padding-block: calc(var(--spacing) * 1.5);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
.file\:text-sm {
|
|
1494
|
+
&::file-selector-button {
|
|
1495
|
+
font-size: var(--text-sm);
|
|
1496
|
+
line-height: var(--tw-leading, var(--text-sm--line-height));
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
.file\:font-semibold {
|
|
1500
|
+
&::file-selector-button {
|
|
1501
|
+
--tw-font-weight: var(--font-weight-semibold);
|
|
1502
|
+
font-weight: var(--font-weight-semibold);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
.file\:text-gray-900 {
|
|
1506
|
+
&::file-selector-button {
|
|
1507
|
+
color: var(--color-gray-900);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
.file\:shadow-xs {
|
|
1511
|
+
&::file-selector-button {
|
|
1512
|
+
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
|
1513
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
.file\:ring-1 {
|
|
1517
|
+
&::file-selector-button {
|
|
1518
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
1519
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
.file\:ring-gray-300 {
|
|
1523
|
+
&::file-selector-button {
|
|
1524
|
+
--tw-ring-color: var(--color-gray-300);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
.file\:ring-inset {
|
|
1528
|
+
&::file-selector-button {
|
|
1529
|
+
--tw-ring-inset: inset;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1443
1532
|
.placeholder\:text-gray-400 {
|
|
1444
1533
|
&::placeholder {
|
|
1445
1534
|
color: var(--color-gray-400);
|
|
@@ -1520,6 +1609,13 @@
|
|
|
1520
1609
|
}
|
|
1521
1610
|
}
|
|
1522
1611
|
}
|
|
1612
|
+
.hover\:underline {
|
|
1613
|
+
&:hover {
|
|
1614
|
+
@media (hover: hover) {
|
|
1615
|
+
text-decoration-line: underline;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1523
1619
|
.hover\:ring-red-200 {
|
|
1524
1620
|
&:hover {
|
|
1525
1621
|
@media (hover: hover) {
|
|
@@ -1527,6 +1623,15 @@
|
|
|
1527
1623
|
}
|
|
1528
1624
|
}
|
|
1529
1625
|
}
|
|
1626
|
+
.hover\:file\:bg-gray-50 {
|
|
1627
|
+
&:hover {
|
|
1628
|
+
@media (hover: hover) {
|
|
1629
|
+
&::file-selector-button {
|
|
1630
|
+
background-color: var(--color-gray-50);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1530
1635
|
.focus\:border-slate-900 {
|
|
1531
1636
|
&:focus {
|
|
1532
1637
|
border-color: var(--color-slate-900);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="mt-2">
|
|
2
|
+
<% if @options.key?(:label) %>
|
|
3
|
+
<%= @form.label @name, @options[:label], class: "block text-sm text-slate-500" %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<div class="mt-2">
|
|
6
|
+
<%= @form.file_field @name, accept: @options[:accept], class: "block w-full text-sm text-gray-900 file:mr-3 file:rounded-md file:border-0 file:bg-white file:px-3 file:py-1.5 file:text-sm file:font-semibold file:text-gray-900 file:shadow-xs file:ring-1 file:ring-inset file:ring-gray-300 hover:file:bg-gray-50" %>
|
|
7
|
+
</div>
|
|
8
|
+
<% if @options.key?(:description) %>
|
|
9
|
+
<p class="mt-1 text-xs text-gray-400"><%= @options[:description] %></p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if @form.error?(@name) %>
|
|
12
|
+
<%= @form.error(@name, class: "text-red-500 text-sm mt-1") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -29,7 +29,7 @@ module Moduler
|
|
|
29
29
|
|
|
30
30
|
def find_session_by_cookie
|
|
31
31
|
if cookies.signed[:session_id]
|
|
32
|
-
Session.active.find_by(id: cookies.signed[:session_id], signed_in_at: Session::LIFETIME.ago..)
|
|
32
|
+
Session.active.joins(:user).where(user: { status: :active }).find_by(id: cookies.signed[:session_id], signed_in_at: Session::LIFETIME.ago..)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
# Blocks the panel for signed-in users until they accept every document.
|
|
3
|
+
# Controllers that must stay available meanwhile (the profile, signing out,
|
|
4
|
+
# public pages) opt out with +skip_document_acceptance+.
|
|
5
|
+
module RequireDocumentAcceptance
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
before_action :require_document_acceptance
|
|
10
|
+
helper_method :document_acceptance_required?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
def skip_document_acceptance(**options)
|
|
15
|
+
skip_before_action :require_document_acceptance, **options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def document_acceptance_required?
|
|
22
|
+
return @document_acceptance_required if defined?(@document_acceptance_required)
|
|
23
|
+
|
|
24
|
+
@document_acceptance_required = Current.user.present? && Current.user.pending_documents.exists?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def require_document_acceptance
|
|
28
|
+
redirect_to moduler.new_document_acceptance_path if document_acceptance_required?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
module Administration
|
|
3
|
+
class DocumentsController < ApplicationController
|
|
4
|
+
before_action -> { authorize([ :moduler, :administration, :document ]) }
|
|
5
|
+
before_action :set_document, only: %i[ edit update ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@filter, @documents = QueryFilter.new(Document.with_attached_file, permitted_filter_parameters).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
@document = Document.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
@document = Document.new(permitted_parameters)
|
|
17
|
+
|
|
18
|
+
if @document.save
|
|
19
|
+
redirect_to moduler.administration_documents_path, notice: t(".notice")
|
|
20
|
+
else
|
|
21
|
+
render :new, status: :unprocessable_entity
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def edit; end
|
|
26
|
+
|
|
27
|
+
def update
|
|
28
|
+
if @document.update(permitted_update_parameters)
|
|
29
|
+
redirect_to moduler.administration_documents_path, notice: t(".notice")
|
|
30
|
+
else
|
|
31
|
+
render :edit, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def set_document
|
|
38
|
+
@document = Document.find(params[:id])
|
|
39
|
+
authorize(@document, policy_class: DocumentPolicy)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def permitted_parameters
|
|
43
|
+
params.expect(document: %i[ name kind file ])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The file is set only when the document is created and cannot be replaced.
|
|
47
|
+
def permitted_update_parameters
|
|
48
|
+
params.expect(document: %i[ name kind ])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def permitted_filter_parameters
|
|
52
|
+
(params.permit(filter: %i[ query ])[:filter] || {}).merge(params.permit(:page))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -41,11 +41,11 @@ module Moduler
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def permitted_parameters
|
|
44
|
-
params.expect(user: %i[ email_address role password ])
|
|
44
|
+
params.expect(user: %i[ email_address role status password ])
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def permitted_update_parameters
|
|
48
|
-
params.expect(user: %i[ email_address role ])
|
|
48
|
+
params.expect(user: %i[ email_address role status ])
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def permitted_filter_parameters
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
class DocumentAcceptancesController < ApplicationController
|
|
3
|
+
skip_document_acceptance
|
|
4
|
+
|
|
5
|
+
before_action :set_document_acceptance
|
|
6
|
+
|
|
7
|
+
def new; end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
if @document_acceptance.call
|
|
11
|
+
redirect_to moduler.root_path, notice: t(".notice")
|
|
12
|
+
else
|
|
13
|
+
flash.now[:alert] = @document_acceptance.errors.full_messages.to_sentence
|
|
14
|
+
|
|
15
|
+
render :new, status: :unprocessable_entity
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def set_document_acceptance
|
|
22
|
+
@document_acceptance = DocumentAcceptance.new(Current.user, params.dig(:document_acceptance, :document_ids))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Moduler
|
|
2
2
|
class SessionsController < ApplicationController
|
|
3
3
|
allow_unauthenticated_access only: %i[ new create ]
|
|
4
|
+
skip_document_acceptance
|
|
4
5
|
|
|
5
6
|
rate_limit to: 10, within: 3.minutes, only: :create,
|
|
6
7
|
with: -> { redirect_to moduler.new_session_path, alert: t("moduler.sessions.create.rate_limited") }
|
|
@@ -18,16 +19,14 @@ module Moduler
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def create
|
|
21
|
-
|
|
22
|
+
user = User.authenticate_by(params.require(:user).permit(:email_address, :password))
|
|
23
|
+
|
|
24
|
+
if user&.active?
|
|
22
25
|
start_new_session_for user
|
|
23
26
|
|
|
24
27
|
redirect_to after_authentication_url
|
|
25
28
|
else
|
|
26
|
-
|
|
27
|
-
user.errors.add(:base, t("moduler.sessions.create.invalid_credentials"))
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
render :new
|
|
29
|
+
render_sign_in_error(user ? "inactive_account" : "invalid_credentials")
|
|
31
30
|
end
|
|
32
31
|
end
|
|
33
32
|
|
|
@@ -35,5 +34,15 @@ module Moduler
|
|
|
35
34
|
terminate_session
|
|
36
35
|
redirect_to moduler.new_session_path, status: :see_other
|
|
37
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def render_sign_in_error(key)
|
|
41
|
+
@user = User.new.tap do |user|
|
|
42
|
+
user.errors.add(:base, t("moduler.sessions.create.#{key}"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
render :new
|
|
46
|
+
end
|
|
38
47
|
end
|
|
39
48
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Moduler
|
|
2
2
|
class UsersController < ApplicationController
|
|
3
3
|
allow_unauthenticated_access only: %i[ new create ]
|
|
4
|
+
skip_document_acceptance
|
|
4
5
|
|
|
5
6
|
rate_limit to: 10, within: 3.minutes, only: :create,
|
|
6
7
|
with: -> { redirect_to moduler.new_user_path, alert: t("moduler.users.create.rate_limited") }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
class Document < ApplicationRecord
|
|
3
|
+
include PgSearch::Model
|
|
4
|
+
|
|
5
|
+
KINDS = %w[ privacy_policy terms_and_conditions data_processing_agreement ].freeze
|
|
6
|
+
|
|
7
|
+
has_many :user_documents, class_name: "Moduler::UserDocument", foreign_key: "moduler_document_id", dependent: :delete_all
|
|
8
|
+
|
|
9
|
+
has_one_attached :file
|
|
10
|
+
|
|
11
|
+
validates :name, presence: true
|
|
12
|
+
validates :kind, presence: true, inclusion: { in: KINDS }
|
|
13
|
+
validates :file, presence: true, content_type: %w[ application/pdf ], size: { less_than: 10.megabytes }
|
|
14
|
+
|
|
15
|
+
pg_search_scope :search_by_query, against: %i[ name kind ], using: { trigram: { word_similarity: true } }
|
|
16
|
+
end
|
|
17
|
+
end
|
data/app/models/moduler/user.rb
CHANGED
|
@@ -3,6 +3,8 @@ module Moduler
|
|
|
3
3
|
include PgSearch::Model
|
|
4
4
|
|
|
5
5
|
has_many :sessions, class_name: "Moduler::Session", foreign_key: "moduler_user_id", dependent: :destroy
|
|
6
|
+
has_many :user_documents, class_name: "Moduler::UserDocument", foreign_key: "moduler_user_id", dependent: :destroy
|
|
7
|
+
has_many :documents, class_name: "Moduler::Document", through: :user_documents
|
|
6
8
|
|
|
7
9
|
has_secure_password
|
|
8
10
|
|
|
@@ -12,7 +14,13 @@ module Moduler
|
|
|
12
14
|
validates :password, length: { minimum: 8 }, allow_nil: true
|
|
13
15
|
|
|
14
16
|
enum :role, { member: 0, administrator: 1 }
|
|
17
|
+
enum :status, { waitlisted: 0, active: 1, inactive: 2 }, default: :active
|
|
15
18
|
|
|
16
19
|
pg_search_scope :search_by_query, against: %i[ email_address ], using: { trigram: { word_similarity: true } }
|
|
20
|
+
|
|
21
|
+
# Documents the user has not accepted yet.
|
|
22
|
+
def pending_documents
|
|
23
|
+
Moduler::Document.where.not(id: user_documents.select(:moduler_document_id))
|
|
24
|
+
end
|
|
17
25
|
end
|
|
18
26
|
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
class UserDocument < ApplicationRecord
|
|
3
|
+
belongs_to :user, class_name: "Moduler::User", foreign_key: "moduler_user_id"
|
|
4
|
+
belongs_to :document, class_name: "Moduler::Document", foreign_key: "moduler_document_id"
|
|
5
|
+
|
|
6
|
+
validates :user, presence: true, uniqueness: { scope: :moduler_document_id }
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
module Administration
|
|
3
|
+
class DocumentPolicy < BasePolicy
|
|
4
|
+
def index?
|
|
5
|
+
administrator?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create?
|
|
9
|
+
administrator?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def update?
|
|
13
|
+
administrator?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def new?
|
|
17
|
+
create?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def edit?
|
|
21
|
+
update?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
# Accepts the documents a user has not accepted yet. Every pending document
|
|
3
|
+
# must be among the accepted ids; otherwise nothing is accepted. Ids of
|
|
4
|
+
# unknown or already accepted documents are ignored.
|
|
5
|
+
class DocumentAcceptance
|
|
6
|
+
include ActiveModel::Validations
|
|
7
|
+
|
|
8
|
+
attr_reader :user, :document_ids
|
|
9
|
+
|
|
10
|
+
validate :all_pending_documents_accepted
|
|
11
|
+
|
|
12
|
+
def initialize(user, document_ids = [])
|
|
13
|
+
@user = user
|
|
14
|
+
@document_ids = Array(document_ids).compact_blank.map(&:to_i)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def pending_documents
|
|
18
|
+
@pending_documents ||= user.pending_documents.with_attached_file.order(:created_at).to_a
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def accepted?(document)
|
|
22
|
+
document_ids.include?(document.id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
return false if invalid?
|
|
27
|
+
|
|
28
|
+
UserDocument.insert_all(rows, unique_by: %i[ moduler_document_id moduler_user_id ])
|
|
29
|
+
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def all_pending_documents_accepted
|
|
36
|
+
errors.add(:base, :not_all_accepted) if pending_documents.any? { |document| !accepted?(document) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def rows
|
|
40
|
+
pending_documents.map { |document| { moduler_user_id: user.id, moduler_document_id: document.id } }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<%= render(Moduler::ListItemComponent.new(document.name, id: dom_id(document))) do |item| %>
|
|
2
|
+
<% item.with_badge(t("moduler.administration.documents.kinds.#{document.kind}")) %>
|
|
3
|
+
|
|
4
|
+
<% item.with_meta do %>
|
|
5
|
+
<%= time_tag document.created_at, t(".created", time: time_ago_in_words(document.created_at)), title: l(document.created_at, format: :long) %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<% if document.file.attached? %>
|
|
9
|
+
<% item.with_action do %>
|
|
10
|
+
<%= render(Moduler::ButtonLinkComponent.new(t(".action.download"), main_app.rails_blob_path(document.file, disposition: "attachment"), size: :sm)) %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<% if Moduler::Administration::DocumentPolicy.new(current_user, document).edit? %>
|
|
15
|
+
<% item.with_action do %>
|
|
16
|
+
<%= render(Moduler::ButtonLinkComponent.new(t(".action.edit"), moduler.edit_administration_document_path(document), size: :sm)) %>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% end %>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%= render(Moduler::Administration::PageComponent.new) do %>
|
|
2
|
+
<%= render(Moduler::PageHeaderComponent.new(t(".title"))) %>
|
|
3
|
+
|
|
4
|
+
<%= form_with model: @document, url: @document.persisted? ? moduler.administration_document_path(@document) : moduler.administration_documents_path, data: { turbo: false }, class: "w-full space-y-6" do |form| %>
|
|
5
|
+
<%= render(::Moduler::Form::BaseErrorComponent.new(form)) %>
|
|
6
|
+
|
|
7
|
+
<div class="max-w-lg space-y-6">
|
|
8
|
+
<%= render(::Moduler::Form::TextComponent.new(form, :name, label: t(".label.name"))) %>
|
|
9
|
+
<%= render(::Moduler::Form::SelectComponent.new(form, :kind, Moduler::Document::KINDS.map { |kind| [ t("moduler.administration.documents.kinds.#{kind}"), kind ] }, label: t(".label.kind"))) %>
|
|
10
|
+
<% if @document.persisted? %>
|
|
11
|
+
<div class="mt-2">
|
|
12
|
+
<p class="block text-sm text-slate-500"><%= t(".label.file") %></p>
|
|
13
|
+
<p id="document_file" class="mt-2 text-sm text-gray-900"><%= @document.file.attached? ? @document.file.filename : t(".description.no_file") %></p>
|
|
14
|
+
<p class="mt-1 text-xs text-gray-400"><%= t(".description.file_locked") %></p>
|
|
15
|
+
</div>
|
|
16
|
+
<% else %>
|
|
17
|
+
<%= render(::Moduler::Form::FileComponent.new(form, :file, label: t(".label.file"), accept: "application/pdf", description: t(".description.file"))) %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<%= render(::Moduler::Form::SubmitComponent.new(form, text: t(".action.save"))) %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render "form" %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= render(Moduler::Administration::PageComponent.new) do %>
|
|
2
|
+
<%= render(Moduler::PageHeaderComponent.new(t(".title"), description: t(".description"))) do |header| %>
|
|
3
|
+
<% header.with_action do %>
|
|
4
|
+
<%= render(Moduler::ButtonLinkComponent.new(t(".action.new"), moduler.new_administration_document_path)) %>
|
|
5
|
+
<% end %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<%= render(Moduler::FilterComponent.new(@filter, moduler.administration_documents_path)) %>
|
|
9
|
+
|
|
10
|
+
<%= render(Moduler::ListComponent.new(@documents, id: "documents", empty: t(".empty"))) do %>
|
|
11
|
+
<%= render partial: "moduler/administration/documents/document", collection: @documents %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render "form" %>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<%= render(::Moduler::Form::SectionComponent.new(t(".section.data"))) do %>
|
|
8
8
|
<%= render(::Moduler::Form::EmailComponent.new(form, :email_address, label: t(".label.email_address"))) %>
|
|
9
9
|
<%= render(::Moduler::Form::SelectComponent.new(form, :role, Moduler::User.roles.keys.map { |role| [ t("moduler.administration.users.roles.#{role}"), role ] }, label: t(".label.role"))) %>
|
|
10
|
+
<%= render(::Moduler::Form::SelectComponent.new(form, :status, Moduler::User.statuses.keys.map { |status| [ t("moduler.administration.users.statuses.#{status}"), status ] }, label: t(".label.status"))) %>
|
|
10
11
|
<% end %>
|
|
11
12
|
|
|
12
13
|
<%= render(::Moduler::Form::SubmitComponent.new(form, text: t(".action.save"))) %>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<%= render(::Moduler::Form::SectionComponent.new(t(".section.data"))) do %>
|
|
8
8
|
<%= render(::Moduler::Form::EmailComponent.new(form, :email_address, label: t(".label.email_address"))) %>
|
|
9
9
|
<%= render(::Moduler::Form::SelectComponent.new(form, :role, Moduler::User.roles.keys.map { |role| [ t("moduler.administration.users.roles.#{role}"), role ] }, label: t(".label.role"))) %>
|
|
10
|
+
<%= render(::Moduler::Form::SelectComponent.new(form, :status, Moduler::User.statuses.keys.map { |status| [ t("moduler.administration.users.statuses.#{status}"), status ] }, label: t(".label.status"))) %>
|
|
10
11
|
<% end %>
|
|
11
12
|
|
|
12
13
|
<%= render(::Moduler::Form::SectionComponent.new(t(".section.password"), divider: true)) do %>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<%= render(Moduler::SidebarActionComponent.new(t(".action.sessions"), moduler.administration_sessions_path, "moduler/administration/sessions")) %>
|
|
5
5
|
|
|
6
6
|
<%= render(Moduler::SidebarActionComponent.new(t(".action.articles"), moduler.administration_articles_path, "moduler/administration/articles")) %>
|
|
7
|
+
<%= render(Moduler::SidebarActionComponent.new(t(".action.documents"), moduler.administration_documents_path, "moduler/administration/documents")) %>
|
|
7
8
|
|
|
8
9
|
<% Array(Moduler.configuration.dig(:administration, :resources)).each do |resource| %>
|
|
9
10
|
<%= render(Moduler::SidebarActionComponent.new(t(".action.#{resource}"), moduler.url_for([:administration, resource.to_sym]), "moduler/administration/#{resource}")) %>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<main class="mx-auto flex w-full max-w-2xl flex-1 flex-col px-4 py-8">
|
|
2
|
+
<div class="mb-6">
|
|
3
|
+
<h1 class="text-base font-semibold text-gray-900"><%= t(".title") %></h1>
|
|
4
|
+
<p class="mt-1 text-sm text-slate-500"><%= t(".description") %></p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<%= form_with url: moduler.document_acceptance_path, scope: :document_acceptance, data: { turbo: false }, class: "space-y-6" do |form| %>
|
|
8
|
+
<ul id="documents" class="divide-y divide-gray-200 rounded-md border border-gray-200 bg-white">
|
|
9
|
+
<% @document_acceptance.pending_documents.each do |document| %>
|
|
10
|
+
<li id="<%= dom_id(document) %>" class="flex items-start gap-3 px-4 py-4 sm:px-5">
|
|
11
|
+
<%= check_box_tag "document_acceptance[document_ids][]", document.id, @document_acceptance.accepted?(document),
|
|
12
|
+
id: "document_acceptance_document_#{document.id}", class: "mt-0.5 h-4 w-4 shrink-0 cursor-pointer rounded border-gray-300 accent-custom" %>
|
|
13
|
+
|
|
14
|
+
<div class="min-w-0 flex-1">
|
|
15
|
+
<label for="document_acceptance_document_<%= document.id %>" class="cursor-pointer text-sm text-gray-900">
|
|
16
|
+
<%= t(".accept", name: document.name) %>
|
|
17
|
+
</label>
|
|
18
|
+
<p class="mt-1 text-xs text-slate-400">
|
|
19
|
+
<%= t("moduler.administration.documents.kinds.#{document.kind}") %>
|
|
20
|
+
<% if document.file.attached? %>
|
|
21
|
+
·
|
|
22
|
+
<%= link_to t(".action.read"), main_app.rails_blob_path(document.file, disposition: "inline"), target: "_blank", rel: "noopener", class: "font-semibold text-custom hover:underline" %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
</li>
|
|
27
|
+
<% end %>
|
|
28
|
+
</ul>
|
|
29
|
+
|
|
30
|
+
<% @document_acceptance.errors.full_messages.each do |message| %>
|
|
31
|
+
<p class="text-sm text-red-500"><%= message %></p>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<%= render(::Moduler::Form::SubmitComponent.new(form, text: t(".action.submit"))) %>
|
|
35
|
+
<% end %>
|
|
36
|
+
</main>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
en:
|
|
2
|
+
moduler:
|
|
3
|
+
administration:
|
|
4
|
+
documents:
|
|
5
|
+
kinds:
|
|
6
|
+
privacy_policy: Privacy policy
|
|
7
|
+
terms_and_conditions: Terms and conditions
|
|
8
|
+
data_processing_agreement: Data processing agreement
|
|
9
|
+
index:
|
|
10
|
+
title: Documents
|
|
11
|
+
description: Legal documents your users accept, such as the privacy policy or terms and conditions
|
|
12
|
+
empty: No documents found
|
|
13
|
+
action:
|
|
14
|
+
new: Create
|
|
15
|
+
document:
|
|
16
|
+
created: Created %{time} ago
|
|
17
|
+
action:
|
|
18
|
+
download: Download
|
|
19
|
+
edit: Edit
|
|
20
|
+
create:
|
|
21
|
+
notice: Document has been created
|
|
22
|
+
update:
|
|
23
|
+
notice: Document has been updated
|
|
24
|
+
form:
|
|
25
|
+
title: Document
|
|
26
|
+
label:
|
|
27
|
+
name: Name
|
|
28
|
+
kind: Kind
|
|
29
|
+
file: File
|
|
30
|
+
description:
|
|
31
|
+
file: PDF, smaller than 10 MB. The file cannot be changed after the document is created.
|
|
32
|
+
file_locked: The file cannot be changed.
|
|
33
|
+
no_file: No file
|
|
34
|
+
action:
|
|
35
|
+
save: Save
|
|
@@ -5,6 +5,10 @@ en:
|
|
|
5
5
|
roles:
|
|
6
6
|
member: Member
|
|
7
7
|
administrator: Administrator
|
|
8
|
+
statuses:
|
|
9
|
+
waitlisted: Waitlisted
|
|
10
|
+
active: Active
|
|
11
|
+
inactive: Inactive
|
|
8
12
|
index:
|
|
9
13
|
title: Users
|
|
10
14
|
description: Manage user accounts and their roles. Search users by email address.
|
|
@@ -16,6 +20,7 @@ en:
|
|
|
16
20
|
label:
|
|
17
21
|
email_address: Email address
|
|
18
22
|
role: Role
|
|
23
|
+
status: Status
|
|
19
24
|
password: Password
|
|
20
25
|
description:
|
|
21
26
|
password: Use at least 8 characters.
|
|
@@ -31,6 +36,7 @@ en:
|
|
|
31
36
|
label:
|
|
32
37
|
email_address: Email address
|
|
33
38
|
role: Role
|
|
39
|
+
status: Status
|
|
34
40
|
section:
|
|
35
41
|
data: User data
|
|
36
42
|
action:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
en:
|
|
2
|
+
activemodel:
|
|
3
|
+
errors:
|
|
4
|
+
models:
|
|
5
|
+
moduler/document_acceptance:
|
|
6
|
+
not_all_accepted: Accept all documents to continue.
|
|
7
|
+
moduler:
|
|
8
|
+
document_acceptances:
|
|
9
|
+
new:
|
|
10
|
+
title: Before you continue
|
|
11
|
+
description: Read and accept the documents below to get access to the panel.
|
|
12
|
+
accept: "I have read and accept: %{name}"
|
|
13
|
+
action:
|
|
14
|
+
read: Read the document
|
|
15
|
+
submit: Accept and continue
|
|
16
|
+
create:
|
|
17
|
+
notice: Thank you. You can now use the panel.
|
data/config/routes.rb
CHANGED
|
@@ -4,6 +4,8 @@ Moduler::Engine.routes.draw do
|
|
|
4
4
|
resource :session, only: %i[new create destroy]
|
|
5
5
|
resource :user, only: %i[new create]
|
|
6
6
|
|
|
7
|
+
resource :document_acceptance, only: %i[ new create ]
|
|
8
|
+
|
|
7
9
|
resource :profile, only: %i[ edit update ], controller: :profile
|
|
8
10
|
namespace :profile do
|
|
9
11
|
namespace :sessions do
|
|
@@ -17,6 +19,7 @@ Moduler::Engine.routes.draw do
|
|
|
17
19
|
resources :sessions, only: %i[ index destroy ]
|
|
18
20
|
|
|
19
21
|
resources :articles
|
|
22
|
+
resources :documents, only: %i[ index new create edit update ]
|
|
20
23
|
|
|
21
24
|
root "home#index"
|
|
22
25
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateModulerUserDocuments < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
create_table :moduler_user_documents do |t|
|
|
4
|
+
t.references :moduler_user, type: :uuid, null: false, foreign_key: true
|
|
5
|
+
t.references :moduler_document, null: false, foreign_key: true, index: false
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :moduler_user_documents, %i[ moduler_document_id moduler_user_id ], unique: true
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/moduler/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moduler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tomasz Kowalewski
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-11-04 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -238,6 +238,8 @@ files:
|
|
|
238
238
|
- app/components/moduler/form/base_error_component.rb
|
|
239
239
|
- app/components/moduler/form/email_component.html.erb
|
|
240
240
|
- app/components/moduler/form/email_component.rb
|
|
241
|
+
- app/components/moduler/form/file_component.html.erb
|
|
242
|
+
- app/components/moduler/form/file_component.rb
|
|
241
243
|
- app/components/moduler/form/password_component.html.erb
|
|
242
244
|
- app/components/moduler/form/password_component.rb
|
|
243
245
|
- app/components/moduler/form/section_component.html.erb
|
|
@@ -261,13 +263,16 @@ files:
|
|
|
261
263
|
- app/components/moduler/sidebar_action_component.html.erb
|
|
262
264
|
- app/components/moduler/sidebar_action_component.rb
|
|
263
265
|
- app/controllers/concerns/moduler/authentication.rb
|
|
266
|
+
- app/controllers/concerns/moduler/require_document_acceptance.rb
|
|
264
267
|
- app/controllers/moduler/administration/application_controller.rb
|
|
265
268
|
- app/controllers/moduler/administration/articles_controller.rb
|
|
269
|
+
- app/controllers/moduler/administration/documents_controller.rb
|
|
266
270
|
- app/controllers/moduler/administration/home_controller.rb
|
|
267
271
|
- app/controllers/moduler/administration/sessions_controller.rb
|
|
268
272
|
- app/controllers/moduler/administration/users_controller.rb
|
|
269
273
|
- app/controllers/moduler/application_controller.rb
|
|
270
274
|
- app/controllers/moduler/articles_controller.rb
|
|
275
|
+
- app/controllers/moduler/document_acceptances_controller.rb
|
|
271
276
|
- app/controllers/moduler/home_controller.rb
|
|
272
277
|
- app/controllers/moduler/profile/sessions/revocation_controller.rb
|
|
273
278
|
- app/controllers/moduler/profile/sessions_controller.rb
|
|
@@ -283,14 +288,18 @@ files:
|
|
|
283
288
|
- app/models/moduler/application_record.rb
|
|
284
289
|
- app/models/moduler/article.rb
|
|
285
290
|
- app/models/moduler/current.rb
|
|
291
|
+
- app/models/moduler/document.rb
|
|
286
292
|
- app/models/moduler/session.rb
|
|
287
293
|
- app/models/moduler/user.rb
|
|
294
|
+
- app/models/moduler/user_document.rb
|
|
288
295
|
- app/policies/moduler/administration/article_policy.rb
|
|
289
296
|
- app/policies/moduler/administration/base_policy.rb
|
|
297
|
+
- app/policies/moduler/administration/document_policy.rb
|
|
290
298
|
- app/policies/moduler/administration/home_policy.rb
|
|
291
299
|
- app/policies/moduler/administration/session_policy.rb
|
|
292
300
|
- app/policies/moduler/administration/user_policy.rb
|
|
293
301
|
- app/policies/moduler/application_policy.rb
|
|
302
|
+
- app/services/moduler/document_acceptance.rb
|
|
294
303
|
- app/views/kaminari/_gap.html.erb
|
|
295
304
|
- app/views/kaminari/_next_page.html.erb
|
|
296
305
|
- app/views/kaminari/_page.html.erb
|
|
@@ -303,6 +312,11 @@ files:
|
|
|
303
312
|
- app/views/moduler/administration/articles/edit.html.erb
|
|
304
313
|
- app/views/moduler/administration/articles/index.html.erb
|
|
305
314
|
- app/views/moduler/administration/articles/new.html.erb
|
|
315
|
+
- app/views/moduler/administration/documents/_document.html.erb
|
|
316
|
+
- app/views/moduler/administration/documents/_form.html.erb
|
|
317
|
+
- app/views/moduler/administration/documents/edit.html.erb
|
|
318
|
+
- app/views/moduler/administration/documents/index.html.erb
|
|
319
|
+
- app/views/moduler/administration/documents/new.html.erb
|
|
306
320
|
- app/views/moduler/administration/home/index.html.erb
|
|
307
321
|
- app/views/moduler/administration/sessions/_session.html.erb
|
|
308
322
|
- app/views/moduler/administration/sessions/index.html.erb
|
|
@@ -318,6 +332,7 @@ files:
|
|
|
318
332
|
- app/views/moduler/common/_sidebar.html.erb
|
|
319
333
|
- app/views/moduler/common/administration/_sidebar.html.erb
|
|
320
334
|
- app/views/moduler/common/profile/_sidebar.html.erb
|
|
335
|
+
- app/views/moduler/document_acceptances/new.html.erb
|
|
321
336
|
- app/views/moduler/home/index.html.erb
|
|
322
337
|
- app/views/moduler/profile/edit.html.erb
|
|
323
338
|
- app/views/moduler/profile/sessions/_session.html.erb
|
|
@@ -326,11 +341,13 @@ files:
|
|
|
326
341
|
- app/views/moduler/users/new.html.erb
|
|
327
342
|
- config/importmap.rb
|
|
328
343
|
- config/locales/moduler/administration/articles/en.yml
|
|
344
|
+
- config/locales/moduler/administration/documents/en.yml
|
|
329
345
|
- config/locales/moduler/administration/sessions/en.yml
|
|
330
346
|
- config/locales/moduler/administration/users/en.yml
|
|
331
347
|
- config/locales/moduler/articles/en.yml
|
|
332
348
|
- config/locales/moduler/common/en.yml
|
|
333
349
|
- config/locales/moduler/components/en.yml
|
|
350
|
+
- config/locales/moduler/document_acceptances/en.yml
|
|
334
351
|
- config/locales/moduler/en.yml
|
|
335
352
|
- config/locales/moduler/profile/en.yml
|
|
336
353
|
- config/locales/moduler/profile/sessions/en.yml
|
|
@@ -342,6 +359,9 @@ files:
|
|
|
342
359
|
- db/migrate/20261015171504_add_role_to_moduler_users.rb
|
|
343
360
|
- db/migrate/20261020191709_enable_pg_trgm.rb
|
|
344
361
|
- db/migrate/20261026203827_create_moduler_articles.rb
|
|
362
|
+
- db/migrate/20261104120000_add_status_to_moduler_users.rb
|
|
363
|
+
- db/migrate/20261104205837_create_moduler_documents.rb
|
|
364
|
+
- db/migrate/20261104205950_create_moduler_user_documents.rb
|
|
345
365
|
- lib/moduler.rb
|
|
346
366
|
- lib/moduler/configuration.rb
|
|
347
367
|
- lib/moduler/engine.rb
|