mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-06 04:11:21 -05:00
docs(groupware): OpenAPI improvements
* refactor some pkg/jmap and groupware methods to make more sense from an API point-of-view * add path parameter documentation, but automate it by injecting their definition into the OpenAPI YAML tree that is extracted from the source code using go-swagger as it is too cumbersome, repetitive and error-prine to document them in the source code; wrote a TypeScript file apidoc-process.ts to do so * add generating an offline HTML file for the OpenAPI documentation using redocly, and injecting a favicon into the resulting HTML; wrote a TypeScript file apidoc-postprocess-html.ts to do so
This commit is contained in:
@@ -31,5 +31,5 @@ const (
|
||||
logDownloadUrl = "download-url"
|
||||
logBlobId = "blob-id"
|
||||
logUploadUrl = "download-url"
|
||||
logSince = "since"
|
||||
logSinceState = "since-state"
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ type Emails struct {
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve specific Emails by their id.
|
||||
func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, ids []string, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, Error) {
|
||||
logger = j.logger(accountId, "GetEmails", session, logger)
|
||||
|
||||
@@ -54,8 +55,9 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, mailboxId string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, Error) {
|
||||
logger = j.loggerParams(accountId, "GetAllEmails", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
// Retrieve all the Emails in a given Mailbox by its id.
|
||||
func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, mailboxId string, offset uint, limit uint, fetchBodies bool, maxBodyValueBytes uint) (Emails, SessionState, Error) {
|
||||
logger = j.loggerParams(accountId, "GetAllEmailsInMailbox", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Uint(logOffset, offset).Uint(logLimit, limit)
|
||||
})
|
||||
|
||||
@@ -115,96 +117,15 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
|
||||
})
|
||||
}
|
||||
|
||||
type EmailsSince struct {
|
||||
Destroyed []string `json:"destroyed,omitzero"`
|
||||
HasMoreChanges bool `json:"hasMoreChanges,omitzero"`
|
||||
NewState State `json:"newState"`
|
||||
Created []Email `json:"created,omitempty"`
|
||||
Updated []Email `json:"updated,omitempty"`
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
func (j *Client) GetEmailsInMailboxSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, mailboxId string, since string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (EmailsSince, SessionState, Error) {
|
||||
logger = j.loggerParams(accountId, "GetEmailsInMailboxSince", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSince, since)
|
||||
})
|
||||
|
||||
changes := MailboxChangesCommand{
|
||||
AccountId: accountId,
|
||||
SinceState: since,
|
||||
}
|
||||
if maxChanges > 0 {
|
||||
changes.MaxChanges = maxChanges
|
||||
}
|
||||
|
||||
getCreated := EmailGetRefCommand{
|
||||
AccountId: accountId,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes > 0 {
|
||||
getCreated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
getUpdated := EmailGetRefCommand{
|
||||
AccountId: accountId,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: CommandMailboxChanges, Path: "/updated", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes > 0 {
|
||||
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(CommandMailboxChanges, changes, "0"),
|
||||
invocation(CommandEmailGet, getCreated, "1"),
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
|
||||
var mailboxResponse MailboxChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandMailboxChanges, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return EmailsSince{
|
||||
Destroyed: mailboxResponse.Destroyed,
|
||||
HasMoreChanges: mailboxResponse.HasMoreChanges,
|
||||
NewState: mailboxResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: createdResponse.State,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, since string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (EmailsSince, SessionState, Error) {
|
||||
// Get all the Emails that have been created, updated or deleted since a given state.
|
||||
func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.Context, logger *log.Logger, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, Error) {
|
||||
logger = j.loggerParams(accountId, "GetEmailsSince", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSince, since)
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
|
||||
})
|
||||
|
||||
changes := EmailChangesCommand{
|
||||
AccountId: accountId,
|
||||
SinceState: since,
|
||||
SinceState: sinceState,
|
||||
}
|
||||
if maxChanges > 0 {
|
||||
changes.MaxChanges = maxChanges
|
||||
@@ -233,32 +154,32 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return EmailsSince{}, "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
return MailboxChanges{}, "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (MailboxChanges, Error) {
|
||||
var changesResponse EmailChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailChanges, "0", &changesResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return MailboxChanges{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return MailboxChanges{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
return MailboxChanges{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return EmailsSince{
|
||||
return MailboxChanges{
|
||||
Destroyed: changesResponse.Destroyed,
|
||||
HasMoreChanges: changesResponse.HasMoreChanges,
|
||||
NewState: changesResponse.NewState,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type MailboxesResponse struct {
|
||||
@@ -85,3 +86,86 @@ func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context
|
||||
return Mailboxes{Mailboxes: response.List, State: response.State}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type MailboxChanges struct {
|
||||
Destroyed []string `json:"destroyed,omitzero"`
|
||||
HasMoreChanges bool `json:"hasMoreChanges,omitzero"`
|
||||
NewState State `json:"newState"`
|
||||
Created []Email `json:"created,omitempty"`
|
||||
Updated []Email `json:"updated,omitempty"`
|
||||
State State `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// Retrieve Email changes in a given Mailbox since a given state.
|
||||
func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, mailboxId string, sinceState string, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (MailboxChanges, SessionState, Error) {
|
||||
logger = j.loggerParams(accountId, "GetMailboxChanges", session, logger, func(z zerolog.Context) zerolog.Context {
|
||||
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, sinceState)
|
||||
})
|
||||
|
||||
changes := MailboxChangesCommand{
|
||||
AccountId: accountId,
|
||||
SinceState: sinceState,
|
||||
}
|
||||
if maxChanges > 0 {
|
||||
changes.MaxChanges = maxChanges
|
||||
}
|
||||
|
||||
getCreated := EmailGetRefCommand{
|
||||
AccountId: accountId,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: CommandMailboxChanges, Path: "/created", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes > 0 {
|
||||
getCreated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
getUpdated := EmailGetRefCommand{
|
||||
AccountId: accountId,
|
||||
FetchAllBodyValues: fetchBodies,
|
||||
IdRef: &ResultReference{Name: CommandMailboxChanges, Path: "/updated", ResultOf: "0"},
|
||||
}
|
||||
if maxBodyValueBytes > 0 {
|
||||
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
|
||||
}
|
||||
|
||||
cmd, err := request(
|
||||
invocation(CommandMailboxChanges, changes, "0"),
|
||||
invocation(CommandEmailGet, getCreated, "1"),
|
||||
invocation(CommandEmailGet, getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return MailboxChanges{}, "", simpleError(err, JmapErrorInvalidJmapRequestPayload)
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (MailboxChanges, Error) {
|
||||
var mailboxResponse MailboxChangesResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandMailboxChanges, "0", &mailboxResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return MailboxChanges{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var createdResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return MailboxChanges{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
var updatedResponse EmailGetResponse
|
||||
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
|
||||
if err != nil {
|
||||
logger.Error().Err(err)
|
||||
return MailboxChanges{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
|
||||
}
|
||||
|
||||
return MailboxChanges{
|
||||
Destroyed: mailboxResponse.Destroyed,
|
||||
HasMoreChanges: mailboxResponse.HasMoreChanges,
|
||||
NewState: mailboxResponse.NewState,
|
||||
Created: createdResponse.List,
|
||||
Updated: createdResponse.List,
|
||||
State: createdResponse.State,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ func TestWithStalwart(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
resp, sessionState, err := j.GetAllEmails(accountId, session, ctx, logger, inboxId, 0, 0, false, 0)
|
||||
resp, sessionState, err := j.GetAllEmailsInMailbox(accountId, session, ctx, logger, inboxId, 0, 0, false, 0)
|
||||
require.NoError(err)
|
||||
require.Equal(session.State, sessionState)
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestRequests(t *testing.T) {
|
||||
require.Len(folders.Mailboxes, 5)
|
||||
require.NotEmpty(sessionState)
|
||||
|
||||
emails, sessionState, err := client.GetAllEmails("a", &session, ctx, &logger, "Inbox", 0, 0, true, 0)
|
||||
emails, sessionState, err := client.GetAllEmailsInMailbox("a", &session, ctx, &logger, "Inbox", 0, 0, true, 0)
|
||||
require.NoError(err)
|
||||
require.Len(emails.Emails, 3)
|
||||
require.NotEmpty(sessionState)
|
||||
|
||||
3
services/groupware/.gitignore
vendored
3
services/groupware/.gitignore
vendored
@@ -1 +1,4 @@
|
||||
/swagger.yml
|
||||
/api.html
|
||||
/api.html.template
|
||||
/node_modules
|
||||
|
||||
@@ -13,12 +13,27 @@ include ../../.make/docs.mk
|
||||
.PHONY: apidoc
|
||||
apidoc: swagger.yml
|
||||
|
||||
.PHONY: tsnode
|
||||
tsnode: node_modules
|
||||
|
||||
.PHONY: node_modules
|
||||
node_modules:
|
||||
pnpm install
|
||||
|
||||
.PHONY: swagger.yml
|
||||
swagger.yml: apidoc.yml
|
||||
swagger generate spec --output=$@ --include='groupware' --include='jmap' --scan-models --input=$<
|
||||
swagger.yml: apidoc.yml tsnode
|
||||
swagger generate spec --include='groupware' --include='jmap' --scan-models --input=$< | NODE_OPTIONS='--no-warnings' pnpm exec ts-node apidoc-process.ts > $@
|
||||
|
||||
APIDOC_PORT=9999
|
||||
|
||||
.PHONY: serve-apidoc
|
||||
serve-apidoc: swagger.yml
|
||||
serve-apidoc: swagger.yml tsnode
|
||||
swagger serve --no-open --port=$(APIDOC_PORT) --host=127.0.0.1 --flavor=redoc $<
|
||||
|
||||
api.html: swagger.yml favicon.png tsnode
|
||||
pnpm exec redocly build-docs --output=$@.template --title="OpenCloud Groupware API" --theme.openapi.hideHostname=false --theme.openapi.hideTryItPanel=false --theme.openapi.pathInMiddlePanel=true $<
|
||||
NODE_OPTIONS='--no-warnings' pnpm exec ts-node ./apidoc-postprocess-html.ts favicon.png < $@.template > $@
|
||||
rm $@.template
|
||||
|
||||
.PHONY: apidoc-static
|
||||
apidoc-static: api.html
|
||||
|
||||
30
services/groupware/README.md
Normal file
30
services/groupware/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Groupware
|
||||
|
||||
The OpenCloud Groupware service provides a REST API for performing all the backend operations needed by the OpenCloud Groupware frontends.
|
||||
|
||||
## OpenAPI Documentation
|
||||
|
||||
To generate the OpenAPI ("Swagger") documentation of the REST API, [`pnpm`](https://pnpm.io/) is a pre-requisite.
|
||||
|
||||
Run the following command in this directory to generate the `swagger.yml` OpenAPI definition file:
|
||||
|
||||
```bash
|
||||
make apidoc
|
||||
```
|
||||
|
||||
To generate a static HTML file using [Redocly](https://redocly.com/), which will generate a file `api.html`:
|
||||
|
||||
```bash
|
||||
make apidoc-static
|
||||
```
|
||||
|
||||
### Path Parameters
|
||||
|
||||
Path parameters are documented in the file [`api-params.yaml`](file:api-params.yaml) and injected into the OpenAPI specification using the script [`apidoc-process.ts`](file:apidoc-process.ts) (which is done automatically when using the `Makefile` as described above.)
|
||||
|
||||
### Favicon
|
||||
|
||||
A [favicon](https://developer.mozilla.org/en-US/docs/Glossary/Favicon) is inserted into the static (Redocly) HTML file as part of the build process in the `Makefile`, using [`favicon.png`](file:favicon.png) as the source, computing its base64 to insert it as an image using a [data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data) in order to embed it.
|
||||
|
||||
That is performed by the script [`apidoc-postprocess-html.ts`](file:apidoc-postprocess-html.ts) (which is done automatically when using then `Makefile` as described above.)
|
||||
|
||||
5
services/groupware/api-params.yaml
Normal file
5
services/groupware/api-params.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
params:
|
||||
account:
|
||||
description: The identifier of the Account to use for this operation
|
||||
mailbox:
|
||||
description: The identifier of the Mailbox to perform this operation on
|
||||
25
services/groupware/apidoc-postprocess-html.ts
Normal file
25
services/groupware/apidoc-postprocess-html.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as fs from 'fs'
|
||||
import * as cheerio from 'cheerio'
|
||||
|
||||
const faviconFile = process.argv[2]
|
||||
const favicon = fs.readFileSync(faviconFile).toString('base64')
|
||||
|
||||
let html = ''
|
||||
process.stdin.on('data', (chunk) => {
|
||||
html += chunk.toString()
|
||||
})
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const $ = cheerio.load(html)
|
||||
$('head').append(`<link rel="icon" href="data:image/png;base64,${favicon}">`)
|
||||
process.stdout.write($.html())
|
||||
process.stdout.write("\n")
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`Error occured while post-processing HTML: ${error.message}`)
|
||||
} else {
|
||||
console.error("Unknown error occurred")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
58
services/groupware/apidoc-process.ts
Normal file
58
services/groupware/apidoc-process.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import * as fs from 'fs'
|
||||
import * as yaml from 'js-yaml'
|
||||
|
||||
interface Param {
|
||||
description: string
|
||||
type: string
|
||||
}
|
||||
|
||||
interface Config {
|
||||
params: {[param:string]:Param}
|
||||
}
|
||||
|
||||
let inputData = ''
|
||||
|
||||
process.stdin.on('data', (chunk) => {
|
||||
inputData += chunk.toString()
|
||||
})
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const config = yaml.load(fs.readFileSync('api-params.yaml', 'utf8')) as Config
|
||||
const params = config.params || {}
|
||||
|
||||
const data = yaml.load(inputData) as any
|
||||
|
||||
for (const path in data.paths) {
|
||||
for (const param in params) {
|
||||
if (path.includes(`{${param}}`)) {
|
||||
const paramsData = params[param] as Param
|
||||
const pathData = data.paths[path] as any
|
||||
for (const verb in pathData) {
|
||||
const verbData = pathData[verb] as any
|
||||
if (!Object.getOwnPropertyNames(verbData).includes('parameters')) {
|
||||
verbData.parameters = []
|
||||
}
|
||||
verbData['parameters'].push({
|
||||
name: param,
|
||||
required: true,
|
||||
type: paramsData.type !== 'undefined' ? paramsData.type : 'string',
|
||||
in: 'path',
|
||||
description: paramsData.description,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(yaml.dump(data))
|
||||
process.stdout.write("\n")
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`Error occured while post-processing OpenAPI: ${error.message}`)
|
||||
} else {
|
||||
console.error("Unknown error occurred")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,43 @@
|
||||
servers:
|
||||
- url: https://localhost:9200/
|
||||
description: Local Development Server
|
||||
tags:
|
||||
- name: init
|
||||
- name: bootstrap
|
||||
x-displayName: Bootstrapping
|
||||
description: Initialization APIs
|
||||
- name: mailboxes
|
||||
- name: account
|
||||
x-displayName: Accounts
|
||||
description: APIs for accounts
|
||||
- name: identity
|
||||
x-displayName: Identities
|
||||
description: APIs for identities
|
||||
- name: mailbox
|
||||
x-displayName: Mailboxes
|
||||
description: APIs that pertain to mailboxes
|
||||
- name: messages
|
||||
- name: message
|
||||
x-displayName: Messages
|
||||
description: APIs about emails
|
||||
- name: vacation
|
||||
description: APIs about vacation responses
|
||||
x-displayName: Vacation Responses
|
||||
description: APIs about vacation responses
|
||||
x-tagGroups:
|
||||
- name: Bootstrapping
|
||||
tags:
|
||||
- bootstrap
|
||||
- name: Accounts
|
||||
tags:
|
||||
- account
|
||||
- name: Emails
|
||||
tags:
|
||||
- identity
|
||||
- mailbox
|
||||
- message
|
||||
- vacation
|
||||
components:
|
||||
securitySchemes:
|
||||
api:
|
||||
description: Authentication for API Calls
|
||||
type: openIdConnect
|
||||
openIdConnectUrl: https://keycloak.opencloud.test/realms/openCloud/.well-known/openid-configuration
|
||||
security:
|
||||
- api
|
||||
|
||||
BIN
services/groupware/favicon.png
Normal file
BIN
services/groupware/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
15
services/groupware/package.json
Normal file
15
services/groupware/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@redocly/cli": "^2.0.8",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"cheerio": "^1.1.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.1"
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,24 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
)
|
||||
|
||||
// When the request succeeds.
|
||||
// swagger:response GetAccountResponse200
|
||||
type SwaggerGetAccountResponse struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*jmap.SessionAccount
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /groupware/accounts/{account} account account
|
||||
// Get attributes of a given account.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: GetAccountResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 404: ErrorResponse404
|
||||
// 500: ErrorResponse500
|
||||
func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
account, err := req.GetAccountForMail()
|
||||
@@ -18,6 +36,22 @@ func (g *Groupware) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// When the request succeeds.
|
||||
// swagger:response GetAccountsResponse200
|
||||
type SwaggerGetAccountsResponse struct {
|
||||
// in: body
|
||||
Body map[string]jmap.SessionAccount
|
||||
}
|
||||
|
||||
// swagger:route GET /groupware/accounts account accounts
|
||||
// Get the list of all of the user's accounts.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: GetAccountsResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 404: ErrorResponse404
|
||||
// 500: ErrorResponse500
|
||||
func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
return response(req.session.Accounts, req.session.State)
|
||||
@@ -47,7 +81,7 @@ type AccountBootstrapResponse struct {
|
||||
}
|
||||
|
||||
// When the request suceeds.
|
||||
// swagger:response IndexResponse
|
||||
// swagger:response GetAccountBootstrapResponse200
|
||||
type SwaggerAccountBootstrapResponse struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
@@ -55,6 +89,15 @@ type SwaggerAccountBootstrapResponse struct {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /groupware/accounts/{account}/bootstrap account accountbootstrap
|
||||
// Get account bootstrapping.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: GetAccountBootstrapResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 404: ErrorResponse404
|
||||
// 500: ErrorResponse500
|
||||
func (g *Groupware) GetAccountBootstrap(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
mailAccountId, err := req.GetAccountIdForMail()
|
||||
|
||||
@@ -16,7 +16,7 @@ type SwaggerGetIdentitiesResponse struct {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{accountid}/identities identities identities
|
||||
// swagger:route GET /groupware/accounts/{account}/identities identity identities
|
||||
// Get the list of identities that are associated with an account.
|
||||
//
|
||||
// responses:
|
||||
|
||||
@@ -142,7 +142,7 @@ type SwaggerIndexResponse struct {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET / init index
|
||||
// swagger:route GET /groupware bootstrap index
|
||||
// Get initial bootstrapping information for a user.
|
||||
//
|
||||
// responses:
|
||||
|
||||
@@ -18,7 +18,7 @@ type SwaggerGetMailboxById200 struct {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/mailboxes/{id} mailboxes mailboxes_by_id
|
||||
// swagger:route GET /groupware/accounts/{account}/mailboxes/{mailbox} mailbox mailboxes_by_id
|
||||
// Get a specific mailbox by its identifier.
|
||||
//
|
||||
// A Mailbox represents a named set of Emails.
|
||||
@@ -73,7 +73,7 @@ type SwaggerMailboxesResponse200 struct {
|
||||
Body []jmap.Mailbox
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/mailboxes mailboxes mailboxes
|
||||
// swagger:route GET /groupware/accounts/{account}/mailboxes mailbox mailboxes
|
||||
// Get the list of all the mailboxes of an account.
|
||||
//
|
||||
// A Mailbox represents a named set of Emails.
|
||||
@@ -134,3 +134,50 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// When the request succeeds.
|
||||
// swagger:response MailboxChangesResponse200
|
||||
type SwaggerMailboxChangesResponse200 struct {
|
||||
// in: body
|
||||
Body *jmap.MailboxChanges
|
||||
}
|
||||
|
||||
// swagger:route GET /groupware/accounts/{account}/mailboxes/{mailbox}/changes mailbox mailboxchanges
|
||||
// Get the changes that occured in a given mailbox since a certain state.
|
||||
//
|
||||
// responses:
|
||||
//
|
||||
// 200: MailboxChangesResponse200
|
||||
// 400: ErrorResponse400
|
||||
// 500: ErrorResponse500
|
||||
func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
|
||||
mailboxId := chi.URLParam(r, UriParamMailboxId)
|
||||
sinceState := r.Header.Get(HeaderSince)
|
||||
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With().Str(HeaderSince, sinceState)
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
}
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
l = l.Str(logAccountId, accountId)
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
changes, sessionState, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, mailboxId, sinceState, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return etagResponse(changes, sessionState, changes.State)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ type SwaggerGetAllMessagesInMailbox200 struct {
|
||||
type SwaggerGetAllMessagesInMailboxSince200 struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
*jmap.EmailsSince
|
||||
*jmap.MailboxChanges
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/mailboxes/{id}/messages messages get_all_messages_in_mailbox
|
||||
// swagger:route GET /groupware/accounts/{account}/mailboxes/{mailbox}/messages message get_all_messages_in_mailbox
|
||||
// Get all the emails in a mailbox.
|
||||
//
|
||||
// Retrieve the list of all the emails that are in a given mailbox.
|
||||
@@ -72,7 +72,7 @@ func (g *Groupware) GetAllMessagesInMailbox(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
logger := log.From(req.logger.With().Str(HeaderSince, since).Str(logAccountId, accountId))
|
||||
|
||||
emails, sessionState, jerr := g.jmap.GetEmailsInMailboxSince(accountId, req.session, req.ctx, logger, mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
emails, sessionState, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, mailboxId, since, true, g.maxBodyValueBytes, maxChanges)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func (g *Groupware) GetAllMessagesInMailbox(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
logger := log.From(l)
|
||||
|
||||
emails, sessionState, jerr := g.jmap.GetAllEmails(accountId, req.session, req.ctx, logger, mailboxId, offset, limit, true, g.maxBodyValueBytes)
|
||||
emails, sessionState, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, mailboxId, offset, limit, true, g.maxBodyValueBytes)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ type SwaggerGetVacationResponse200 struct {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /accounts/{account}/vacation vacation getvacation
|
||||
// swagger:route GET /groupware/accounts/{account}/vacation vacation getvacation
|
||||
// Get vacation notice information.
|
||||
//
|
||||
// A vacation response sends an automatic reply when a message is delivered to the mail store, informing the original
|
||||
@@ -54,7 +54,7 @@ type SwaggerSetVacationResponse200 struct {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route PUT /accounts/{account}/vacation vacation setvacation
|
||||
// swagger:route PUT /groupware/accounts/{account}/vacation vacation setvacation
|
||||
// Set the vacation notice information.
|
||||
//
|
||||
// A vacation response sends an automatic reply when a message is delivered to the mail store, informing the original
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
// Security:
|
||||
// - bearer
|
||||
//
|
||||
// SecurityDefinitions:
|
||||
// bearer:
|
||||
// type: http
|
||||
// scheme: bearer
|
||||
// bearerFormat: JWT
|
||||
//
|
||||
// swagger:meta
|
||||
package groupware
|
||||
|
||||
@@ -47,10 +41,3 @@ type SwaggerErrorResponse500 struct {
|
||||
*ErrorResponse
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters vacation mailboxes
|
||||
type SwaggerAccountParams struct {
|
||||
// The identifier of the account.
|
||||
// in: path
|
||||
Account string `json:"account"`
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
r.Get("/", g.GetMailboxes) // ?name=&role=&subcribed=
|
||||
r.Get("/{mailbox}", g.GetMailbox)
|
||||
r.Get("/{mailbox}/messages", g.GetAllMessagesInMailbox)
|
||||
r.Get("/{mailbox}/changes", g.GetMailboxChanges)
|
||||
})
|
||||
r.Route("/messages", func(r chi.Router) {
|
||||
r.Get("/", g.GetMessages) // ?fetchemails=true&fetchbodies=true&text=&subject=&body=&keyword=&keyword=&...
|
||||
|
||||
2434
services/groupware/pnpm-lock.yaml
generated
Normal file
2434
services/groupware/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
services/groupware/tsconfig.json
Normal file
10
services/groupware/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ts-node": {
|
||||
"transpileOnly": true,
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"esm": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user