aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/extra-utils/users
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-07-21 15:51:30 +0200
committerChocobozzz <me@florianbigard.com>2021-07-21 15:51:30 +0200
commita24bd1ed41b43790bab6ba789580bb4e85f07d85 (patch)
treea54b0f6c921ba83a6e909cd0ced325b2d4b8863c /shared/extra-utils/users
parent5f26f13b3c16ac5ae0a3b0a7142d84a9528cf565 (diff)
parentc63830f15403ac4e750829f27d8bbbdc9a59282c (diff)
downloadPeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.tar.gz
PeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.tar.zst
PeerTube-a24bd1ed41b43790bab6ba789580bb4e85f07d85.zip
Merge branch 'next' into develop
Diffstat (limited to 'shared/extra-utils/users')
-rw-r--r--shared/extra-utils/users/accounts-command.ts56
-rw-r--r--shared/extra-utils/users/accounts.ts87
-rw-r--r--shared/extra-utils/users/actors.ts73
-rw-r--r--shared/extra-utils/users/blocklist-command.ts139
-rw-r--r--shared/extra-utils/users/blocklist.ts238
-rw-r--r--shared/extra-utils/users/index.ts9
-rw-r--r--shared/extra-utils/users/login-command.ts132
-rw-r--r--shared/extra-utils/users/login.ts124
-rw-r--r--shared/extra-utils/users/notifications-command.ts86
-rw-r--r--shared/extra-utils/users/notifications.ts (renamed from shared/extra-utils/users/user-notifications.ts)167
-rw-r--r--shared/extra-utils/users/subscriptions-command.ts99
-rw-r--r--shared/extra-utils/users/user-subscriptions.ts93
-rw-r--r--shared/extra-utils/users/users-command.ts414
-rw-r--r--shared/extra-utils/users/users.ts415
14 files changed, 1052 insertions, 1080 deletions
diff --git a/shared/extra-utils/users/accounts-command.ts b/shared/extra-utils/users/accounts-command.ts
new file mode 100644
index 000000000..2f586104e
--- /dev/null
+++ b/shared/extra-utils/users/accounts-command.ts
@@ -0,0 +1,56 @@
1import { HttpStatusCode, ResultList } from '@shared/models'
2import { Account } from '../../models/actors'
3import { AccountVideoRate, VideoRateType } from '../../models/videos'
4import { AbstractCommand, OverrideCommandOptions } from '../shared'
5
6export class AccountsCommand extends AbstractCommand {
7
8 list (options: OverrideCommandOptions & {
9 sort?: string // default -createdAt
10 } = {}) {
11 const { sort = '-createdAt' } = options
12 const path = '/api/v1/accounts'
13
14 return this.getRequestBody<ResultList<Account>>({
15 ...options,
16
17 path,
18 query: { sort },
19 implicitToken: false,
20 defaultExpectedStatus: HttpStatusCode.OK_200
21 })
22 }
23
24 get (options: OverrideCommandOptions & {
25 accountName: string
26 }) {
27 const path = '/api/v1/accounts/' + options.accountName
28
29 return this.getRequestBody<Account>({
30 ...options,
31
32 path,
33 implicitToken: false,
34 defaultExpectedStatus: HttpStatusCode.OK_200
35 })
36 }
37
38 listRatings (options: OverrideCommandOptions & {
39 accountName: string
40 rating?: VideoRateType
41 }) {
42 const { rating, accountName } = options
43 const path = '/api/v1/accounts/' + accountName + '/ratings'
44
45 const query = { rating }
46
47 return this.getRequestBody<ResultList<AccountVideoRate>>({
48 ...options,
49
50 path,
51 query,
52 implicitToken: true,
53 defaultExpectedStatus: HttpStatusCode.OK_200
54 })
55 }
56}
diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts
deleted file mode 100644
index 4ea7f1402..000000000
--- a/shared/extra-utils/users/accounts.ts
+++ /dev/null
@@ -1,87 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import * as request from 'supertest'
4import { expect } from 'chai'
5import { existsSync, readdir } from 'fs-extra'
6import { join } from 'path'
7import { Account } from '../../models/actors'
8import { root } from '../miscs/miscs'
9import { makeGetRequest } from '../requests/requests'
10import { VideoRateType } from '../../models/videos'
11import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
12
13function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = HttpStatusCode.OK_200) {
14 const path = '/api/v1/accounts'
15
16 return makeGetRequest({
17 url,
18 query: { sort },
19 path,
20 statusCodeExpected
21 })
22}
23
24function getAccount (url: string, accountName: string, statusCodeExpected = HttpStatusCode.OK_200) {
25 const path = '/api/v1/accounts/' + accountName
26
27 return makeGetRequest({
28 url,
29 path,
30 statusCodeExpected
31 })
32}
33
34async function expectAccountFollows (url: string, nameWithDomain: string, followersCount: number, followingCount: number) {
35 const res = await getAccountsList(url)
36 const account = res.body.data.find((a: Account) => a.name + '@' + a.host === nameWithDomain)
37
38 const message = `${nameWithDomain} on ${url}`
39 expect(account.followersCount).to.equal(followersCount, message)
40 expect(account.followingCount).to.equal(followingCount, message)
41}
42
43async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
44 const testDirectory = 'test' + serverNumber
45
46 for (const directory of [ 'avatars' ]) {
47 const directoryPath = join(root(), testDirectory, directory)
48
49 const directoryExists = existsSync(directoryPath)
50 expect(directoryExists).to.be.true
51
52 const files = await readdir(directoryPath)
53 for (const file of files) {
54 expect(file).to.not.contain(filename)
55 }
56 }
57}
58
59function getAccountRatings (
60 url: string,
61 accountName: string,
62 accessToken: string,
63 rating?: VideoRateType,
64 statusCodeExpected = HttpStatusCode.OK_200
65) {
66 const path = '/api/v1/accounts/' + accountName + '/ratings'
67
68 const query = rating ? { rating } : {}
69
70 return request(url)
71 .get(path)
72 .query(query)
73 .set('Accept', 'application/json')
74 .set('Authorization', 'Bearer ' + accessToken)
75 .expect(statusCodeExpected)
76 .expect('Content-Type', /json/)
77}
78
79// ---------------------------------------------------------------------------
80
81export {
82 getAccount,
83 expectAccountFollows,
84 getAccountsList,
85 checkActorFilesWereRemoved,
86 getAccountRatings
87}
diff --git a/shared/extra-utils/users/actors.ts b/shared/extra-utils/users/actors.ts
new file mode 100644
index 000000000..cfcc7d0a7
--- /dev/null
+++ b/shared/extra-utils/users/actors.ts
@@ -0,0 +1,73 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@server/helpers/core-utils'
7import { Account, VideoChannel } from '@shared/models'
8import { PeerTubeServer } from '../server'
9
10async function expectChannelsFollows (options: {
11 server: PeerTubeServer
12 handle: string
13 followers: number
14 following: number
15}) {
16 const { server } = options
17 const { data } = await server.channels.list()
18
19 return expectActorFollow({ ...options, data })
20}
21
22async function expectAccountFollows (options: {
23 server: PeerTubeServer
24 handle: string
25 followers: number
26 following: number
27}) {
28 const { server } = options
29 const { data } = await server.accounts.list()
30
31 return expectActorFollow({ ...options, data })
32}
33
34async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
35 const testDirectory = 'test' + serverNumber
36
37 for (const directory of [ 'avatars' ]) {
38 const directoryPath = join(root(), testDirectory, directory)
39
40 const directoryExists = await pathExists(directoryPath)
41 expect(directoryExists).to.be.true
42
43 const files = await readdir(directoryPath)
44 for (const file of files) {
45 expect(file).to.not.contain(filename)
46 }
47 }
48}
49
50export {
51 expectAccountFollows,
52 expectChannelsFollows,
53 checkActorFilesWereRemoved
54}
55
56// ---------------------------------------------------------------------------
57
58function expectActorFollow (options: {
59 server: PeerTubeServer
60 data: (Account | VideoChannel)[]
61 handle: string
62 followers: number
63 following: number
64}) {
65 const { server, data, handle, followers, following } = options
66
67 const actor = data.find(a => a.name + '@' + a.host === handle)
68 const message = `${handle} on ${server.url}`
69
70 expect(actor, message).to.exist
71 expect(actor.followersCount).to.equal(followers, message)
72 expect(actor.followingCount).to.equal(following, message)
73}
diff --git a/shared/extra-utils/users/blocklist-command.ts b/shared/extra-utils/users/blocklist-command.ts
new file mode 100644
index 000000000..14491a1ae
--- /dev/null
+++ b/shared/extra-utils/users/blocklist-command.ts
@@ -0,0 +1,139 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { AccountBlock, HttpStatusCode, ResultList, ServerBlock } from '@shared/models'
4import { AbstractCommand, OverrideCommandOptions } from '../shared'
5
6type ListBlocklistOptions = OverrideCommandOptions & {
7 start: number
8 count: number
9 sort: string // default -createdAt
10}
11
12export class BlocklistCommand extends AbstractCommand {
13
14 listMyAccountBlocklist (options: ListBlocklistOptions) {
15 const path = '/api/v1/users/me/blocklist/accounts'
16
17 return this.listBlocklist<AccountBlock>(options, path)
18 }
19
20 listMyServerBlocklist (options: ListBlocklistOptions) {
21 const path = '/api/v1/users/me/blocklist/servers'
22
23 return this.listBlocklist<ServerBlock>(options, path)
24 }
25
26 listServerAccountBlocklist (options: ListBlocklistOptions) {
27 const path = '/api/v1/server/blocklist/accounts'
28
29 return this.listBlocklist<AccountBlock>(options, path)
30 }
31
32 listServerServerBlocklist (options: ListBlocklistOptions) {
33 const path = '/api/v1/server/blocklist/servers'
34
35 return this.listBlocklist<ServerBlock>(options, path)
36 }
37
38 // ---------------------------------------------------------------------------
39
40 addToMyBlocklist (options: OverrideCommandOptions & {
41 account?: string
42 server?: string
43 }) {
44 const { account, server } = options
45
46 const path = account
47 ? '/api/v1/users/me/blocklist/accounts'
48 : '/api/v1/users/me/blocklist/servers'
49
50 return this.postBodyRequest({
51 ...options,
52
53 path,
54 fields: {
55 accountName: account,
56 host: server
57 },
58 implicitToken: true,
59 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
60 })
61 }
62
63 addToServerBlocklist (options: OverrideCommandOptions & {
64 account?: string
65 server?: string
66 }) {
67 const { account, server } = options
68
69 const path = account
70 ? '/api/v1/server/blocklist/accounts'
71 : '/api/v1/server/blocklist/servers'
72
73 return this.postBodyRequest({
74 ...options,
75
76 path,
77 fields: {
78 accountName: account,
79 host: server
80 },
81 implicitToken: true,
82 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
83 })
84 }
85
86 // ---------------------------------------------------------------------------
87
88 removeFromMyBlocklist (options: OverrideCommandOptions & {
89 account?: string
90 server?: string
91 }) {
92 const { account, server } = options
93
94 const path = account
95 ? '/api/v1/users/me/blocklist/accounts/' + account
96 : '/api/v1/users/me/blocklist/servers/' + server
97
98 return this.deleteRequest({
99 ...options,
100
101 path,
102 implicitToken: true,
103 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
104 })
105 }
106
107 removeFromServerBlocklist (options: OverrideCommandOptions & {
108 account?: string
109 server?: string
110 }) {
111 const { account, server } = options
112
113 const path = account
114 ? '/api/v1/server/blocklist/accounts/' + account
115 : '/api/v1/server/blocklist/servers/' + server
116
117 return this.deleteRequest({
118 ...options,
119
120 path,
121 implicitToken: true,
122 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
123 })
124 }
125
126 private listBlocklist <T> (options: ListBlocklistOptions, path: string) {
127 const { start, count, sort = '-createdAt' } = options
128
129 return this.getRequestBody<ResultList<T>>({
130 ...options,
131
132 path,
133 query: { start, count, sort },
134 implicitToken: true,
135 defaultExpectedStatus: HttpStatusCode.OK_200
136 })
137 }
138
139}
diff --git a/shared/extra-utils/users/blocklist.ts b/shared/extra-utils/users/blocklist.ts
deleted file mode 100644
index bdf7ee58a..000000000
--- a/shared/extra-utils/users/blocklist.ts
+++ /dev/null
@@ -1,238 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests'
4import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
5
6function getAccountBlocklistByAccount (
7 url: string,
8 token: string,
9 start: number,
10 count: number,
11 sort = '-createdAt',
12 statusCodeExpected = HttpStatusCode.OK_200
13) {
14 const path = '/api/v1/users/me/blocklist/accounts'
15
16 return makeGetRequest({
17 url,
18 token,
19 query: { start, count, sort },
20 path,
21 statusCodeExpected
22 })
23}
24
25function addAccountToAccountBlocklist (
26 url: string,
27 token: string,
28 accountToBlock: string,
29 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
30) {
31 const path = '/api/v1/users/me/blocklist/accounts'
32
33 return makePostBodyRequest({
34 url,
35 path,
36 token,
37 fields: {
38 accountName: accountToBlock
39 },
40 statusCodeExpected
41 })
42}
43
44function removeAccountFromAccountBlocklist (
45 url: string,
46 token: string,
47 accountToUnblock: string,
48 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
49) {
50 const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock
51
52 return makeDeleteRequest({
53 url,
54 path,
55 token,
56 statusCodeExpected
57 })
58}
59
60function getServerBlocklistByAccount (
61 url: string,
62 token: string,
63 start: number,
64 count: number,
65 sort = '-createdAt',
66 statusCodeExpected = HttpStatusCode.OK_200
67) {
68 const path = '/api/v1/users/me/blocklist/servers'
69
70 return makeGetRequest({
71 url,
72 token,
73 query: { start, count, sort },
74 path,
75 statusCodeExpected
76 })
77}
78
79function addServerToAccountBlocklist (
80 url: string,
81 token: string,
82 serverToBlock: string,
83 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
84) {
85 const path = '/api/v1/users/me/blocklist/servers'
86
87 return makePostBodyRequest({
88 url,
89 path,
90 token,
91 fields: {
92 host: serverToBlock
93 },
94 statusCodeExpected
95 })
96}
97
98function removeServerFromAccountBlocklist (
99 url: string,
100 token: string,
101 serverToBlock: string,
102 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
103) {
104 const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock
105
106 return makeDeleteRequest({
107 url,
108 path,
109 token,
110 statusCodeExpected
111 })
112}
113
114function getAccountBlocklistByServer (
115 url: string,
116 token: string,
117 start: number,
118 count: number,
119 sort = '-createdAt',
120 statusCodeExpected = HttpStatusCode.OK_200
121) {
122 const path = '/api/v1/server/blocklist/accounts'
123
124 return makeGetRequest({
125 url,
126 token,
127 query: { start, count, sort },
128 path,
129 statusCodeExpected
130 })
131}
132
133function addAccountToServerBlocklist (
134 url: string,
135 token: string,
136 accountToBlock: string,
137 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
138) {
139 const path = '/api/v1/server/blocklist/accounts'
140
141 return makePostBodyRequest({
142 url,
143 path,
144 token,
145 fields: {
146 accountName: accountToBlock
147 },
148 statusCodeExpected
149 })
150}
151
152function removeAccountFromServerBlocklist (
153 url: string,
154 token: string,
155 accountToUnblock: string,
156 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
157) {
158 const path = '/api/v1/server/blocklist/accounts/' + accountToUnblock
159
160 return makeDeleteRequest({
161 url,
162 path,
163 token,
164 statusCodeExpected
165 })
166}
167
168function getServerBlocklistByServer (
169 url: string,
170 token: string,
171 start: number,
172 count: number,
173 sort = '-createdAt',
174 statusCodeExpected = HttpStatusCode.OK_200
175) {
176 const path = '/api/v1/server/blocklist/servers'
177
178 return makeGetRequest({
179 url,
180 token,
181 query: { start, count, sort },
182 path,
183 statusCodeExpected
184 })
185}
186
187function addServerToServerBlocklist (
188 url: string,
189 token: string,
190 serverToBlock: string,
191 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
192) {
193 const path = '/api/v1/server/blocklist/servers'
194
195 return makePostBodyRequest({
196 url,
197 path,
198 token,
199 fields: {
200 host: serverToBlock
201 },
202 statusCodeExpected
203 })
204}
205
206function removeServerFromServerBlocklist (
207 url: string,
208 token: string,
209 serverToBlock: string,
210 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
211) {
212 const path = '/api/v1/server/blocklist/servers/' + serverToBlock
213
214 return makeDeleteRequest({
215 url,
216 path,
217 token,
218 statusCodeExpected
219 })
220}
221
222// ---------------------------------------------------------------------------
223
224export {
225 getAccountBlocklistByAccount,
226 addAccountToAccountBlocklist,
227 removeAccountFromAccountBlocklist,
228 getServerBlocklistByAccount,
229 addServerToAccountBlocklist,
230 removeServerFromAccountBlocklist,
231
232 getAccountBlocklistByServer,
233 addAccountToServerBlocklist,
234 removeAccountFromServerBlocklist,
235 getServerBlocklistByServer,
236 addServerToServerBlocklist,
237 removeServerFromServerBlocklist
238}
diff --git a/shared/extra-utils/users/index.ts b/shared/extra-utils/users/index.ts
new file mode 100644
index 000000000..460a06f70
--- /dev/null
+++ b/shared/extra-utils/users/index.ts
@@ -0,0 +1,9 @@
1export * from './accounts-command'
2export * from './actors'
3export * from './blocklist-command'
4export * from './login'
5export * from './login-command'
6export * from './notifications'
7export * from './notifications-command'
8export * from './subscriptions-command'
9export * from './users-command'
diff --git a/shared/extra-utils/users/login-command.ts b/shared/extra-utils/users/login-command.ts
new file mode 100644
index 000000000..143f72a59
--- /dev/null
+++ b/shared/extra-utils/users/login-command.ts
@@ -0,0 +1,132 @@
1import { HttpStatusCode, PeerTubeProblemDocument } from '@shared/models'
2import { unwrapBody } from '../requests'
3import { AbstractCommand, OverrideCommandOptions } from '../shared'
4
5export class LoginCommand extends AbstractCommand {
6
7 login (options: OverrideCommandOptions & {
8 client?: { id?: string, secret?: string }
9 user?: { username: string, password?: string }
10 } = {}) {
11 const { client = this.server.store.client, user = this.server.store.user } = options
12 const path = '/api/v1/users/token'
13
14 const body = {
15 client_id: client.id,
16 client_secret: client.secret,
17 username: user.username,
18 password: user.password ?? 'password',
19 response_type: 'code',
20 grant_type: 'password',
21 scope: 'upload'
22 }
23
24 return unwrapBody<{ access_token: string, refresh_token: string } & PeerTubeProblemDocument>(this.postBodyRequest({
25 ...options,
26
27 path,
28 requestType: 'form',
29 fields: body,
30 implicitToken: false,
31 defaultExpectedStatus: HttpStatusCode.OK_200
32 }))
33 }
34
35 getAccessToken (arg1?: { username: string, password?: string }): Promise<string>
36 getAccessToken (arg1: string, password?: string): Promise<string>
37 async getAccessToken (arg1?: { username: string, password?: string } | string, password?: string) {
38 let user: { username: string, password?: string }
39
40 if (!arg1) user = this.server.store.user
41 else if (typeof arg1 === 'object') user = arg1
42 else user = { username: arg1, password }
43
44 try {
45 const body = await this.login({ user })
46
47 return body.access_token
48 } catch (err) {
49 throw new Error(`Cannot authenticate. Please check your username/password. (${err})`)
50 }
51 }
52
53 loginUsingExternalToken (options: OverrideCommandOptions & {
54 username: string
55 externalAuthToken: string
56 }) {
57 const { username, externalAuthToken } = options
58 const path = '/api/v1/users/token'
59
60 const body = {
61 client_id: this.server.store.client.id,
62 client_secret: this.server.store.client.secret,
63 username: username,
64 response_type: 'code',
65 grant_type: 'password',
66 scope: 'upload',
67 externalAuthToken
68 }
69
70 return this.postBodyRequest({
71 ...options,
72
73 path,
74 requestType: 'form',
75 fields: body,
76 implicitToken: false,
77 defaultExpectedStatus: HttpStatusCode.OK_200
78 })
79 }
80
81 logout (options: OverrideCommandOptions & {
82 token: string
83 }) {
84 const path = '/api/v1/users/revoke-token'
85
86 return unwrapBody<{ redirectUrl: string }>(this.postBodyRequest({
87 ...options,
88
89 path,
90 requestType: 'form',
91 implicitToken: false,
92 defaultExpectedStatus: HttpStatusCode.OK_200
93 }))
94 }
95
96 refreshToken (options: OverrideCommandOptions & {
97 refreshToken: string
98 }) {
99 const path = '/api/v1/users/token'
100
101 const body = {
102 client_id: this.server.store.client.id,
103 client_secret: this.server.store.client.secret,
104 refresh_token: options.refreshToken,
105 response_type: 'code',
106 grant_type: 'refresh_token'
107 }
108
109 return this.postBodyRequest({
110 ...options,
111
112 path,
113 requestType: 'form',
114 fields: body,
115 implicitToken: false,
116 defaultExpectedStatus: HttpStatusCode.OK_200
117 })
118 }
119
120 getClient (options: OverrideCommandOptions = {}) {
121 const path = '/api/v1/oauth-clients/local'
122
123 return this.getRequestBody<{ client_id: string, client_secret: string }>({
124 ...options,
125
126 path,
127 host: this.server.host,
128 implicitToken: false,
129 defaultExpectedStatus: HttpStatusCode.OK_200
130 })
131 }
132}
diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts
index 39e1a2747..f1df027d3 100644
--- a/shared/extra-utils/users/login.ts
+++ b/shared/extra-utils/users/login.ts
@@ -1,133 +1,19 @@
1import * as request from 'supertest' 1import { PeerTubeServer } from '../server/server'
2 2
3import { ServerInfo } from '../server/servers' 3function setAccessTokensToServers (servers: PeerTubeServer[]) {
4import { getClient } from '../server/clients'
5import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
6
7type Client = { id: string, secret: string }
8type User = { username: string, password: string }
9type Server = { url: string, client: Client, user: User }
10
11function login (url: string, client: Client, user: User, expectedStatus = HttpStatusCode.OK_200) {
12 const path = '/api/v1/users/token'
13
14 const body = {
15 client_id: client.id,
16 client_secret: client.secret,
17 username: user.username,
18 password: user.password,
19 response_type: 'code',
20 grant_type: 'password',
21 scope: 'upload'
22 }
23
24 return request(url)
25 .post(path)
26 .type('form')
27 .send(body)
28 .expect(expectedStatus)
29}
30
31function logout (url: string, token: string, expectedStatus = HttpStatusCode.OK_200) {
32 const path = '/api/v1/users/revoke-token'
33
34 return request(url)
35 .post(path)
36 .set('Authorization', 'Bearer ' + token)
37 .type('form')
38 .expect(expectedStatus)
39}
40
41async function serverLogin (server: Server) {
42 const res = await login(server.url, server.client, server.user, HttpStatusCode.OK_200)
43
44 return res.body.access_token as string
45}
46
47function refreshToken (server: ServerInfo, refreshToken: string, expectedStatus = HttpStatusCode.OK_200) {
48 const path = '/api/v1/users/token'
49
50 const body = {
51 client_id: server.client.id,
52 client_secret: server.client.secret,
53 refresh_token: refreshToken,
54 response_type: 'code',
55 grant_type: 'refresh_token'
56 }
57
58 return request(server.url)
59 .post(path)
60 .type('form')
61 .send(body)
62 .expect(expectedStatus)
63}
64
65async function userLogin (server: Server, user: User, expectedStatus = HttpStatusCode.OK_200) {
66 const res = await login(server.url, server.client, user, expectedStatus)
67
68 return res.body.access_token as string
69}
70
71async function getAccessToken (url: string, username: string, password: string) {
72 const resClient = await getClient(url)
73 const client = {
74 id: resClient.body.client_id,
75 secret: resClient.body.client_secret
76 }
77
78 const user = { username, password }
79
80 try {
81 const res = await login(url, client, user)
82 return res.body.access_token
83 } catch (err) {
84 throw new Error('Cannot authenticate. Please check your username/password.')
85 }
86}
87
88function setAccessTokensToServers (servers: ServerInfo[]) {
89 const tasks: Promise<any>[] = [] 4 const tasks: Promise<any>[] = []
90 5
91 for (const server of servers) { 6 for (const server of servers) {
92 const p = serverLogin(server).then(t => { server.accessToken = t }) 7 const p = server.login.getAccessToken()
8 .then(t => { server.accessToken = t })
93 tasks.push(p) 9 tasks.push(p)
94 } 10 }
95 11
96 return Promise.all(tasks) 12 return Promise.all(tasks)
97} 13}
98 14
99function loginUsingExternalToken (server: Server, username: string, externalAuthToken: string, expectedStatus = HttpStatusCode.OK_200) {
100 const path = '/api/v1/users/token'
101
102 const body = {
103 client_id: server.client.id,
104 client_secret: server.client.secret,
105 username: username,
106 response_type: 'code',
107 grant_type: 'password',
108 scope: 'upload',
109 externalAuthToken
110 }
111
112 return request(server.url)
113 .post(path)
114 .type('form')
115 .send(body)
116 .expect(expectedStatus)
117}
118
119// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
120 16
121export { 17export {
122 login, 18 setAccessTokensToServers
123 logout,
124 serverLogin,
125 refreshToken,
126 userLogin,
127 getAccessToken,
128 setAccessTokensToServers,
129 Server,
130 Client,
131 User,
132 loginUsingExternalToken
133} 19}
diff --git a/shared/extra-utils/users/notifications-command.ts b/shared/extra-utils/users/notifications-command.ts
new file mode 100644
index 000000000..2d79a3747
--- /dev/null
+++ b/shared/extra-utils/users/notifications-command.ts
@@ -0,0 +1,86 @@
1import { HttpStatusCode, ResultList } from '@shared/models'
2import { UserNotification, UserNotificationSetting } from '../../models/users'
3import { AbstractCommand, OverrideCommandOptions } from '../shared'
4
5export class NotificationsCommand extends AbstractCommand {
6
7 updateMySettings (options: OverrideCommandOptions & {
8 settings: UserNotificationSetting
9 }) {
10 const path = '/api/v1/users/me/notification-settings'
11
12 return this.putBodyRequest({
13 ...options,
14
15 path,
16 fields: options.settings,
17 implicitToken: true,
18 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
19 })
20 }
21
22 list (options: OverrideCommandOptions & {
23 start?: number
24 count?: number
25 unread?: boolean
26 sort?: string
27 }) {
28 const { start, count, unread, sort = '-createdAt' } = options
29 const path = '/api/v1/users/me/notifications'
30
31 return this.getRequestBody<ResultList<UserNotification>>({
32 ...options,
33
34 path,
35 query: {
36 start,
37 count,
38 sort,
39 unread
40 },
41 implicitToken: true,
42 defaultExpectedStatus: HttpStatusCode.OK_200
43 })
44 }
45
46 markAsRead (options: OverrideCommandOptions & {
47 ids: number[]
48 }) {
49 const { ids } = options
50 const path = '/api/v1/users/me/notifications/read'
51
52 return this.postBodyRequest({
53 ...options,
54
55 path,
56 fields: { ids },
57 implicitToken: true,
58 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
59 })
60 }
61
62 markAsReadAll (options: OverrideCommandOptions) {
63 const path = '/api/v1/users/me/notifications/read-all'
64
65 return this.postBodyRequest({
66 ...options,
67
68 path,
69 implicitToken: true,
70 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
71 })
72 }
73
74 async getLastest (options: OverrideCommandOptions = {}) {
75 const { total, data } = await this.list({
76 ...options,
77 start: 0,
78 count: 1,
79 sort: '-createdAt'
80 })
81
82 if (total === 0) return undefined
83
84 return data[0]
85 }
86}
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/notifications.ts
index 844f4442d..4c42fad3e 100644
--- a/shared/extra-utils/users/user-notifications.ts
+++ b/shared/extra-utils/users/notifications.ts
@@ -3,91 +3,36 @@
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { inspect } from 'util' 4import { inspect } from 'util'
5import { AbuseState, PluginType } from '@shared/models' 5import { AbuseState, PluginType } from '@shared/models'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
7import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users' 6import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
8import { MockSmtpServer } from '../miscs/email' 7import { MockSmtpServer } from '../mock-servers/mock-email'
9import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' 8import { PeerTubeServer } from '../server'
10import { doubleFollow } from '../server/follows' 9import { doubleFollow } from '../server/follows'
11import { flushAndRunMultipleServers, ServerInfo } from '../server/servers' 10import { createMultipleServers } from '../server/servers'
12import { getUserNotificationSocket } from '../socket/socket-io' 11import { setAccessTokensToServers } from './login'
13import { setAccessTokensToServers, userLogin } from './login'
14import { createUser, getMyUserInformation } from './users'
15 12
16function updateMyNotificationSettings ( 13function getAllNotificationsSettings (): UserNotificationSetting {
17 url: string, 14 return {
18 token: string, 15 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
19 settings: UserNotificationSetting, 16 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
20 statusCodeExpected = HttpStatusCode.NO_CONTENT_204 17 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
21) { 18 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
22 const path = '/api/v1/users/me/notification-settings' 19 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
23 20 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
24 return makePutBodyRequest({ 21 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
25 url, 22 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
26 path, 23 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
27 token, 24 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
28 fields: settings, 25 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
29 statusCodeExpected 26 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
30 }) 27 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
31} 28 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
32 29 newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
33async function getUserNotifications ( 30 newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
34 url: string, 31 }
35 token: string,
36 start: number,
37 count: number,
38 unread?: boolean,
39 sort = '-createdAt',
40 statusCodeExpected = HttpStatusCode.OK_200
41) {
42 const path = '/api/v1/users/me/notifications'
43
44 return makeGetRequest({
45 url,
46 path,
47 token,
48 query: {
49 start,
50 count,
51 sort,
52 unread
53 },
54 statusCodeExpected
55 })
56}
57
58function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = HttpStatusCode.NO_CONTENT_204) {
59 const path = '/api/v1/users/me/notifications/read'
60
61 return makePostBodyRequest({
62 url,
63 path,
64 token,
65 fields: { ids },
66 statusCodeExpected
67 })
68}
69
70function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) {
71 const path = '/api/v1/users/me/notifications/read-all'
72
73 return makePostBodyRequest({
74 url,
75 path,
76 token,
77 statusCodeExpected
78 })
79}
80
81async function getLastNotification (serverUrl: string, accessToken: string) {
82 const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
83
84 if (res.body.total === 0) return undefined
85
86 return res.body.data[0] as UserNotification
87} 32}
88 33
89type CheckerBaseParams = { 34type CheckerBaseParams = {
90 server: ServerInfo 35 server: PeerTubeServer
91 emails: any[] 36 emails: any[]
92 socketNotifications: UserNotification[] 37 socketNotifications: UserNotification[]
93 token: string 38 token: string
@@ -105,7 +50,7 @@ async function checkNotification (
105 const check = base.check || { web: true, mail: true } 50 const check = base.check || { web: true, mail: true }
106 51
107 if (check.web) { 52 if (check.web) {
108 const notification = await getLastNotification(base.server.url, base.token) 53 const notification = await base.server.notifications.getLastest({ token: base.token })
109 54
110 if (notification || checkType !== 'absence') { 55 if (notification || checkType !== 'absence') {
111 notificationChecker(notification, checkType) 56 notificationChecker(notification, checkType)
@@ -681,27 +626,6 @@ async function checkNewPluginVersion (base: CheckerBaseParams, pluginType: Plugi
681 await checkNotification(base, notificationChecker, emailNotificationFinder, type) 626 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
682} 627}
683 628
684function getAllNotificationsSettings (): UserNotificationSetting {
685 return {
686 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
687 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
688 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
689 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
690 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
691 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
692 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
693 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
694 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
695 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
696 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
697 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
698 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
699 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
700 newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
701 newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
702 }
703}
704
705async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) { 629async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) {
706 const userNotifications: UserNotification[] = [] 630 const userNotifications: UserNotification[] = []
707 const adminNotifications: UserNotification[] = [] 631 const adminNotifications: UserNotification[] = []
@@ -719,7 +643,7 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an
719 limit: 20 643 limit: 20
720 } 644 }
721 } 645 }
722 const servers = await flushAndRunMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg)) 646 const servers = await createMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg))
723 647
724 await setAccessTokensToServers(servers) 648 await setAccessTokensToServers(servers)
725 649
@@ -727,42 +651,33 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an
727 await doubleFollow(servers[0], servers[1]) 651 await doubleFollow(servers[0], servers[1])
728 } 652 }
729 653
730 const user = { 654 const user = { username: 'user_1', password: 'super password' }
731 username: 'user_1', 655 await servers[0].users.create({ ...user, videoQuota: 10 * 1000 * 1000 })
732 password: 'super password' 656 const userAccessToken = await servers[0].login.getAccessToken(user)
733 }
734 await createUser({
735 url: servers[0].url,
736 accessToken: servers[0].accessToken,
737 username: user.username,
738 password: user.password,
739 videoQuota: 10 * 1000 * 1000
740 })
741 const userAccessToken = await userLogin(servers[0], user)
742 657
743 await updateMyNotificationSettings(servers[0].url, userAccessToken, getAllNotificationsSettings()) 658 await servers[0].notifications.updateMySettings({ token: userAccessToken, settings: getAllNotificationsSettings() })
744 await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, getAllNotificationsSettings()) 659 await servers[0].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
745 660
746 if (serversCount > 1) { 661 if (serversCount > 1) {
747 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, getAllNotificationsSettings()) 662 await servers[1].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
748 } 663 }
749 664
750 { 665 {
751 const socket = getUserNotificationSocket(servers[0].url, userAccessToken) 666 const socket = servers[0].socketIO.getUserNotificationSocket({ token: userAccessToken })
752 socket.on('new-notification', n => userNotifications.push(n)) 667 socket.on('new-notification', n => userNotifications.push(n))
753 } 668 }
754 { 669 {
755 const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken) 670 const socket = servers[0].socketIO.getUserNotificationSocket()
756 socket.on('new-notification', n => adminNotifications.push(n)) 671 socket.on('new-notification', n => adminNotifications.push(n))
757 } 672 }
758 673
759 if (serversCount > 1) { 674 if (serversCount > 1) {
760 const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken) 675 const socket = servers[1].socketIO.getUserNotificationSocket()
761 socket.on('new-notification', n => adminNotificationsServer2.push(n)) 676 socket.on('new-notification', n => adminNotificationsServer2.push(n))
762 } 677 }
763 678
764 const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken) 679 const { videoChannels } = await servers[0].users.getMyInfo()
765 const channelId = resChannel.body.videoChannels[0].id 680 const channelId = videoChannels[0].id
766 681
767 return { 682 return {
768 userNotifications, 683 userNotifications,
@@ -778,11 +693,11 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an
778// --------------------------------------------------------------------------- 693// ---------------------------------------------------------------------------
779 694
780export { 695export {
696 getAllNotificationsSettings,
697
781 CheckerBaseParams, 698 CheckerBaseParams,
782 CheckerType, 699 CheckerType,
783 getAllNotificationsSettings,
784 checkNotification, 700 checkNotification,
785 markAsReadAllNotifications,
786 checkMyVideoImportIsFinished, 701 checkMyVideoImportIsFinished,
787 checkUserRegistered, 702 checkUserRegistered,
788 checkAutoInstanceFollowing, 703 checkAutoInstanceFollowing,
@@ -792,14 +707,10 @@ export {
792 checkNewCommentOnMyVideo, 707 checkNewCommentOnMyVideo,
793 checkNewBlacklistOnMyVideo, 708 checkNewBlacklistOnMyVideo,
794 checkCommentMention, 709 checkCommentMention,
795 updateMyNotificationSettings,
796 checkNewVideoAbuseForModerators, 710 checkNewVideoAbuseForModerators,
797 checkVideoAutoBlacklistForModerators, 711 checkVideoAutoBlacklistForModerators,
798 checkNewAbuseMessage, 712 checkNewAbuseMessage,
799 checkAbuseStateChange, 713 checkAbuseStateChange,
800 getUserNotifications,
801 markAsReadNotifications,
802 getLastNotification,
803 checkNewInstanceFollower, 714 checkNewInstanceFollower,
804 prepareNotificationsTest, 715 prepareNotificationsTest,
805 checkNewCommentAbuseForModerators, 716 checkNewCommentAbuseForModerators,
diff --git a/shared/extra-utils/users/subscriptions-command.ts b/shared/extra-utils/users/subscriptions-command.ts
new file mode 100644
index 000000000..edc60e612
--- /dev/null
+++ b/shared/extra-utils/users/subscriptions-command.ts
@@ -0,0 +1,99 @@
1import { HttpStatusCode, ResultList, Video, VideoChannel } from '@shared/models'
2import { AbstractCommand, OverrideCommandOptions } from '../shared'
3
4export class SubscriptionsCommand extends AbstractCommand {
5
6 add (options: OverrideCommandOptions & {
7 targetUri: string
8 }) {
9 const path = '/api/v1/users/me/subscriptions'
10
11 return this.postBodyRequest({
12 ...options,
13
14 path,
15 fields: { uri: options.targetUri },
16 implicitToken: true,
17 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
18 })
19 }
20
21 list (options: OverrideCommandOptions & {
22 sort?: string // default -createdAt
23 search?: string
24 } = {}) {
25 const { sort = '-createdAt', search } = options
26 const path = '/api/v1/users/me/subscriptions'
27
28 return this.getRequestBody<ResultList<VideoChannel>>({
29 ...options,
30
31 path,
32 query: {
33 sort,
34 search
35 },
36 implicitToken: true,
37 defaultExpectedStatus: HttpStatusCode.OK_200
38 })
39 }
40
41 listVideos (options: OverrideCommandOptions & {
42 sort?: string // default -createdAt
43 } = {}) {
44 const { sort = '-createdAt' } = options
45 const path = '/api/v1/users/me/subscriptions/videos'
46
47 return this.getRequestBody<ResultList<Video>>({
48 ...options,
49
50 path,
51 query: { sort },
52 implicitToken: true,
53 defaultExpectedStatus: HttpStatusCode.OK_200
54 })
55 }
56
57 get (options: OverrideCommandOptions & {
58 uri: string
59 }) {
60 const path = '/api/v1/users/me/subscriptions/' + options.uri
61
62 return this.getRequestBody<VideoChannel>({
63 ...options,
64
65 path,
66 implicitToken: true,
67 defaultExpectedStatus: HttpStatusCode.OK_200
68 })
69 }
70
71 remove (options: OverrideCommandOptions & {
72 uri: string
73 }) {
74 const path = '/api/v1/users/me/subscriptions/' + options.uri
75
76 return this.deleteRequest({
77 ...options,
78
79 path,
80 implicitToken: true,
81 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
82 })
83 }
84
85 exist (options: OverrideCommandOptions & {
86 uris: string[]
87 }) {
88 const path = '/api/v1/users/me/subscriptions/exist'
89
90 return this.getRequestBody<{ [id: string ]: boolean }>({
91 ...options,
92
93 path,
94 query: { 'uris[]': options.uris },
95 implicitToken: true,
96 defaultExpectedStatus: HttpStatusCode.OK_200
97 })
98 }
99}
diff --git a/shared/extra-utils/users/user-subscriptions.ts b/shared/extra-utils/users/user-subscriptions.ts
deleted file mode 100644
index edc7a3562..000000000
--- a/shared/extra-utils/users/user-subscriptions.ts
+++ /dev/null
@@ -1,93 +0,0 @@
1import { makeDeleteRequest, makeGetRequest, makePostBodyRequest } from '../requests/requests'
2import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
3
4function addUserSubscription (url: string, token: string, targetUri: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) {
5 const path = '/api/v1/users/me/subscriptions'
6
7 return makePostBodyRequest({
8 url,
9 path,
10 token,
11 statusCodeExpected,
12 fields: { uri: targetUri }
13 })
14}
15
16function listUserSubscriptions (parameters: {
17 url: string
18 token: string
19 sort?: string
20 search?: string
21 statusCodeExpected?: number
22}) {
23 const { url, token, sort = '-createdAt', search, statusCodeExpected = HttpStatusCode.OK_200 } = parameters
24 const path = '/api/v1/users/me/subscriptions'
25
26 return makeGetRequest({
27 url,
28 path,
29 token,
30 statusCodeExpected,
31 query: {
32 sort,
33 search
34 }
35 })
36}
37
38function listUserSubscriptionVideos (url: string, token: string, sort = '-createdAt', statusCodeExpected = HttpStatusCode.OK_200) {
39 const path = '/api/v1/users/me/subscriptions/videos'
40
41 return makeGetRequest({
42 url,
43 path,
44 token,
45 statusCodeExpected,
46 query: { sort }
47 })
48}
49
50function getUserSubscription (url: string, token: string, uri: string, statusCodeExpected = HttpStatusCode.OK_200) {
51 const path = '/api/v1/users/me/subscriptions/' + uri
52
53 return makeGetRequest({
54 url,
55 path,
56 token,
57 statusCodeExpected
58 })
59}
60
61function removeUserSubscription (url: string, token: string, uri: string, statusCodeExpected = HttpStatusCode.NO_CONTENT_204) {
62 const path = '/api/v1/users/me/subscriptions/' + uri
63
64 return makeDeleteRequest({
65 url,
66 path,
67 token,
68 statusCodeExpected
69 })
70}
71
72function areSubscriptionsExist (url: string, token: string, uris: string[], statusCodeExpected = HttpStatusCode.OK_200) {
73 const path = '/api/v1/users/me/subscriptions/exist'
74
75 return makeGetRequest({
76 url,
77 path,
78 query: { 'uris[]': uris },
79 token,
80 statusCodeExpected
81 })
82}
83
84// ---------------------------------------------------------------------------
85
86export {
87 areSubscriptionsExist,
88 addUserSubscription,
89 listUserSubscriptions,
90 getUserSubscription,
91 listUserSubscriptionVideos,
92 removeUserSubscription
93}
diff --git a/shared/extra-utils/users/users-command.ts b/shared/extra-utils/users/users-command.ts
new file mode 100644
index 000000000..d66ad15f2
--- /dev/null
+++ b/shared/extra-utils/users/users-command.ts
@@ -0,0 +1,414 @@
1import { omit, pick } from 'lodash'
2import {
3 HttpStatusCode,
4 MyUser,
5 ResultList,
6 User,
7 UserAdminFlag,
8 UserCreateResult,
9 UserRole,
10 UserUpdate,
11 UserUpdateMe,
12 UserVideoQuota,
13 UserVideoRate
14} from '@shared/models'
15import { ScopedToken } from '@shared/models/users/user-scoped-token'
16import { unwrapBody } from '../requests'
17import { AbstractCommand, OverrideCommandOptions } from '../shared'
18
19export class UsersCommand extends AbstractCommand {
20
21 askResetPassword (options: OverrideCommandOptions & {
22 email: string
23 }) {
24 const { email } = options
25 const path = '/api/v1/users/ask-reset-password'
26
27 return this.postBodyRequest({
28 ...options,
29
30 path,
31 fields: { email },
32 implicitToken: false,
33 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
34 })
35 }
36
37 resetPassword (options: OverrideCommandOptions & {
38 userId: number
39 verificationString: string
40 password: string
41 }) {
42 const { userId, verificationString, password } = options
43 const path = '/api/v1/users/' + userId + '/reset-password'
44
45 return this.postBodyRequest({
46 ...options,
47
48 path,
49 fields: { password, verificationString },
50 implicitToken: false,
51 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
52 })
53 }
54
55 // ---------------------------------------------------------------------------
56
57 askSendVerifyEmail (options: OverrideCommandOptions & {
58 email: string
59 }) {
60 const { email } = options
61 const path = '/api/v1/users/ask-send-verify-email'
62
63 return this.postBodyRequest({
64 ...options,
65
66 path,
67 fields: { email },
68 implicitToken: false,
69 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
70 })
71 }
72
73 verifyEmail (options: OverrideCommandOptions & {
74 userId: number
75 verificationString: string
76 isPendingEmail?: boolean // default false
77 }) {
78 const { userId, verificationString, isPendingEmail = false } = options
79 const path = '/api/v1/users/' + userId + '/verify-email'
80
81 return this.postBodyRequest({
82 ...options,
83
84 path,
85 fields: {
86 verificationString,
87 isPendingEmail
88 },
89 implicitToken: false,
90 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
91 })
92 }
93
94 // ---------------------------------------------------------------------------
95
96 banUser (options: OverrideCommandOptions & {
97 userId: number
98 reason?: string
99 }) {
100 const { userId, reason } = options
101 const path = '/api/v1/users' + '/' + userId + '/block'
102
103 return this.postBodyRequest({
104 ...options,
105
106 path,
107 fields: { reason },
108 implicitToken: true,
109 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
110 })
111 }
112
113 unbanUser (options: OverrideCommandOptions & {
114 userId: number
115 }) {
116 const { userId } = options
117 const path = '/api/v1/users' + '/' + userId + '/unblock'
118
119 return this.postBodyRequest({
120 ...options,
121
122 path,
123 implicitToken: true,
124 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
125 })
126 }
127
128 // ---------------------------------------------------------------------------
129
130 getMyScopedTokens (options: OverrideCommandOptions = {}) {
131 const path = '/api/v1/users/scoped-tokens'
132
133 return this.getRequestBody<ScopedToken>({
134 ...options,
135
136 path,
137 implicitToken: true,
138 defaultExpectedStatus: HttpStatusCode.OK_200
139 })
140 }
141
142 renewMyScopedTokens (options: OverrideCommandOptions = {}) {
143 const path = '/api/v1/users/scoped-tokens'
144
145 return this.postBodyRequest({
146 ...options,
147
148 path,
149 implicitToken: true,
150 defaultExpectedStatus: HttpStatusCode.OK_200
151 })
152 }
153
154 // ---------------------------------------------------------------------------
155
156 create (options: OverrideCommandOptions & {
157 username: string
158 password?: string
159 videoQuota?: number
160 videoQuotaDaily?: number
161 role?: UserRole
162 adminFlags?: UserAdminFlag
163 }) {
164 const {
165 username,
166 adminFlags,
167 password = 'password',
168 videoQuota = 42000000,
169 videoQuotaDaily = -1,
170 role = UserRole.USER
171 } = options
172
173 const path = '/api/v1/users'
174
175 return unwrapBody<{ user: UserCreateResult }>(this.postBodyRequest({
176 ...options,
177
178 path,
179 fields: {
180 username,
181 password,
182 role,
183 adminFlags,
184 email: username + '@example.com',
185 videoQuota,
186 videoQuotaDaily
187 },
188 implicitToken: true,
189 defaultExpectedStatus: HttpStatusCode.OK_200
190 })).then(res => res.user)
191 }
192
193 async generate (username: string) {
194 const password = 'password'
195 const user = await this.create({ username, password })
196
197 const token = await this.server.login.getAccessToken({ username, password })
198
199 const me = await this.getMyInfo({ token })
200
201 return {
202 token,
203 userId: user.id,
204 userChannelId: me.videoChannels[0].id
205 }
206 }
207
208 async generateUserAndToken (username: string) {
209 const password = 'password'
210 await this.create({ username, password })
211
212 return this.server.login.getAccessToken({ username, password })
213 }
214
215 register (options: OverrideCommandOptions & {
216 username: string
217 password?: string
218 displayName?: string
219 channel?: {
220 name: string
221 displayName: string
222 }
223 }) {
224 const { username, password = 'password', displayName, channel } = options
225 const path = '/api/v1/users/register'
226
227 return this.postBodyRequest({
228 ...options,
229
230 path,
231 fields: {
232 username,
233 password,
234 email: username + '@example.com',
235 displayName,
236 channel
237 },
238 implicitToken: false,
239 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
240 })
241 }
242
243 // ---------------------------------------------------------------------------
244
245 getMyInfo (options: OverrideCommandOptions = {}) {
246 const path = '/api/v1/users/me'
247
248 return this.getRequestBody<MyUser>({
249 ...options,
250
251 path,
252 implicitToken: true,
253 defaultExpectedStatus: HttpStatusCode.OK_200
254 })
255 }
256
257 getMyQuotaUsed (options: OverrideCommandOptions = {}) {
258 const path = '/api/v1/users/me/video-quota-used'
259
260 return this.getRequestBody<UserVideoQuota>({
261 ...options,
262
263 path,
264 implicitToken: true,
265 defaultExpectedStatus: HttpStatusCode.OK_200
266 })
267 }
268
269 getMyRating (options: OverrideCommandOptions & {
270 videoId: number | string
271 }) {
272 const { videoId } = options
273 const path = '/api/v1/users/me/videos/' + videoId + '/rating'
274
275 return this.getRequestBody<UserVideoRate>({
276 ...options,
277
278 path,
279 implicitToken: true,
280 defaultExpectedStatus: HttpStatusCode.OK_200
281 })
282 }
283
284 deleteMe (options: OverrideCommandOptions = {}) {
285 const path = '/api/v1/users/me'
286
287 return this.deleteRequest({
288 ...options,
289
290 path,
291 implicitToken: true,
292 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
293 })
294 }
295
296 updateMe (options: OverrideCommandOptions & UserUpdateMe) {
297 const path = '/api/v1/users/me'
298
299 const toSend: UserUpdateMe = omit(options, 'url', 'accessToken')
300
301 return this.putBodyRequest({
302 ...options,
303
304 path,
305 fields: toSend,
306 implicitToken: true,
307 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
308 })
309 }
310
311 updateMyAvatar (options: OverrideCommandOptions & {
312 fixture: string
313 }) {
314 const { fixture } = options
315 const path = '/api/v1/users/me/avatar/pick'
316
317 return this.updateImageRequest({
318 ...options,
319
320 path,
321 fixture,
322 fieldname: 'avatarfile',
323
324 implicitToken: true,
325 defaultExpectedStatus: HttpStatusCode.OK_200
326 })
327 }
328
329 // ---------------------------------------------------------------------------
330
331 get (options: OverrideCommandOptions & {
332 userId: number
333 withStats?: boolean // default false
334 }) {
335 const { userId, withStats } = options
336 const path = '/api/v1/users/' + userId
337
338 return this.getRequestBody<User>({
339 ...options,
340
341 path,
342 query: { withStats },
343 implicitToken: true,
344 defaultExpectedStatus: HttpStatusCode.OK_200
345 })
346 }
347
348 list (options: OverrideCommandOptions & {
349 start?: number
350 count?: number
351 sort?: string
352 search?: string
353 blocked?: boolean
354 } = {}) {
355 const path = '/api/v1/users'
356
357 return this.getRequestBody<ResultList<User>>({
358 ...options,
359
360 path,
361 query: pick(options, [ 'start', 'count', 'sort', 'search', 'blocked' ]),
362 implicitToken: true,
363 defaultExpectedStatus: HttpStatusCode.OK_200
364 })
365 }
366
367 remove (options: OverrideCommandOptions & {
368 userId: number
369 }) {
370 const { userId } = options
371 const path = '/api/v1/users/' + userId
372
373 return this.deleteRequest({
374 ...options,
375
376 path,
377 implicitToken: true,
378 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
379 })
380 }
381
382 update (options: OverrideCommandOptions & {
383 userId: number
384 email?: string
385 emailVerified?: boolean
386 videoQuota?: number
387 videoQuotaDaily?: number
388 password?: string
389 adminFlags?: UserAdminFlag
390 pluginAuth?: string
391 role?: UserRole
392 }) {
393 const path = '/api/v1/users/' + options.userId
394
395 const toSend: UserUpdate = {}
396 if (options.password !== undefined && options.password !== null) toSend.password = options.password
397 if (options.email !== undefined && options.email !== null) toSend.email = options.email
398 if (options.emailVerified !== undefined && options.emailVerified !== null) toSend.emailVerified = options.emailVerified
399 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend.videoQuota = options.videoQuota
400 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend.videoQuotaDaily = options.videoQuotaDaily
401 if (options.role !== undefined && options.role !== null) toSend.role = options.role
402 if (options.adminFlags !== undefined && options.adminFlags !== null) toSend.adminFlags = options.adminFlags
403 if (options.pluginAuth !== undefined) toSend.pluginAuth = options.pluginAuth
404
405 return this.putBodyRequest({
406 ...options,
407
408 path,
409 fields: toSend,
410 implicitToken: true,
411 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
412 })
413 }
414}
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
deleted file mode 100644
index 0f15962ad..000000000
--- a/shared/extra-utils/users/users.ts
+++ /dev/null
@@ -1,415 +0,0 @@
1import { omit } from 'lodash'
2import * as request from 'supertest'
3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
4import { UserUpdateMe } from '../../models/users'
5import { UserAdminFlag } from '../../models/users/user-flag.model'
6import { UserRegister } from '../../models/users/user-register.model'
7import { UserRole } from '../../models/users/user-role'
8import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateImageRequest } from '../requests/requests'
9import { ServerInfo } from '../server/servers'
10import { userLogin } from './login'
11
12function createUser (parameters: {
13 url: string
14 accessToken: string
15 username: string
16 password: string
17 videoQuota?: number
18 videoQuotaDaily?: number
19 role?: UserRole
20 adminFlags?: UserAdminFlag
21 specialStatus?: number
22}) {
23 const {
24 url,
25 accessToken,
26 username,
27 adminFlags,
28 password = 'password',
29 videoQuota = 1000000,
30 videoQuotaDaily = -1,
31 role = UserRole.USER,
32 specialStatus = HttpStatusCode.OK_200
33 } = parameters
34
35 const path = '/api/v1/users'
36 const body = {
37 username,
38 password,
39 role,
40 adminFlags,
41 email: username + '@example.com',
42 videoQuota,
43 videoQuotaDaily
44 }
45
46 return request(url)
47 .post(path)
48 .set('Accept', 'application/json')
49 .set('Authorization', 'Bearer ' + accessToken)
50 .send(body)
51 .expect(specialStatus)
52}
53
54async function generateUser (server: ServerInfo, username: string) {
55 const password = 'my super password'
56 const resCreate = await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
57
58 const token = await userLogin(server, { username, password })
59
60 const resMe = await getMyUserInformation(server.url, token)
61
62 return {
63 token,
64 userId: resCreate.body.user.id,
65 userChannelId: resMe.body.videoChannels[0].id
66 }
67}
68
69async function generateUserAccessToken (server: ServerInfo, username: string) {
70 const password = 'my super password'
71 await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
72
73 return userLogin(server, { username, password })
74}
75
76function registerUser (url: string, username: string, password: string, specialStatus = HttpStatusCode.NO_CONTENT_204) {
77 const path = '/api/v1/users/register'
78 const body = {
79 username,
80 password,
81 email: username + '@example.com'
82 }
83
84 return request(url)
85 .post(path)
86 .set('Accept', 'application/json')
87 .send(body)
88 .expect(specialStatus)
89}
90
91function registerUserWithChannel (options: {
92 url: string
93 user: { username: string, password: string, displayName?: string }
94 channel: { name: string, displayName: string }
95}) {
96 const path = '/api/v1/users/register'
97 const body: UserRegister = {
98 username: options.user.username,
99 password: options.user.password,
100 email: options.user.username + '@example.com',
101 channel: options.channel
102 }
103
104 if (options.user.displayName) {
105 Object.assign(body, { displayName: options.user.displayName })
106 }
107
108 return makePostBodyRequest({
109 url: options.url,
110 path,
111 fields: body,
112 statusCodeExpected: HttpStatusCode.NO_CONTENT_204
113 })
114}
115
116function getMyUserInformation (url: string, accessToken: string, specialStatus = HttpStatusCode.OK_200) {
117 const path = '/api/v1/users/me'
118
119 return request(url)
120 .get(path)
121 .set('Accept', 'application/json')
122 .set('Authorization', 'Bearer ' + accessToken)
123 .expect(specialStatus)
124 .expect('Content-Type', /json/)
125}
126
127function getUserScopedTokens (url: string, token: string, statusCodeExpected = HttpStatusCode.OK_200) {
128 const path = '/api/v1/users/scoped-tokens'
129
130 return makeGetRequest({
131 url,
132 path,
133 token,
134 statusCodeExpected
135 })
136}
137
138function renewUserScopedTokens (url: string, token: string, statusCodeExpected = HttpStatusCode.OK_200) {
139 const path = '/api/v1/users/scoped-tokens'
140
141 return makePostBodyRequest({
142 url,
143 path,
144 token,
145 statusCodeExpected
146 })
147}
148
149function deleteMe (url: string, accessToken: string, specialStatus = HttpStatusCode.NO_CONTENT_204) {
150 const path = '/api/v1/users/me'
151
152 return request(url)
153 .delete(path)
154 .set('Accept', 'application/json')
155 .set('Authorization', 'Bearer ' + accessToken)
156 .expect(specialStatus)
157}
158
159function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatus = HttpStatusCode.OK_200) {
160 const path = '/api/v1/users/me/video-quota-used'
161
162 return request(url)
163 .get(path)
164 .set('Accept', 'application/json')
165 .set('Authorization', 'Bearer ' + accessToken)
166 .expect(specialStatus)
167 .expect('Content-Type', /json/)
168}
169
170function getUserInformation (url: string, accessToken: string, userId: number, withStats = false) {
171 const path = '/api/v1/users/' + userId
172
173 return request(url)
174 .get(path)
175 .query({ withStats })
176 .set('Accept', 'application/json')
177 .set('Authorization', 'Bearer ' + accessToken)
178 .expect(HttpStatusCode.OK_200)
179 .expect('Content-Type', /json/)
180}
181
182function getMyUserVideoRating (url: string, accessToken: string, videoId: number | string, specialStatus = HttpStatusCode.OK_200) {
183 const path = '/api/v1/users/me/videos/' + videoId + '/rating'
184
185 return request(url)
186 .get(path)
187 .set('Accept', 'application/json')
188 .set('Authorization', 'Bearer ' + accessToken)
189 .expect(specialStatus)
190 .expect('Content-Type', /json/)
191}
192
193function getUsersList (url: string, accessToken: string) {
194 const path = '/api/v1/users'
195
196 return request(url)
197 .get(path)
198 .set('Accept', 'application/json')
199 .set('Authorization', 'Bearer ' + accessToken)
200 .expect(HttpStatusCode.OK_200)
201 .expect('Content-Type', /json/)
202}
203
204function getUsersListPaginationAndSort (
205 url: string,
206 accessToken: string,
207 start: number,
208 count: number,
209 sort: string,
210 search?: string,
211 blocked?: boolean
212) {
213 const path = '/api/v1/users'
214
215 const query = {
216 start,
217 count,
218 sort,
219 search,
220 blocked
221 }
222
223 return request(url)
224 .get(path)
225 .query(query)
226 .set('Accept', 'application/json')
227 .set('Authorization', 'Bearer ' + accessToken)
228 .expect(HttpStatusCode.OK_200)
229 .expect('Content-Type', /json/)
230}
231
232function removeUser (url: string, userId: number | string, accessToken: string, expectedStatus = HttpStatusCode.NO_CONTENT_204) {
233 const path = '/api/v1/users'
234
235 return request(url)
236 .delete(path + '/' + userId)
237 .set('Accept', 'application/json')
238 .set('Authorization', 'Bearer ' + accessToken)
239 .expect(expectedStatus)
240}
241
242function blockUser (
243 url: string,
244 userId: number | string,
245 accessToken: string,
246 expectedStatus = HttpStatusCode.NO_CONTENT_204,
247 reason?: string
248) {
249 const path = '/api/v1/users'
250 let body: any
251 if (reason) body = { reason }
252
253 return request(url)
254 .post(path + '/' + userId + '/block')
255 .send(body)
256 .set('Accept', 'application/json')
257 .set('Authorization', 'Bearer ' + accessToken)
258 .expect(expectedStatus)
259}
260
261function unblockUser (url: string, userId: number | string, accessToken: string, expectedStatus = HttpStatusCode.NO_CONTENT_204) {
262 const path = '/api/v1/users'
263
264 return request(url)
265 .post(path + '/' + userId + '/unblock')
266 .set('Accept', 'application/json')
267 .set('Authorization', 'Bearer ' + accessToken)
268 .expect(expectedStatus)
269}
270
271function updateMyUser (options: { url: string, accessToken: string, statusCodeExpected?: HttpStatusCode } & UserUpdateMe) {
272 const path = '/api/v1/users/me'
273
274 const toSend: UserUpdateMe = omit(options, 'url', 'accessToken')
275
276 return makePutBodyRequest({
277 url: options.url,
278 path,
279 token: options.accessToken,
280 fields: toSend,
281 statusCodeExpected: options.statusCodeExpected || HttpStatusCode.NO_CONTENT_204
282 })
283}
284
285function updateMyAvatar (options: {
286 url: string
287 accessToken: string
288 fixture: string
289}) {
290 const path = '/api/v1/users/me/avatar/pick'
291
292 return updateImageRequest({ ...options, path, fieldname: 'avatarfile' })
293}
294
295function updateUser (options: {
296 url: string
297 userId: number
298 accessToken: string
299 email?: string
300 emailVerified?: boolean
301 videoQuota?: number
302 videoQuotaDaily?: number
303 password?: string
304 adminFlags?: UserAdminFlag
305 pluginAuth?: string
306 role?: UserRole
307}) {
308 const path = '/api/v1/users/' + options.userId
309
310 const toSend = {}
311 if (options.password !== undefined && options.password !== null) toSend['password'] = options.password
312 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
313 if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified
314 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
315 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
316 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
317 if (options.adminFlags !== undefined && options.adminFlags !== null) toSend['adminFlags'] = options.adminFlags
318 if (options.pluginAuth !== undefined) toSend['pluginAuth'] = options.pluginAuth
319
320 return makePutBodyRequest({
321 url: options.url,
322 path,
323 token: options.accessToken,
324 fields: toSend,
325 statusCodeExpected: HttpStatusCode.NO_CONTENT_204
326 })
327}
328
329function askResetPassword (url: string, email: string) {
330 const path = '/api/v1/users/ask-reset-password'
331
332 return makePostBodyRequest({
333 url,
334 path,
335 fields: { email },
336 statusCodeExpected: HttpStatusCode.NO_CONTENT_204
337 })
338}
339
340function resetPassword (
341 url: string,
342 userId: number,
343 verificationString: string,
344 password: string,
345 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
346) {
347 const path = '/api/v1/users/' + userId + '/reset-password'
348
349 return makePostBodyRequest({
350 url,
351 path,
352 fields: { password, verificationString },
353 statusCodeExpected
354 })
355}
356
357function askSendVerifyEmail (url: string, email: string) {
358 const path = '/api/v1/users/ask-send-verify-email'
359
360 return makePostBodyRequest({
361 url,
362 path,
363 fields: { email },
364 statusCodeExpected: HttpStatusCode.NO_CONTENT_204
365 })
366}
367
368function verifyEmail (
369 url: string,
370 userId: number,
371 verificationString: string,
372 isPendingEmail = false,
373 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
374) {
375 const path = '/api/v1/users/' + userId + '/verify-email'
376
377 return makePostBodyRequest({
378 url,
379 path,
380 fields: {
381 verificationString,
382 isPendingEmail
383 },
384 statusCodeExpected
385 })
386}
387
388// ---------------------------------------------------------------------------
389
390export {
391 createUser,
392 registerUser,
393 getMyUserInformation,
394 getMyUserVideoRating,
395 deleteMe,
396 registerUserWithChannel,
397 getMyUserVideoQuotaUsed,
398 getUsersList,
399 getUsersListPaginationAndSort,
400 removeUser,
401 updateUser,
402 updateMyUser,
403 getUserInformation,
404 blockUser,
405 unblockUser,
406 askResetPassword,
407 resetPassword,
408 renewUserScopedTokens,
409 updateMyAvatar,
410 generateUser,
411 askSendVerifyEmail,
412 generateUserAccessToken,
413 verifyEmail,
414 getUserScopedTokens
415}