mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-18 00:55:35 +00:00
@uppy/companion: improve Zoom folder structure (#5739)
Co-authored-by: Mikael Finstad <finstaden@gmail.com>
This commit is contained in:
parent
0b8b5ee9e8
commit
d8d1e9b5fe
5 changed files with 261 additions and 3081 deletions
|
|
@ -6,8 +6,7 @@
|
|||
"body-parser": "^1.20.3",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.15.6",
|
||||
"light-server": "^2.4.0",
|
||||
"upload-server": "^1.1.6"
|
||||
"light-server": "^2.4.0"
|
||||
},
|
||||
"license": "ISC",
|
||||
"main": "index.js",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
"morgan": "1.10.0",
|
||||
"ms": "2.1.3",
|
||||
"node-schedule": "2.1.1",
|
||||
"p-map": "^7.0.3",
|
||||
"prom-client": "15.1.2",
|
||||
"serialize-error": "^11.0.0",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
const moment = require('moment-timezone')
|
||||
|
||||
const DEFAULT_RANGE_MOS = 23
|
||||
|
||||
const MIMETYPES = {
|
||||
MP4: 'video/mp4',
|
||||
M4A: 'audio/mp4',
|
||||
|
|
@ -28,40 +26,10 @@ const ICONS = {
|
|||
TIMELINE: 'file',
|
||||
}
|
||||
|
||||
const getDateName = (start, end) => {
|
||||
return `${start.format('YYYY-MM-DD')} - ${end.format('YYYY-MM-DD')}`
|
||||
}
|
||||
|
||||
const getAccountCreationDate = (results) => {
|
||||
return moment.utc(results.created_at)
|
||||
}
|
||||
|
||||
const getUserEmail = (results) => {
|
||||
return results.email
|
||||
}
|
||||
|
||||
const getDateFolderId = (start, end) => {
|
||||
return `${start.format('YYYY-MM-DD')}_${end.format('YYYY-MM-DD')}`
|
||||
}
|
||||
|
||||
const getDateFolderRequestPath = (start, end) => {
|
||||
return `?from=${start.format('YYYY-MM-DD')}&to=${end.format('YYYY-MM-DD')}`
|
||||
}
|
||||
|
||||
const getDateFolderModified = (end) => {
|
||||
return end.format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
const getDateNextPagePath = (end) => {
|
||||
return `?cursor=${end.format('YYYY-MM-DD')}`
|
||||
}
|
||||
|
||||
const getNextPagePath = (results) => {
|
||||
if (results.next_page_token) {
|
||||
return `?cursor=${results.next_page_token}&from=${results.from}&to=${results.to}`
|
||||
}
|
||||
return null
|
||||
}
|
||||
// we rely on the file_type attribute to differentiate a recording file from other items
|
||||
const getIsFolder = (item) => {
|
||||
return !item.file_type
|
||||
|
|
@ -135,67 +103,31 @@ const getItemTopic = (item) => {
|
|||
return item.topic
|
||||
}
|
||||
|
||||
exports.initializeData = (body, initialEnd = null) => {
|
||||
let end = initialEnd || moment.utc().tz(body.timezone || 'UTC')
|
||||
const accountCreation = getAccountCreationDate(body).tz(body.timezone || 'UTC').startOf('day')
|
||||
const defaultLimit = end.clone().subtract(DEFAULT_RANGE_MOS, 'months').date(1).startOf('day')
|
||||
const allResultsShown = accountCreation > defaultLimit
|
||||
const limit = allResultsShown ? accountCreation : defaultLimit
|
||||
// if the limit is mid-month, keep that exact date
|
||||
let start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1).startOf('day')
|
||||
|
||||
const data = {
|
||||
items: [],
|
||||
username: getUserEmail(body),
|
||||
}
|
||||
|
||||
while (end.isAfter(limit)) {
|
||||
data.items.push({
|
||||
isFolder: true,
|
||||
icon: 'folder',
|
||||
name: getDateName(start, end),
|
||||
mimeType: null,
|
||||
id: getDateFolderId(start, end),
|
||||
thumbnail: null,
|
||||
requestPath: getDateFolderRequestPath(start, end),
|
||||
modifiedDate: getDateFolderModified(end),
|
||||
size: null,
|
||||
})
|
||||
end = start.clone().subtract(1, 'days').endOf('day')
|
||||
start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1).startOf('day')
|
||||
}
|
||||
data.nextPagePath = allResultsShown ? null : getDateNextPagePath(end)
|
||||
return data
|
||||
}
|
||||
|
||||
exports.adaptData = (userResponse, results, query) => {
|
||||
exports.adaptData = (userResponse, results) => {
|
||||
if (!results) {
|
||||
return { items: [] }
|
||||
}
|
||||
|
||||
// we query the zoom api by date (from 00:00 - 23:59 UTC) which may include
|
||||
// extra results for 00:00 - 23:59 local time that we want to filter out.
|
||||
const utcFrom = moment.tz(query.from, userResponse.timezone || 'UTC').startOf('day').tz('UTC')
|
||||
const utcTo = moment.tz(query.to, userResponse.timezone || 'UTC').endOf('day').tz('UTC')
|
||||
|
||||
const data = {
|
||||
nextPagePath: getNextPagePath(results),
|
||||
nextPagePath: null,
|
||||
items: [],
|
||||
username: getUserEmail(userResponse),
|
||||
}
|
||||
|
||||
let items = []
|
||||
let itemsToProcess = []
|
||||
if (results.meetings) {
|
||||
items = results.meetings
|
||||
.map(item => { return { ...item, utcStart: moment.utc(item.start_time) } })
|
||||
.filter(item => moment.utc(item.start_time).isAfter(utcFrom) && moment.utc(item.start_time).isBefore(utcTo))
|
||||
} else {
|
||||
items = results.recording_files
|
||||
itemsToProcess = results.meetings
|
||||
.filter(meeting =>
|
||||
meeting.recording_files &&
|
||||
meeting.recording_files.some(file => !file.deleted_time && file.status === 'completed' && file.download_url)
|
||||
)
|
||||
} else if (results.recording_files) {
|
||||
itemsToProcess = results.recording_files
|
||||
.map(item => { return { ...item, topic: results.topic } })
|
||||
.filter(file => file.file_type !== 'TIMELINE')
|
||||
.filter(file => file.file_type !== 'TIMELINE' && !file.deleted_time && file.status === 'completed' && file.download_url)
|
||||
}
|
||||
|
||||
items.forEach(item => {
|
||||
itemsToProcess.forEach(item => {
|
||||
data.items.push({
|
||||
isFolder: getIsFolder(item),
|
||||
icon: getIcon(item),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
const moment = require('moment-timezone')
|
||||
|
||||
const Provider = require('../Provider')
|
||||
const { initializeData, adaptData } = require('./adapter')
|
||||
const { adaptData } = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../providerErrors')
|
||||
const { prepareStream, getBasicAuthHeader } = require('../../helpers/utils')
|
||||
|
||||
const got = require('../../got')
|
||||
|
||||
const pMap = import('p-map')
|
||||
|
||||
const BASE_URL = 'https://zoom.us/v2'
|
||||
const PAGE_SIZE = 300
|
||||
const DEAUTH_EVENT_NAME = 'app_deauthorized'
|
||||
|
|
@ -34,50 +36,78 @@ class Zoom extends Provider {
|
|||
return 'zoom'
|
||||
}
|
||||
|
||||
/*
|
||||
- returns list of months by default
|
||||
- drill down for specific files in each month
|
||||
*/
|
||||
async list (options) {
|
||||
return this.#withErrorHandling('provider.zoom.list.error', async () => {
|
||||
const { token } = options
|
||||
const query = options.query || {}
|
||||
const { cursor, from, to } = query
|
||||
const meetingId = options.directory || ''
|
||||
const requestedYear = query.year ? parseInt(query.year, 10) : null
|
||||
|
||||
const client = await getClient({ token })
|
||||
const user = await client.get('users/me', { responseType: 'json' }).json()
|
||||
|
||||
const { timezone } = user
|
||||
|
||||
if (!from && !to && !meetingId) {
|
||||
const end = cursor && moment.utc(cursor).endOf('day').tz(timezone || 'UTC')
|
||||
return initializeData(user, end)
|
||||
}
|
||||
|
||||
if (from && to) {
|
||||
/* we need to convert local datetime to UTC date for Zoom query
|
||||
eg: user in PST (UTC-08:00) wants 2020-08-01 (00:00) to 2020-08-31 (23:59)
|
||||
=> in UTC, that's 2020-07-31 (16:00) to 2020-08-31 (15:59)
|
||||
*/
|
||||
const searchParams = {
|
||||
page_size: PAGE_SIZE,
|
||||
from: moment.tz(from, timezone || 'UTC').startOf('day').tz('UTC').format('YYYY-MM-DD'),
|
||||
to: moment.tz(to, timezone || 'UTC').endOf('day').tz('UTC').format('YYYY-MM-DD'),
|
||||
}
|
||||
if (cursor) searchParams.next_page_token = cursor
|
||||
|
||||
const meetingsInfo = await client.get('users/me/recordings', { searchParams, responseType: 'json' }).json()
|
||||
|
||||
return adaptData(user, meetingsInfo, query)
|
||||
}
|
||||
const userTz = timezone || 'UTC'
|
||||
|
||||
if (meetingId) {
|
||||
const recordingInfo = await client.get(`meetings/${encodeURIComponent(meetingId)}/recordings`, { responseType: 'json' }).json()
|
||||
return adaptData(user, recordingInfo, query)
|
||||
return adaptData(user, recordingInfo)
|
||||
}
|
||||
|
||||
throw new Error('Invalid list() arguments')
|
||||
if (requestedYear) {
|
||||
const now = moment.tz(userTz)
|
||||
const numMonths = now.get('year') === requestedYear ? now.get('month') + 1 : 12
|
||||
const monthsToCheck = Array.from({ length: numMonths }, (_, i) => i) // in moment, months are 0-indexed
|
||||
|
||||
// Run each month in parallel:
|
||||
const allMeetingsInYear = (await (await pMap).default(monthsToCheck, async (month) => {
|
||||
const startDate = moment.tz({ year: requestedYear, month, day: 1 }, userTz).startOf('month')
|
||||
const endDate = startDate.clone().endOf('month')
|
||||
|
||||
const searchParams = {
|
||||
page_size: PAGE_SIZE,
|
||||
from: startDate.clone().tz('UTC').format('YYYY-MM-DD'),
|
||||
to: endDate.clone().tz('UTC').format('YYYY-MM-DD'),
|
||||
}
|
||||
|
||||
const paginatedMeetings = []
|
||||
do {
|
||||
const currentChunkMeetingsInfo = await client.get('users/me/recordings', { searchParams, responseType: 'json' }).json()
|
||||
paginatedMeetings.push(...(currentChunkMeetingsInfo.meetings ?? []))
|
||||
searchParams.next_page_token = currentChunkMeetingsInfo.next_page_token
|
||||
} while (searchParams.next_page_token)
|
||||
|
||||
return paginatedMeetings
|
||||
}, { concurrency: 3 })).flat() // this is effectively a flatMap
|
||||
// concurrency 3 seems like a sensible number...
|
||||
|
||||
const finalResult = { meetings: allMeetingsInYear }
|
||||
return adaptData(user, finalResult)
|
||||
}
|
||||
|
||||
const accountCreationDate = moment.utc(user.created_at)
|
||||
const startYear = accountCreationDate.year()
|
||||
const currentYear = moment.tz(userTz).year()
|
||||
const years = []
|
||||
|
||||
for (let year = currentYear; year >= startYear; year--) {
|
||||
years.push({
|
||||
isFolder: true,
|
||||
icon: 'folder',
|
||||
name: `${year}`,
|
||||
mimeType: null,
|
||||
id: `${year}`,
|
||||
thumbnail: null,
|
||||
requestPath: `?year=${year}`,
|
||||
modifiedDate: `${year}-12-31`, // Representative date
|
||||
size: null,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
username: user.email,
|
||||
items: years,
|
||||
nextPagePath: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue