Skip to content

Commit

Permalink
Merge pull request #247 from raimohanska/api-improvements
Browse files Browse the repository at this point in the history
API improvements
  • Loading branch information
raimohanska authored Mar 9, 2024
2 parents 0fef948 + 3ae25d8 commit 51f73d6
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 19 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ All boards created accessible to anyone with the link by default. If you Sign In

## API

For a full list of API endpoints, see https://ourboard.io/api-docs.

All POST and PUT endpoints accept application/json content.

API requests against boards with restricted access require you to supply an API_TOKEN header with a valid API token.
Expand Down Expand Up @@ -147,6 +145,14 @@ Payload:
}
```

Response:

```js
{
"id": "ITEM_ID"
}
```

### PUT /api/v1/board/:boardId/item/:itemId

Creates a new item on given board or updates an existing one.
Expand Down
10 changes: 7 additions & 3 deletions backend/src/api/board-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ok } from "typera-common/response"
import { body } from "typera-express/parser"
import { BoardAccessPolicyCodec } from "../../../common/src/domain"
import { updateBoard } from "../board-store"
import { apiTokenHeader, checkBoardAPIAccess, route } from "./utils"
import { apiTokenHeader, checkBoardAPIAccess, dispatchSystemAppEvent, route } from "./utils"

/**
* Changes board name and, optionally, access policy.
Expand All @@ -15,10 +15,14 @@ export const boardUpdate = route
.put("/api/v1/board/:boardId")
.use(apiTokenHeader, body(t.type({ name: NonEmptyString, accessPolicy: BoardAccessPolicyCodec })))
.handler((request) =>
checkBoardAPIAccess(request, async () => {
checkBoardAPIAccess(request, async (board) => {
const { boardId } = request.routeParams
const { name, accessPolicy } = request.body
await updateBoard({ boardId, name, accessPolicy })
await updateBoard({ boardId, name, accessPolicy: accessPolicy ?? board.board.accessPolicy })
dispatchSystemAppEvent(board, { action: "board.rename", boardId, name })
if (accessPolicy) {
dispatchSystemAppEvent(board, { action: "board.setAccessPolicy", boardId, accessPolicy })
}
return ok({ ok: true })
}),
)
4 changes: 2 additions & 2 deletions backend/src/api/item-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const itemCreate = route
checkBoardAPIAccess(request, async (board) => {
const { type, text, color, container } = request.body
console.log(`POST item for board ${board.board.id}: ${JSON.stringify(request.req.body)}`)
addItem(board, type, text, color, container)
return ok({ ok: true })
const item = addItem(board, type, text, color, container)
return ok(item)
}),
)
3 changes: 2 additions & 1 deletion backend/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function getItemAttributesForContainer(container: string | undefined, boa
const containerItem = findContainer(container, board)
if (containerItem) {
return {
containedId: containerItem.id,
containerId: containerItem.id,
x: containerItem.x + 2,
y: containerItem.y + 2,
}
Expand Down Expand Up @@ -108,6 +108,7 @@ export function addItem(
const item: Note = { ...newNote(text, color || DEFAULT_NOTE_COLOR), ...itemAttributes }
const appEvent: AppEvent = { action: "item.add", boardId: board.board.id, items: [item], connections: [] }
dispatchSystemAppEvent(board, appEvent)
return item
}

export class InvalidRequest extends Error {
Expand Down
33 changes: 23 additions & 10 deletions backend/src/board-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ export async function getBoardInfo(id: Id): Promise<BoardInfo | null> {
}

const selectBoardQuery = `
with allow_lists as (
select id, content, public_read, public_write, (
select jsonb_agg(jsonb_build_object('domain', domain, 'access', access, 'email', email))
from board_access a
where a.board_id = b.id
) as allow_list
from board b for update
)
select id,
jsonb_set (content - 'accessPolicy', '{accessPolicy}', cast(case when allow_list is null then 'null' else (json_build_object('allowList', allow_list, 'publicRead', public_read, 'publicWrite', public_write)) end as jsonb)) as content
from allow_lists
jsonb_set (content - 'accessPolicy', '{accessPolicy}',
cast(json_build_object(
'allowList', (
coalesce((
select jsonb_agg(jsonb_strip_nulls(jsonb_build_object('domain', domain, 'access', access, 'email', email)))
from board_access
where board_access.board_id = board.id
), '[]')
),
'publicRead', public_read,
'publicWrite', public_write
) as jsonb))
as content
from board
where id=$1
`

Expand All @@ -46,6 +50,15 @@ export async function fetchBoard(id: Id): Promise<BoardAndAccessTokens | null> {
return null
} else {
const snapshot = result.rows[0].content as Board
if (
snapshot.accessPolicy &&
snapshot.accessPolicy.allowList.length === 0 &&
snapshot.accessPolicy.publicRead &&
snapshot.accessPolicy.publicWrite
) {
// Effectively no access policy
delete snapshot.accessPolicy
}
let historyEventCount = 0
let lastSerial = 0
let board = snapshot
Expand Down
13 changes: 13 additions & 0 deletions backend/src/express-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import openapiDoc from "./openapi"
import { possiblyRequireAuth } from "./require-auth"
import { createGetSignedPutUrl } from "./storage"
import { WsWrapper } from "./ws-wrapper"
import Cookies from "cookies"
import { removeAuthenticatedUser, setAuthenticatedUser } from "./http-session"

dotenv.config()

Expand All @@ -27,7 +29,18 @@ export const startExpressServer = (httpPort?: number, httpsPort?: number): (() =

if (authProvider) {
setupAuth(app, authProvider)
} else {
app.get("/logout", async (req, res) => {
removeAuthenticatedUser(req, res)
res.redirect("/")
})
}
app.get("/test-callback", async (req, res) => {
const cookies = new Cookies(req, res)
const returnTo = cookies.get("returnTo") || "/"
setAuthenticatedUser(req, res, { domain: null, email: "[email protected]", name: "Ourboard tester" })
res.redirect(returnTo)
})

possiblyRequireAuth(app)

Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const config: PlaywrightTestConfig = {
timeout: 60000, // Timeout per test file (default 30000)
retries: ci ? 2 : 0,
use: {
baseURL: "http://localhost:8080",
baseURL: "http://localhost:1337",
actionTimeout: 15000,
trace: "retain-on-failure",
},
Expand Down
Loading

0 comments on commit 51f73d6

Please sign in to comment.