Skip to content
This repository was archived by the owner on Oct 25, 2023. It is now read-only.

Commit 0551faa

Browse files
authored
split smuggler-api interface into distinct storage & authn APIs (#391)
1 parent e7b9881 commit 0551faa

File tree

10 files changed

+354
-136
lines changed

10 files changed

+354
-136
lines changed

elementary/src/NodeCard.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { useState } from 'react'
44
import { useAsyncEffect } from 'use-async-effect'
55
import styled from '@emotion/styled'
66

7-
import type { NodeTextData, TNode } from 'smuggler-api'
7+
import type { Ack, NodeTextData, TNode } from 'smuggler-api'
88
import { smuggler } from 'smuggler-api'
99
import { NodeTextEditor } from './editor/NodeTextEditor'
1010
import { SlateText, TDoc } from './editor/types'
@@ -21,7 +21,7 @@ export function NodeCard({
2121
className,
2222
}: {
2323
node: TNode
24-
saveNode: (text: NodeTextData) => Promise<Response> | undefined
24+
saveNode: (text: NodeTextData) => Promise<Ack> | undefined
2525
className?: string
2626
}) {
2727
const saveText = (text: SlateText) => {
@@ -42,7 +42,7 @@ export function NodeCardFetching({
4242
className,
4343
}: {
4444
nid: string
45-
saveNode: (text: NodeTextData) => Promise<Response> | undefined
45+
saveNode: (text: NodeTextData) => Promise<Ack> | undefined
4646
className?: string
4747
}) {
4848
const [node, setNode] = useState<TNode | null>(null)

smuggler-api/src/api.ts renamed to smuggler-api/src/api_cloud.ts

Lines changed: 40 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Implementation of smuggler APIs like @see StorageApi which interact with a
3+
* cloud-hosted infrastructure to perform their job.
4+
*/
5+
16
import {
27
AccountInfo,
38
Ack,
@@ -7,18 +12,14 @@ import {
712
GenerateBlobIndexResponse,
813
GetUserExternalAssociationsResponse,
914
NewNodeResponse,
10-
Nid,
1115
NodeAttrsSearchRequest,
1216
NodeAttrsSearchResponse,
1317
NodeBatch,
1418
NodeCreateRequestBody,
1519
NodeCreatedVia,
1620
NodeEdges,
17-
NodeExtattrs,
18-
NodeIndexText,
1921
NodePatchRequest,
2022
NodeTextData,
21-
NodeType,
2223
OriginId,
2324
TEdge,
2425
TNode,
@@ -29,7 +30,17 @@ import {
2930
UserExternalPipelineId,
3031
UserExternalPipelineIngestionProgress,
3132
} from './types'
32-
33+
import type {
34+
UniqueNodeLookupKey,
35+
NonUniqueNodeLookupKey,
36+
NodeLookupKey,
37+
CreateNodeArgs,
38+
GetNodeSliceArgs,
39+
NodeBatchRequestBody,
40+
CreateEdgeArgs,
41+
StorageApi,
42+
BlobUploadRequestArgs,
43+
} from './storage_api'
3344
import { makeUrl } from './api_url'
3445

3546
import { TNodeSliceIterator, GetNodesSliceFn } from './node_slice_iterator'
@@ -43,36 +54,11 @@ import moment from 'moment'
4354
import { StatusCode } from './status_codes'
4455
import { authCookie } from './auth/cookie'
4556
import { makeEmptyNodeTextData, NodeUtil } from './typesutil'
57+
import { AuthenticationApi } from './authentication_api'
4658

4759
const kHeaderCreatedAt = 'x-created-at'
4860
const kHeaderLastModified = 'last-modified'
4961

50-
/**
51-
* Unique lookup keys that can match at most 1 node
52-
*/
53-
export type UniqueNodeLookupKey =
54-
/** Due to nid's nature there can be at most 1 node with a particular nid */
55-
| { nid: string }
56-
/** Unique because many nodes can refer to the same URL, but only one of them
57-
* can be a bookmark */
58-
| { webBookmark: { url: string } }
59-
60-
export type NonUniqueNodeLookupKey =
61-
/** Can match more than 1 node because multiple parts of a single web page
62-
* can be quoted */
63-
| { webQuote: { url: string } }
64-
/** Can match more than 1 node because many nodes can refer to
65-
* the same URL:
66-
* - 0 or 1 can be @see NoteType.Url
67-
* - AND at the same time more than 1 can be @see NodeType.WebQuote */
68-
| { url: string }
69-
70-
/**
71-
* All the different types of keys that can be used to identify (during lookup,
72-
* for example) one or more nodes.
73-
*/
74-
export type NodeLookupKey = UniqueNodeLookupKey | NonUniqueNodeLookupKey
75-
7662
export function isUniqueLookupKey(
7763
key: NodeLookupKey
7864
): key is UniqueNodeLookupKey {
@@ -82,18 +68,6 @@ export function isUniqueLookupKey(
8268
return false
8369
}
8470

85-
export type CreateNodeArgs = {
86-
text: NodeTextData
87-
from_nid?: string[]
88-
to_nid?: string[]
89-
index_text?: NodeIndexText
90-
extattrs?: NodeExtattrs
91-
ntype?: NodeType
92-
origin?: OriginId
93-
created_via?: NodeCreatedVia
94-
created_at?: Date
95-
}
96-
9771
async function createNode(
9872
{
9973
text,
@@ -174,19 +148,13 @@ async function createOrUpdateNode(
174148
)
175149
}
176150

177-
const updateArgs: UpdateNodeArgs = {
178-
nid: existingNode.nid,
151+
const patch: NodePatchRequest = {
179152
text: args.text,
180153
index_text: args.index_text,
181154
}
182155

183-
const resp = await updateNode(updateArgs, signal)
184-
if (!resp.ok) {
185-
throw _makeResponseError(resp, `Failed to update node ${existingNode.nid}`)
186-
}
187-
return {
188-
nid: existingNode.nid,
189-
}
156+
await updateNode({ nid: existingNode.nid, ...patch }, signal)
157+
return { nid: existingNode.nid }
190158
}
191159

192160
/**
@@ -247,10 +215,6 @@ function describeWhatWouldPreventNodeUpdate(args: CreateNodeArgs, node: TNode) {
247215
return `[what] - [attempted update arg] vs [existing node value]: ${diff}`
248216
}
249217

250-
/**
251-
* Lookup all the nodes that match a given key. For unique lookup keys either
252-
* 0 or 1 nodes will be returned. For non-unique more than 1 node can be returned.
253-
*/
254218
async function lookupNodes(
255219
key: UniqueNodeLookupKey,
256220
signal?: AbortSignal
@@ -314,10 +278,7 @@ async function lookupNodes(key: NodeLookupKey, signal?: AbortSignal) {
314278
}
315279

316280
async function uploadFiles(
317-
files: File[],
318-
from_nid: Optional<string>,
319-
to_nid: Optional<string>,
320-
createdVia: NodeCreatedVia,
281+
{ files, from_nid, to_nid, createdVia }: BlobUploadRequestArgs,
321282
signal?: AbortSignal
322283
): Promise<UploadMultipartResponse> {
323284
const query: UploadMultipartQuery = { created_via: createdVia }
@@ -358,7 +319,7 @@ async function buildFilesSearchIndex(
358319
return await resp.json()
359320
}
360321

361-
function mimeTypeIsSupportedByBuildIndex(mimeType: MimeType) {
322+
function mimeTypeIsSupportedByBuildIndex(mimeType: MimeType): boolean {
362323
return Mime.isImage(mimeType)
363324
}
364325

@@ -445,12 +406,8 @@ async function getNode({
445406
return node
446407
}
447408

448-
type NodePatchRequestBody = {
449-
nids: Nid[]
450-
}
451-
452409
async function getNodeBatch(
453-
req: NodePatchRequestBody,
410+
req: NodeBatchRequestBody,
454411
signal?: AbortSignal
455412
): Promise<NodeBatch> {
456413
const res = await fetch(makeUrl(`/node-batch-get`), {
@@ -468,14 +425,10 @@ async function getNodeBatch(
468425
}
469426
}
470427

471-
type UpdateNodeArgs = {
472-
nid: string
473-
text?: NodeTextData
474-
index_text?: NodeIndexText
475-
preserve_update_time?: boolean
476-
}
477-
478-
async function updateNode(args: UpdateNodeArgs, signal?: AbortSignal) {
428+
async function updateNode(
429+
args: { nid: string } & NodePatchRequest,
430+
signal?: AbortSignal
431+
): Promise<Ack> {
479432
const { nid, text, index_text, preserve_update_time } = args
480433
const headers = {
481434
'Content-type': MimeType.JSON,
@@ -485,12 +438,16 @@ async function updateNode(args: UpdateNodeArgs, signal?: AbortSignal) {
485438
index_text,
486439
preserve_update_time,
487440
}
488-
return fetch(makeUrl(`/node/${nid}`), {
441+
const resp = await fetch(makeUrl(`/node/${nid}`), {
489442
method: 'PATCH',
490443
body: JSON.stringify(request),
491444
headers,
492445
signal,
493446
})
447+
if (resp.ok) {
448+
return await resp.json()
449+
}
450+
throw _makeResponseError(resp)
494451
}
495452

496453
async function getAuth({
@@ -508,7 +465,7 @@ async function getAuth({
508465
return await resp.json()
509466
}
510467

511-
export const getNodesSlice: GetNodesSliceFn = async ({
468+
const getNodesSlice: GetNodesSliceFn = async ({
512469
end_time,
513470
start_time,
514471
offset,
@@ -575,13 +532,7 @@ function _getNodesSliceIter({
575532
limit,
576533
origin,
577534
bucket_time_size,
578-
}: {
579-
end_time?: number
580-
start_time?: number
581-
limit?: number
582-
origin?: OriginId
583-
bucket_time_size?: number
584-
}) {
535+
}: GetNodeSliceArgs) {
585536
return new TNodeSliceIterator(
586537
getNodesSlice,
587538
start_time,
@@ -596,11 +547,7 @@ async function createEdge({
596547
from,
597548
to,
598549
signal,
599-
}: {
600-
from?: string
601-
to?: string
602-
signal: AbortSignal
603-
}): Promise<TEdge> {
550+
}: CreateEdgeArgs): Promise<TEdge> {
604551
verifyIsNotNull(from)
605552
verifyIsNotNull(to)
606553
const req = {
@@ -644,14 +591,14 @@ async function getNodeAllEdges(
644591

645592
async function switchEdgeStickiness({
646593
eid,
647-
signal,
648594
on,
649595
off,
596+
signal,
650597
}: {
651598
eid: string
652-
signal: AbortSignal
653599
on: Optional<boolean>
654600
off: Optional<boolean>
601+
signal: AbortSignal
655602
}): Promise<Ack> {
656603
verifyIsNotNull(eid)
657604
const req = {
@@ -704,7 +651,7 @@ async function createSession(
704651
password: string,
705652
permissions: number | null,
706653
signal?: AbortSignal
707-
) {
654+
): Promise<{}> {
708655
verifyIsNotNull(email)
709656
verifyIsNotNull(password)
710657
verifyIsNotNull(signal)
@@ -909,7 +856,7 @@ async function passwordChange(
909856
old_password: string,
910857
new_password: string,
911858
signal?: AbortSignal
912-
) {
859+
): Promise<Ack> {
913860
const value = { old_password, new_password }
914861
const resp = await fetch(makeUrl('/auth/password-recover/change'), {
915862
method: 'POST',
@@ -1010,7 +957,7 @@ function _makeResponseError(response: Response, message?: string): Error {
1010957
})
1011958
}
1012959

1013-
export const smuggler = {
960+
export const smuggler: StorageApi & AuthenticationApi = {
1014961
getAuth,
1015962
node: {
1016963
get: getNode,
@@ -1035,16 +982,6 @@ export const smuggler = {
1035982
},
1036983
blob_index: {
1037984
build: buildFilesSearchIndex,
1038-
/** 'cfg' section is intended to expose properties of one of smuggler's
1039-
* endpoints *without using network*. This information is unlikely to change
1040-
* during application runtime which makes repeated network usage wasteful.
1041-
*
1042-
* It may be tempting to bake them into respective endpoint methods directly
1043-
* (e.g. into 'blob_index.build'), but that is intentionally avoided to
1044-
* keep a clearer boundary between raw REST endpoints and helper functionality.
1045-
*
1046-
* Similar sections may become useful for other endpoints
1047-
*/
1048985
cfg: {
1049986
supportsMime: mimeTypeIsSupportedByBuildIndex,
1050987
},

smuggler-api/src/auth/account.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { smuggler } from './../api'
1+
import { smuggler } from './../api_cloud'
22
import { authCookie } from './cookie'
33
import { AccountInterface } from './account_interface'
44
import type { LocalCrypto } from './account_interface'

smuggler-api/src/auth/knocker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { smuggler, SmugglerError } from './../api'
1+
import { smuggler, SmugglerError } from '../api_cloud'
22
import { StatusCode } from './../status_codes'
33
import { authCookie } from './cookie'
44

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Ack, AccountInfo } from './types'
2+
3+
export type AuthenticationApi = {
4+
getAuth: ({ signal }: { signal?: AbortSignal }) => Promise<AccountInfo>
5+
session: {
6+
create: (
7+
email: string,
8+
password: string,
9+
permissions: number | null,
10+
signal?: AbortSignal
11+
) => Promise<{}>
12+
delete: ({ signal }: { signal: AbortSignal }) => Promise<Ack>
13+
update: (signal?: AbortSignal) => Promise<Ack>
14+
}
15+
user: {
16+
password: {
17+
recover: ({
18+
email,
19+
signal,
20+
}: {
21+
email: string
22+
signal: AbortSignal
23+
}) => Promise<Ack>
24+
reset: ({
25+
token,
26+
new_password,
27+
signal,
28+
}: {
29+
token: string
30+
new_password: string
31+
signal: AbortSignal
32+
}) => Promise<Ack>
33+
change: (
34+
old_password: string,
35+
new_password: string,
36+
signal?: AbortSignal
37+
) => Promise<Ack>
38+
}
39+
register: ({
40+
name,
41+
email,
42+
signal,
43+
}: {
44+
name: string
45+
email: string
46+
signal?: AbortSignal
47+
}) => Promise<Ack>
48+
}
49+
}

0 commit comments

Comments
 (0)