-
-
Notifications
You must be signed in to change notification settings - Fork 377
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
1,075 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
coverage | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"semi": false, | ||
"printWidth": 100, | ||
"quoteProps": "consistent", | ||
"singleQuote": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
}) | ||
}) |
Oops, something went wrong.