diff options
author | Chocobozzz <me@florianbigard.com> | 2023-07-31 14:34:36 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-08-11 15:02:33 +0200 |
commit | 3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch) | |
tree | e4510b39bdac9c318fdb4b47018d08f15368b8f0 /server/lib/user.ts | |
parent | 04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff) | |
download | PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip |
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge
conflicts, but it's a major step forward:
* Server can be faster at startup because imports() are async and we can
easily lazy import big modules
* Angular doesn't seem to support ES import (with .js extension), so we
had to correctly organize peertube into a monorepo:
* Use yarn workspace feature
* Use typescript reference projects for dependencies
* Shared projects have been moved into "packages", each one is now a
node module (with a dedicated package.json/tsconfig.json)
* server/tools have been moved into apps/ and is now a dedicated app
bundled and published on NPM so users don't have to build peertube
cli tools manually
* server/tests have been moved into packages/ so we don't compile
them every time we want to run the server
* Use isolatedModule option:
* Had to move from const enum to const
(https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums)
* Had to explictely specify "type" imports when used in decorators
* Prefer tsx (that uses esbuild under the hood) instead of ts-node to
load typescript files (tests with mocha or scripts):
* To reduce test complexity as esbuild doesn't support decorator
metadata, we only test server files that do not import server
models
* We still build tests files into js files for a faster CI
* Remove unmaintained peertube CLI import script
* Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'server/lib/user.ts')
-rw-r--r-- | server/lib/user.ts | 301 |
1 files changed, 0 insertions, 301 deletions
diff --git a/server/lib/user.ts b/server/lib/user.ts deleted file mode 100644 index 56995cca3..000000000 --- a/server/lib/user.ts +++ /dev/null | |||
@@ -1,301 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize/types' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { CONFIG } from '@server/initializers/config' | ||
4 | import { UserModel } from '@server/models/user/user' | ||
5 | import { MActorDefault } from '@server/types/models/actor' | ||
6 | import { ActivityPubActorType } from '../../shared/models/activitypub' | ||
7 | import { UserAdminFlag, UserNotificationSetting, UserNotificationSettingValue, UserRole } from '../../shared/models/users' | ||
8 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' | ||
9 | import { sequelizeTypescript } from '../initializers/database' | ||
10 | import { AccountModel } from '../models/account/account' | ||
11 | import { UserNotificationSettingModel } from '../models/user/user-notification-setting' | ||
12 | import { MAccountDefault, MChannelActor } from '../types/models' | ||
13 | import { MRegistration, MUser, MUserDefault, MUserId } from '../types/models/user' | ||
14 | import { generateAndSaveActorKeys } from './activitypub/actors' | ||
15 | import { getLocalAccountActivityPubUrl } from './activitypub/url' | ||
16 | import { Emailer } from './emailer' | ||
17 | import { LiveQuotaStore } from './live/live-quota-store' | ||
18 | import { buildActorInstance, findAvailableLocalActorName } from './local-actor' | ||
19 | import { Redis } from './redis' | ||
20 | import { createLocalVideoChannel } from './video-channel' | ||
21 | import { createWatchLaterPlaylist } from './video-playlist' | ||
22 | |||
23 | type ChannelNames = { name: string, displayName: string } | ||
24 | |||
25 | function buildUser (options: { | ||
26 | username: string | ||
27 | password: string | ||
28 | email: string | ||
29 | |||
30 | role?: UserRole // Default to UserRole.User | ||
31 | adminFlags?: UserAdminFlag // Default to UserAdminFlag.NONE | ||
32 | |||
33 | emailVerified: boolean | null | ||
34 | |||
35 | videoQuota?: number // Default to CONFIG.USER.VIDEO_QUOTA | ||
36 | videoQuotaDaily?: number // Default to CONFIG.USER.VIDEO_QUOTA_DAILY | ||
37 | |||
38 | pluginAuth?: string | ||
39 | }): MUser { | ||
40 | const { | ||
41 | username, | ||
42 | password, | ||
43 | email, | ||
44 | role = UserRole.USER, | ||
45 | emailVerified, | ||
46 | videoQuota = CONFIG.USER.VIDEO_QUOTA, | ||
47 | videoQuotaDaily = CONFIG.USER.VIDEO_QUOTA_DAILY, | ||
48 | adminFlags = UserAdminFlag.NONE, | ||
49 | pluginAuth | ||
50 | } = options | ||
51 | |||
52 | return new UserModel({ | ||
53 | username, | ||
54 | password, | ||
55 | email, | ||
56 | |||
57 | nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | ||
58 | p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED, | ||
59 | videosHistoryEnabled: CONFIG.USER.HISTORY.VIDEOS.ENABLED, | ||
60 | |||
61 | autoPlayVideo: true, | ||
62 | |||
63 | role, | ||
64 | emailVerified, | ||
65 | adminFlags, | ||
66 | |||
67 | videoQuota, | ||
68 | videoQuotaDaily, | ||
69 | |||
70 | pluginAuth | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | // --------------------------------------------------------------------------- | ||
75 | |||
76 | async function createUserAccountAndChannelAndPlaylist (parameters: { | ||
77 | userToCreate: MUser | ||
78 | userDisplayName?: string | ||
79 | channelNames?: ChannelNames | ||
80 | validateUser?: boolean | ||
81 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { | ||
82 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters | ||
83 | |||
84 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { | ||
85 | const userOptions = { | ||
86 | transaction: t, | ||
87 | validate: validateUser | ||
88 | } | ||
89 | |||
90 | const userCreated: MUserDefault = await userToCreate.save(userOptions) | ||
91 | userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) | ||
92 | |||
93 | const accountCreated = await createLocalAccountWithoutKeys({ | ||
94 | name: userCreated.username, | ||
95 | displayName: userDisplayName, | ||
96 | userId: userCreated.id, | ||
97 | applicationId: null, | ||
98 | t | ||
99 | }) | ||
100 | userCreated.Account = accountCreated | ||
101 | |||
102 | const channelAttributes = await buildChannelAttributes({ user: userCreated, transaction: t, channelNames }) | ||
103 | const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t) | ||
104 | |||
105 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) | ||
106 | |||
107 | return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } | ||
108 | }) | ||
109 | |||
110 | const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([ | ||
111 | generateAndSaveActorKeys(account.Actor), | ||
112 | generateAndSaveActorKeys(videoChannel.Actor) | ||
113 | ]) | ||
114 | |||
115 | account.Actor = accountActorWithKeys | ||
116 | videoChannel.Actor = channelActorWithKeys | ||
117 | |||
118 | return { user, account, videoChannel } | ||
119 | } | ||
120 | |||
121 | async function createLocalAccountWithoutKeys (parameters: { | ||
122 | name: string | ||
123 | displayName?: string | ||
124 | userId: number | null | ||
125 | applicationId: number | null | ||
126 | t: Transaction | undefined | ||
127 | type?: ActivityPubActorType | ||
128 | }) { | ||
129 | const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters | ||
130 | const url = getLocalAccountActivityPubUrl(name) | ||
131 | |||
132 | const actorInstance = buildActorInstance(type, url, name) | ||
133 | const actorInstanceCreated: MActorDefault = await actorInstance.save({ transaction: t }) | ||
134 | |||
135 | const accountInstance = new AccountModel({ | ||
136 | name: displayName || name, | ||
137 | userId, | ||
138 | applicationId, | ||
139 | actorId: actorInstanceCreated.id | ||
140 | }) | ||
141 | |||
142 | const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t }) | ||
143 | accountInstanceCreated.Actor = actorInstanceCreated | ||
144 | |||
145 | return accountInstanceCreated | ||
146 | } | ||
147 | |||
148 | async function createApplicationActor (applicationId: number) { | ||
149 | const accountCreated = await createLocalAccountWithoutKeys({ | ||
150 | name: SERVER_ACTOR_NAME, | ||
151 | userId: null, | ||
152 | applicationId, | ||
153 | t: undefined, | ||
154 | type: 'Application' | ||
155 | }) | ||
156 | |||
157 | accountCreated.Actor = await generateAndSaveActorKeys(accountCreated.Actor) | ||
158 | |||
159 | return accountCreated | ||
160 | } | ||
161 | |||
162 | // --------------------------------------------------------------------------- | ||
163 | |||
164 | async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) { | ||
165 | const verificationString = await Redis.Instance.setUserVerifyEmailVerificationString(user.id) | ||
166 | let verifyEmailUrl = `${WEBSERVER.URL}/verify-account/email?userId=${user.id}&verificationString=${verificationString}` | ||
167 | |||
168 | if (isPendingEmail) verifyEmailUrl += '&isPendingEmail=true' | ||
169 | |||
170 | const to = isPendingEmail | ||
171 | ? user.pendingEmail | ||
172 | : user.email | ||
173 | |||
174 | const username = user.username | ||
175 | |||
176 | Emailer.Instance.addVerifyEmailJob({ username, to, verifyEmailUrl, isRegistrationRequest: false }) | ||
177 | } | ||
178 | |||
179 | async function sendVerifyRegistrationEmail (registration: MRegistration) { | ||
180 | const verificationString = await Redis.Instance.setRegistrationVerifyEmailVerificationString(registration.id) | ||
181 | const verifyEmailUrl = `${WEBSERVER.URL}/verify-account/email?registrationId=${registration.id}&verificationString=${verificationString}` | ||
182 | |||
183 | const to = registration.email | ||
184 | const username = registration.username | ||
185 | |||
186 | Emailer.Instance.addVerifyEmailJob({ username, to, verifyEmailUrl, isRegistrationRequest: true }) | ||
187 | } | ||
188 | |||
189 | // --------------------------------------------------------------------------- | ||
190 | |||
191 | async function getOriginalVideoFileTotalFromUser (user: MUserId) { | ||
192 | // Don't use sequelize because we need to use a sub query | ||
193 | const query = UserModel.generateUserQuotaBaseSQL({ | ||
194 | withSelect: true, | ||
195 | whereUserId: '$userId', | ||
196 | daily: false | ||
197 | }) | ||
198 | |||
199 | const base = await UserModel.getTotalRawQuery(query, user.id) | ||
200 | |||
201 | return base + LiveQuotaStore.Instance.getLiveQuotaOf(user.id) | ||
202 | } | ||
203 | |||
204 | // Returns cumulative size of all video files uploaded in the last 24 hours. | ||
205 | async function getOriginalVideoFileTotalDailyFromUser (user: MUserId) { | ||
206 | // Don't use sequelize because we need to use a sub query | ||
207 | const query = UserModel.generateUserQuotaBaseSQL({ | ||
208 | withSelect: true, | ||
209 | whereUserId: '$userId', | ||
210 | daily: true | ||
211 | }) | ||
212 | |||
213 | const base = await UserModel.getTotalRawQuery(query, user.id) | ||
214 | |||
215 | return base + LiveQuotaStore.Instance.getLiveQuotaOf(user.id) | ||
216 | } | ||
217 | |||
218 | async function isAbleToUploadVideo (userId: number, newVideoSize: number) { | ||
219 | const user = await UserModel.loadById(userId) | ||
220 | |||
221 | if (user.videoQuota === -1 && user.videoQuotaDaily === -1) return Promise.resolve(true) | ||
222 | |||
223 | const [ totalBytes, totalBytesDaily ] = await Promise.all([ | ||
224 | getOriginalVideoFileTotalFromUser(user), | ||
225 | getOriginalVideoFileTotalDailyFromUser(user) | ||
226 | ]) | ||
227 | |||
228 | const uploadedTotal = newVideoSize + totalBytes | ||
229 | const uploadedDaily = newVideoSize + totalBytesDaily | ||
230 | |||
231 | logger.debug( | ||
232 | 'Check user %d quota to upload another video.', userId, | ||
233 | { totalBytes, totalBytesDaily, videoQuota: user.videoQuota, videoQuotaDaily: user.videoQuotaDaily, newVideoSize } | ||
234 | ) | ||
235 | |||
236 | if (user.videoQuotaDaily === -1) return uploadedTotal < user.videoQuota | ||
237 | if (user.videoQuota === -1) return uploadedDaily < user.videoQuotaDaily | ||
238 | |||
239 | return uploadedTotal < user.videoQuota && uploadedDaily < user.videoQuotaDaily | ||
240 | } | ||
241 | |||
242 | // --------------------------------------------------------------------------- | ||
243 | |||
244 | export { | ||
245 | getOriginalVideoFileTotalFromUser, | ||
246 | getOriginalVideoFileTotalDailyFromUser, | ||
247 | createApplicationActor, | ||
248 | createUserAccountAndChannelAndPlaylist, | ||
249 | createLocalAccountWithoutKeys, | ||
250 | |||
251 | sendVerifyUserEmail, | ||
252 | sendVerifyRegistrationEmail, | ||
253 | |||
254 | isAbleToUploadVideo, | ||
255 | buildUser | ||
256 | } | ||
257 | |||
258 | // --------------------------------------------------------------------------- | ||
259 | |||
260 | function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) { | ||
261 | const values: UserNotificationSetting & { userId: number } = { | ||
262 | userId: user.id, | ||
263 | newVideoFromSubscription: UserNotificationSettingValue.WEB, | ||
264 | newCommentOnMyVideo: UserNotificationSettingValue.WEB, | ||
265 | myVideoImportFinished: UserNotificationSettingValue.WEB, | ||
266 | myVideoPublished: UserNotificationSettingValue.WEB, | ||
267 | abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
268 | videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
269 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
270 | newUserRegistration: UserNotificationSettingValue.WEB, | ||
271 | commentMention: UserNotificationSettingValue.WEB, | ||
272 | newFollow: UserNotificationSettingValue.WEB, | ||
273 | newInstanceFollower: UserNotificationSettingValue.WEB, | ||
274 | abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
275 | abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
276 | autoInstanceFollowing: UserNotificationSettingValue.WEB, | ||
277 | newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
278 | newPluginVersion: UserNotificationSettingValue.WEB, | ||
279 | myVideoStudioEditionFinished: UserNotificationSettingValue.WEB | ||
280 | } | ||
281 | |||
282 | return UserNotificationSettingModel.create(values, { transaction: t }) | ||
283 | } | ||
284 | |||
285 | async function buildChannelAttributes (options: { | ||
286 | user: MUser | ||
287 | transaction?: Transaction | ||
288 | channelNames?: ChannelNames | ||
289 | }) { | ||
290 | const { user, transaction, channelNames } = options | ||
291 | |||
292 | if (channelNames) return channelNames | ||
293 | |||
294 | const channelName = await findAvailableLocalActorName(user.username + '_channel', transaction) | ||
295 | const videoChannelDisplayName = `Main ${user.username} channel` | ||
296 | |||
297 | return { | ||
298 | name: channelName, | ||
299 | displayName: videoChannelDisplayName | ||
300 | } | ||
301 | } | ||