enforce usage limits for the API

This commit is contained in:
Hongbo Wu 2024-04-09 18:13:38 +08:00
parent 5f239d2dcb
commit cd6f3e6bbe
4 changed files with 69 additions and 4 deletions

View file

@ -29,6 +29,7 @@ import { logger } from './utils/logger'
import { ReadingProgressDataSource } from './datasources/reading_progress_data_source'
import { createPrometheusExporterPlugin } from '@bmatei/apollo-prometheus-exporter'
import { ApolloServerPlugin } from 'apollo-server-plugin-base'
import { countDailyServiceUsage } from './services/service_usage'
const signToken = promisify(jwt.sign)
const pubsub = createPubSubClient()
@ -115,10 +116,25 @@ export function makeApolloServer(app: Express): ApolloServer {
// enforce usage limits for the API
const usageLimitPlugin = (): ApolloServerPlugin<RequestContext> => {
// TODO: load the limit from the DB into memory when the server starts
// hardcode the limit for now
const MAX_SENT_EMAIL_PER_DAY = 3
return {
async requestDidStart(contextValue) {
// get graphql query from the request
console.log(contextValue)
const query = contextValue.request.query
// get the user id from the claims
const userId = contextValue.context.claims?.uid
const action = 'replyToEmail'
if (userId && query?.includes(action)) {
// get the user's email sent count from the DB
const emailSentCount = await countDailyServiceUsage(userId, action)
if (emailSentCount >= MAX_SENT_EMAIL_PER_DAY) {
// if the user has reached the limit, throw an error
throw new Error('You have reached the daily email limit')
}
}
},
}
}

View file

@ -0,0 +1,25 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm'
import { User } from './user'
@Entity('service_usage')
export class ServiceUsage {
@PrimaryGeneratedColumn('uuid')
id!: string
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
user!: User
@Column('varchar')
action!: string
@CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
createdAt!: Date
}

View file

@ -0,0 +1,22 @@
import { Between } from 'typeorm'
import { ServiceUsage } from '../entity/service_usage'
import { authTrx, getRepository } from '../repository'
import { DateTime } from 'luxon'
const repo = getRepository(ServiceUsage)
export const countDailyServiceUsage = async (
userId: string,
action: string
) => {
return authTrx((tx) =>
tx.withRepository(repo).countBy({
user: { id: userId },
action,
createdAt: Between(
DateTime.now().startOf('day').toJSDate(),
DateTime.now().endOf('day').toJSDate()
),
})
)
}

View file

@ -28,9 +28,11 @@ CREATE TABLE omnivore.service_usage (
CREATE INDEX ON omnivore.service_usage (user_id);
CREATE POLICY create_service_usage on omnivore.service_usage
FOR INSERT TO omnivore_user
WITH CHECK (true);
ALTER TABLE omnivore.service_usage ENABLE ROW LEVEL SECURITY;
CREATE POLICY service_usage_policy on omnivore.service_usage
USING (user_id = omnivore.get_current_user_id())
WITH CHECK (user_id = omnivore.get_current_user_id());
GRANT SELECT, INSERT ON omnivore.service_usage TO omnivore_user;