Browse Source

save actor remote address when creating events (#12)

* save actor remote address when creating events

* fix tests

* update docker-compose
Klaudiusz Dembler 3 years ago
parent
commit
a91572fe6f
7 changed files with 52 additions and 23 deletions
  1. 8 4
      docker-compose.yml
  2. 2 0
      src/main.ts
  3. 3 0
      src/models/shared.ts
  4. 11 6
      src/resolvers/followsInfo.ts
  5. 15 8
      src/resolvers/viewsInfo.ts
  6. 10 4
      src/server.ts
  7. 3 1
      src/types.d.ts

+ 8 - 4
docker-compose.yml

@@ -1,12 +1,16 @@
-version: "3.8"
+version: '3.3'
 services:
   orion:
     image: orion:latest
     environment:
-    - ORION_PORT=6116
-    - ORION_MONGO_HOSTNAME=mongo
+      - ORION_PORT=6116
+      - ORION_MONGO_HOSTNAME=mongo
     ports:
-    - "6116:6116"
+      - '127.0.0.1:6116:6116'
     restart: always
   mongo:
+    container_name: mongo
+    restart: always
     image: library/mongo:4.4
+    volumes:
+      - ./db:/data/db

+ 2 - 0
src/main.ts

@@ -14,6 +14,8 @@ const main = async () => {
   const server = await createServer(mongoose, aggregates)
   const app = Express()
   server.applyMiddleware({ app })
+
+  app.enable('trust proxy')
   app.listen({ port: config.port }, () =>
     console.log(`🚀 Server listening at ==> http://localhost:${config.port}${server.graphqlPath}`)
   )

+ 3 - 0
src/models/shared.ts

@@ -8,6 +8,9 @@ export class GenericEvent {
   @prop({ required: true })
   timestamp: Date
 
+  @prop({ required: false, index: true })
+  actorId?: string
+
   type: unknown
 }
 

+ 11 - 6
src/resolvers/followsInfo.ts

@@ -1,7 +1,7 @@
 import { Args, ArgsType, Ctx, Field, ID, Mutation, Query, Resolver } from 'type-graphql'
 import { ChannelFollowsInfo } from '../entities/ChannelFollowsInfo'
 import { ChannelEventType, saveChannelEvent, UnsequencedChannelEvent } from '../models/ChannelEvent'
-import { Context } from '../types'
+import { OrionContext } from '../types'
 
 @ArgsType()
 class ChannelFollowsArgs {
@@ -26,7 +26,7 @@ export class ChannelFollowsInfosResolver {
   @Query(() => ChannelFollowsInfo, { nullable: true, description: 'Get follows count for a single channel' })
   async channelFollows(
     @Args() { channelId }: ChannelFollowsArgs,
-    @Ctx() ctx: Context
+    @Ctx() ctx: OrionContext
   ): Promise<ChannelFollowsInfo | null> {
     return getFollowsInfo(channelId, ctx)
   }
@@ -34,17 +34,18 @@ export class ChannelFollowsInfosResolver {
   @Query(() => [ChannelFollowsInfo], { description: 'Get follows counts for a list of channels', nullable: 'items' })
   async batchedChannelFollows(
     @Args() { channelIdList }: BatchedChannelFollowsArgs,
-    @Ctx() ctx: Context
+    @Ctx() ctx: OrionContext
   ): Promise<(ChannelFollowsInfo | null)[]> {
     return channelIdList.map((channelId) => getFollowsInfo(channelId, ctx))
   }
 
   @Mutation(() => ChannelFollowsInfo, { description: 'Add a single follow to the target channel' })
-  async followChannel(@Args() { channelId }: FollowChannelArgs, @Ctx() ctx: Context): Promise<ChannelFollowsInfo> {
+  async followChannel(@Args() { channelId }: FollowChannelArgs, @Ctx() ctx: OrionContext): Promise<ChannelFollowsInfo> {
     const event: UnsequencedChannelEvent = {
       channelId,
       type: ChannelEventType.FollowChannel,
       timestamp: new Date(),
+      actorId: ctx.remoteHost,
     }
 
     await saveChannelEvent(event)
@@ -54,11 +55,15 @@ export class ChannelFollowsInfosResolver {
   }
 
   @Mutation(() => ChannelFollowsInfo, { description: 'Remove a single follow from the target channel' })
-  async unfollowChannel(@Args() { channelId }: UnfollowChannelArgs, @Ctx() ctx: Context): Promise<ChannelFollowsInfo> {
+  async unfollowChannel(
+    @Args() { channelId }: UnfollowChannelArgs,
+    @Ctx() ctx: OrionContext
+  ): Promise<ChannelFollowsInfo> {
     const event: UnsequencedChannelEvent = {
       channelId,
       type: ChannelEventType.UnfollowChannel,
       timestamp: new Date(),
+      actorId: ctx.remoteHost,
     }
 
     await saveChannelEvent(event)
@@ -68,7 +73,7 @@ export class ChannelFollowsInfosResolver {
   }
 }
 
-const getFollowsInfo = (channelId: string, ctx: Context): ChannelFollowsInfo | null => {
+const getFollowsInfo = (channelId: string, ctx: OrionContext): ChannelFollowsInfo | null => {
   const follows = ctx.followsAggregate.channelFollows(channelId)
   if (follows != null) {
     return {

+ 15 - 8
src/resolvers/viewsInfo.ts

@@ -1,7 +1,7 @@
 import { Args, ArgsType, Ctx, Field, ID, Mutation, Query, Resolver } from 'type-graphql'
 import { EntityViewsInfo } from '../entities/EntityViewsInfo'
 import { saveVideoEvent, VideoEventType, UnsequencedVideoEvent } from '../models/VideoEvent'
-import { Context } from '../types'
+import { OrionContext } from '../types'
 
 @ArgsType()
 class VideoViewsArgs {
@@ -39,38 +39,45 @@ class AddVideoViewArgs {
 @Resolver()
 export class VideoViewsInfosResolver {
   @Query(() => EntityViewsInfo, { nullable: true, description: 'Get views count for a single video' })
-  async videoViews(@Args() { videoId }: VideoViewsArgs, @Ctx() ctx: Context): Promise<EntityViewsInfo | null> {
+  async videoViews(@Args() { videoId }: VideoViewsArgs, @Ctx() ctx: OrionContext): Promise<EntityViewsInfo | null> {
     return getVideoViewsInfo(videoId, ctx)
   }
 
   @Query(() => [EntityViewsInfo], { description: 'Get views counts for a list of videos', nullable: 'items' })
   async batchedVideoViews(
     @Args() { videoIdList }: BatchedVideoViewsArgs,
-    @Ctx() ctx: Context
+    @Ctx() ctx: OrionContext
   ): Promise<(EntityViewsInfo | null)[]> {
     return videoIdList.map((videoId) => getVideoViewsInfo(videoId, ctx))
   }
 
   @Query(() => EntityViewsInfo, { nullable: true, description: 'Get views count for a single channel' })
-  async channelViews(@Args() { channelId }: ChannelViewsArgs, @Ctx() ctx: Context): Promise<EntityViewsInfo | null> {
+  async channelViews(
+    @Args() { channelId }: ChannelViewsArgs,
+    @Ctx() ctx: OrionContext
+  ): Promise<EntityViewsInfo | null> {
     return getChannelViewsInfo(channelId, ctx)
   }
 
   @Query(() => [EntityViewsInfo], { description: 'Get views counts for a list of channels', nullable: 'items' })
   async batchedChannelsViews(
     @Args() { channelIdList }: BatchedChannelViewsArgs,
-    @Ctx() ctx: Context
+    @Ctx() ctx: OrionContext
   ): Promise<(EntityViewsInfo | null)[]> {
     return channelIdList.map((channelId) => getChannelViewsInfo(channelId, ctx))
   }
 
   @Mutation(() => EntityViewsInfo, { description: "Add a single view to the target video's count" })
-  async addVideoView(@Args() { videoId, channelId }: AddVideoViewArgs, @Ctx() ctx: Context): Promise<EntityViewsInfo> {
+  async addVideoView(
+    @Args() { videoId, channelId }: AddVideoViewArgs,
+    @Ctx() ctx: OrionContext
+  ): Promise<EntityViewsInfo> {
     const event: UnsequencedVideoEvent = {
       videoId,
       channelId,
       type: VideoEventType.AddView,
       timestamp: new Date(),
+      actorId: ctx.remoteHost,
     }
 
     await saveVideoEvent(event)
@@ -90,12 +97,12 @@ const buildViewsObject = (id: string, views: number | null): EntityViewsInfo | n
   return null
 }
 
-const getVideoViewsInfo = (videoId: string, ctx: Context): EntityViewsInfo | null => {
+const getVideoViewsInfo = (videoId: string, ctx: OrionContext): EntityViewsInfo | null => {
   const views = ctx.viewsAggregate.videoViews(videoId)
   return buildViewsObject(videoId, views)
 }
 
-const getChannelViewsInfo = (channelId: string, ctx: Context): EntityViewsInfo | null => {
+const getChannelViewsInfo = (channelId: string, ctx: OrionContext): EntityViewsInfo | null => {
   const views = ctx.viewsAggregate.channelViews(channelId)
   return buildViewsObject(channelId, views)
 }

+ 10 - 4
src/server.ts

@@ -1,11 +1,13 @@
 import 'reflect-metadata'
 import { ApolloServer } from 'apollo-server-express'
+import { ExpressContext } from 'apollo-server-express/dist/ApolloServer'
+import { ContextFunction } from 'apollo-server-core'
 import { connect, Mongoose } from 'mongoose'
 import { buildSchema } from 'type-graphql'
 
 import { FollowsAggregate, ViewsAggregate } from './aggregates'
 import { ChannelFollowsInfosResolver, VideoViewsInfosResolver } from './resolvers'
-import { Aggregates, Context } from './types'
+import { Aggregates, OrionContext } from './types'
 
 export const createServer = async (mongoose: Mongoose, aggregates: Aggregates) => {
   await mongoose.connection
@@ -16,11 +18,15 @@ export const createServer = async (mongoose: Mongoose, aggregates: Aggregates) =
     validate: false,
   })
 
-  const context: Context = {
+  const contextFn: ContextFunction<ExpressContext, OrionContext> = ({ req }) => ({
     ...aggregates,
-  }
+    remoteHost: req?.ip,
+  })
 
-  return new ApolloServer({ schema, context })
+  return new ApolloServer({
+    schema,
+    context: contextFn,
+  })
 }
 
 export const connectMongoose = async (connectionUri: string) => {

+ 3 - 1
src/types.d.ts

@@ -5,4 +5,6 @@ export type Aggregates = {
   followsAggregate: FollowsAggregate
 }
 
-export type Context = Aggregates
+export type OrionContext = {
+  remoteHost?: string
+} & Aggregates