mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Merge pull request #28246 from overleaf/td-ts-project-dashboard-jsdoc
Working JSDoc type annotations on project list controller GitOrigin-RevId: b26833affb0fc2ecd38e869c2523e914eabe6548
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// @ts-check
|
||||
const { ForbiddenError, UserNotFoundError } = require('../Errors/Errors')
|
||||
const {
|
||||
getUserCapabilities,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('./types').PrivilegeLevelsType} */
|
||||
const PrivilegeLevels = {
|
||||
NONE: false,
|
||||
READ_ONLY: 'readOnly',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('./types').SourcesType} */
|
||||
module.exports = {
|
||||
INVITE: 'invite',
|
||||
TOKEN: 'token',
|
||||
|
||||
28
services/web/app/src/Features/Authorization/types.d.ts
vendored
Normal file
28
services/web/app/src/Features/Authorization/types.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
type ValueOf<T> = T[keyof T]
|
||||
|
||||
export const SourcesType = {
|
||||
INVITE: 'invite',
|
||||
TOKEN: 'token',
|
||||
OWNER: 'owner',
|
||||
} as const
|
||||
|
||||
export type Source = ValueOf<typeof SourcesType>
|
||||
|
||||
export const PrivilegeLevelsType = {
|
||||
NONE: false,
|
||||
READ_ONLY: 'readOnly',
|
||||
READ_AND_WRITE: 'readAndWrite',
|
||||
REVIEW: 'reviewer',
|
||||
OWNER: 'owner',
|
||||
} as const
|
||||
|
||||
export type PrivilegeLevel = ValueOf<typeof PrivilegeLevelsType>
|
||||
|
||||
export const PublicAccessLevelsType = {
|
||||
READ_ONLY: 'readOnly', // LEGACY
|
||||
READ_AND_WRITE: 'readAndWrite', // LEGACY
|
||||
PRIVATE: 'private',
|
||||
TOKEN_BASED: 'tokenBased',
|
||||
} as const
|
||||
|
||||
export type PublicAccessLevel = ValueOf<typeof PublicAccessLevelsType>
|
||||
@@ -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
|
||||
|
||||
@@ -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<void>}
|
||||
* @param {FormattedProject[]} projects
|
||||
* @returns {Promise<Project[]>}
|
||||
* @private
|
||||
*/
|
||||
async function _injectProjectUsers(projects) {
|
||||
@@ -711,6 +730,7 @@ async function _injectProjectUsers(projects) {
|
||||
last_name: 1,
|
||||
email: 1,
|
||||
}
|
||||
/** @type {Record<string, UserRef>} */
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
24
services/web/app/src/Features/Project/types.d.ts
vendored
24
services/web/app/src/Features/Project/types.d.ts
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"scripts/**/*",
|
||||
"test/acceptance/**/*",
|
||||
"test/smoke/**/*",
|
||||
"test/unit/**/*"
|
||||
"test/unit/**/*",
|
||||
"types/backend/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
14
services/web/types/backend/express/request.d.ts
vendored
Normal file
14
services/web/types/backend/express/request.d.ts
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
29
services/web/types/backend/express/session-data.d.ts
vendored
Normal file
29
services/web/types/backend/express/session-data.d.ts
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
10
services/web/types/backend/i18next.d.ts
vendored
Normal file
10
services/web/types/backend/i18next.d.ts
vendored
Normal file
@@ -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<string, any>, components?: any): string
|
||||
}
|
||||
}
|
||||
12
services/web/types/project/dashboard/api.d.ts
vendored
12
services/web/types/project/dashboard/api.d.ts
vendored
@@ -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<
|
||||
|
||||
Reference in New Issue
Block a user