Skip to content

Commit

Permalink
add api jssdk
Browse files Browse the repository at this point in the history
  • Loading branch information
akellbl4 committed Jul 22, 2022
1 parent 189166f commit e07638d
Show file tree
Hide file tree
Showing 25 changed files with 1,075 additions and 0 deletions.
11 changes: 11 additions & 0 deletions frontend/packages/api/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
charset = utf-8
indent_style = tab
end_of_line = lf
insert_final_newline = true

[{*.json,.prettierrc}]
indent_style = space

3 changes: 3 additions & 0 deletions frontend/packages/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
coverage
dist
17 changes: 17 additions & 0 deletions frontend/packages/api/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
coverage/**
dist/**
node_modules/**
src/**
test/**
.editorconfig
.eslintrc.cjs
.prettierrc
files.txt
jest.config.ts
pnpm-lock.yaml
postpublish.sh
prepublish.sh
tsconfig.common.json
tsconfig.dev.json
tsconfig.json

6 changes: 6 additions & 0 deletions frontend/packages/api/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": false,
"printWidth": 100,
"quoteProps": "consistent",
"singleQuote": true
}
11 changes: 11 additions & 0 deletions frontend/packages/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @remark42/api

Implementation of API methods for Remark42

## Development

- If you don't have `pnpm` installed run `npm i -g pnpm`
- Install dependencies `pnpm i`
- Run development mode with `pnpm run dev`
- Build lib with `pnpm run build`
- Run tests with `pnpm run test`
15 changes: 15 additions & 0 deletions frontend/packages/api/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Config } from '@jest/types'

const config: Config.InitialOptions = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.ts$': '@swc/jest',
},
collectCoverageFrom: ['src/**/*.ts'],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
moduleNameMapper: {
'^@test/(.*)$': '<rootDir>/test/$1',
},
}

export default config
47 changes: 47 additions & 0 deletions frontend/packages/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@remark42/api",
"version": "0.6.0-alpha.11",
"description": "Implementation of API methods for Remark42",
"repository": {
"type": "git",
"url": "git+https://github.com/umputun/remark42.git#master"
},
"bugs": {
"url": "https://github.com/umputun/remark42/issues"
},
"homepage": "https://github.com/umputun/remark42/tree/master/frontend/packages/api#readme",
"keywords": [
"remark42",
"comments"
],
"author": "Paul Mineev",
"license": "MIT",
"scripts": {
"dev": "tsc -w -p tsconfig.dev.json",
"build": "tsc",
"prebuild": "rm -rf dist",
"prepublish": "./prepublish.sh",
"postpublish": "./postpublish.sh",
"test": "jest",
"coverage": "jest --coverage",
"lint": "eslint src --ext=.ts,.js",
"type-check": "tsc --noEmit",
"format": "prettier --write '**/*.{ts,js,json}'"
},
"devDependencies": {
"@jest/types": "^28.1.1",
"@swc/core": "^1.2.203",
"@swc/jest": "^0.2.21",
"@types/jest": "^28.1.2",
"@types/node": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"jest": "^28.1.1",
"jest-environment-jsdom": "^28.1.1",
"prettier": "^2.7.1",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
}
}
93 changes: 93 additions & 0 deletions frontend/packages/api/src/clients/admin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { createFetchMock, getLastFetchUrl } from '@test/lib'
import { BlockTTL, createAdminClient } from './admin'

describe('Admin Client', () => {
const client = createAdminClient({ siteId: 'mysite', baseUrl: '/remark42' })

it('should create an admin client', () => {
expect(client).toBeDefined()
})

it('should return list of blocked users', async () => {
const data = [{ id: 1 }, { id: 2 }]
const fetchMock = createFetchMock(data)
const users = await client.getBlockedUsers()

expect(users).toEqual(data)
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/blocked?site=mysite')
})

it.each`
ttl | expected
${'permanently'} | ${'/remark42/api/v1/user/1?block=1&site=mysite&ttl=0'}
${'1440m'} | ${'/remark42/api/v1/user/1?block=1&site=mysite&ttl=1440m'}
${'43200m'} | ${'/remark42/api/v1/user/1?block=1&site=mysite&ttl=43200m'}
`('should block user with ttl: $input', async (p) => {
const { ttl, expected } = p as { ttl: BlockTTL; expected: string }
const fetchMock = createFetchMock()
await client.blockUser('1', ttl)

expect(getLastFetchUrl(fetchMock)).toBe(expected)
})

it('should unblock user', async () => {
const fetchMock = createFetchMock()
await client.unblockUser('1')

expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/user/1?block=0&site=mysite')
})

it('should mark user as verified', async () => {
const fetchMock = createFetchMock()
await client.verifyUser('1')
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/verify/1?site=mysite&verified=1')
})

it('should mark user as unverified', async () => {
const fetchMock = createFetchMock()
await client.unverifyUser('1')
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/verify/1?site=mysite&verified=0')
})

it('should approve removing request', async () => {
const fetchMock = createFetchMock()
await client.approveRemovingRequest('token')
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/deleteme?site=mysite&token=token')
})

it('should pin comment', async () => {
const fetchMock = createFetchMock()
await client.pinComment('1')
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/pin/1?pinned=1&site=mysite')
})

it('should unpin comment', async () => {
const fetchMock = createFetchMock()
await client.unpinComment('1')
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/api/v1/pin/1?pinned=0&site=mysite')
})

it('should remove comment', async () => {
const fetchMock = createFetchMock()
await client.removeComment('/post/1', '1')
expect(getLastFetchUrl(fetchMock)).toBe(
'/remark42/api/v1/comment/1?site=mysite&url=%2Fpost%2F1'
)
})

it('should enable commenting on a page', async () => {
const fetchMock = createFetchMock()
await client.enableCommenting('/post/1')
expect(getLastFetchUrl(fetchMock)).toBe(
'/remark42/api/v1/readonly?ro=1&site=mysite&url=%2Fpost%2F1'
)
})

it('should disable commenting on a page', async () => {
const fetchMock = createFetchMock()
await client.disableCommenting('/post/1')
expect(getLastFetchUrl(fetchMock)).toBe(
'/remark42/api/v1/readonly?ro=0&site=mysite&url=%2Fpost%2F1'
)
})
})
119 changes: 119 additions & 0 deletions frontend/packages/api/src/clients/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { API_BASE } from '../consts'
import { createFetcher } from '../lib/fetcher'
import { ClientParams, User } from '../typings'

