Skip to content

Commit

Permalink
feat: created dockerfile for tracker ms
Browse files Browse the repository at this point in the history
  • Loading branch information
orig committed Dec 11, 2023
1 parent 9dbd855 commit d92def4
Show file tree
Hide file tree
Showing 16 changed files with 363 additions and 14 deletions.
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"runtimeArgs": ["nx", "serve", "backend"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/apps/backend"
},
{
"name": "Debug tracker",
"request": "launch",
"type": "node",
"runtimeExecutable": "npx",
"runtimeArgs": ["nx", "serve", "tracker"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/apps/tracker"
}
]
}
6 changes: 3 additions & 3 deletions apps/backend/src/shortener/producer/shortener.producer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { ProducerService } from '@reduced.to/queue-manager';
import { AppConfigService } from '@reduced.to/config';

const SHORTENER_PRODUCER_NAME = 'shortener';
const SHORTENER_QUEUE_NAME = 'stats';

@Injectable()
export class ShortenerProducer extends ProducerService {
constructor() {
super(SHORTENER_PRODUCER_NAME, SHORTENER_QUEUE_NAME);
constructor(config: AppConfigService) {
super(SHORTENER_PRODUCER_NAME, config.getConfig().tracker.stats.queueName);
}
}
8 changes: 8 additions & 0 deletions apps/tracker/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
dist
npm-debug.log
CONTRIBUTE.MD
README.md
.gitignore
LICENSE
.DS_Store
47 changes: 47 additions & 0 deletions apps/tracker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# --------------------------------------------
# Dependencies Stage
# --------------------------------------------
FROM node:19.2-alpine3.15 as dependencies
WORKDIR /app

COPY package*.json ./

RUN apk add --update python3 make g++\
&& rm -rf /var/cache/apk/*

RUN npm ci

# --------------------------------------------
# Build Stage
# --------------------------------------------
# Intermediate docker image to build the bundle in and install dependencies
FROM node:19.2-alpine3.15 as build
WORKDIR /app

COPY . .
COPY --from=dependencies /app/node_modules ./node_modules

# Run prisma generate & build the bundle in production mode
RUN npx nx build tracker --prod --skip-nx-cache

# --------------------------------------------
# Production Stage
# --------------------------------------------
FROM node:19.2-alpine3.15 as production
WORKDIR /app

COPY --from=build /app/dist/apps/tracker ./tracker
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/libs/ ./libs

EXPOSE 3001

# Start the application
CMD sh -c "npx nx migrate-deploy prisma && node backend/main.js"







35 changes: 31 additions & 4 deletions apps/tracker/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"outputs": [
"{options.outputPath}"
],
"defaultConfiguration": "production",
"options": {
"target": "node",
Expand Down Expand Up @@ -39,14 +41,20 @@
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": ["apps/tracker/**/*.ts"]
"lintFilePatterns": [
"apps/tracker/**/*.ts"
]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "apps/tracker/jest.config.ts",
"passWithNoTests": true
Expand All @@ -57,6 +65,25 @@
"codeCoverage": true
}
}
},
"docker-build": {
"dependsOn": [
"build"
],
"command": "docker build -f apps/tracker/Dockerfile . -t tracker"
},
"push-image-to-registry": {
"dependsOn": [
"docker-build"
],
"executor": "nx:run-commands",
"options": {
"commands": [
"docker image tag tracker ghcr.io/{args.repository}/tracker:master",
"docker push ghcr.io/{args.repository}/tracker:master"
],
"parallel": false
}
}
},
"tags": []
Expand Down
3 changes: 1 addition & 2 deletions apps/tracker/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { AppConfigModule } from '@reduced.to/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppLoggerModule } from '@reduced.to/logger';
import { QueueManagerService } from '@reduced.to/queue-manager';
import { StatsModule } from '../stats/stats.module';

@Global()
@Module({
imports: [AppConfigModule, AppLoggerModule, StatsModule],
controllers: [AppController],
providers: [AppService, QueueManagerService],
providers: [AppService],
})
export class AppModule {}
29 changes: 26 additions & 3 deletions apps/tracker/src/stats/stats.consumer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import { Injectable } from '@nestjs/common';
import { ConsumerService } from '@reduced.to/queue-manager';
import { AppConfigService } from '@reduced.to/config';
import { AppLoggerSerivce } from '@reduced.to/logger';
import { StatsService } from './stats.service';
import * as geoip from 'geoip-lite';
import { createHash } from 'node:crypto';
import { Message } from 'memphis-dev/*';

