Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions app/Controllers/Http/ParticipationImagesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ import InvalidInput from 'App/Exceptions/InvalidInput'
import { selectedImageIds } from 'App/Helpers/coCreatorAttribution'

export default class ParticipationImagesController extends BaseController {
/**
* Contributions for a set, paginated.
*
* Previously these were embedded in the set submission response in full,
* which meant every visitor downloaded every contribution before the page
* could paint. Filter by `filter[creatorAddress]` to page through a single
* artist's work.
*/
public async list({ params, request }: HttpContextContract) {
const { page = 1, limit = 24, filter = {}, sort = '-createdAt' } = request.qs()

const submission = await SetSubmission.query().where('uuid', params.id).firstOrFail()

const query = ParticipationImage.query()
.where('setSubmissionId', submission.id)
.whereNull('deletedAt')
.preload('image')
.preload('creator', (creatorQuery) => creatorQuery.preload('pfp'))

await this.applyFilters(query, filter)
await this.applySorts(query, sort)

return query.paginate(page, limit)
}

/** Contribution and contributor counts, so a client can size a list without fetching it. */
public async stats({ params }: HttpContextContract) {
const submission = await SetSubmission.query().where('uuid', params.id).firstOrFail()

const [contributions] = await Database.from('participation_images')
.where('set_submission_id', submission.id)
.whereNull('deleted_at')
.count('* as total')
.countDistinct('creator_address as contributors')

return {
contributions: Number(contributions.total),
contributors: Number(contributions.contributors),
}
}

public async store({ request, session }: HttpContextContract) {
const address = session.get('siwe')?.address?.toLowerCase()
if (!address) throw new NotAuthenticated()
Expand Down
25 changes: 18 additions & 7 deletions app/Controllers/Http/SetSubmissionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ export default class SetSubmissionsController extends BaseController {
return submission
}

public async show({ params }: HttpContextContract) {
public async show({ params, request }: HttpContextContract) {
const { includes = [] } = request.qs()
const requestedIncludes = Array.isArray(includes) ? includes : [includes]

const submission = await SetSubmission.query()
.where('uuid', params.id)
.preload('set')
Expand All @@ -177,12 +180,20 @@ export default class SetSubmissionsController extends BaseController {
query.preload('cover')
query.orderBy('sortIndex')
})
.preload('participationImages', (query) => {
query.whereNull('deletedAt')
query.preload('image')
query.preload('creator', (creatorQuery) => creatorQuery.preload('pfp'))
query.orderBy('createdAt', 'desc')
})
/*
* Only embedded when asked for. A busy set carries thousands of
* contributions, each with its image and creator, which made this
* response several megabytes and dwarfed everything else on it.
* Use the paginated `/:id/participation` route instead.
*/
.if(requestedIncludes.includes('participationImages'), (query) =>
query.preload('participationImages', (participationQuery) => {
participationQuery.whereNull('deletedAt')
participationQuery.preload('image')
participationQuery.preload('creator', (creatorQuery) => creatorQuery.preload('pfp'))
participationQuery.orderBy('createdAt', 'desc')
}),
)
.firstOrFail()
// TODO: Implement rich content links
// .preload('richContentLinks', query => {
Expand Down
3 changes: 3 additions & 0 deletions start/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ Route.group(() => {
Route.post('/:id/subscribe', 'SetSubscriptionsController.subscribe')
}).middleware(['auth'])

Route.get('/:id/participation', 'ParticipationImagesController.list')
Route.get('/:id/participation-stats', 'ParticipationImagesController.stats')

Route.get('/:id/subscribers', 'SetSubscriptionsController.listSubscribers')
Route.get('/:id/history', 'SetSubscriptionsController.history')
Route.get('/:id/nodes-stats', 'SetSubscriptionsController.nodesStats')
Expand Down