mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
feat: admin section with repair seeds+add role helpers
This commit is contained in:
@@ -67,6 +67,10 @@ The forum integrates seamlessly with your Nextcloud instance, using your existin
|
||||
<command>OCA\Forum\Command\SetRole</command>
|
||||
<command>OCA\Forum\Command\TestNotifier</command>
|
||||
</commands>
|
||||
<settings>
|
||||
<admin>OCA\Forum\Settings\AdminSettings</admin>
|
||||
<admin-section>OCA\Forum\Sections\AdminSection</admin-section>
|
||||
</settings>
|
||||
<navigations>
|
||||
<navigation role="all">
|
||||
<name>Forum</name>
|
||||
|
||||
@@ -11,9 +11,12 @@ use OCA\Forum\Attribute\RequirePermission;
|
||||
use OCA\Forum\Db\CategoryMapper;
|
||||
use OCA\Forum\Db\ForumUserMapper;
|
||||
use OCA\Forum\Db\PostMapper;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Db\ThreadMapper;
|
||||
use OCA\Forum\Db\UserRoleMapper;
|
||||
use OCA\Forum\Migration\SeedHelper;
|
||||
use OCA\Forum\Service\AdminSettingsService;
|
||||
use OCA\Forum\Service\UserRoleService;
|
||||
use OCA\Forum\Service\UserService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
@@ -23,6 +26,7 @@ use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Migration\IOutput;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class AdminController extends OCSController {
|
||||
@@ -36,6 +40,8 @@ class AdminController extends OCSController {
|
||||
private PostMapper $postMapper,
|
||||
private CategoryMapper $categoryMapper,
|
||||
private UserRoleMapper $userRoleMapper,
|
||||
private RoleMapper $roleMapper,
|
||||
private UserRoleService $userRoleService,
|
||||
private IUserManager $userManager,
|
||||
private IUserSession $userSession,
|
||||
private AdminSettingsService $settingsService,
|
||||
@@ -228,4 +234,202 @@ class AdminController extends OCSController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the repair seeds command to restore default forum data
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{success: bool, message: string}, array{}>
|
||||
*
|
||||
* 200: Seeds repaired successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/admin/repair-seeds')]
|
||||
public function repairSeeds(): DataResponse {
|
||||
try {
|
||||
$messages = [];
|
||||
$migrationOutput = new class($messages) implements IOutput {
|
||||
/** @var array<string> */
|
||||
private array $messages;
|
||||
|
||||
public function __construct(array &$messages) {
|
||||
$this->messages = &$messages;
|
||||
}
|
||||
|
||||
public function info($message): void {
|
||||
$this->messages[] = $message;
|
||||
}
|
||||
|
||||
public function warning($message): void {
|
||||
$this->messages[] = '[Warning] ' . $message;
|
||||
}
|
||||
|
||||
public function debug($message): void {
|
||||
$this->messages[] = '[Debug] ' . $message;
|
||||
}
|
||||
|
||||
public function startProgress($max = 0): void {
|
||||
}
|
||||
|
||||
public function advance($step = 1, $description = ''): void {
|
||||
}
|
||||
|
||||
public function finishProgress(): void {
|
||||
}
|
||||
};
|
||||
|
||||
SeedHelper::seedAll($migrationOutput, true);
|
||||
|
||||
$this->logger->info('Forum repair seeds completed successfully');
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => implode("\n", $messages),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error running repair seeds: ' . $e->getMessage());
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => 'Failed to repair seeds: ' . $e->getMessage(),
|
||||
], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available roles
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{roles: list<array<string, mixed>>}, array{}>
|
||||
*
|
||||
* 200: Roles list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/admin/roles')]
|
||||
public function getRoles(): DataResponse {
|
||||
try {
|
||||
$roles = $this->roleMapper->findAll();
|
||||
$rolesData = array_map(fn ($role) => [
|
||||
'id' => $role->getId(),
|
||||
'name' => $role->getName(),
|
||||
'roleType' => $role->getRoleType(),
|
||||
], $roles);
|
||||
return new DataResponse(['roles' => $rolesData]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error fetching roles: ' . $e->getMessage());
|
||||
return new DataResponse(['error' => 'Failed to fetch roles'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a role to a user
|
||||
*
|
||||
* @param string $userId The user ID
|
||||
* @param int $roleId The role ID to assign
|
||||
* @return DataResponse<Http::STATUS_OK, array{success: bool, message: string}, array{}>
|
||||
*
|
||||
* 200: Role assigned successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/admin/users/{userId}/roles')]
|
||||
public function assignRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
// Check if user exists
|
||||
$user = $this->userManager->get($userId);
|
||||
if ($user === null) {
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => "User '$userId' does not exist.",
|
||||
], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if role exists
|
||||
try {
|
||||
$role = $this->roleMapper->find($roleId);
|
||||
} catch (\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => "Role with ID '$roleId' does not exist.",
|
||||
], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if user already has this role
|
||||
if ($this->userRoleService->hasRole($userId, $roleId)) {
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => "User '$userId' already has the role '{$role->getName()}'.",
|
||||
]);
|
||||
}
|
||||
|
||||
// Assign the role
|
||||
$this->userRoleService->assignRole($userId, $roleId, skipIfExists: false);
|
||||
$this->logger->info("Assigned role '{$role->getName()}' to user '$userId'");
|
||||
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => "Successfully assigned role '{$role->getName()}' to user '$userId'.",
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error assigning role: ' . $e->getMessage());
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => 'Failed to assign role: ' . $e->getMessage(),
|
||||
], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a role from a user
|
||||
*
|
||||
* @param string $userId The user ID
|
||||
* @param int $roleId The role ID to remove
|
||||
* @return DataResponse<Http::STATUS_OK, array{success: bool, message: string}, array{}>
|
||||
*
|
||||
* 200: Role removed successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'DELETE', url: '/api/admin/users/{userId}/roles/{roleId}')]
|
||||
public function removeRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
// Check if user exists
|
||||
$user = $this->userManager->get($userId);
|
||||
if ($user === null) {
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => "User '$userId' does not exist.",
|
||||
], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if role exists
|
||||
try {
|
||||
$role = $this->roleMapper->find($roleId);
|
||||
} catch (\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => "Role with ID '$roleId' does not exist.",
|
||||
], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Remove the role
|
||||
$removed = $this->userRoleService->removeRole($userId, $roleId);
|
||||
if (!$removed) {
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => "User '$userId' does not have the role '{$role->getName()}'.",
|
||||
]);
|
||||
}
|
||||
|
||||
$this->logger->info("Removed role '{$role->getName()}' from user '$userId'");
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => "Successfully removed role '{$role->getName()}' from user '$userId'.",
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error removing role: ' . $e->getMessage());
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => 'Failed to remove role: ' . $e->getMessage(),
|
||||
], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
lib/Sections/AdminSection.php
Normal file
32
lib/Sections/AdminSection.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Forum\Sections;
|
||||
|
||||
use OCA\Forum\AppInfo;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\IIconSection;
|
||||
|
||||
class AdminSection implements IIconSection {
|
||||
public function __construct(
|
||||
private IL10N $l,
|
||||
private IURLGenerator $urlGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath(AppInfo\Application::APP_ID, 'app-dark.svg');
|
||||
}
|
||||
|
||||
public function getID(): string {
|
||||
return AppInfo\Application::APP_ID;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l->t('Forum');
|
||||
}
|
||||
|
||||
public function getPriority(): int {
|
||||
return 80;
|
||||
}
|
||||
}
|
||||
42
lib/Settings/AdminSettings.php
Normal file
42
lib/Settings/AdminSettings.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Forum\Settings;
|
||||
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\Settings\ISettings;
|
||||
|
||||
class AdminSettings implements ISettings {
|
||||
public function __construct(
|
||||
private IAppConfig $config,
|
||||
private IL10N $l,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm(): TemplateResponse {
|
||||
return new TemplateResponse(Application::APP_ID, 'settings', [
|
||||
'script' => Application::getViteEntryScript('admin.ts'),
|
||||
'style' => Application::getViteEntryScript('style.css'),
|
||||
], '');
|
||||
}
|
||||
|
||||
public function getSection(): string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the form should be rather on the top or bottom of
|
||||
* the admin section. The forms are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
*
|
||||
* E.g.: 70
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
454
openapi.json
454
openapi.json
@@ -454,6 +454,460 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/repair-seeds": {
|
||||
"post": {
|
||||
"operationId": "admin-repair-seeds",
|
||||
"summary": "Run the repair seeds command to restore default forum data",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Seeds repaired successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"success",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/roles": {
|
||||
"get": {
|
||||
"operationId": "admin-get-roles",
|
||||
"summary": "Get all available roles",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Roles list returned",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"roles"
|
||||
],
|
||||
"properties": {
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/users/{userId}/roles": {
|
||||
"post": {
|
||||
"operationId": "admin-assign-role",
|
||||
"summary": "Assign a role to a user",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"roleId"
|
||||
],
|
||||
"properties": {
|
||||
"roleId": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The role ID to assign"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
"description": "The user ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Role assigned successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"success",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/users/{userId}/roles/{roleId}": {
|
||||
"delete": {
|
||||
"operationId": "admin-remove-role",
|
||||
"summary": "Remove a role from a user",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
"description": "The user ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "roleId",
|
||||
"in": "path",
|
||||
"description": "The role ID to remove",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Role removed successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"success",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/bbcodes": {
|
||||
"get": {
|
||||
"operationId": "bb_code-index",
|
||||
|
||||
277
src/AdminSettings.vue
Normal file
277
src/AdminSettings.vue
Normal file
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div id="forum-settings" class="section">
|
||||
<h2>{{ strings.title }}</h2>
|
||||
|
||||
<NcSettingsSection :name="strings.repairSeedsHeader">
|
||||
<NcNoteCard type="info">
|
||||
{{ strings.repairSeedsHelp }}
|
||||
</NcNoteCard>
|
||||
|
||||
<div class="settings-section-content">
|
||||
<div class="repair-seeds-container">
|
||||
<NcButton :disabled="repairSeedsLoading" @click="runRepairSeeds">
|
||||
<template #icon>
|
||||
<WrenchIcon v-if="!repairSeedsLoading" :size="20" />
|
||||
<NcLoadingIcon v-else :size="20" />
|
||||
</template>
|
||||
{{ strings.runRepairSeeds }}
|
||||
</NcButton>
|
||||
|
||||
<NcNoteCard v-if="repairSeedsResult" :type="repairSeedsSuccess ? 'success' : 'error'">
|
||||
<pre class="repair-seeds-output">{{ repairSeedsResult }}</pre>
|
||||
</NcNoteCard>
|
||||
</div>
|
||||
</div>
|
||||
</NcSettingsSection>
|
||||
|
||||
<NcSettingsSection :name="strings.userRolesHeader">
|
||||
<NcNoteCard type="info">
|
||||
{{ strings.userRolesHelp }}
|
||||
</NcNoteCard>
|
||||
|
||||
<div class="settings-section-content">
|
||||
<div class="user-role-form">
|
||||
<div class="field-row">
|
||||
<div class="form-group">
|
||||
<label for="user-id">{{ strings.userIdLabel }}</label>
|
||||
<NcTextField
|
||||
id="user-id"
|
||||
v-model="userId"
|
||||
:placeholder="strings.userIdPlaceholder"
|
||||
:disabled="assignRoleLoading"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role-select">{{ strings.roleLabel }}</label>
|
||||
<NcSelect
|
||||
input-id="role-select"
|
||||
v-model="selectedRole"
|
||||
:options="roleOptions"
|
||||
:placeholder="strings.rolePlaceholder"
|
||||
:disabled="assignRoleLoading || rolesLoading"
|
||||
:loading="rolesLoading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<NcButton
|
||||
variant="primary"
|
||||
:disabled="!canAssignRole || assignRoleLoading"
|
||||
@click="assignRole"
|
||||
>
|
||||
<template #icon>
|
||||
<PlusIcon v-if="!assignRoleLoading" :size="20" />
|
||||
<NcLoadingIcon v-else :size="20" />
|
||||
</template>
|
||||
{{ strings.assignRole }}
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<NcNoteCard v-if="assignRoleResult" :type="assignRoleSuccess ? 'success' : 'error'">
|
||||
<p>{{ assignRoleResult }}</p>
|
||||
</NcNoteCard>
|
||||
</div>
|
||||
</div>
|
||||
</NcSettingsSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
||||
import NcSelect from '@nextcloud/vue/components/NcSelect'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import WrenchIcon from '@icons/Wrench.vue'
|
||||
import PlusIcon from '@icons/Plus.vue'
|
||||
|
||||
import { ocs } from '@/axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
export default {
|
||||
name: 'AdminSettings',
|
||||
components: {
|
||||
NcSettingsSection,
|
||||
NcButton,
|
||||
NcSelect,
|
||||
NcNoteCard,
|
||||
NcTextField,
|
||||
NcLoadingIcon,
|
||||
WrenchIcon,
|
||||
PlusIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Repair seeds
|
||||
repairSeedsLoading: false,
|
||||
repairSeedsResult: null,
|
||||
repairSeedsSuccess: false,
|
||||
|
||||
// User roles
|
||||
rolesLoading: true,
|
||||
roles: [],
|
||||
userId: '',
|
||||
selectedRole: null,
|
||||
assignRoleLoading: false,
|
||||
assignRoleResult: null,
|
||||
assignRoleSuccess: false,
|
||||
|
||||
strings: {
|
||||
title: t('forum', 'Forum'),
|
||||
repairSeedsHeader: t('forum', 'Repair Seeds'),
|
||||
repairSeedsHelp: t(
|
||||
'forum',
|
||||
'Run the repair seeds command to restore default forum data (roles, categories, permissions, BBCodes). This is safe to run multiple times as it will skip data that already exists.',
|
||||
),
|
||||
runRepairSeeds: t('forum', 'Run Repair Seeds'),
|
||||
userRolesHeader: t('forum', 'User Roles'),
|
||||
userRolesHelp: t(
|
||||
'forum',
|
||||
'Assign forum roles to users. This allows you to grant administrative or moderator privileges to specific users.',
|
||||
),
|
||||
userIdLabel: t('forum', 'User ID'),
|
||||
userIdPlaceholder: t('forum', 'Enter user ID'),
|
||||
roleLabel: t('forum', 'Role'),
|
||||
rolePlaceholder: t('forum', 'Select a role'),
|
||||
assignRole: t('forum', 'Assign Role'),
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
roleOptions() {
|
||||
return this.roles.map((role) => ({
|
||||
id: role.id,
|
||||
label: role.name,
|
||||
}))
|
||||
},
|
||||
canAssignRole() {
|
||||
return this.userId.trim() !== '' && this.selectedRole !== null
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchRoles()
|
||||
},
|
||||
methods: {
|
||||
async fetchRoles() {
|
||||
try {
|
||||
this.rolesLoading = true
|
||||
const resp = await ocs.get('/admin/roles')
|
||||
this.roles = resp.data.roles
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch roles', e)
|
||||
} finally {
|
||||
this.rolesLoading = false
|
||||
}
|
||||
},
|
||||
async runRepairSeeds() {
|
||||
try {
|
||||
this.repairSeedsLoading = true
|
||||
this.repairSeedsResult = null
|
||||
const resp = await ocs.post('/admin/repair-seeds')
|
||||
this.repairSeedsSuccess = resp.data.success
|
||||
this.repairSeedsResult = resp.data.message
|
||||
if (resp.data.success) {
|
||||
await this.fetchRoles()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to run repair seeds', e)
|
||||
this.repairSeedsSuccess = false
|
||||
this.repairSeedsResult =
|
||||
e.response?.data?.message || t('forum', 'Failed to run repair seeds')
|
||||
} finally {
|
||||
this.repairSeedsLoading = false
|
||||
}
|
||||
},
|
||||
async assignRole() {
|
||||
if (!this.canAssignRole) return
|
||||
|
||||
try {
|
||||
this.assignRoleLoading = true
|
||||
this.assignRoleResult = null
|
||||
const resp = await ocs.post(
|
||||
`/admin/users/${encodeURIComponent(this.userId.trim())}/roles`,
|
||||
{
|
||||
roleId: this.selectedRole.id,
|
||||
},
|
||||
)
|
||||
this.assignRoleSuccess = resp.data.success
|
||||
this.assignRoleResult = resp.data.message
|
||||
if (resp.data.success) {
|
||||
// Clear form on success
|
||||
this.userId = ''
|
||||
this.selectedRole = null
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to assign role', e)
|
||||
this.assignRoleSuccess = false
|
||||
this.assignRoleResult = e.response?.data?.message || t('forum', 'Failed to assign role')
|
||||
} finally {
|
||||
this.assignRoleLoading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#forum-settings {
|
||||
h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.settings-section-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.repair-seeds-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.repair-seeds-output {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.user-role-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-width: 600px;
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.form-group {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
src/admin.ts
Normal file
5
src/admin.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import AdminSettings from './AdminSettings.vue'
|
||||
import './style.scss'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
createApp(AdminSettings).mount('#forum-settings')
|
||||
12
templates/settings.php
Normal file
12
templates/settings.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCP\Util;
|
||||
|
||||
/* @var array $_ */
|
||||
$script = $_['script'];
|
||||
$style = $_['style'];
|
||||
Util::addScript(Application::APP_ID, Application::JS_DIR . "/$script");
|
||||
Util::addStyle(Application::APP_ID, Application::CSS_DIR . "/$style");
|
||||
?>
|
||||
<div id="forum-settings"></div>
|
||||
@@ -34,6 +34,7 @@ const nextcloudSharedList = [
|
||||
export default createAppConfig(
|
||||
{
|
||||
app: path.resolve(path.join('src', 'app.ts')),
|
||||
admin: path.resolve(path.join('src', 'admin.ts')),
|
||||
},
|
||||
{
|
||||
config: {
|
||||
|
||||
Reference in New Issue
Block a user