Skip to content

Commit 48bbce1

Browse files
authored
Merge pull request #383 from AugistineCreates/feature/license-service
Add License Management & Activation Service
2 parents 86e77f3 + 695d8f1 commit 48bbce1

23 files changed

Lines changed: 608 additions & 0 deletions
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all"
4+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM node:20-alpine AS builder
2+
WORKDIR /app
3+
4+
COPY package*.json ./
5+
RUN npm install --omit=dev
6+
COPY . .
7+
RUN npm run build
8+
9+
FROM node:20-alpine
10+
WORKDIR /app
11+
COPY --from=builder /app/dist ./dist
12+
COPY --from=builder /app/package*.json ./
13+
EXPOSE 3000
14+
CMD ["node", "dist/main"]
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<p align="center">
2+
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
3+
</p>
4+
5+
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
6+
[circleci-url]: https://circleci.com/gh/nestjs/nest
7+
8+
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
9+
<p align="center">
10+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
11+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
12+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
13+
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
14+
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
15+
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
16+
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
17+
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
18+
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
19+
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
20+
</p>
21+
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
22+
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
23+
24+
## Description
25+
26+
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
27+
28+
## Project setup
29+
30+
```bash
31+
$ npm install
32+
```
33+
34+
## Compile and run the project
35+
36+
```bash
37+
# development
38+
$ npm run start
39+
40+
# watch mode
41+
$ npm run start:dev
42+
43+
# production mode
44+
$ npm run start:prod
45+
```
46+
47+
## Run tests
48+
49+
```bash
50+
# unit tests
51+
$ npm run test
52+
53+
# e2e tests
54+
$ npm run test:e2e
55+
56+
# test coverage
57+
$ npm run test:cov
58+
```
59+
60+
## Deployment
61+
62+
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
63+
64+
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
65+
66+
```bash
67+
$ npm install -g @nestjs/mau
68+
$ mau deploy
69+
```
70+
71+
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
72+
73+
## Resources
74+
75+
Check out a few resources that may come in handy when working with NestJS:
76+
77+
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
78+
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
79+
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
80+
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
81+
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
82+
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
83+
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
84+
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
85+
86+
## Support
87+
88+
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
89+
90+
## Stay in touch
91+
92+
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
93+
- Website - [https://nestjs.com](https://nestjs.com/)
94+
- Twitter - [@nestframework](https://twitter.com/nestframework)
95+
96+
## License
97+
98+
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @ts-check
2+
import eslint from '@eslint/js';
3+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
4+
import globals from 'globals';
5+
import tseslint from 'typescript-eslint';
6+
7+
export default tseslint.config(
8+
{
9+
ignores: ['eslint.config.mjs'],
10+
},
11+
eslint.configs.recommended,
12+
...tseslint.configs.recommendedTypeChecked,
13+
eslintPluginPrettierRecommended,
14+
{
15+
languageOptions: {
16+
globals: {
17+
...globals.node,
18+
...globals.jest,
19+
},
20+
sourceType: 'commonjs',
21+
parserOptions: {
22+
projectService: true,
23+
tsconfigRootDir: import.meta.dirname,
24+
},
25+
},
26+
},
27+
{
28+
rules: {
29+
'@typescript-eslint/no-explicit-any': 'off',
30+
'@typescript-eslint/no-floating-promises': 'warn',
31+
'@typescript-eslint/no-unsafe-argument': 'warn',
32+
"prettier/prettier": ["error", { endOfLine: "auto" }],
33+
},
34+
},
35+
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "https://json.schemastore.org/nest-cli",
3+
"collection": "@nestjs/schematics",
4+
"sourceRoot": "src",
5+
"compilerOptions": {
6+
"deleteOutDir": true
7+
}
8+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"name": "license-service",
3+
"version": "0.0.1",
4+
"description": "",
5+
"author": "",
6+
"private": true,
7+
"license": "UNLICENSED",
8+
"scripts": {
9+
"build": "nest build",
10+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11+
"start": "nest start",
12+
"start:dev": "nest start --watch",
13+
"start:debug": "nest start --debug --watch",
14+
"start:prod": "node dist/main",
15+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
16+
"test": "jest",
17+
"test:watch": "jest --watch",
18+
"test:cov": "jest --coverage",
19+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20+
"test:e2e": "jest --config ./test/jest-e2e.json"
21+
},
22+
"dependencies": {
23+
"@nestjs/common": "^11.0.1",
24+
"@nestjs/core": "^11.0.1",
25+
"@nestjs/platform-express": "^11.0.1",
26+
"reflect-metadata": "^0.2.2",
27+
"rxjs": "^7.8.1"
28+
},
29+
"devDependencies": {
30+
"@eslint/eslintrc": "^3.2.0",
31+
"@eslint/js": "^9.18.0",
32+
"@nestjs/cli": "^11.0.0",
33+
"@nestjs/schematics": "^11.0.0",
34+
"@nestjs/testing": "^11.0.1",
35+
"@types/express": "^5.0.0",
36+
"@types/jest": "^30.0.0",
37+
"@types/node": "^24.0.0",
38+
"@types/supertest": "^7.0.0",
39+
"eslint": "^9.18.0",
40+
"eslint-config-prettier": "^10.0.1",
41+
"eslint-plugin-prettier": "^5.2.2",
42+
"globals": "^17.0.0",
43+
"jest": "^30.0.0",
44+
"prettier": "^3.4.2",
45+
"source-map-support": "^0.5.21",
46+
"supertest": "^7.0.0",
47+
"ts-jest": "^29.2.5",
48+
"ts-loader": "^9.5.2",
49+
"ts-node": "^10.9.2",
50+
"tsconfig-paths": "^4.2.0",
51+
"typescript": "^5.7.3",
52+
"typescript-eslint": "^8.20.0"
53+
},
54+
"jest": {
55+
"moduleFileExtensions": [
56+
"js",
57+
"json",
58+
"ts"
59+
],
60+
"rootDir": "src",
61+
"testRegex": ".*\\.spec\\.ts$",
62+
"transform": {
63+
"^.+\\.(t|j)s$": "ts-jest"
64+
},
65+
"collectCoverageFrom": [
66+
"**/*.(t|j)s"
67+
],
68+
"coverageDirectory": "../coverage",
69+
"testEnvironment": "node"
70+
}
71+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { Activation } from './entities/activation.entity';
4+
import { ActivationService } from './activation.service';
5+
import { ActivationController } from './activation.controller';
6+
7+
@Module({
8+
imports: [TypeOrmModule.forFeature([Activation])],
9+
providers: [ActivationService],
10+
controllers: [ActivationController],
11+
exports: [ActivationService],
12+
})
13+
export class ActivationModule {}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository } from 'typeorm';
4+
import { Activation } from './entities/activation.entity';
5+
import { License } from '../license/entities/license.entity';
6+
7+
@Injectable()
8+
export class ActivationService {
9+
constructor(
10+
@InjectRepository(Activation)
11+
private readonly activationRepo: Repository<Activation>,
12+
@InjectRepository(License)
13+
private readonly licenseRepo: Repository<License>,
14+
) {}
15+
16+
async activate(key: string, deviceId: string): Promise<Activation> {
17+
const license = await this.licenseRepo.findOne({ where: { key } });
18+
if (!license) {
19+
throw new NotFoundException('License key not found');
20+
}
21+
if (license.revoked) {
22+
throw new BadRequestException('License has been revoked');
23+
}
24+
if (license.expiresAt && license.expiresAt < new Date()) {
25+
throw new BadRequestException('License has expired');
26+
}
27+
// Ensure one activation per device per license
28+
const existing = await this.activationRepo.findOne({ where: { license: { id: license.id }, deviceId } });
29+
if (existing) {
30+
return existing;
31+
}
32+
const activation = this.activationRepo.create({
33+
license,
34+
deviceId,
35+
});
36+
return this.activationRepo.save(activation);
37+
}
38+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
2+
import { License } from './license.entity';
3+
4+
@Entity('activations')
5+
export class Activation {
6+
@PrimaryGeneratedColumn('uuid')
7+
id: string;
8+
9+
@ManyToOne(() => License, { eager: true })
10+
@JoinColumn({ name: 'licenseId' })
11+
license: License;
12+
13+
@Column()
14+
deviceId: string;
15+
16+
@Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
17+
activatedAt: Date;
18+
19+
@Column({ nullable: true })
20+
offlineToken: string;
21+
22+
@CreateDateColumn()
23+
createdAt: Date;
24+
25+
@UpdateDateColumn()
26+
updatedAt: Date;
27+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { AppController } from './app.controller';
3+
import { AppService } from './app.service';
4+
5+
describe('AppController', () => {
6+
let appController: AppController;
7+
8+
beforeEach(async () => {
9+
const app: TestingModule = await Test.createTestingModule({
10+
controllers: [AppController],
11+
providers: [AppService],
12+
}).compile();
13+
14+
appController = app.get<AppController>(AppController);
15+
});
16+
17+
describe('root', () => {
18+
it('should return "Hello World!"', () => {
19+
expect(appController.getHello()).toBe('Hello World!');
20+
});
21+
});
22+
});

0 commit comments

Comments
 (0)