Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
img-manager
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ivan Lan
img-manager
Commits
68348823
Commit
68348823
authored
Mar 08, 2025
by
Ivan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 修复滚动加载 page 初始化大于 1 的情况
parent
24f5149a
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
215 additions
and
55 deletions
+215
-55
application_controller.rb
app/controllers/application_controller.rb
+2
-2
images_controller.rb
app/controllers/images_controller.rb
+60
-40
Layout.jsx
app/frontend/pages/Layout.jsx
+1
-1
Index.jsx
app/frontend/pages/images/Index.jsx
+0
-0
New.jsx
app/frontend/pages/images/New.jsx
+3
-3
image.rb
app/models/image.rb
+13
-0
tag.rb
app/models/tag.rb
+4
-0
user.rb
app/models/user.rb
+34
-8
20250308072606_create_active_storage_tables.active_storage.rb
...0308072606_create_active_storage_tables.active_storage.rb
+57
-0
20250308073302_add_roles_to_users.rb
db/migrate/20250308073302_add_roles_to_users.rb
+9
-0
schema.rb
db/schema.rb
+32
-1
No files found.
app/controllers/application_controller.rb
View file @
68348823
class
ApplicationController
<
ActionController
::
Base
include
Authentication
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser
versions: :modern
#
allow_browser versions: :modern
inertia_share
do
{
auth:
authenticated?
.
user
&
.
as_json
(
only:
[
:id
,
:name
,
:email_address
,
:
admin
])
auth:
authenticated?
.
user
&
.
as_json
(
only:
[
:id
,
:name
,
:email_address
,
:
roles
])
}
end
end
app/controllers/images_controller.rb
View file @
68348823
class
ImagesController
<
ApplicationController
# Allow unauthenticated access to index and show actions
# allow_unauthenticated_access only: [:index, :show, :search]
before_action
:set_image
,
only:
[
:show
,
:edit
,
:update
,
:destroy
,
:approve
,
:reject
]
before_action
:authorize_admin
,
only:
[
:approve
,
:reject
,
:destroy
]
before_action
:set_image
,
only:
[
:show
,
:edit
,
:update
,
:destroy
,
:approve
,
:reject
]
before_action
:authorize_admin
,
only:
[
:approve
,
:reject
,
:destroy
]
def
index
@q
=
Image
.
includes
(
:user
,
:tags
).
ransack
(
params
[
:q
])
@images
=
@q
.
result
(
distinct:
true
).
with_attached_file
.
order
(
created_at: :desc
).
page
(
params
[
:page
]).
per
(
12
)
render
inertia:
'images/Index'
,
props:
{
images:
@images
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
filters:
params
[
:q
]
||
{}
}
respond_to
do
|
format
|
format
.
html
do
render
inertia:
"images/Index"
,
props:
{
images:
@images
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
filters:
params
[
:q
]
||
{},
pagination:
{
current_page:
@images
.
current_page
,
total_pages:
@images
.
total_pages
,
total_count:
@images
.
total_count
}
}
end
format
.
json
do
render
json:
{
images:
@images
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
pagination:
{
current_page:
@images
.
current_page
,
total_pages:
@images
.
total_pages
,
total_count:
@images
.
total_count
}
}
end
end
end
def
show
render
inertia:
'images/Show'
,
props:
{
image:
@image
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
render
inertia:
"images/Show"
,
props:
{
image:
@image
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
can_edit:
Current
.
user
==
@image
.
user
,
can_approve:
Current
.
user
&
.
admin?
}
...
...
@@ -25,20 +45,20 @@ class ImagesController < ApplicationController
def
new
@image
=
Image
.
new
render
inertia:
'images/New'
render
inertia:
"images/New"
end
def
create
@image
=
Current
.
user
.
images
.
new
(
image_params
)
if
@image
.
save
if
params
[
:tags
].
present?
@image
.
add_tags
(
params
[
:tags
].
split
(
','
))
@image
.
add_tags
(
params
[
:tags
].
split
(
","
))
end
redirect_to
image_path
(
@image
),
notice:
'Image was successfully uploaded and is pending review.'
redirect_to
image_path
(
@image
),
notice:
"Image was successfully uploaded and is pending review."
else
render
inertia:
'images/New'
,
props:
{
image:
@image
.
as_json
(
methods:
[
:errors
]),
render
inertia:
"images/New"
,
props:
{
image:
@image
.
as_json
(
methods:
[
:errors
]),
errors:
@image
.
errors
},
status: :unprocessable_entity
end
...
...
@@ -46,24 +66,24 @@ class ImagesController < ApplicationController
def
edit
authorize_user
render
inertia:
'images/Edit'
,
props:
{
image:
@image
.
as_json
(
include:
[
:tags
],
methods:
[
:file_url
]),
tags:
@image
.
tags
.
pluck
(
:name
).
join
(
', '
)
render
inertia:
"images/Edit"
,
props:
{
image:
@image
.
as_json
(
include:
[
:tags
],
methods:
[
:file_url
]),
tags:
@image
.
tags
.
pluck
(
:name
).
join
(
", "
)
}
end
def
update
authorize_user
if
@image
.
update
(
image_params
)
if
params
[
:tags
].
present?
@image
.
tags
.
clear
@image
.
add_tags
(
params
[
:tags
].
split
(
','
))
@image
.
add_tags
(
params
[
:tags
].
split
(
","
))
end
redirect_to
image_path
(
@image
),
notice:
'Image was successfully updated.'
redirect_to
image_path
(
@image
),
notice:
"Image was successfully updated."
else
render
inertia:
'images/Edit'
,
props:
{
image:
@image
.
as_json
(
include:
[
:tags
],
methods:
[
:file_url
,
:errors
]),
render
inertia:
"images/Edit"
,
props:
{
image:
@image
.
as_json
(
include:
[
:tags
],
methods:
[
:file_url
,
:errors
]),
tags:
params
[
:tags
],
errors:
@image
.
errors
},
status: :unprocessable_entity
...
...
@@ -72,49 +92,49 @@ class ImagesController < ApplicationController
def
destroy
@image
.
destroy
redirect_to
images_path
,
notice:
'Image was successfully deleted.'
redirect_to
images_path
,
notice:
"Image was successfully deleted."
end
def
search
@q
=
Image
.
approved
.
includes
(
:user
,
:tags
).
ransack
(
params
[
:q
])
@images
=
@q
.
result
(
distinct:
true
).
with_attached_file
.
order
(
created_at: :desc
).
page
(
params
[
:page
]).
per
(
12
)
render
inertia:
'images/Search'
,
props:
{
images:
@images
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
render
inertia:
"images/Search"
,
props:
{
images:
@images
.
as_json
(
include:
[
:user
,
:tags
],
methods:
[
:file_url
]),
filters:
params
[
:q
]
||
{},
tags:
Tag
.
all
.
pluck
(
:name
)
}
end
def
approve
@image
.
approved!
redirect_to
image_path
(
@image
),
notice:
'Image was successfully approved.'
redirect_to
image_path
(
@image
),
notice:
"Image was successfully approved."
end
def
reject
@image
.
rejected!
redirect_to
image_path
(
@image
),
notice:
'Image was rejected.'
redirect_to
image_path
(
@image
),
notice:
"Image was rejected."
end
private
def
set_image
@image
=
Image
.
find
(
params
[
:id
])
end
def
image_params
params
.
require
(
:image
).
permit
(
:title
,
:file
)
end
def
authorize_user
unless
Current
.
user
==
@image
.
user
redirect_to
images_path
,
alert:
'You are not authorized to perform this action.'
redirect_to
images_path
,
alert:
"You are not authorized to perform this action."
end
end
def
authorize_admin
unless
Current
.
user
&
.
admin?
redirect_to
images_path
,
alert:
'You are not authorized to perform this action.'
redirect_to
images_path
,
alert:
"You are not authorized to perform this action."
end
end
end
app/frontend/pages/Layout.jsx
View file @
68348823
...
...
@@ -166,7 +166,7 @@ export default function Layout({ children, user }) {
Upload
</
Link
>
)
}
{
user
?.
role
===
'admin'
&&
(
{
user
?.
role
s
?.
includes
(
'admin'
)
&&
(
<
Link
href=
"/admin"
className=
"border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
...
...
app/frontend/pages/images/Index.jsx
View file @
68348823
This diff is collapsed.
Click to expand it.
app/frontend/pages/images/New.jsx
View file @
68348823
import
{
useState
}
from
'react'
import
{
Head
,
useForm
}
from
'@inertiajs/react'
import
{
Head
,
useForm
,
router
}
from
'@inertiajs/react'
import
Layout
from
'../Layout'
import
*
as
Form
from
'@radix-ui/react-form'
export
default
function
New
({
auth
,
errors
=
{}
})
{
const
[
preview
,
setPreview
]
=
useState
(
null
)
const
{
data
,
setData
,
p
ost
,
p
rocessing
}
=
useForm
({
const
{
data
,
setData
,
processing
}
=
useForm
({
title
:
''
,
file
:
null
,
tags
:
''
,
...
...
@@ -13,7 +13,7 @@ export default function New({ auth, errors = {} }) {
const
handleSubmit
=
(
e
)
=>
{
e
.
preventDefault
()
post
(
'/images'
)
router
.
post
(
'/images'
,
{
image
:
data
}
)
}
const
handleFileChange
=
(
e
)
=>
{
...
...
app/models/image.rb
View file @
68348823
...
...
@@ -16,6 +16,19 @@ class Image < ApplicationRecord
scope
:approved
,
->
{
where
(
status: :approved
)
}
scope
:rejected
,
->
{
where
(
status: :rejected
)
}
def
self
.
ransackable_attributes
(
auth_object
=
nil
)
[
"created_at"
,
"id"
,
"id_value"
,
"status"
,
"title"
,
"updated_at"
,
"user_id"
]
end
def
self
.
ransackable_associations
(
auth_object
=
nil
)
[
"image_tags"
,
"tags"
,
"user"
]
end
# Returns the URL for the attached file
def
file_url
file
.
attached?
?
Rails
.
application
.
routes
.
url_helpers
.
rails_blob_path
(
file
,
only_path:
true
)
:
nil
end
# Method to add tags to an image
def
add_tags
(
tag_names
)
tag_names
.
each
do
|
name
|
...
...
app/models/tag.rb
View file @
68348823
...
...
@@ -6,6 +6,10 @@ class Tag < ApplicationRecord
before_save
:downcase_name
def
self
.
ransackable_attributes
(
auth_object
=
nil
)
[
"created_at"
,
"id"
,
"id_value"
,
"status"
,
"name"
,
"updated_at"
,
"user_id"
]
end
private
def
downcase_name
...
...
app/models/user.rb
View file @
68348823
...
...
@@ -14,13 +14,39 @@ class User < ApplicationRecord
attribute
:notify_on_new_login
,
:boolean
,
default:
true
attribute
:max_sessions
,
:integer
,
default:
5
# Admin role
# enum role: { user: 0, admin: 1 }, _default: :user
# 确保 roles 字段始终是数组
# attribute :roles, :json, default: -> { [] }
serialize
:roles
,
coder:
JSON
,
yaml:
true
# 定义可用的角色
AVAILABLE_ROLES
=
%w[admin editor viewer]
# 添加角色
def
add_role
(
role
)
return
unless
AVAILABLE_ROLES
.
include?
(
role
.
to_s
)
current_roles
=
(
roles
||
[]).
map
(
&
:to_s
)
current_roles
<<
role
.
to_s
unless
current_roles
.
include?
(
role
.
to_s
)
update
(
roles:
current_roles
)
end
# 移除角色
def
remove_role
(
role
)
current_roles
=
(
roles
||
[]).
map
(
&
:to_s
)
current_roles
.
delete
(
role
.
to_s
)
update
(
roles:
current_roles
)
end
# 检查是否有指定角色
def
has_role?
(
role
)
Rails
.
logger
.
info
(
"roles
#{
roles
.
class
}
"
)
(
roles
||
[]).
map
(
&
:to_s
).
include?
(
role
.
to_s
)
end
# 向后兼容的 admin? 方法
def
admin?
role
==
"admin"
has_role?
(
"admin"
)
end
def
security_settings
{
require_two_factor:
require_two_factor
,
...
...
@@ -29,20 +55,20 @@ class User < ApplicationRecord
max_sessions:
max_sessions
}
end
def
enforce_max_sessions
return
unless
max_sessions
>
0
# Get all sessions except the current one, ordered by last activity
other_sessions
=
sessions
.
where
.
not
(
id:
Current
.
session
&
.
id
).
order
(
updated_at: :desc
)
# If we have more sessions than allowed, destroy the oldest ones
if
other_sessions
.
count
>=
max_sessions
sessions_to_remove
=
other_sessions
.
offset
(
max_sessions
-
1
)
sessions_to_remove
.
destroy_all
end
end
def
notify_on_new_login?
notify_on_new_login
end
...
...
db/migrate/20250308072606_create_active_storage_tables.active_storage.rb
0 → 100644
View file @
68348823
# This migration comes from active_storage (originally 20170806125915)
class
CreateActiveStorageTables
<
ActiveRecord
::
Migration
[
7.0
]
def
change
# Use Active Record's configured type for primary and foreign keys
primary_key_type
,
foreign_key_type
=
primary_and_foreign_key_types
create_table
:active_storage_blobs
,
id:
primary_key_type
do
|
t
|
t
.
string
:key
,
null:
false
t
.
string
:filename
,
null:
false
t
.
string
:content_type
t
.
text
:metadata
t
.
string
:service_name
,
null:
false
t
.
bigint
:byte_size
,
null:
false
t
.
string
:checksum
if
connection
.
supports_datetime_with_precision?
t
.
datetime
:created_at
,
precision:
6
,
null:
false
else
t
.
datetime
:created_at
,
null:
false
end
t
.
index
[
:key
],
unique:
true
end
create_table
:active_storage_attachments
,
id:
primary_key_type
do
|
t
|
t
.
string
:name
,
null:
false
t
.
references
:record
,
null:
false
,
polymorphic:
true
,
index:
false
,
type:
foreign_key_type
t
.
references
:blob
,
null:
false
,
type:
foreign_key_type
if
connection
.
supports_datetime_with_precision?
t
.
datetime
:created_at
,
precision:
6
,
null:
false
else
t
.
datetime
:created_at
,
null:
false
end
t
.
index
[
:record_type
,
:record_id
,
:name
,
:blob_id
],
name: :index_active_storage_attachments_uniqueness
,
unique:
true
t
.
foreign_key
:active_storage_blobs
,
column: :blob_id
end
create_table
:active_storage_variant_records
,
id:
primary_key_type
do
|
t
|
t
.
belongs_to
:blob
,
null:
false
,
index:
false
,
type:
foreign_key_type
t
.
string
:variation_digest
,
null:
false
t
.
index
[
:blob_id
,
:variation_digest
],
name: :index_active_storage_variant_records_uniqueness
,
unique:
true
t
.
foreign_key
:active_storage_blobs
,
column: :blob_id
end
end
private
def
primary_and_foreign_key_types
config
=
Rails
.
configuration
.
generators
setting
=
config
.
options
[
config
.
orm
][
:primary_key_type
]
primary_key_type
=
setting
||
:primary_key
foreign_key_type
=
setting
||
:bigint
[
primary_key_type
,
foreign_key_type
]
end
end
db/migrate/20250308073302_add_roles_to_users.rb
0 → 100644
View file @
68348823
class
AddRolesToUsers
<
ActiveRecord
::
Migration
[
8.0
]
def
change
add_column
:users
,
:roles
,
:string
,
default:
'[]'
# 如果之前有 role 字段,我们需要将其移除
# 注意:这里假设之前没有 role 字段,如果有的话会自动处理
remove_column
:users
,
:role
if
column_exists?
(
:users
,
:role
)
end
end
db/schema.rb
View file @
68348823
...
...
@@ -10,7 +10,35 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord
::
Schema
[
8.0
].
define
(
version:
2025_03_08_064523
)
do
ActiveRecord
::
Schema
[
8.0
].
define
(
version:
2025_03_08_073302
)
do
create_table
"active_storage_attachments"
,
force: :cascade
do
|
t
|
t
.
string
"name"
,
null:
false
t
.
string
"record_type"
,
null:
false
t
.
bigint
"record_id"
,
null:
false
t
.
bigint
"blob_id"
,
null:
false
t
.
datetime
"created_at"
,
null:
false
t
.
index
[
"blob_id"
],
name:
"index_active_storage_attachments_on_blob_id"
t
.
index
[
"record_type"
,
"record_id"
,
"name"
,
"blob_id"
],
name:
"index_active_storage_attachments_uniqueness"
,
unique:
true
end
create_table
"active_storage_blobs"
,
force: :cascade
do
|
t
|
t
.
string
"key"
,
null:
false
t
.
string
"filename"
,
null:
false
t
.
string
"content_type"
t
.
text
"metadata"
t
.
string
"service_name"
,
null:
false
t
.
bigint
"byte_size"
,
null:
false
t
.
string
"checksum"
t
.
datetime
"created_at"
,
null:
false
t
.
index
[
"key"
],
name:
"index_active_storage_blobs_on_key"
,
unique:
true
end
create_table
"active_storage_variant_records"
,
force: :cascade
do
|
t
|
t
.
bigint
"blob_id"
,
null:
false
t
.
string
"variation_digest"
,
null:
false
t
.
index
[
"blob_id"
,
"variation_digest"
],
name:
"index_active_storage_variant_records_uniqueness"
,
unique:
true
end
create_table
"image_tags"
,
force: :cascade
do
|
t
|
t
.
integer
"image_id"
,
null:
false
t
.
integer
"tag_id"
,
null:
false
...
...
@@ -50,9 +78,12 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_08_064523) do
t
.
datetime
"created_at"
,
null:
false
t
.
datetime
"updated_at"
,
null:
false
t
.
string
"name"
t
.
string
"roles"
,
default:
"[]"
t
.
index
[
"email_address"
],
name:
"index_users_on_email_address"
,
unique:
true
end
add_foreign_key
"active_storage_attachments"
,
"active_storage_blobs"
,
column:
"blob_id"
add_foreign_key
"active_storage_variant_records"
,
"active_storage_blobs"
,
column:
"blob_id"
add_foreign_key
"image_tags"
,
"images"
add_foreign_key
"image_tags"
,
"tags"
add_foreign_key
"images"
,
"users"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment