mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
enforce usage limits for the API
This commit is contained in:
parent
5f239d2dcb
commit
cd6f3e6bbe
4 changed files with 69 additions and 4 deletions
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
packages/api/src/entity/service_usage.ts
Normal file
25
packages/api/src/entity/service_usage.ts
Normal 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
|
||||
}
|
||||
22
packages/api/src/services/service_usage.ts
Normal file
22
packages/api/src/services/service_usage.ts
Normal 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()
|
||||
),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue