From 36cbe840dde919f5a46af2f9005972fb956138fa Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:10:06 +0100 Subject: [PATCH] Merge pull request #28246 from overleaf/td-ts-project-dashboard-jsdoc Working JSDoc type annotations on project list controller GitOrigin-RevId: b26833affb0fc2ecd38e869c2523e914eabe6548 --- .../Authorization/PermissionsController.js | 1 + .../Features/Authorization/PrivilegeLevels.js | 3 + .../Authorization/PublicAccessLevels.js | 4 + .../app/src/Features/Authorization/Sources.js | 3 + .../app/src/Features/Authorization/types.d.ts | 28 ++++ .../Collaborators/CollaboratorsGetter.js | 18 +-- .../Project/ProjectListController.mjs | 133 +++++++++++------- .../web/app/src/Features/Project/types.d.ts | 24 ++++ .../web/app/src/Features/User/UserGetter.js | 26 ++-- .../Project/ProjectListController.test.mjs | 40 ++++-- services/web/tsconfig.backend.json | 3 +- .../web/types/backend/express/request.d.ts | 14 ++ .../types/backend/express/session-data.d.ts | 29 ++++ services/web/types/backend/i18next.d.ts | 10 ++ services/web/types/project/dashboard/api.d.ts | 12 +- 15 files changed, 258 insertions(+), 90 deletions(-) create mode 100644 services/web/app/src/Features/Authorization/types.d.ts create mode 100644 services/web/types/backend/express/request.d.ts create mode 100644 services/web/types/backend/express/session-data.d.ts create mode 100644 services/web/types/backend/i18next.d.ts diff --git a/services/web/app/src/Features/Authorization/PermissionsController.js b/services/web/app/src/Features/Authorization/PermissionsController.js index 1006c32224..4d884e80cb 100644 --- a/services/web/app/src/Features/Authorization/PermissionsController.js +++ b/services/web/app/src/Features/Authorization/PermissionsController.js @@ -1,3 +1,4 @@ +// @ts-check const { ForbiddenError, UserNotFoundError } = require('../Errors/Errors') const { getUserCapabilities, diff --git a/services/web/app/src/Features/Authorization/PrivilegeLevels.js b/services/web/app/src/Features/Authorization/PrivilegeLevels.js index ff03cf8762..e78669954c 100644 --- a/services/web/app/src/Features/Authorization/PrivilegeLevels.js +++ b/services/web/app/src/Features/Authorization/PrivilegeLevels.js @@ -1,3 +1,6 @@ +// @ts-check + +/** @type {import('./types').PrivilegeLevelsType} */ const PrivilegeLevels = { NONE: false, READ_ONLY: 'readOnly', diff --git a/services/web/app/src/Features/Authorization/PublicAccessLevels.js b/services/web/app/src/Features/Authorization/PublicAccessLevels.js index 285acd19e0..166dbd898f 100644 --- a/services/web/app/src/Features/Authorization/PublicAccessLevels.js +++ b/services/web/app/src/Features/Authorization/PublicAccessLevels.js @@ -1,3 +1,5 @@ +// @ts-check + /** * Note: * It used to be that `project.publicAccessLevel` could be set to `private`, @@ -9,6 +11,8 @@ * `publicAccessLevel` to the legacy values, there are projects in the system * that already have those values set. */ + +/** @type {import('./types').PublicAccessLevelsType} */ module.exports = { READ_ONLY: 'readOnly', // LEGACY READ_AND_WRITE: 'readAndWrite', // LEGACY diff --git a/services/web/app/src/Features/Authorization/Sources.js b/services/web/app/src/Features/Authorization/Sources.js index e84126af14..bfc3ef4d22 100644 --- a/services/web/app/src/Features/Authorization/Sources.js +++ b/services/web/app/src/Features/Authorization/Sources.js @@ -1,3 +1,6 @@ +// @ts-check + +/** @type {import('./types').SourcesType} */ module.exports = { INVITE: 'invite', TOKEN: 'token', diff --git a/services/web/app/src/Features/Authorization/types.d.ts b/services/web/app/src/Features/Authorization/types.d.ts new file mode 100644 index 0000000000..0a26bcd45c --- /dev/null +++ b/services/web/app/src/Features/Authorization/types.d.ts @@ -0,0 +1,28 @@ +type ValueOf = T[keyof T] + +export const SourcesType = { + INVITE: 'invite', + TOKEN: 'token', + OWNER: 'owner', +} as const + +export type Source = ValueOf + +export const PrivilegeLevelsType = { + NONE: false, + READ_ONLY: 'readOnly', + READ_AND_WRITE: 'readAndWrite', + REVIEW: 'reviewer', + OWNER: 'owner', +} as const + +export type PrivilegeLevel = ValueOf + +export const PublicAccessLevelsType = { + READ_ONLY: 'readOnly', // LEGACY + READ_AND_WRITE: 'readAndWrite', // LEGACY + PRIVATE: 'private', + TOKEN_BASED: 'tokenBased', +} as const + +export type PublicAccessLevel = ValueOf diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js b/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js index f5ea94c2a5..cb299a35eb 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js @@ -12,18 +12,20 @@ const ProjectEditorHandler = require('../Project/ProjectEditorHandler') const Sources = require('../Authorization/Sources') const PrivilegeLevels = require('../Authorization/PrivilegeLevels') +/** @import { PrivilegeLevel, Source, PublicAccessLevel } from "../Authorization/types" */ + /** * @typedef ProjectMember * @property {string} id - * @property {typeof PrivilegeLevels[keyof PrivilegeLevels]} privilegeLevel - * @property {typeof Sources[keyof Sources]} source + * @property {PrivilegeLevel} privilegeLevel + * @property {Source} source * @property {boolean} [pendingEditor] * @property {boolean} [pendingReviewer] */ /** * @typedef LoadedProjectMember - * @property {typeof PrivilegeLevels[keyof PrivilegeLevels]} privilegeLevel + * @property {PrivilegeLevel} privilegeLevel * @property {{_id: ObjectId, email: string, features: any, first_name: string, last_name: string, signUpDate: Date}} user * @property {boolean} [pendingEditor] * @property {boolean} [pendingReviewer] @@ -34,11 +36,11 @@ class ProjectAccess { /** @type {ProjectMember[]} */ #members - /** @type {typeof PublicAccessLevels[keyof PublicAccessLevels]} */ + /** @type {PublicAccessLevel} */ #publicAccessLevel /** - * @param {{ owner_ref: ObjectId; collaberator_refs: ObjectId[]; readOnly_refs: ObjectId[]; tokenAccessReadAndWrite_refs: ObjectId[]; tokenAccessReadOnly_refs: ObjectId[]; publicAccesLevel: typeof PublicAccessLevels[keyof PublicAccessLevels]; pendingEditor_refs: ObjectId[]; reviewer_refs: ObjectId[]; pendingReviewer_refs: ObjectId[]; }} project + * @param {{ owner_ref: ObjectId; collaberator_refs: ObjectId[]; readOnly_refs: ObjectId[]; tokenAccessReadAndWrite_refs: ObjectId[]; tokenAccessReadOnly_refs: ObjectId[]; publicAccesLevel: PublicAccessLevel; pendingEditor_refs: ObjectId[]; reviewer_refs: ObjectId[]; pendingReviewer_refs: ObjectId[]; }} project */ constructor(project) { this.#members = _getMemberIdsWithPrivilegeLevelsFromFields( @@ -99,7 +101,7 @@ class ProjectAccess { } /** - * @return {typeof PublicAccessLevels[keyof PublicAccessLevels]} + * @return {PublicAccessLevel} */ publicAccessLevel() { return this.#publicAccessLevel @@ -121,7 +123,7 @@ class ProjectAccess { /** * @param {string | ObjectId} userId - * @return {typeof PrivilegeLevels[keyof PrivilegeLevels]} + * @return {PrivilegeLevel} */ privilegeLevelForUser(userId) { if (!userId) return PrivilegeLevels.NONE @@ -427,7 +429,7 @@ async function userIsReadWriteTokenMember(userId, projectId) { * @param {ObjectId[]} readOnlyIds * @param {ObjectId[]} tokenAccessIds * @param {ObjectId[]} tokenAccessReadOnlyIds - * @param {typeof PublicAccessLevels[keyof PublicAccessLevels]} publicAccessLevel + * @param {PublicAccessLevel} publicAccessLevel * @param {ObjectId[]} pendingEditorIds * @param {ObjectId[]} reviewerIds * @param {ObjectId[]} pendingReviewerIds diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs index b758bba050..d2dc626d2f 100644 --- a/services/web/app/src/Features/Project/ProjectListController.mjs +++ b/services/web/app/src/Features/Project/ProjectListController.mjs @@ -1,4 +1,4 @@ -// ts-check +// @ts-check import _ from 'lodash' import Metrics from '@overleaf/metrics' @@ -31,11 +31,19 @@ import PermissionsManager from '../Authorization/PermissionsManager.js' import AnalyticsManager from '../Analytics/AnalyticsManager.js' /** - * @import { GetProjectsRequest, GetProjectsResponse, AllUsersProjects, MongoProject } from "./types" - * @import { ProjectApi, Filters, Page, Sort } from "../../../../types/project/dashboard/api" - * @import { Tag } from "../Tags/types" + * @import { GetProjectsRequest, GetProjectsResponse, AllUsersProjects, MongoProject, FormattedProject, MongoTag } from "./types" + * @import { Project, ProjectApi, ProjectAccessLevel, Filters, Page, Sort, UserRef } from "../../../../types/project/dashboard/api" + * @import { Affiliation } from "../../../../types/affiliation" + * @import { Source } from "../Authorization/types" */ +/** + * @param {Affiliation} affiliation + * @param session + * @param linkedInstitutionIds + * @returns {boolean} + * @private + */ const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => { if (!affiliation.institution) return false @@ -55,6 +63,10 @@ const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => { return false } +/** + * @param {Affiliation[]} affiliations + * @returns {Array<{ name: string, url: string }>} + */ const _buildPortalTemplatesList = affiliations => { if (affiliations == null) { affiliations = [] @@ -64,7 +76,7 @@ const _buildPortalTemplatesList = affiliations => { const uniqueAffiliations = _.uniqBy(affiliations, 'institution.id') for (const aff of uniqueAffiliations) { const hasSlug = aff.portal?.slug - const hasTemplates = aff.portal?.templates_count > 0 + const hasTemplates = (aff.portal?.templates_count || 0) > 0 if (hasSlug && hasTemplates) { const portalPath = aff.institution.isUniversity ? '/edu/' : '/org/' @@ -198,14 +210,25 @@ async function projectListPage(req, res, next) { logger.err({ err: error, userId }, 'Failed to load the active survey') } - if (user && UserPrimaryEmailCheckHandler.requiresPrimaryEmailCheck(user)) { + if ( + user && + UserPrimaryEmailCheckHandler.requiresPrimaryEmailCheck({ + email: user.email, + emails: user.emails, + lastPrimaryEmailCheck: user.lastPrimaryEmailCheck, + signUpDate: user.signUpDate, + }) + ) { return res.redirect('/user/emails/primary-email-check') } } const tags = await TagsHandler.promises.getAllTags(userId) - let userEmailsData = { list: [], allInReconfirmNotificationPeriods: [] } + /** @type {{ list: any[], allInReconfirmNotificationPeriods?: any[], error?: any }} */ + let userEmailsData = { + list: [], + } try { const fullEmails = await UserGetter.promises.getUserFullEmails(userId) @@ -226,7 +249,7 @@ async function projectListPage(req, res, next) { allInReconfirmNotificationPeriods, } } catch (error) { - userEmailsData = error + userEmailsData.error = error } } } catch (error) { @@ -526,7 +549,7 @@ async function getProjectsJson(req, res) { * @param {Filters} filters * @param {Sort} sort * @param {Page} page - * @returns {Promise<{totalSize: number, projects: ProjectApi[]}>} + * @returns {Promise<{totalSize: number, projects: Project[]}>} * @private */ async function _getProjects( @@ -535,16 +558,15 @@ async function _getProjects( sort = { by: 'lastUpdated', order: 'desc' }, page = { size: 20 } ) { - const [ - /** @type {AllUsersProjects} **/ allProjects, - /** @type {Tag[]} **/ tags, - ] = await Promise.all([ + /** @type {[AllUsersProjects, MongoTag[]]} */ + const results = await Promise.all([ ProjectGetter.promises.findAllUsersProjects( userId, 'name lastUpdated lastUpdatedBy publicAccesLevel archived trashed owner_ref tokens' ), TagsHandler.promises.getAllTags(userId), ]) + const [allProjects, tags] = results const formattedProjects = _formatProjects(allProjects, userId) const filteredProjects = _applyFilters( formattedProjects, @@ -554,18 +576,18 @@ async function _getProjects( ) const pagedProjects = _sortAndPaginate(filteredProjects, sort, page) - await _injectProjectUsers(pagedProjects) + const projects = await _injectProjectUsers(pagedProjects) return { totalSize: filteredProjects.length, - projects: pagedProjects, + projects, } } /** * @param {AllUsersProjects} projects * @param {string} userId - * @returns {Project[]} + * @returns {FormattedProject[]} * @private */ function _formatProjects(projects, userId) { @@ -578,7 +600,7 @@ function _formatProjects(projects, userId) { tokenReadOnly, } = projects - const formattedProjects = /** @type {Project[]} **/ [] + const formattedProjects = /** @type {FormattedProject[]} **/ [] for (const project of owned) { formattedProjects.push( _formatProjectInfo(project, 'owner', Sources.OWNER, userId) @@ -622,11 +644,11 @@ function _formatProjects(projects, userId) { } /** - * @param {Project[]} projects - * @param {Tag[]} tags + * @param {FormattedProject[]} projects + * @param {MongoTag[]} tags * @param {Filters} filters * @param {string} userId - * @returns {Project[]} + * @returns {FormattedProject[]} * @private */ function _applyFilters(projects, tags, filters, userId) { @@ -637,10 +659,10 @@ function _applyFilters(projects, tags, filters, userId) { } /** - * @param {Project[]} projects + * @param {FormattedProject[]} projects * @param {Sort} sort * @param {Page} page - * @returns {Project[]} + * @returns {FormattedProject[]} * @private */ function _sortAndPaginate(projects, sort, page) { @@ -661,38 +683,35 @@ function _sortAndPaginate(projects, sort, page) { /** * @param {MongoProject} project - * @param {string} accessLevel - * @param {'owner' | 'invite' | 'token'} source + * @param {ProjectAccessLevel} accessLevel + * @param {Source} source * @param {string} userId - * @returns {object} + * @returns {FormattedProject} * @private */ function _formatProjectInfo(project, accessLevel, source, userId) { const archived = ProjectHelper.isArchived(project, userId) // If a project is simultaneously trashed and archived, we will consider it archived but not trashed. const trashed = ProjectHelper.isTrashed(project, userId) && !archived + const readOnlyTokenAccess = + accessLevel === PrivilegeLevels.READ_ONLY && source === Sources.TOKEN - const model = { + return { id: project._id.toString(), name: project.name, - owner_ref: project.owner_ref, + owner_ref: readOnlyTokenAccess ? null : project.owner_ref, lastUpdated: project.lastUpdated, - lastUpdatedBy: project.lastUpdatedBy, + lastUpdatedBy: readOnlyTokenAccess ? null : project.lastUpdatedBy, accessLevel, source, archived, trashed, } - if (accessLevel === PrivilegeLevels.READ_ONLY && source === Sources.TOKEN) { - model.owner_ref = null - model.lastUpdatedBy = null - } - return model } /** - * @param {Project[]} projects - * @returns {Promise} + * @param {FormattedProject[]} projects + * @returns {Promise} * @private */ async function _injectProjectUsers(projects) { @@ -711,6 +730,7 @@ async function _injectProjectUsers(projects) { last_name: 1, email: 1, } + /** @type {Record} */ const users = {} for (const user of await UserGetter.promises.getUsers(userIds, projection)) { const userId = user._id.toString() @@ -721,21 +741,30 @@ async function _injectProjectUsers(projects) { lastName: user.last_name, } } - for (const project of projects) { - if (project.owner_ref != null) { - project.owner = users[project.owner_ref.toString()] - } - if (project.lastUpdatedBy != null) { - project.lastUpdatedBy = users[project.lastUpdatedBy.toString()] || null - } - delete project.owner_ref - } + return projects.map(project => ({ + id: project.id, + name: project.name, + archived: project.archived, + trashed: project.trashed, + accessLevel: project.accessLevel, + source: project.source, + lastUpdated: project.lastUpdated.toISOString(), + lastUpdatedBy: + project.lastUpdatedBy == null + ? null + : users[project.lastUpdatedBy.toString()] || null, + owner: + project.owner_ref == null + ? undefined + : users[project.owner_ref.toString()], + owner_ref: undefined, + })) } /** * @param {any} project - * @param {Tag[]} tags + * @param {MongoTag[]} tags * @param {Filters} filters * @private */ @@ -777,14 +806,14 @@ function _matchesFilters(project, tags, filters) { * @private */ function _hasActiveFilter(filters) { - return ( + return Boolean( filters.ownedByUser || - filters.sharedWithUser || - filters.archived || - filters.trashed || - filters.tag === null || - filters.tag?.length || - filters.search?.length + filters.sharedWithUser || + filters.archived || + filters.trashed || + filters.tag === null || + filters.tag?.length || + filters.search?.length ) } diff --git a/services/web/app/src/Features/Project/types.d.ts b/services/web/app/src/Features/Project/types.d.ts index 215e03c5f7..4124e8f045 100644 --- a/services/web/app/src/Features/Project/types.d.ts +++ b/services/web/app/src/Features/Project/types.d.ts @@ -2,8 +2,11 @@ import express from 'express' import { GetProjectsRequestBody, GetProjectsResponseBody, + ProjectAccessLevel, + UserRef, } from '../../../../types/project/dashboard/api' import { ObjectId } from 'mongodb-legacy' +import { Source } from '../Authorization/types' export type GetProjectsRequest = express.Request< unknown, @@ -30,10 +33,31 @@ export type MongoProject = { }[] } +export type MongoTag = { + user_id: string + name: string + color?: string | null + project_ids?: string[] +} + export type AllUsersProjects = { owned: MongoProject[] readAndWrite: MongoProject[] readOnly: MongoProject[] tokenReadAndWrite: MongoProject[] tokenReadOnly: MongoProject[] + review: MongoProject[] +} + +export type FormattedProject = { + id: string + name: string + owner_ref?: string | null + owner? + lastUpdated: Date + lastUpdatedBy: string | null | UserRef + archived: boolean + trashed: boolean + accessLevel: ProjectAccessLevel + source: Source } diff --git a/services/web/app/src/Features/User/UserGetter.js b/services/web/app/src/Features/User/UserGetter.js index 1bc86f98cf..4325bb4ea5 100644 --- a/services/web/app/src/Features/User/UserGetter.js +++ b/services/web/app/src/Features/User/UserGetter.js @@ -377,17 +377,19 @@ const decorateFullEmails = ( return emailsData } -UserGetter.promises = promisifyAll(UserGetter, { - without: [ - 'getSsoUsersAtInstitution', - 'getUserFullEmails', - 'getUserFeatures', - 'getWritefullData', - ], -}) -UserGetter.promises.getUserFullEmails = getUserFullEmails -UserGetter.promises.getSsoUsersAtInstitution = getSsoUsersAtInstitution -UserGetter.promises.getUserFeatures = getUserFeatures -UserGetter.promises.getWritefullData = getWritefullData +UserGetter.promises = { + ...promisifyAll(UserGetter, { + without: [ + 'getSsoUsersAtInstitution', + 'getUserFullEmails', + 'getUserFeatures', + 'getWritefullData', + ], + }), + getUserFullEmails, + getSsoUsersAtInstitution, + getUserFeatures, + getWritefullData, +} module.exports = UserGetter diff --git a/services/web/test/unit/src/Project/ProjectListController.test.mjs b/services/web/test/unit/src/Project/ProjectListController.test.mjs index 70dd4a6658..143d98a81e 100644 --- a/services/web/test/unit/src/Project/ProjectListController.test.mjs +++ b/services/web/test/unit/src/Project/ProjectListController.test.mjs @@ -298,19 +298,25 @@ describe('ProjectListController', function () { describe('projectListPage', function () { beforeEach(function (ctx) { ctx.projects = [ - { _id: 1, lastUpdated: 1, owner_ref: 'user-1' }, + { _id: 1, lastUpdated: new Date(1), owner_ref: 'user-1' }, { _id: 2, - lastUpdated: 2, + lastUpdated: new Date(2), owner_ref: 'user-2', lastUpdatedBy: 'user-1', }, ] - ctx.readAndWrite = [{ _id: 5, lastUpdated: 5, owner_ref: 'user-1' }] - ctx.readOnly = [{ _id: 3, lastUpdated: 3, owner_ref: 'user-1' }] - ctx.tokenReadAndWrite = [{ _id: 6, lastUpdated: 5, owner_ref: 'user-4' }] - ctx.tokenReadOnly = [{ _id: 7, lastUpdated: 4, owner_ref: 'user-5' }] - ctx.review = [{ _id: 8, lastUpdated: 4, owner_ref: 'user-6' }] + ctx.readAndWrite = [ + { _id: 5, lastUpdated: new Date(5), owner_ref: 'user-1' }, + ] + ctx.readOnly = [{ _id: 3, lastUpdated: new Date(3), owner_ref: 'user-1' }] + ctx.tokenReadAndWrite = [ + { _id: 6, lastUpdated: new Date(5), owner_ref: 'user-4' }, + ] + ctx.tokenReadOnly = [ + { _id: 7, lastUpdated: new Date(4), owner_ref: 'user-5' }, + ] + ctx.review = [{ _id: 8, lastUpdated: new Date(4), owner_ref: 'user-6' }] ctx.allProjects = { owned: ctx.projects, readAndWrite: ctx.readAndWrite, @@ -854,17 +860,21 @@ describe('ProjectListController', function () { describe('projectListReactPage with duplicate projects', function () { beforeEach(function (ctx) { ctx.projects = [ - { _id: 1, lastUpdated: 1, owner_ref: 'user-1' }, - { _id: 2, lastUpdated: 2, owner_ref: 'user-2' }, + { _id: 1, lastUpdated: new Date(1), owner_ref: 'user-1' }, + { _id: 2, lastUpdated: new Date(2), owner_ref: 'user-2' }, + ] + ctx.readAndWrite = [ + { _id: 5, lastUpdated: new Date(5), owner_ref: 'user-1' }, + ] + ctx.readOnly = [{ _id: 3, lastUpdated: new Date(3), owner_ref: 'user-1' }] + ctx.tokenReadAndWrite = [ + { _id: 6, lastUpdated: new Date(5), owner_ref: 'user-4' }, ] - ctx.readAndWrite = [{ _id: 5, lastUpdated: 5, owner_ref: 'user-1' }] - ctx.readOnly = [{ _id: 3, lastUpdated: 3, owner_ref: 'user-1' }] - ctx.tokenReadAndWrite = [{ _id: 6, lastUpdated: 5, owner_ref: 'user-4' }] ctx.tokenReadOnly = [ - { _id: 6, lastUpdated: 5, owner_ref: 'user-4' }, // Also in tokenReadAndWrite - { _id: 7, lastUpdated: 4, owner_ref: 'user-5' }, + { _id: 6, lastUpdated: new Date(5), owner_ref: 'user-4' }, // Also in tokenReadAndWrite + { _id: 7, lastUpdated: new Date(4), owner_ref: 'user-5' }, ] - ctx.review = [{ _id: 8, lastUpdated: 5, owner_ref: 'user-6' }] + ctx.review = [{ _id: 8, lastUpdated: new Date(5), owner_ref: 'user-6' }] ctx.allProjects = { owned: ctx.projects, readAndWrite: ctx.readAndWrite, diff --git a/services/web/tsconfig.backend.json b/services/web/tsconfig.backend.json index 2e2351076b..ba2843fe06 100644 --- a/services/web/tsconfig.backend.json +++ b/services/web/tsconfig.backend.json @@ -9,6 +9,7 @@ "scripts/**/*", "test/acceptance/**/*", "test/smoke/**/*", - "test/unit/**/*" + "test/unit/**/*", + "types/backend/**/*" ] } diff --git a/services/web/types/backend/express/request.d.ts b/services/web/types/backend/express/request.d.ts new file mode 100644 index 0000000000..29eb4304b5 --- /dev/null +++ b/services/web/types/backend/express/request.d.ts @@ -0,0 +1,14 @@ +import 'express' +import OAuth2Server from '@node-oauth/oauth2-server' +import type SessionData from 'express-session' + +// Add properties to Express's Request object that are defined in JS middleware +// or controllers and expected to be present in controllers. +declare module 'express' { + // eslint-disable-next-line no-unused-vars + interface Request { + session: SessionData + userRestrictions?: Set + oauth_user?: OAuth2Server.User + } +} diff --git a/services/web/types/backend/express/session-data.d.ts b/services/web/types/backend/express/session-data.d.ts new file mode 100644 index 0000000000..ef3ae0e443 --- /dev/null +++ b/services/web/types/backend/express/session-data.d.ts @@ -0,0 +1,29 @@ +import 'express-session' + +// Add properties to Express's SessionData object that are expected to be +// present in controllers. +declare module 'express-session' { + // eslint-disable-next-line no-unused-vars + interface SessionData { + postCheckoutRedirect?: string + postLoginRedirect?: string + postOnboardingRedirect?: string + sharedProjectData?: any + templateData?: any + saml?: { + reconfirmed?: boolean + linked?: { + universityName?: string + providerName?: string + } + linkedGroup?: any + requestedEmail?: string + emailNonCanonical?: string + institutionEmail?: string + registerIntercept?: boolean + error?: any + } + samlBeta?: boolean + // Add further properties as needed + } +} diff --git a/services/web/types/backend/i18next.d.ts b/services/web/types/backend/i18next.d.ts new file mode 100644 index 0000000000..d2ed71ba43 --- /dev/null +++ b/services/web/types/backend/i18next.d.ts @@ -0,0 +1,10 @@ +import 'i18next' + +// Add our custom translate function from Translations.js into the i18next i18n +// object type definition +declare module 'i18next' { + // eslint-disable-next-line no-unused-vars + interface i18n { + translate(key: string, vars?: Record, components?: any): string + } +} diff --git a/services/web/types/project/dashboard/api.d.ts b/services/web/types/project/dashboard/api.d.ts index a05af68e5b..f5110f126d 100644 --- a/services/web/types/project/dashboard/api.d.ts +++ b/services/web/types/project/dashboard/api.d.ts @@ -1,5 +1,6 @@ import { SortingOrder } from '../../sorting-order' import { MergeAndOverride } from '../../utils' +import { Source } from '../../../app/src/Features/Authorization/types' export type Page = { size: number @@ -33,6 +34,13 @@ export type UserRef = { lastName: string } +export type ProjectAccessLevel = + | 'owner' + | 'readWrite' + | 'readOnly' + | 'readAndWrite' + | 'review' + export type ProjectApi = { id: string name: string @@ -41,8 +49,8 @@ export type ProjectApi = { lastUpdatedBy: UserRef | null archived: boolean trashed: boolean - accessLevel: 'owner' | 'readWrite' | 'readOnly' | 'readAndWrite' - source: 'owner' | 'invite' | 'token' + accessLevel: ProjectAccessLevel + source: Source } export type Project = MergeAndOverride<