Merge pull request #41963 from nextcloud/backport/41957/stable28

[stable28] feat(out-of-office): Add OCS endpoint to set and clear absence
This commit is contained in:
Joas Schilling 2023-12-01 11:39:12 +01:00 committed by GitHub
commit 385495050b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 360 additions and 112 deletions

View file

@ -30,11 +30,11 @@ return [
['name' => 'invitation_response#decline', 'url' => '/invitation/decline/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#options', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#processMoreOptionsResult', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'POST'],
['name' => 'availability_settings#updateAbsence', 'url' => '/settings/absence', 'verb' => 'POST'],
['name' => 'availability_settings#clearAbsence', 'url' => '/settings/absence', 'verb' => 'DELETE'],
],
'ocs' => [
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],
['name' => 'out_of_office#getCurrentOutOfOfficeData', 'url' => '/api/v1/outOfOffice/{userId}', 'verb' => 'GET'],
['name' => 'out_of_office#setOutOfOffice', 'url' => '/api/v1/outOfOffice/{userId}', 'verb' => 'POST'],
['name' => 'out_of_office#clearOutOfOffice', 'url' => '/api/v1/outOfOffice/{userId}', 'verb' => 'DELETE'],
],
];

View file

@ -193,7 +193,6 @@ return array(
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => $baseDir . '/../lib/Connector/Sabre/SharesPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\TagList' => $baseDir . '/../lib/Connector/Sabre/TagList.php',
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Controller\\AvailabilitySettingsController' => $baseDir . '/../lib/Controller/AvailabilitySettingsController.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',

View file

@ -208,7 +208,6 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/SharesPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\TagList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagList.php',
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Controller\\AvailabilitySettingsController' => __DIR__ . '/..' . '/../lib/Controller/AvailabilitySettingsController.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',

View file

@ -1,99 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Controller;
use DateTimeImmutable;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\Service\AbsenceService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\User\IAvailabilityCoordinator;
class AvailabilitySettingsController extends Controller {
public function __construct(
IRequest $request,
private ?IUserSession $userSession,
private AbsenceService $absenceService,
private IAvailabilityCoordinator $coordinator,
) {
parent::__construct(Application::APP_ID, $request);
}
/**
* @throws \OCP\DB\Exception
* @throws \Exception
*/
#[NoAdminRequired]
public function updateAbsence(
string $firstDay,
string $lastDay,
string $status,
string $message,
): Response {
$user = $this->userSession?->getUser();
if ($user === null) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$parsedFirstDay = new DateTimeImmutable($firstDay);
$parsedLastDay = new DateTimeImmutable($lastDay);
if ($parsedFirstDay->getTimestamp() >= $parsedLastDay->getTimestamp()) {
throw new \Exception('First day is on or after last day');
}
$absence = $this->absenceService->createOrUpdateAbsence(
$user,
$firstDay,
$lastDay,
$status,
$message,
);
$this->coordinator->clearCache($user->getUID());
return new JSONResponse($absence);
}
/**
* @throws \OCP\DB\Exception
*/
#[NoAdminRequired]
public function clearAbsence(): Response {
$user = $this->userSession?->getUser();
if ($user === null) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->absenceService->clearAbsence($user);
$this->coordinator->clearCache($user->getUID());
return new JSONResponse([]);
}
}

View file

@ -26,14 +26,18 @@ declare(strict_types=1);
namespace OCA\DAV\Controller;
use DateTimeImmutable;
use OCA\DAV\Db\AbsenceMapper;
use OCA\DAV\ResponseDefinitions;
use OCA\DAV\Service\AbsenceService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\User\IAvailabilityCoordinator;
/**
* @psalm-import-type DAVOutOfOfficeData from ResponseDefinitions
@ -44,6 +48,9 @@ class OutOfOfficeController extends OCSController {
string $appName,
IRequest $request,
private AbsenceMapper $absenceMapper,
private ?IUserSession $userSession,
private AbsenceService $absenceService,
private IAvailabilityCoordinator $coordinator,
) {
parent::__construct($appName, $request);
}
@ -74,4 +81,74 @@ class OutOfOfficeController extends OCSController {
'message' => $data->getMessage(),
]);
}
/**
* Set out-of-office absence
*
* @param string $firstDay First day of the absence in format `YYYY-MM-DD`
* @param string $lastDay Last day of the absence in format `YYYY-MM-DD`
* @param string $status Short text that is set as user status during the absence
* @param string $message Longer multiline message that is shown to others during the absence
* @return DataResponse<Http::STATUS_OK, DAVOutOfOfficeData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'firstDay'}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, null, array{}>
*
* 200: Absence data
* 400: When the first day is not before the last day
* 401: When the user is not logged in
*/
#[NoAdminRequired]
public function setOutOfOffice(
string $firstDay,
string $lastDay,
string $status,
string $message,
): DataResponse {
$user = $this->userSession?->getUser();
if ($user === null) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$parsedFirstDay = new DateTimeImmutable($firstDay);
$parsedLastDay = new DateTimeImmutable($lastDay);
if ($parsedFirstDay->getTimestamp() > $parsedLastDay->getTimestamp()) {
return new DataResponse(['error' => 'firstDay'], Http::STATUS_BAD_REQUEST);
}
$data = $this->absenceService->createOrUpdateAbsence(
$user,
$firstDay,
$lastDay,
$status,
$message,
);
$this->coordinator->clearCache($user->getUID());
return new DataResponse([
'id' => $data->getId(),
'userId' => $data->getUserId(),
'firstDay' => $data->getFirstDay(),
'lastDay' => $data->getLastDay(),
'status' => $data->getStatus(),
'message' => $data->getMessage(),
]);
}
/**
* Clear the out-of-office
*
* @return DataResponse<Http::STATUS_OK|Http::STATUS_UNAUTHORIZED, null, array{}>
*
* 200: When the absence was cleared successfully
* 401: When the user is not logged in
*/
#[NoAdminRequired]
public function clearOutOfOffice(): DataResponse {
$user = $this->userSession?->getUser();
if ($user === null) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$this->absenceService->clearAbsence($user);
$this->coordinator->clearCache($user->getUID());
return new DataResponse(null);
}
}

