first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import { db, ObjectId } from './mongodb.js'
export async function touchContact(userId, contactId) {
await db.contacts.updateOne(
{ user_id: new ObjectId(userId.toString()) },
{
$inc: {
[`contacts.${contactId}.n`]: 1,
},
$set: {
[`contacts.${contactId}.ts`]: new Date(),
},
},
{ upsert: true }
)
}
export async function getContacts(userId) {
const user = await db.contacts.findOne({
user_id: new ObjectId(userId.toString()),
})
return user?.contacts
}

View File

@@ -0,0 +1,6 @@
export class NotFoundError extends Error {
constructor(message) {
super(message)
this.name = 'NotFoundError'
}
}

View File

@@ -0,0 +1,48 @@
import logger from '@overleaf/logger'
import * as ContactManager from './ContactManager.js'
import { buildContactIds } from './contacts.js'
const CONTACT_LIMIT = 50
export function addContact(req, res, next) {
const { user_id: userId } = req.params
const { contact_id: contactId } = req.body
if (contactId == null || contactId === '') {
res.status(400).send('contact_id should be a non-blank string')
return
}
logger.debug({ userId, contactId }, 'adding contact')
Promise.all([
ContactManager.touchContact(userId, contactId),
ContactManager.touchContact(contactId, userId),
])
.then(() => {
res.sendStatus(204)
})
.catch(error => {
next(error)
})
}
export function getContacts(req, res, next) {
const { user_id: userId } = req.params
const { limit } = req.query
const contactLimit =
limit == null ? CONTACT_LIMIT : Math.min(parseInt(limit, 10), CONTACT_LIMIT)
logger.debug({ userId }, 'getting contacts')
ContactManager.getContacts(userId)
.then(contacts => {
res.json({
contact_ids: buildContactIds(contacts, contactLimit),
})
})
.catch(error => {
next(error)
})
}

View File

@@ -0,0 +1,13 @@
export function buildContactIds(contacts, limit) {
return Object.entries(contacts || {})
.map(([id, { n, ts }]) => ({ id, n, ts }))
.sort(sortContacts)
.slice(0, limit)
.map(contact => contact.id)
}
// sort by decreasing count, decreasing timestamp.
// i.e. highest count, most recent first.
function sortContacts(a, b) {
return a.n === b.n ? b.ts - a.ts : b.n - a.n
}

View File

@@ -0,0 +1,17 @@
import Metrics from '@overleaf/metrics'
import Settings from '@overleaf/settings'
import { MongoClient } from 'mongodb'
export { ObjectId } from 'mongodb'
export const mongoClient = new MongoClient(
Settings.mongo.url,
Settings.mongo.options
)
const mongoDb = mongoClient.db()
export const db = {
contacts: mongoDb.collection('contacts'),
}
Metrics.mongodb.monitor(mongoClient)

View File

@@ -0,0 +1,32 @@
import * as Metrics from '@overleaf/metrics'
import logger from '@overleaf/logger'
import express from 'express'
import bodyParser from 'body-parser'
import * as HttpController from './HttpController.js'
import * as Errors from './Errors.js'
logger.initialize('contacts')
Metrics.event_loop?.monitor(logger)
Metrics.open_sockets.monitor()
export const app = express()
app.use(Metrics.http.monitor(logger))
Metrics.injectMetricsRoute(app)
app.get('/user/:user_id/contacts', HttpController.getContacts)
app.post(
'/user/:user_id/contacts',
bodyParser.json({ limit: '2mb' }),
HttpController.addContact
)
app.get('/status', (req, res) => res.send('contacts is alive'))
app.use(function (error, req, res, next) {
logger.error({ err: error }, 'request errored')
if (error instanceof Errors.NotFoundError) {
return res.sendStatus(404)
} else {
return res.status(500).send('Oops, something went wrong')
}
})