export type BlockTTL = 'permanently' | '43200m' | '10080m' | '1440m'
export type BlockUserResponse = {
block: boolean
site_id: string
user_id: string
}

export function createAdminClient({ siteId, baseUrl }: ClientParams) {
const fetcher = createFetcher(siteId, `${baseUrl}${API_BASE}`)

async function toggleUserVerification(id: string, verified: 0 | 1): Promise<void> {
return fetcher.put(`/verify/${id}`, { verified })
}

async function toggleCommentPin(id: string, pinned: 0 | 1): Promise<void> {
return fetcher.put(`/pin/${id}`, { pinned })
}

async function toggleCommenting(url: string, ro: 0 | 1): Promise<void> {
return fetcher.put('/readonly', { url, ro })
}

async function toggleUserBlock(id: string, ttl?: BlockTTL): Promise<BlockUserResponse> {
const params = ttl
? {
block: 1,
ttl: ttl === 'permanently' ? 0 : ttl,
}
: { block: 0 }

return fetcher.put<BlockUserResponse>(`/user/${id}`, params)
}

return {
/**
* Request list of blocked users
*/
async getBlockedUsers(): Promise<User[]> {
return fetcher.get<User[]>('/blocked')
},
/**
* Block user from commenting
* @param id user ID
* @param ttl block duration
*/
async blockUser(id: string, ttl: BlockTTL): Promise<BlockUserResponse> {
return toggleUserBlock(id, ttl)
},
/**
* Unblock user from commenting
* @param id user ID
*/
async unblockUser(id: string): Promise<BlockUserResponse> {
return toggleUserBlock(id)
},
/**
* Mark user as verified
* @param id user ID
*/
async verifyUser(id: string): Promise<void> {
return toggleUserVerification(id, 1)
},
/**
* Mark user as unverified
* @param id user ID
*/
async unverifyUser(id: string): Promise<void> {
return toggleUserVerification(id, 0)
},
/**
* Approve request to remove user data
* @param token token from email
*/
async approveRemovingRequest(token: string): Promise<void> {
return fetcher.get('/deleteme', { token })
},

/**
* Mark comment as pinned
* @param id comment ID
*/
async pinComment(id: string): Promise<void> {
return toggleCommentPin(id, 1)
},
/**
* Mark comment as unpinned
* @param id comment ID
*/
async unpinComment(id: string): Promise<void> {
return toggleCommentPin(id, 0)
},
/**
* Remove comment
* @param url page URL
* @param id comment ID
*/
async removeComment(url: string, id: string): Promise<void> {
return fetcher.delete(`/comment/${id}`, { url })
},
/**
* Enable commenting on a page
* @param url page URL
*/
async enableCommenting(url: string) {
return toggleCommenting(url, 1)
},
/**
* Disable commenting on a page
* @param url page URL
*/
async disableCommenting(url: string) {
return toggleCommenting(url, 0)
},
}
}
48 changes: 48 additions & 0 deletions frontend/packages/api/src/clients/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createFetchMock, getLastFetchUrl } from '@test/lib'
import { createAuthClient } from './auth'

describe('Auth Client', () => {
const client = createAuthClient({ siteId: 'mysite', baseUrl: '/remark42' })

it('should authorize as anonymouse', async () => {
const fetchMock = createFetchMock()
await client.anonymous('username')

expect(getLastFetchUrl(fetchMock)).toBe(
'/remark42/auth/anonymous/login?aud=mysite&site=mysite&user=username'
)
})
it('should authorize with email', async () => {
const fetchMock = createFetchMock()
const tokenVerification = await client.email('[email protected]', 'username')

expect(getLastFetchUrl(fetchMock)).toBe(
'/remark42/auth/email/login?address=username%40example.com&site=mysite&user=username'
)

await tokenVerification('token')

expect(getLastFetchUrl(fetchMock)).toBe('/remark42/auth/email/login?site=mysite&token=token')
})

it('should authorize with telegram', async () => {
const fetchMock = createFetchMock(undefined, {
json: () => Promise.resolve({ bot: 'remark42bot', token: 'token' }),
})
const telegramAuth = await client.telegram()

expect(getLastFetchUrl(fetchMock)).toBe('/remark42/auth/telegram/login?site=mysite')
expect(telegramAuth.bot).toBe('remark42bot')
expect(telegramAuth.token).toBe('token')

await telegramAuth.verify()
expect(getLastFetchUrl(fetchMock)).toBe('/remark42/auth/telegram/login?site=mysite&token=token')
})

it('should logout', async () => {
const fetchMock = createFetchMock()
await client.logout()

expect(getLastFetchUrl(fetchMock)).toBe('/remark42/auth/logout?site=mysite')
})
})
Loading

0 comments on commit e07638d

Please sign in to comment.