// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Contents.Models;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared;
using Squidex.Web;
using Squidex.Web.GraphQL;
namespace Squidex.Areas.Api.Controllers.Contents
{
[SchemaMustBePublished]
public sealed class ContentsController : ApiController
{
private readonly IContentQueryService contentQuery;
private readonly IContentWorkflow contentWorkflow;
private readonly GraphQLRunner graphQLRunner;
public ContentsController(ICommandBus commandBus,
IContentQueryService contentQuery,
IContentWorkflow contentWorkflow,
GraphQLRunner graphQLRunner)
: base(commandBus)
{
this.contentQuery = contentQuery;
this.contentWorkflow = contentWorkflow;
this.graphQLRunner = graphQLRunner;
}
///
/// GraphQL endpoint.
///
/// The name of the app.
///
/// 200 => Contents returned or mutated.
/// 404 => App not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[HttpPost]
[Route("content/{app}/graphql/")]
[Route("content/{app}/graphql/batch")]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
public Task GetGraphQL(string app)
{
return graphQLRunner.InvokeAsync(HttpContext);
}
///
/// Queries contents.
///
/// The name of the app.
/// The required query object.
///
/// 200 => Contents returned.
/// 404 => App not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetAllContents(string app, AllContentsByGetDto query)
{
var contents = await contentQuery.QueryAsync(Context, query?.ToQuery() ?? Q.Empty, HttpContext.RequestAborted);
var response = Deferred.AsyncResponse(() =>
{
return ContentsDto.FromContentsAsync(contents, Resources, null, contentWorkflow);
});
return Ok(response);
}
///
/// Queries contents.
///
/// The name of the app.
/// The required query object.
///
/// 200 => Contents returned.
/// 404 => App not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetAllContentsPost(string app, [FromBody] AllContentsByPostDto query)
{
var contents = await contentQuery.QueryAsync(Context, query?.ToQuery() ?? Q.Empty, HttpContext.RequestAborted);
var response = Deferred.AsyncResponse(() =>
{
return ContentsDto.FromContentsAsync(contents, Resources, null, contentWorkflow);
});
return Ok(response);
}
///
/// Queries contents.
///
/// The name of the app.
/// The name of the schema.
/// The optional ids of the content to fetch.
/// The optional json query.
///
/// 200 => Contents retunred.
/// 404 => Schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/{schema}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetContents(string app, string schema, [FromQuery] string? ids = null, [FromQuery] string? q = null)
{
var contents = await contentQuery.QueryAsync(Context, schema, CreateQuery(ids, q), HttpContext.RequestAborted);
var response = Deferred.AsyncResponse(() =>
{
return ContentsDto.FromContentsAsync(contents, Resources, Schema, contentWorkflow);
});
return Ok(response);
}
///
/// Queries contents.
///
/// The name of the app.
/// The name of the schema.
/// The required query object.
///
/// 200 => Contents returned.
/// 404 => Schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/{schema}/query")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetContentsPost(string app, string schema, [FromBody] QueryDto query)
{
var contents = await contentQuery.QueryAsync(Context, schema, query?.ToQuery() ?? Q.Empty, HttpContext.RequestAborted);
var response = Deferred.AsyncResponse(() =>
{
return ContentsDto.FromContentsAsync(contents, Resources, Schema, contentWorkflow);
});
return Ok(response);
}
///
/// Get a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content to fetch.
///
/// 200 => Content returned.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetContent(string app, string schema, DomainId id)
{
var content = await contentQuery.FindAsync(Context, schema, id, ct: HttpContext.RequestAborted);
if (content == null)
{
return NotFound();
}
var response = Deferred.Response(() =>
{
return ContentDto.FromDomain(content, Resources);
});
return Ok(response);
}
///
/// Get a content item validity.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content to fetch.
///
/// 204 => Content is valid.
/// 400 => Content not valid.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/{schema}/{id}/validity")]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetContentValidity(string app, string schema, DomainId id)
{
var command = new ValidateContent { ContentId = id };
await CommandBus.PublishAsync(command, HttpContext.RequestAborted);
return NoContent();
}
///
/// Get all references of a content.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content to fetch.
/// The optional json query.
///
/// 200 => Contents returned.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/{schema}/{id}/references")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetReferences(string app, string schema, DomainId id, [FromQuery] string? q = null)
{
var contents = await contentQuery.QueryAsync(Context, CreateQuery(null, q).WithReferencing(id), HttpContext.RequestAborted);
var response = Deferred.AsyncResponse(() =>
{
return ContentsDto.FromContentsAsync(contents, Resources, null, contentWorkflow);
});
return Ok(response);
}
///
/// Get a referencing contents of a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content to fetch.
/// The optional json query.
///
/// 200 => Content returned.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/{schema}/{id}/referencing")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
public async Task GetReferencing(string app, string schema, DomainId id, [FromQuery] string? q = null)
{
var contents = await contentQuery.QueryAsync(Context, CreateQuery(null, q).WithReference(id), HttpContext.RequestAborted);
var response = Deferred.AsyncResponse(() =>
{
return ContentsDto.FromContentsAsync(contents, Resources, null, contentWorkflow);
});
return Ok(response);
}
///
/// Get a content by version.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content to fetch.
/// The version fo the content to fetch.
///
/// 200 => Content version returned.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpGet]
[Route("content/{app}/{schema}/{id}/{version}/")]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsReadOwn)]
[ApiCosts(1)]
public async Task GetContentVersion(string app, string schema, DomainId id, int version)
{
var content = await contentQuery.FindAsync(Context, schema, id, version, HttpContext.RequestAborted);
if (content == null)
{
return NotFound();
}
var response = ContentDto.FromDomain(content, Resources);
return Ok(response.Data);
}
///
/// Create a content item.
///
/// The name of the app.
/// The name of the schema.
/// The request parameters.
///
/// 201 => Content created.
/// 400 => Content request not valid.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/{schema}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status201Created)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsCreate)]
[ApiCosts(1)]
public async Task PostContent(string app, string schema, CreateContentDto request)
{
var command = request.ToCommand();
var response = await InvokeCommandAsync(command);
return CreatedAtAction(nameof(GetContent), new { app, schema, id = command.ContentId }, response);
}
///
/// Import content items.
///
/// The name of the app.
/// The name of the schema.
/// The import request.
///
/// 200 => Contents created.
/// 400 => Content request not valid.
/// 404 => Content references, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/{schema}/import")]
[ProducesResponseType(typeof(BulkResultDto[]), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsCreate)]
[ApiCosts(5)]
[Obsolete("Use bulk endpoint now.")]
public async Task PostContents(string app, string schema, [FromBody] ImportContentsDto request)
{
var command = request.ToCommand();
var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted);
var result = context.Result();
var response = result.Select(x => BulkResultDto.FromDomain(x, HttpContext)).ToArray();
return Ok(response);
}
///
/// Bulk update content items.
///
/// The name of the app.
/// The name of the schema.
/// The bulk update request.
///
/// 201 => Contents created, update or delete.
/// 400 => Contents request not valid.
/// 404 => Contents references, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/{schema}/bulk")]
[ProducesResponseType(typeof(BulkResultDto[]), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsReadOwn)]
[ApiCosts(5)]
public async Task BulkUpdateContents(string app, string schema, [FromBody] BulkUpdateContentsDto request)
{
var command = request.ToCommand();
var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted);
var result = context.Result();
var response = result.Select(x => BulkResultDto.FromDomain(x, HttpContext)).ToArray();
return Ok(response);
}
///
/// Upsert a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to update.
/// The request parameters.
///
/// 200 => Content created or updated.
/// 400 => Content request not valid.
/// 404 => Content references, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsUpsert)]
[ApiCosts(1)]
public async Task PostUpsertContent(string app, string schema, DomainId id, UpsertContentDto request)
{
var command = request.ToCommand(id);
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Update a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to update.
/// The full data for the content item.
///
/// 200 => Content updated.
/// 400 => Content request not valid.
/// 404 => Content references, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPut]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsUpdateOwn)]
[ApiCosts(1)]
public async Task PutContent(string app, string schema, DomainId id, [FromBody] ContentData request)
{
var command = new UpdateContent { ContentId = id, Data = request };
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Patchs a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to patch.
/// The patch for the content item.
///
/// 200 => Content patched.
/// 400 => Content request not valid.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPatch]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsUpdateOwn)]
[ApiCosts(1)]
public async Task PatchContent(string app, string schema, DomainId id, [FromBody] ContentData request)
{
var command = new PatchContent { ContentId = id, Data = request };
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Change status of a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to change.
/// The status request.
///
/// 200 => Content status changed.
/// 400 => Content request not valid.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPut]
[Route("content/{app}/{schema}/{id}/status/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsChangeStatusOwn)]
[ApiCosts(1)]
public async Task PutContentStatus(string app, string schema, DomainId id, [FromBody] ChangeStatusDto request)
{
var command = request.ToCommand(id);
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Cancel status change of a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to cancel.
///
/// 200 => Content status change cancelled.
/// 400 => Content request not valid.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpDelete]
[Route("content/{app}/{schema}/{id}/status/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsChangeStatusOwn)]
[ApiCosts(1)]
public async Task DeleteContentStatus(string app, string schema, DomainId id)
{
var command = new CancelContentSchedule { ContentId = id };
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Create a new draft version.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to create the draft for.
///
/// 200 => Content draft created.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpPost]
[Route("content/{app}/{schema}/{id}/draft/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsVersionCreateOwn)]
[ApiCosts(1)]
public async Task CreateDraft(string app, string schema, DomainId id)
{
var command = new CreateContentDraft { ContentId = id };
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Delete the draft version.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to delete the draft from.
///
/// 200 => Content draft deleted.
/// 404 => Content, schema or app not found.
///
///
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
///
[HttpDelete]
[Route("content/{app}/{schema}/{id}/draft/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsVersionDeleteOwn)]
[ApiCosts(1)]
public async Task DeleteVersion(string app, string schema, DomainId id)
{
var command = new DeleteContentDraft { ContentId = id };
var response = await InvokeCommandAsync(command);
return Ok(response);
}
///
/// Delete a content item.
///
/// The name of the app.
/// The name of the schema.
/// The ID of the content item to delete.
/// The request parameters.
///
/// 204 => Content deleted.
/// 400 => Content cannot be deleted.
/// 404 => Content, schema or app not found.
///
///
/// You can create an generated documentation for your app at /api/content/{appName}/docs.
///
[HttpDelete]
[Route("content/{app}/{schema}/{id}/")]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsDeleteOwn)]
[ApiCosts(1)]
public async Task DeleteContent(string app, string schema, DomainId id, DeleteContentDto request)
{
var command = request.ToCommand(id);
await CommandBus.PublishAsync(command, HttpContext.RequestAborted);
return NoContent();
}
private async Task InvokeCommandAsync(ICommand command)
{
var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted);
var result = context.Result();
var response = ContentDto.FromDomain(result, Resources);
return response;
}
private Q CreateQuery(string? ids, string? q)
{
return Q.Empty
.WithIds(ids)
.WithJsonQuery(q)
.WithODataQuery(Request.QueryString.ToString());
}
}
}