feat: add admin/editor roles to users, restrict globals to adminOnly
- Users: added role field (admin/editor), role field locked to adminOnly update - SiteSettings, AnnouncementBar, PopupAnnouncement: update restricted to adminOnly - Added src/access/adminOnly.ts helper
This commit is contained in:
parent
ccdc739c22
commit
ed4062aef0
BIN
media/Queen_band-400x281.webp
Normal file
BIN
media/Queen_band-400x281.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
media/Queen_band-800x561.webp
Normal file
BIN
media/Queen_band-800x561.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
media/Queen_band.webp
Normal file
BIN
media/Queen_band.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
5
src/access/adminOnly.ts
Normal file
5
src/access/adminOnly.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import type { Access } from 'payload'
|
||||||
|
|
||||||
|
export const adminOnly: Access = ({ req: { user } }) => {
|
||||||
|
return user?.role === 'admin'
|
||||||
|
}
|
||||||
@ -1,18 +1,20 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
import { authenticated } from '../../access/authenticated'
|
import { authenticated } from '../../access/authenticated'
|
||||||
|
|
||||||
|
// Only admins can manage users
|
||||||
|
const adminOnly = ({ req: { user } }: any) => user?.role === 'admin'
|
||||||
|
|
||||||
export const Users: CollectionConfig = {
|
export const Users: CollectionConfig = {
|
||||||
slug: 'users',
|
slug: 'users',
|
||||||
access: {
|
access: {
|
||||||
admin: authenticated,
|
admin: authenticated,
|
||||||
create: authenticated,
|
create: adminOnly,
|
||||||
delete: authenticated,
|
delete: adminOnly,
|
||||||
read: authenticated,
|
read: authenticated,
|
||||||
update: authenticated,
|
update: authenticated, // users can update themselves; field-level locks the role field
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
defaultColumns: ['name', 'email'],
|
defaultColumns: ['name', 'email', 'role'],
|
||||||
useAsTitle: 'name',
|
useAsTitle: 'name',
|
||||||
},
|
},
|
||||||
auth: true,
|
auth: true,
|
||||||
@ -20,6 +22,25 @@ export const Users: CollectionConfig = {
|
|||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'role',
|
||||||
|
type: 'select',
|
||||||
|
label: 'Roll',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'editor',
|
||||||
|
options: [
|
||||||
|
{ label: 'Admin', value: 'admin' },
|
||||||
|
{ label: 'Redaktör', value: 'editor' },
|
||||||
|
],
|
||||||
|
// Only admins can change roles
|
||||||
|
access: {
|
||||||
|
update: adminOnly,
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
description: 'Admin har full åtkomst. Redaktör kan hantera sidor, inlägg och media.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { GlobalConfig } from 'payload'
|
import type { GlobalConfig } from 'payload'
|
||||||
|
import { adminOnly } from '../access/adminOnly'
|
||||||
|
|
||||||
export const AnnouncementBar: GlobalConfig = {
|
export const AnnouncementBar: GlobalConfig = {
|
||||||
slug: 'announcement-bar',
|
slug: 'announcement-bar',
|
||||||
@ -6,6 +7,10 @@ export const AnnouncementBar: GlobalConfig = {
|
|||||||
admin: {
|
admin: {
|
||||||
group: 'Globala inställningar',
|
group: 'Globala inställningar',
|
||||||
},
|
},
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
update: adminOnly,
|
||||||
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'enabled',
|
name: 'enabled',
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { GlobalConfig } from 'payload'
|
import type { GlobalConfig } from 'payload'
|
||||||
|
import { adminOnly } from '../access/adminOnly'
|
||||||
|
|
||||||
export const PopupAnnouncement: GlobalConfig = {
|
export const PopupAnnouncement: GlobalConfig = {
|
||||||
slug: 'popup-announcement',
|
slug: 'popup-announcement',
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import type { GlobalConfig } from 'payload'
|
import type { GlobalConfig } from 'payload'
|
||||||
|
import { adminOnly } from '../access/adminOnly'
|
||||||
|
|
||||||
export const SiteSettings: GlobalConfig = {
|
export const SiteSettings: GlobalConfig = {
|
||||||
slug: 'site-settings',
|
slug: 'site-settings',
|
||||||
label: 'Webbplatsinställningar',
|
label: 'Webbplatsinställningar',
|
||||||
access: {
|
access: {
|
||||||
|
update: adminOnly,
|
||||||
read: () => true,
|
read: () => true,
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
22880
src/migrations/20260218_145924.json
Normal file
22880
src/migrations/20260218_145924.json
Normal file
File diff suppressed because it is too large
Load Diff
15
src/migrations/20260218_145924.ts
Normal file
15
src/migrations/20260218_145924.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||||
|
|
||||||
|
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TYPE "public"."enum_users_role" AS ENUM('admin', 'editor');
|
||||||
|
ALTER TABLE "users" ALTER COLUMN "name" SET NOT NULL;
|
||||||
|
ALTER TABLE "users" ADD COLUMN "role" "enum_users_role" DEFAULT 'editor' NOT NULL;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "users" ALTER COLUMN "name" DROP NOT NULL;
|
||||||
|
ALTER TABLE "users" DROP COLUMN "role";
|
||||||
|
DROP TYPE "public"."enum_users_role";`)
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import * as migration_20260216_183152 from './20260216_183152';
|
|||||||
import * as migration_20260216_184110 from './20260216_184110';
|
import * as migration_20260216_184110 from './20260216_184110';
|
||||||
import * as migration_20260216_184342 from './20260216_184342';
|
import * as migration_20260216_184342 from './20260216_184342';
|
||||||
import * as migration_20260218_130902 from './20260218_130902';
|
import * as migration_20260218_130902 from './20260218_130902';
|
||||||
|
import * as migration_20260218_145924 from './20260218_145924';
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
@ -40,6 +41,11 @@ export const migrations = [
|
|||||||
{
|
{
|
||||||
up: migration_20260218_130902.up,
|
up: migration_20260218_130902.up,
|
||||||
down: migration_20260218_130902.down,
|
down: migration_20260218_130902.down,
|
||||||
name: '20260218_130902'
|
name: '20260218_130902',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
up: migration_20260218_145924.up,
|
||||||
|
down: migration_20260218_145924.down,
|
||||||
|
name: '20260218_145924'
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1397,7 +1397,11 @@ export interface Category {
|
|||||||
*/
|
*/
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
name?: string | null;
|
name: string;
|
||||||
|
/**
|
||||||
|
* Admin har full åtkomst. Redaktör kan hantera sidor, inlägg och media.
|
||||||
|
*/
|
||||||
|
role: 'admin' | 'editor';
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -2448,6 +2452,7 @@ export interface CategoriesSelect<T extends boolean = true> {
|
|||||||
*/
|
*/
|
||||||
export interface UsersSelect<T extends boolean = true> {
|
export interface UsersSelect<T extends boolean = true> {
|
||||||
name?: T;
|
name?: T;
|
||||||
|
role?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
email?: T;
|
email?: T;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user