@Injectable()
export class StatsConsumer extends ConsumerService {
constructor(config: AppConfigService) {
constructor(config: AppConfigService, private readonly loggerService: AppLoggerSerivce, private readonly statsService: StatsService) {
super('tracker', config.getConfig().tracker.stats.queueName);
}
async onMessage(message: any): Promise<void> {
console.log('StatsConsumer', message);

async onMessage(message: Message): Promise<void> {
const { ip, userAgent, key, url } = message.getDataAsJson() as { ip: string; userAgent: string; key: string; url: string };
message.ack();

const hashedIp = createHash('sha256').update(ip).digest('hex');
const isUniqueVisit = await this.statsService.isUniqueVisit(key, hashedIp);

if (!isUniqueVisit) {
return;
}

const geo = geoip.lookup(ip);
await this.statsService.addVisit(key, {
hashedIp,
ua: userAgent,
geo,
});

this.loggerService.log(`Added unique visit for ${key}`);
}
}
3 changes: 2 additions & 1 deletion apps/tracker/src/stats/stats.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
import { StatsService } from './stats.service';
import { StatsConsumer } from './stats.consumer';
import { QueueManagerModule, QueueManagerService } from '@reduced.to/queue-manager';
import { PrismaModule } from '@reduced.to/prisma';

@Module({
imports: [QueueManagerModule],
imports: [PrismaModule, QueueManagerModule],
providers: [StatsService, StatsConsumer, QueueManagerService],
exports: [StatsService],
})
Expand Down
35 changes: 34 additions & 1 deletion apps/tracker/src/stats/stats.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,37 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@reduced.to/prisma';

@Injectable()
export class StatsService {}
export class StatsService {
constructor(private readonly prismaService: PrismaService) {}

async addVisit(key: string, opts: { hashedIp: string; ua: string; geo: object }) {
const { hashedIp, ua, geo } = opts;

return this.prismaService.visit.create({
data: {
ip: hashedIp,
userAgent: ua,
...(geo && { geo }),
link: {
connect: {
key,
},
},
},
});
}

async isUniqueVisit(key: string, hashedIp: string) {
const visit = await this.prismaService.visit.findFirst({
where: {
ip: hashedIp,
link: {
key: key,
},
},
});

return visit === null;
}
}
35 changes: 35 additions & 0 deletions docker/deployment/tracker/tracker-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: tracker-deployment
annotations:
# force policy will ensure that deployment is updated
# even when tag is unchanged (latest remains)
keel.sh/policy: force
keel.sh/trigger: poll # <-- actively query registry, otherwise defaults to webhooks
spec:
replicas: 1
selector:
matchLabels:
app: tracker
template:
metadata:
labels:
app: tracker
spec:
containers:
- name: tracker
image: ghcr.io/origranot/reduced.to/tracker:master
imagePullPolicy: Always
envFrom:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: 3000
resources:
requests:
memory: '256Mi'
cpu: '100m'
limits:
memory: '1024Mi'
cpu: '500m'
11 changes: 11 additions & 0 deletions docker/deployment/tracker/tracker-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: tracker-service
spec:
selector:
app: tracker
ports:
- protocol: TCP
port: 3001
targetPort: 3001
13 changes: 13 additions & 0 deletions docker/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ services:
depends_on:
- postgres
- redis

tracker:
container_name: tracker
build:
context: ../..
dockerfile: apps/tracker/Dockerfile
restart: always
ports:
- '3001:3001'
env_file: ../../.env
depends_on:
- postgres
- redis
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- AlterTable
ALTER TABLE "Link" ADD COLUMN "clicks" INTEGER NOT NULL DEFAULT 0;

-- CreateTable
CREATE TABLE "Visit" (
"id" TEXT NOT NULL,
"ip" TEXT NOT NULL,
"userAgent" TEXT,
"geo" JSONB,
"linkId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Visit_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Visit_linkId_idx" ON "Visit"("linkId");

-- CreateIndex
CREATE INDEX "Link_url_clicks_idx" ON "Link"("url", "clicks");

-- AddForeignKey
ALTER TABLE "Visit" ADD CONSTRAINT "Visit_linkId_fkey" FOREIGN KEY ("linkId") REFERENCES "Link"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
13 changes: 13 additions & 0 deletions libs/prisma/src/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ model Link {
createdAt DateTime @default(now())
Report Report[]
clicks Int @default(0)
Visit Visit[]
@@index(userId)
@@index(key)
Expand All @@ -69,6 +70,18 @@ model Report {
@@index(category)
}

model Visit {
id String @id @default(uuid())
ip String // Hashed IP
userAgent String?
geo Json?
link Link @relation(fields: [linkId], references: [id])
linkId String
createdAt DateTime @default(now())
@@index(linkId)
}

enum Role {
USER
ADMIN
Expand Down
Loading

0 comments on commit d92def4

Please sign in to comment.