View file

@ -317,6 +317,277 @@
}
}
}
},
"post": {
"operationId": "out_of_office-set-out-of-office",
"summary": "Set out-of-office absence",
"tags": [
"out_of_office"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "firstDay",
"in": "query",
"description": "First day of the absence in format `YYYY-MM-DD`",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "lastDay",
"in": "query",
"description": "Last day of the absence in format `YYYY-MM-DD`",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "status",
"in": "query",
"description": "Short text that is set as user status during the absence",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "message",
"in": "query",
"description": "Longer multiline message that is shown to others during the absence",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "userId",
"in": "path",
"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": "Absence data",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"$ref": "#/components/schemas/OutOfOfficeData"
}
}
}
}
}
}
}
},
"400": {
"description": "When the first day is not before the last day",
"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": [
"error"
],
"properties": {
"error": {
"type": "string",
"enum": [
"firstDay"
]
}
}
}
}
}
}
}
}
}
},
"401": {
"description": "When the 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": {
"nullable": true
}
}
}
}
}
}
}
}
}
},
"delete": {
"operationId": "out_of_office-clear-out-of-office",
"summary": "Clear the out-of-office",
"tags": [
"out_of_office"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "userId",
"in": "path",
"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": "When the absence was cleared successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"nullable": true
}
}
}
}
}
}
}
},
"401": {
"description": "When the 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": {
"nullable": true
}
}
}
}
}
}
}
}
}
}
}
},

View file

@ -57,7 +57,8 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import { generateUrl } from '@nextcloud/router'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { formatDateAsYMD } from '../utils/date.js'
import { loadState } from '@nextcloud/initial-state'
@ -110,7 +111,7 @@ export default {
this.loading = true
try {
await axios.post(generateUrl('/apps/dav/settings/absence'), {
await axios.post(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}', { userId: getCurrentUser().uid }), {
firstDay: formatDateAsYMD(this.firstDay),
lastDay: formatDateAsYMD(this.lastDay),
status: this.status,
@ -127,7 +128,7 @@ export default {
async clearAbsence() {
this.loading = true
try {
await axios.delete(generateUrl('/apps/dav/settings/absence'))
await axios.delete(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}', { userId: getCurrentUser().uid }))
this.resetForm()
showSuccess(this.$t('dav', 'Absence cleared'))
} catch (error) {

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long