Changeset View
Standalone View
keyserver/src/creators/invite-link-creator.js
- This file was added.
// @flow | |||||
import type { | |||||
CreateOrUpdatePublicLinkRequest, | |||||
InviteLink, | |||||
} from 'lib/types/link-types.js'; | |||||
import { threadPermissions } from 'lib/types/thread-permission-types.js'; | |||||
import { ServerError } from 'lib/utils/errors.js'; | |||||
import createIDs from './id-creator.js'; | |||||
import { dbQuery, SQL } from '../database/database.js'; | |||||
import { fetchPrimaryInviteLinks } from '../fetchers/link-fetchers.js'; | |||||
import { fetchServerThreadInfos } from '../fetchers/thread-fetchers.js'; | |||||
import { checkThreadPermission } from '../fetchers/thread-permission-fetchers.js'; | |||||
import { Viewer } from '../session/viewer.js'; | |||||
const secretRegex = /^[a-zA-Z0-9]+$/; | |||||
async function createOrUpdatePublicLink( | |||||
viewer: Viewer, | |||||
request: CreateOrUpdatePublicLinkRequest, | |||||
): Promise<InviteLink> { | |||||
if (!secretRegex.test(request.name)) { | |||||
throw new ServerError('invalid_parameters'); | |||||
} | |||||
kamil: Can this be done via validatior?
(I think `tcomb` supports regular expressions in built-in… | |||||
tomekAuthorUnsubmitted Done Inline ActionsMakes sense! tomek: Makes sense! | |||||
tomekAuthorUnsubmitted Done Inline ActionsI've implemented it and it would require some changes in terms of error handling. Currently, for invalid text, the error message is data isn't of type T - which doesn't make too much sense. So updating this to be a validator should happen as a part of https://linear.app/comm/issue/ENG-3916/better-error-handling. tomek: I've implemented it and it would require some changes in terms of error handling. Currently… | |||||
const permissionPromise = checkThreadPermission( | |||||
viewer, | |||||
request.communityID, | |||||
threadPermissions.MANAGE_INVITE_LINKS, | |||||
); | |||||
const existingPrimaryLinksPromise = fetchPrimaryInviteLinks(viewer); | |||||
const fetchThreadInfoPromise = fetchServerThreadInfos( | |||||
SQL`t.id = ${request.communityID}`, | |||||
); | |||||
const [hasPermission, existingPrimaryLinks, { threadInfos }] = | |||||
await Promise.all([ | |||||
permissionPromise, | |||||
existingPrimaryLinksPromise, | |||||
fetchThreadInfoPromise, | |||||
]); | |||||
if (!hasPermission) { | |||||
throw new ServerError('invalid_credentials'); | |||||
} | |||||
const threadInfo = threadInfos[request.communityID]; | |||||
if (!threadInfo) { | |||||
throw new ServerError('invalid_parameters'); | |||||
} | |||||
const defaultRoleID = Object.keys(threadInfo.roles).find( | |||||
roleID => threadInfo.roles[roleID].isDefault, | |||||
); | |||||
if (!defaultRoleID) { | |||||
throw new ServerError('invalid_parameters'); | |||||
} | |||||
const existingPrimaryLink = existingPrimaryLinks.find( | |||||
link => link.communityID === request.communityID && link.primary, | |||||
); | |||||
if (existingPrimaryLink) { | |||||
const query = SQL` | |||||
UPDATE invite_links | |||||
SET name = ${request.name} | |||||
WHERE \`primary\` = 1 AND community = ${request.communityID} | |||||
`; | |||||
try { | |||||
await dbQuery(query); | |||||
} catch { | |||||
throw new ServerError('invalid_parameters'); | |||||
kamilUnsubmitted Not Done Inline Actionsinvalid_parameters will always be the case here? I feel like it's more affectedRows = 0 case only. kamil: `invalid_parameters` will always be the case here? I feel like it's more `affectedRows = 0`… | |||||
tomekAuthorUnsubmitted Done Inline ActionsIn case of an exception we can't talk about affected rows. We can check what is inside the exception and based on that conclude that it is because a unique constraint was violated - we can do that as a part of https://linear.app/comm/issue/ENG-3916/better-error-handling. tomek: In case of an exception we can't talk about affected rows. We can check what is inside the… | |||||
} | |||||
return { | |||||
name: request.name, | |||||
primary: true, | |||||
role: defaultRoleID, | |||||
communityID: request.communityID, | |||||
expirationTime: 0, | |||||
limitOfUses: null, | |||||
numberOfUses: 0, | |||||
}; | |||||
} | |||||
const [id] = await createIDs('invite_links', 1); | |||||
const row = [id, request.name, true, request.communityID, defaultRoleID]; | |||||
const createLinkQuery = SQL` | |||||
INSERT INTO invite_links(id, name, \`primary\`, community, role) | |||||
kamilUnsubmitted Not Done Inline ActionsHow about expiration_time, limit_of_uses columns? kamil: How about `expiration_time`, `limit_of_uses` columns?
this will probably work that way but… | |||||
tomekAuthorUnsubmitted Done Inline ActionsThey are nullable, so don't need the default value. tomek: They are nullable, so don't need the default value. | |||||
SELECT ${row} | |||||
WHERE NOT EXISTS ( | |||||
SELECT i.id | |||||
FROM invite_links i | |||||
WHERE i.\`primary\` = 1 AND i.community = ${request.communityID} | |||||
kamilUnsubmitted Not Done Inline ActionsI am a bit confused about naming, you are using phrase public and still setting primary = 1. kamil: I am a bit confused about naming, you are using phrase `public` and still setting `primary = 1`. | |||||
tomekAuthorUnsubmitted Done Inline ActionsInitially, during the design syncs we were using mostly term primary. And the implementation started with that in mind. In the meantime, the copy on UI contains term public. So indeed, these are the same. And public is more recent. So agree, it should be clarified by replacing primary with public. But that would require significant amount of work - created https://linear.app/comm/issue/ENG-3921/rename-all-the-usages-of-primary-to-public-links to track that. tomek: Initially, during the design syncs we were using mostly term `primary`. And the implementation… | |||||
kamilUnsubmitted Not Done Inline ActionsThanks for the explanation, makes sense to me kamil: Thanks for the explanation, makes sense to me | |||||
) | |||||
`; | |||||
let result = null; | |||||
try { | |||||
result = (await dbQuery(createLinkQuery))[0]; | |||||
} catch { | |||||
throw new ServerError('invalid_parameters'); | |||||
} | |||||
if (result.affectedRows === 0) { | |||||
const deleteIDs = SQL` | |||||
DELETE FROM ids | |||||
WHERE id = ${id} | |||||
`; | |||||
await dbQuery(deleteIDs); | |||||
throw new ServerError('invalid_parameters'); | |||||
} | |||||
return { | |||||
name: request.name, | |||||
primary: true, | |||||
role: defaultRoleID, | |||||
communityID: request.communityID, | |||||
expirationTime: 0, | |||||
limitOfUses: null, | |||||
numberOfUses: 0, | |||||
}; | |||||
} | |||||
export { createOrUpdatePublicLink }; |
Can this be done via validatior?
(I think tcomb supports regular expressions in built-in validators)