Skip to content
This repository has been archived by the owner on Feb 10, 2025. It is now read-only.

Commit

Permalink
adding contributor policy actions
Browse files Browse the repository at this point in the history
  • Loading branch information
tomgobich committed Jul 6, 2023
1 parent 2e40fb0 commit d06a954
Show file tree
Hide file tree
Showing 31 changed files with 485 additions and 252 deletions.
45 changes: 34 additions & 11 deletions app/Controllers/Http/CollectionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import CollectionService from 'App/Services/CollectionService'
import TaxonomyService from 'App/Services/TaxonomyService'

export default class CollectionsController {
public async index ({ view, request, auth }: HttpContextContract) {
public async index ({ view, request, auth, bouncer }: HttpContextContract) {
await bouncer.with('CollectionPolicy').authorize('viewList')

const page = request.input('page', 1)
const collections = await auth.user!.related('collections').query()
const collections = await Collection.query()
.preload('children', query => query.withCount('posts').select('id'))
.withCount('posts')
.whereNull('parentId')
Expand All @@ -30,26 +32,35 @@ export default class CollectionsController {
return view.render('studio/collections/index', { collections, collectionCounts })
}

public async create ({ view }: HttpContextContract) {
public async create ({ view, bouncer }: HttpContextContract) {
await bouncer.with('CollectionPolicy').authorize('create')

const states = State
const statuses = Status
const collectionTypes = CollectionType
const taxonomies = await TaxonomyService.getAllForTree()
const collections = await Collection.query().whereNull('parentId').select('id', 'name').orderBy('name')

return view.render('studio/collections/createOrEdit', { states, statuses, collectionTypes, taxonomies, collections })
}

public async store ({ request, response, session, auth }: HttpContextContract) {
public async store ({ request, response, session, auth, bouncer }: HttpContextContract) {
await bouncer.with('CollectionPolicy').authorize('create')

let collection = await Collection.firstOrNewById(undefined)

const data = await request.validate(CollectionValidator)

const collection = await CollectionService.updateOrCreate(undefined, { ...data, ownerId: auth.user!.id })
collection = await CollectionService.updateOrCreate(collection, { ...data, ownerId: auth.user!.id })

session.flash('success', "Your collection has been created")

return response.redirect().toRoute('studio.collections.edit', { id: collection.id })
}

public async stub ({ request, response, auth }: HttpContextContract) {
public async stub ({ request, response, auth, bouncer }: HttpContextContract) {
await bouncer.with('CollectionPolicy').authorize('create')

const data = await request.validate({
schema: schema.create({
parentId: schema.number([rules.exists({ table: 'collections', column: 'id' })])
Expand All @@ -64,8 +75,11 @@ export default class CollectionsController {
public async show ({}: HttpContextContract) {
}

public async edit ({ view, params }: HttpContextContract) {
public async edit ({ view, params, bouncer }: HttpContextContract) {
const collection = await Collection.findOrFail(params.id)

await bouncer.with('CollectionPolicy').authorize('update', collection)

const states = State
const statuses = Status
const collectionTypes = CollectionType
Expand All @@ -89,15 +103,24 @@ export default class CollectionsController {
return view.render('studio/collections/createOrEdit', { collection, collections, children, states, statuses, collectionTypes, taxonomies })
}

public async update ({ request, response, params }: HttpContextContract) {
public async update ({ request, response, params, bouncer }: HttpContextContract) {
const collection = await Collection.firstOrNewById(params.id)

await bouncer.with('CollectionPolicy').authorize('update', collection)

const data = await request.validate(CollectionValidator)
const isOwner = await bouncer.with('CollectionPolicy').allows('isOwner', collection)

await CollectionService.updateOrCreate(params.id, data)
await CollectionService.updateOrCreate(collection, data, isOwner)

return response.redirect().toRoute('studio.collections.index')
}

public async destroy ({ request, response, params }: HttpContextContract) {
public async destroy ({ request, response, params, bouncer }: HttpContextContract) {
const _collection = await Collection.findOrFail(params.id)

await bouncer.with('CollectionPolicy').authorize('delete', _collection)

const collection = await CollectionService.delete(params.id)

if (request.accepts(['json'])) {
Expand Down
10 changes: 9 additions & 1 deletion app/Controllers/Http/DashboardController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Collection from 'App/Models/Collection'
import Post from 'App/Models/Post'
import Taxonomy from 'App/Models/Taxonomy'

export default class DashboardController {
public async index({ view }: HttpContextContract) {
return view.render('studio/index')
const postCount = await Post.query().apply(s => s.published()).getCount()
const postSeconds = await Post.query().sum('video_seconds').first()
const seriesCount = await Collection.series().wherePublic().getCount()
const topicCount = await Taxonomy.query().getCount()

return view.render('studio/index', { postCount, postSeconds, seriesCount, topicCount })
}
}
2 changes: 1 addition & 1 deletion app/Controllers/Http/PostsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import AssetTypes from 'App/Enums/AssetTypes'
export default class PostsController {

public async index({ request, view, auth, bouncer, params }: HttpContextContract) {
await bouncer.with('StudioPolicy').authorize('viewPosts')
await bouncer.with('PostPolicy').authorize('viewList')

const { pattern } = request.qs()
const page = request.input('page', 1)
Expand Down
26 changes: 20 additions & 6 deletions app/Controllers/Http/TaxonomiesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import CacheService from 'App/Services/CacheService'
import AssetService from 'App/Services/AssetService';

export default class TaxonomiesController {
public async index({ view }: HttpContextContract) {
public async index({ view, bouncer }: HttpContextContract) {
await bouncer.with('TaxonomyPolicy').authorize('viewList')

const taxonomies = await Taxonomy.query()
.withCount('posts')
.withCount('collections')

return view.render('studio/taxonomies/index', { taxonomies })
}

public async create({ view, request }: HttpContextContract) {
public async create({ view, request, bouncer }: HttpContextContract) {
await bouncer.with('TaxonomyPolicy').authorize('create')

const { rootParentId, parentId } = request.qs()
const parent = parentId ? await Taxonomy.findOrFail(parentId) : null

Expand All @@ -25,7 +29,9 @@ export default class TaxonomiesController {
})
}

public async store({ request, response }: HttpContextContract) {
public async store({ request, response, bouncer }: HttpContextContract) {
await bouncer.with('TaxonomyPolicy').authorize('create')

const { postIds, ...data } = await request.validate(TaxonomyValidator)

const taxonomy = await Taxonomy.create(data)
Expand All @@ -37,17 +43,22 @@ export default class TaxonomiesController {

public async show({}: HttpContextContract) {}

public async edit({ view, params }: HttpContextContract) {
public async edit({ view, params, bouncer }: HttpContextContract) {
const taxonomy = await Taxonomy.findOrFail(params.id)

await bouncer.with('TaxonomyPolicy').authorize('update', taxonomy)

await taxonomy.load('asset')
await taxonomy.load('posts', query => query.orderBy('pivot_sort_order'))

return view.render('studio/taxonomies/createOrEdit', { taxonomy })
}

public async update({ request, response, params }: HttpContextContract) {
public async update({ request, response, params, bouncer }: HttpContextContract) {
const taxonomy = await Taxonomy.findOrFail(params.id)

await bouncer.with('TaxonomyPolicy').authorize('update', taxonomy)

const { postIds, assetTypeIds, altTexts, credits, ...data } = await request.validate(TaxonomyValidator)

if (data.assetId) {
Expand All @@ -61,8 +72,11 @@ export default class TaxonomiesController {
return response.redirect().toRoute('studio.taxonomies.index')
}

public async destroy({ response, params }: HttpContextContract) {
public async destroy({ response, params, bouncer }: HttpContextContract) {
const taxonomy = await Taxonomy.findOrFail(params.id)

await bouncer.with('TaxonomyPolicy').authorize('delete', taxonomy)

const flatChildren = await TaxonomyService.getFlatChildren(taxonomy.id)
const flatChildrenIds = flatChildren.reverse().map(c => c.id)

Expand Down
3 changes: 2 additions & 1 deletion app/Enums/Roles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
enum Role {
USER = 1,
ADMIN = 2
ADMIN = 2,
CONTRIBUTOR = 3
}

export default Role;
7 changes: 7 additions & 0 deletions app/Models/Taxonomy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import Database from '@ioc:Adonis/Lucid/Database'
import States from 'App/Enums/States'
import History from 'App/Models/History'
import HistoryTypes from 'App/Enums/HistoryTypes'
import User from './User'

export default class Taxonomy extends AppBaseModel {
@column({ isPrimary: true })
public id: number

@column()
public ownerId: number

@column()
public rootParentId: number | null

Expand Down Expand Up @@ -54,6 +58,9 @@ export default class Taxonomy extends AppBaseModel {
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime

@belongsTo(() => User)
public owner: BelongsTo<typeof User>

@belongsTo(() => Asset)
public asset: BelongsTo<typeof Asset>

Expand Down
4 changes: 4 additions & 0 deletions app/Policies/BasePolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export default class BasePolicy extends BouncerBasePolicy {
return user?.roleId === Role.ADMIN
}

protected canContribute(user: User | null) {
return this.isAdmin(user) || user?.roleId === Role.CONTRIBUTOR
}

protected isAuthenticated(user: User | null) {
return !!user
}
Expand Down
40 changes: 40 additions & 0 deletions app/Policies/CollectionPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import BasePolicy from './BasePolicy'
import User from 'App/Models/User'
import Collection from 'App/Models/Collection'

export default class CollectionPolicy extends BasePolicy {
public async before(user: User) {
if (this.isAdmin(user)) {
return true
}
}

public async viewList(user: User) {
return this.canContribute(user)
}

public async view(user: User, _collection: Collection) {
return this.canContribute(user)
}

public async feature(_user: User) {
return false
}

public async create(user: User) {
return this.canContribute(user)
}

public async update(user: User, _collection: Collection) {
return this.canContribute(user)
}

public async delete(user: User, collection: Collection) {
return this.isOwner(user, collection)
}

public async isOwner(user: User, collection: Collection) {
if (!collection) return this.canContribute(user)
return collection.ownerId === user.id
}
}
6 changes: 5 additions & 1 deletion app/Policies/PostPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ export default class PostPolicy extends BasePolicy {
}
}

public async viewList(user: User) {
return this.canContribute(user)
}

public async view(user: User, post: Post) {
const isOwner = await this.isOwner(user, post)
return isOwner || post.isViewable
}

public async store(user: User) {
return this.isAdmin(user)
return this.canContribute(user)
}

public async update(user: User, post: Post) {
Expand Down
4 changes: 2 additions & 2 deletions app/Policies/StudioPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default class StudioPolicy extends BasePolicy {
return false
}

public async viewDashboard(_: User) {
return false
public async viewDashboard(user: User) {
return this.canContribute(user)
}

public async viewPosts(_: User) {
Expand Down
50 changes: 30 additions & 20 deletions app/Policies/TaxonomyPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,38 @@ import User from 'App/Models/User'
import Taxonomy from 'App/Models/Taxonomy'

export default class TaxonomyPolicy extends BasePolicy {
public async viewList() {
return true
}


public async view(_: User, taxonomy: Taxonomy) {
// TODO: apply scopes to ensure collections & posts are public
await taxonomy.loadCount('collections')
await taxonomy.loadCount('posts')

return taxonomy.collections.length || taxonomy.posts.length
}
public async before(user: User) {
if (this.isAdmin(user)) {
return true
}
}

public async viewList(user: User) {
return this.canContribute(user)
}

public async view(user: User, _taxonomy: Taxonomy) {
return this.canContribute(user)
}

public async feature(_user: User) {
return false
}

public async create(user: User) {
return this.isAdmin(user)
}

public async update(user: User) {
return this.isAdmin(user)
}
return this.canContribute(user)
}

public async update(user: User, _taxonomy: Taxonomy) {
return this.canContribute(user)
}

public async delete(user: User, taxonomy: Taxonomy) {
return this.isOwner(user, taxonomy)
}

public async delete(user: User) {
return this.isAdmin(user)
public async isOwner(user: User, taxonomy: Taxonomy) {
if (!taxonomy) return this.canContribute(user)
return taxonomy.ownerId === user.id
}
}
10 changes: 5 additions & 5 deletions app/Services/CollectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ export default class CollectionService {
return subCollections
}

public static async updateOrCreate(collectionId: number | undefined, { postIds, taxonomyIds, assetTypeIds, altTexts, credits, subcollectionCollectionIds = [], subcollectionCollectionNames = [], subcollectionPostIds = [], ...data }: { [x: string]: any }) {
const collection = await Collection.firstOrNewById(collectionId)

await collection.merge(data).save()
public static async updateOrCreate(collection: Collection, { postIds, taxonomyIds, assetTypeIds, altTexts, credits, subcollectionCollectionIds = [], subcollectionCollectionNames = [], subcollectionPostIds = [], ...data }: { [x: string]: any }, isOwner: boolean) {
if (isOwner) {
await collection.merge(data).save()
}

if (data.assetId) {
if (isOwner && data.assetId) {
await AssetService.syncAssetTypes([data.assetId], assetTypeIds, altTexts, credits)
}

Expand Down
Loading

0 comments on commit d06a954

Please sign in to comment.