1. Go to this page and download the library: Download convenia/graphql-laravel library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
namespace App\GraphQL\Schemas;
use Rebing\GraphQL\Support\Contracts\ConfigConvertible;
class DefaultSchema implements ConfigConvertible
{
public function toConfig(): array
{
return [
'query' => [
ExampleQuery::class,
],
'mutation' => [
ExampleMutation::class,
],
'types' => [
],
];
}
}
namespace App\GraphQL\Types;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
// Note: only necessary if you use `SelectFields`
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user',
// Use 'alias', if the database column is different from the type name.
// This is supported for discrete values as well as relations.
// - you can also use `DB::raw()` to solve more complex issues
// - or a callback returning the value (string or `DB::raw()` result)
'alias' => 'user_id',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'resolve' => function($root, array $args) {
// If you want to resolve the field yourself,
// it can be done here
return strtolower($root->email);
}
],
// Uses the 'getIsMeAttribute' function on our custom User model
'isMe' => [
'type' => Type::boolean(),
'description' => 'True, if the queried user is the current user',
'selectable' => false, // Does not try to query this from the database
]
];
}
// You can also resolve a field by declaring a method in the class
// with the following format resolve[FIELD_NAME]Field()
protected function resolveEmailField($root, array $args)
{
return strtolower($root->email);
}
}
namespace App\GraphQL\Mutations;
use Closure;
use GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
class UserProfilePhotoMutation extends Mutation
{
protected $attributes = [
'name' => 'userProfilePhoto',
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'profilePicture' => [
'name' => 'profilePicture',
'type' => GraphQL::type('Upload'),
'rules' => ['
class UpdateUserEmailMutation extends Mutation
{
//...
public function args(): array
{
return [
'id' => [
'name' => 'id',
'type' => Type::string(),
'rules' => ['
namespace App\GraphQL\Mutations;
use Closure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
class UpdateUserEmailMutation extends Mutation
{
protected $attributes = [
'name' => 'updateUserEmail'
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'id' => [
'name' => 'id',
'type' => Type::string(),
],
'email' => [
'name' => 'email',
'type' => Type::string(),
]
];
}
protected function rules(array $args = []): array
{
return [
'id' => ['
public function validationErrorMessages(array $args = []): array
{
return [
'name.ase enter your email address',
'email.email' => 'Please enter a valid email address',
'email.exists' => 'Sorry, this email address is already in use',
];
}
public function validationAttributes(array $args = []): array
{
return [
'email' => 'email address',
];
}
namespace App\GraphQL\Queries;
use Closure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\SelectFields;
use Rebing\GraphQL\Support\Query;
use SomeClassNamespace\SomeClassThatDoLogging;
class UsersQuery extends Query
{
protected $attributes = [
'name' => 'users',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('User'));
}
public function args(): array
{
return [
'id' => [
'name' => 'id',
'type' => Type::string(),
]
];
}
public function resolve($root, array $args, $context, ResolveInfo $info, SelectFields $fields, SomeClassThatDoLogging $logging)
{
$logging->log('fetched user');
$select = $fields->getSelect();
$with = $fields->getRelations();
$users = User::select($select)->with($with);
return $users->get();
}
}
namespace App\GraphQL\Middleware;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Pagination\Paginator;
use Rebing\GraphQL\Support\Middleware;
class ResolvePage extends Middleware
{
public function handle($root, array $args, $context, ResolveInfo $info, Closure $next)
{
Paginator::currentPageResolver(function () use ($args) {
return $args['pagination']['page'] ?? 1;
});
return $next($root, $args, $context, $info);
}
}
namespace App\GraphQL\Queries;
use App\GraphQL\Middleware;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\Query;
class UsersQuery extends Query
{
protected $middleware = [
Middleware\Logstash::class,
Middleware\ResolvePage::class,
];
}
namespace App\GraphQL\Queries;
use App\GraphQL\Middleware;
use Rebing\GraphQL\Support\Query as BaseQuery;
abstract class Query extends BaseQuery
{
protected $middleware = [
Middleware\Logstash::class,
Middleware\ResolvePage::class,
];
}
protected function getMiddleware(): array
{
return array_merge([...], $this->middleware);
}
namespace App\GraphQL\Middleware;
use Countable;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Rebing\GraphQL\Support\Middleware;
class Logstash extends Middleware
{
public function terminate($root, array $args, $context, ResolveInfo $info, $result): void
{
Log::channel('logstash')->info('', (
collect([
'query' => $info->fieldName,
'operation' => $info->operation->name->value ?? null,
'type' => $info->operation->operation,
'fields' => array_keys(Arr::dot($info->getFieldSelection($depth = PHP_INT_MAX))),
'schema' => Arr::first(Route::current()->parameters()) ?? Config::get('graphql.default_schema', 'default'),
'vars' => $this->formatVariableDefinitions($info->operation->variableDefinitions),
])
->when($result instanceof Countable, function ($metadata) use ($result) {
return $metadata->put('count', $result->count());
})
->when($result instanceof AbstractPaginator, function ($metadata) use ($result) {
return $metadata->put('per_page', $result->perPage());
})
->when($result instanceof LengthAwarePaginator, function ($metadata) use ($result) {
return $metadata->put('total', $result->total());
})
->merge($this->formatArguments($args))
->toArray()
));
}
private function formatArguments(array $args): array
{
return collect(Arr::sanitize($args))
->mapWithKeys(function ($value, $key) {
return ["\${$key}" => $value];
})
->toArray();
}
private function formatVariableDefinitions(?iterable $variableDefinitions = []): array
{
return collect($variableDefinitions)
->map(function ($def) {
return Printer::doPrint($def);
})
->toArray();
}
}
namespace App\GraphQL\Queries;
use Auth;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
class UsersQuery extends Query
{
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null, Closure $getSelectFields = null): bool
{
// true, if logged in
return ! Auth::guest();
}
// ...
}
namespace App\GraphQL\Queries;
use Auth;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
class UsersQuery extends Query
{
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null, Closure $getSelectFields = null): bool
{
if (isset($args['id'])) {
return Auth::id() == $args['id'];
}
return true;
}
// ...
}
namespace App\GraphQL\Queries;
use Auth;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
class UsersQuery extends Query
{
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null, Closure $getSelectFields = null): bool
{
if (isset($args['id'])) {
return Auth::id() == $args['id'];
}
return true;
}
public function getAuthorizationMessage(): string
{
return 'You are not authorized to perform this action';
}
// ...
}
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(array $args, $ctx): bool {
return $args['id'] == Auth::id();
}
]
];
}
// ...
}
use Auth;
use Rebing\GraphQL\Support\Privacy;
class MePrivacy extends Privacy
{
public function validate(array $queryArgs, $queryContext = null): bool
{
return $queryArgs['id'] == Auth::id();
}
}
use MePrivacy;
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => MePrivacy::class,
]
];
}
// ...
}
namespace App\GraphQL\Fields;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Field;
class PictureField extends Field
{
protected $attributes = [
'description' => 'A picture',
];
public function type(): Type
{
return Type::string();
}
public function args(): array
{
return [
'width' => [
'type' => Type::int(),
'description' => 'The width of the picture'
],
'height' => [
'type' => Type::int(),
'description' => 'The height of the picture'
]
];
}
protected function resolve($root, array $args)
{
$width = isset($args['width']) ? $args['width']:100;
$height = isset($args['height']) ? $args['height']:100;
return 'http://placehold.it/'.$width.'x'.$height;
}
}
namespace App\GraphQL\Types;
use App\GraphQL\Fields\PictureField;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
//Instead of passing an array, you pass a class path to your custom field
'picture' => PictureField::class
];
}
}
namespace App\GraphQL\Fields;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Field;
class FormattableDate extends Field
{
protected $attributes = [
'description' => 'A field that can output a date in all sorts of ways.',
];
public function __construct(array $settings = [])
{
$this->attributes = \array_merge($this->attributes, $settings);
}
public function type(): Type
{
return Type::string();
}
public function args(): array
{
return [
'format' => [
'type' => Type::string(),
'defaultValue' => 'Y-m-d H:i',
'description' => 'Defaults to Y-m-d H:i',
],
'relative' => [
'type' => Type::boolean(),
'defaultValue' => false,
],
];
}
protected function resolve($root, array $args): ?string
{
$date = $root->{$this->getProperty()};
if (!$date instanceof Carbon) {
return null;
}
if ($args['relative']) {
return $date->diffForHumans();
}
return $date->format($args['format']);
}
protected function getProperty(): string
{
return $this->attributes['alias'] ?? $this->attributes['name'];
}
}
namespace App\GraphQL\Types;
use App\GraphQL\Fields\FormattableDate;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
// You can simply supply an instance of the class
'dateOfBirth' => new FormattableDate,
// Because the constructor of `FormattableDate` accepts our the array of parameters,
// we can override them very easily.
// Imagine we want our field to be called `createdAt`, but our database column
// is called `created_at`:
'createdAt' => new FormattableDate([
'alias' => 'created_at',
])
];
}
}
namespace App\GraphQL\Queries;
use Closure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\SelectFields;
use Rebing\GraphQL\Support\Query;
class UsersQuery extends Query
{
protected $attributes = [
'name' => 'users',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('User'));
}
public function args(): array
{
return [
'id' => [
'name' => 'id',
'type' => Type::string(),
],
'email' => [
'name' => 'email',
'type' => Type::string(),
]
];
}
public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
{
/** @var SelectFields $fields */
$fields = $getSelectFields();
$select = $fields->getSelect();
$with = $fields->getRelations();
$users = User::select($select)->with($with);
return $users->get();
}
}
namespace App\GraphQL\Types;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
/**
* @var array
*/
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
/**
* @return array
*/
public function fields(): array
{
return [
'uuid' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The uuid of the user'
],
'email' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The email of user'
],
'profile' => [
'type' => GraphQL::type('Profile'),
'description' => 'The user profile',
],
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'description' => 'The user posts',
// Can also be defined as a string
'always' => ['title', 'body'],
]
];
}
}
class ProfileType extends GraphQLType
{
protected $attributes = [
'name' => 'Profile',
'description' => 'A user profile',
'model' => UserProfileModel::class,
];
public function fields(): array
{
return [
'name' => [
'type' => Type::string(),
'description' => 'The name of user'
]
];
}
}
class PostType extends GraphQLType
{
protected $attributes = [
'name' => 'Post',
'description' => 'A post',
'model' => PostModel::class,
];
public function fields(): array
{
return [
'title' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The title of the post'
],
'body' => [
'type' => Type::string(),
'description' => 'The body the post'
]
];
}
}
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
// ...
// Relation
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'description' => 'A list of posts written by the user',
'args' => [
'date_from' => [
'type' => Type::string(),
],
],
// $args are the local arguments passed to the relation
// $query is the relation builder object
// $ctx is the GraphQL context (can be customized by overriding `\Rebing\GraphQL\GraphQLController::queryContext`
// The return value should be the query builder or void
'query' => function (array $args, $query, $ctx): void {
$query->addSelect('some_column')
->where('posts.created_at', '>', $args['date_from']);
}
]
];
}
}
namespace App\GraphQL\Queries;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class PostsQuery extends Query
{
public function type(): Type
{
return GraphQL::paginate('posts');
}
// ...
public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
{
$fields = $getSelectFields();
return Post::with($fields->getRelations())
->select($fields->getSelect())
->paginate($args['limit'], ['*'], 'page', $args['page']);
}
}
namespace App\GraphQL\Queries;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class PostsQuery extends Query
{
public function type(): Type
{
return Type::nonNull(GraphQL::simplePaginate('posts'));
}
// ...
public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
{
$fields = $getSelectFields();
return Post::with($fields->getRelations())
->select($fields->getSelect())
->simplePaginate($args['limit'], ['*'], 'page', $args['page']);
}
}
namespace App\GraphQL\Enums;
use Rebing\GraphQL\Support\EnumType;
class EpisodeEnum extends EnumType
{
protected $attributes = [
'name' => 'episode',
'description' => 'The types of demographic elements',
'values' => [
'NEWHOPE' => 'NEWHOPE',
'EMPIRE' => 'EMPIRE',
'JEDI' => 'JEDI',
],
];
}
namespace App\GraphQL\Types;
use Rebing\GraphQL\Support\Type as GraphQLType;
class TestType extends GraphQLType
{
public function fields(): array
{
return [
'episode_type' => [
'type' => GraphQL::type('EpisodeEnum')
]
];
}
}
namespace App\GraphQL\Unions;
use App\Post;
use GraphQL;
use Rebing\GraphQL\Support\UnionType;
class SearchResultUnion extends UnionType
{
protected $attributes = [
'name' => 'searchResult',
];
public function types(): array
{
return [
GraphQL::type('Post'),
GraphQL::type('Episode'),
];
}
public function resolveType($value)
{
if ($value instanceof Post) {
return GraphQL::type('Post');
} elseif ($value instanceof Episode) {
return GraphQL::type('Episode');
}
}
}
namespace App\GraphQL\Interfaces;
use GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\InterfaceType;
class CharacterInterface extends InterfaceType
{
protected $attributes = [
'name' => 'character',
'description' => 'Character interface.',
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the character.'
],
'name' => Type::string(),
'appearsIn' => [
'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))),
'description' => 'A list of episodes in which the character has an appearance.'
],
];
}
public function resolveType($root)
{
// Use the resolveType to resolve the Type which is implemented trough this interface
$type = $root['type'];
if ($type === 'human') {
return GraphQL::type('Human');
} elseif ($type === 'droid') {
return GraphQL::type('Droid');
}
}
}
namespace App\GraphQL\Types;
use GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
use GraphQL\Type\Definition\Type;
class HumanType extends GraphQLType
{
protected $attributes = [
'name' => 'human',
'description' => 'A human.'
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the human.',
],
'name' => Type::string(),
'appearsIn' => [
'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))),
'description' => 'A list of episodes in which the human has an appearance.'
],
'totalCredits' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The total amount of credits this human owns.'
]
];
}
public function interfaces(): array
{
return [
GraphQL::type('Character')
];
}
}
public function types(): array
{
return[
GraphQL::type('Human'),
GraphQL::type('Droid'),
];
}
public function fields(): array
{
$interface = GraphQL::type('Character');
return [
$interface->getField('id'),
$interface->getField('name'),
$interface->getField('appearsIn'),
'totalCredits' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The total amount of credits this human owns.'
]
];
}
public function fields(): array
{
$interface = GraphQL::type('Character');
return array_merge($interface->getFields(), [
'totalCredits' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The total amount of credits this human owns.'
]
]);
}
namespace App\GraphQL\InputObject;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\InputType;
class ReviewInput extends InputType
{
protected $attributes = [
'name' => 'reviewInput',
'description' => 'A review with a comment and a score (0 to 5)'
];
public function fields(): array
{
return [
'comment' => [
'name' => 'comment',
'description' => 'A comment (250 max chars)',
'type' => Type::string(),
// You can define Laravel Validation here
'rules' => ['max:250']
],
'score' => [
'name' => 'score',
'description' => 'A score (0 to 5)',
'type' => Type::int(),
// You must use 'integer' on rules if you want to validate if the number is inside a range
// Otherwise it will validate the number of 'characters' the number can have.
'rules' => ['integer', 'min:0', 'max:5']
]
];
}
}
namespace App\GraphQL\InputObject;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\InputType;
class UserInput extends InputType
{
protected $attributes = [
'name' => 'userInput',
'description' => 'A user.'
];
public function fields(): array
{
return [
'firstName' => [
'alias' => 'first_name',
'description' => 'The first name of the user',
'type' => Type::string(),
'rules' => ['max:30']
],
'lastName' => [
'alias' => 'last_name',
'description' => 'The last name of the user',
'type' => Type::string(),
'rules' => ['max:30']
]
];
}
}
namespace App\GraphQL\Mutations;
use Closure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Mutation;
class UpdateUserMutation extends Mutation
{
protected $attributes = [
'name' => 'updateUser'
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string())
],
'input' => [
'type' => GraphQL::type('UserInput')
]
];
}
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
{
$user = User::find($args['id']);
$user->fill($args['input']));
$user->save();
return $user;
}
}
class UserType extends GraphQLType
{
// ...
public function fields(): array
{
return [
// ...
// JSON column containing all posts made by this user
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'description' => 'A list of posts written by the user',
// Now this will simply request the "posts" column, and it won't
// query for all the underlying columns in the "post" object
// The value defaults to true
'is_relation' => false
]
];
}
// ...
}
namespace App\GraphQL\Types;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
],
'address' => [
'type' => Type::string(),
'description' => 'The address of user',
'deprecationReason' => 'Deprecated due to address field split'
],
'address_line_1' => [
'type' => Type::string(),
'description' => 'The address line 1 of user',
],
'address_line_2' => [
'type' => Type::string(),
'description' => 'The address line 2 of user',
],
];
}
}
namespace App\Providers;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\ServiceProvider;
use Rebing\GraphQL\Support\Facades\GraphQL;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
GraphQL::macro('listOf', function (string $name): Type {
return Type::listOf(GraphQL::type($name));
});
}
}
public function type(): Type
{
return GraphQL::wrapType(
'PostType',
'PostMessageType',
\App\GraphQL\Types\WrapMessagesType::class,
);
}
public function resolve($root, array $args)
{
return [
'data' => Post::find($args['post_id']),
'messages' => new Collection([
new SimpleMessage("Congratulations, the post was found"),
new SimpleMessage("This post cannot be edited", "warning"),
]),
];
}