Skip to content

Commit b49fc9c

Browse files
committed
feat: v1
0 parents  commit b49fc9c

23 files changed

+3501
-0
lines changed

.env.example

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
APIKEY=
2+
MONGODB_USER=
3+
MONGODB_PASSWORD=
4+
MONGODB_URL=
5+
MONGODB_DATABASE=

.eslintrc.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
node: true
5+
},
6+
"parser": "@typescript-eslint/parser",
7+
"plugins": [
8+
"@typescript-eslint"
9+
],
10+
"extends": [
11+
"eslint:recommended",
12+
"plugin:@typescript-eslint/eslint-recommended",
13+
"plugin:@typescript-eslint/recommended"
14+
],
15+
rules: {
16+
'no-unused-vars': 'warn',
17+
"indent": "off",
18+
"@typescript-eslint/indent": ["error", 2]
19+
},
20+
}

.gihub/workflows/main.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
name: Build - Deploy
3+
4+
on:
5+
push:
6+
branches: [ master ]
7+
8+
jobs:
9+
build:
10+
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
node-version: [16.6.1]
16+
17+
steps:
18+
- uses: actions/checkout@v2
19+
- name: Use Node.js ${{ matrix.node-version }}
20+
uses: actions/setup-node@v1
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
- run: yarn
24+
- run: yarn build
25+
26+
deploy:
27+
needs: build
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v2
31+
- uses: akhileshns/[email protected]
32+
with:
33+
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
34+
heroku_app_name: "upcoming-movies-markkop"
35+
heroku_email: ${{secrets.HEROKU_EMAIL}}

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.env
3+
dist

Procfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: yarn start

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# 📟 Code-Codes - API
2+
3+
This is the API version of the CodeCodes gamification Discord Bot.
4+
This doc shall be improved someday
5+
6+
## Routes
7+
8+
All routes require a `x-apikey` as header
9+
10+
### /token/claim [POST]
11+
Body:
12+
```
13+
{
14+
"code": "CODECON21",
15+
16+
"tag": "Mark Kop"
17+
}
18+
```
19+

package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "codecodes-api",
3+
"version": "1.0.0",
4+
"license": "MIT",
5+
"author": "Marcelo 'Mark' Kopmann",
6+
"scripts": {
7+
"dev": "nodemon --watch 'src/' --exec 'ts-node src/index.ts' -e ts ",
8+
"build": "tsc",
9+
"start": "node dist/index.js"
10+
},
11+
"dependencies": {
12+
"cors": "^2.8.5",
13+
"dotenv": "^10.0.0",
14+
"express": "^4.17.1",
15+
"mongoose": "^5.13.7",
16+
"morgan": "^1.10.0"
17+
},
18+
"devDependencies": {
19+
"@types/cors": "^2.8.12",
20+
"@types/express": "^4.17.13",
21+
"@types/morgan": "^1.9.3",
22+
"@types/node": "^16.6.1",
23+
"@typescript-eslint/eslint-plugin": "^4.29.1",
24+
"@typescript-eslint/parser": "^4.29.1",
25+
"eslint": "^7.32.0",
26+
"eslint-config-standard": "^16.0.3",
27+
"eslint-plugin-import": "^2.24.0",
28+
"eslint-plugin-node": "^11.1.0",
29+
"eslint-plugin-promise": "^5.1.0",
30+
"eslint-plugin-standard": "^5.0.0",
31+
"nodemon": "^2.0.12",
32+
"ts-node": "^10.2.0",
33+
"typescript": "^4.3.5"
34+
}
35+
}

src/config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
claim: {
3+
enabled: true,
4+
disabledMessage: "Resgates estão desabilitados. Obrigado por participar!"
5+
}
6+
}

src/controllers/token.ts

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { NextFunction, Request, Response } from 'express'
2+
import { getDatabaseTokenByCode, updateDatabaseToken } from '../services/token'
3+
import { getDatabaseUserById, updateDatabaseUser } from '../services/user'
4+
import config from '../config'
5+
6+
export async function claim (request: Request, response: Response, next: NextFunction): Promise<void> {
7+
try {
8+
if (!config.claim.enabled) {
9+
response.json({
10+
status: 'error',
11+
message: config.claim.disabledMessage
12+
})
13+
return
14+
}
15+
16+
const { body: { code, email: userId, name: tag } } = request
17+
18+
if (!code) {
19+
response.json({
20+
status: 'error',
21+
message: 'No code was provided'
22+
})
23+
return
24+
}
25+
26+
const token = await getDatabaseTokenByCode(code)
27+
28+
const { claimedBy, remainingClaims, value, decreaseValue, minimumValue, expireAt } = token
29+
30+
if (!remainingClaims) {
31+
response.json({
32+
status: 'error',
33+
message: 'Vish, acabaram os resgates disponíveis para esse token :('
34+
})
35+
return
36+
}
37+
38+
const isExpired = expireAt && new Date(Date.now()) > new Date(expireAt)
39+
if (isExpired) {
40+
response.json({
41+
status: 'error',
42+
message: 'Esse token expirou :('
43+
})
44+
return
45+
}
46+
47+
const hasAlreadyClaimed = claimedBy.some(claimedUser => claimedUser.id === userId)
48+
if (hasAlreadyClaimed) {
49+
response.json({
50+
status: 'error',
51+
message: 'Você já resgatou esse token :eyes:'
52+
})
53+
return
54+
}
55+
56+
const timesClaimed = claimedBy.length
57+
let scoreAcquired = value - (timesClaimed * decreaseValue)
58+
if (scoreAcquired < minimumValue) {
59+
scoreAcquired = minimumValue
60+
}
61+
62+
let userCurrentScore = 0
63+
let tokensClaimed = []
64+
const user = await getDatabaseUserById(userId)
65+
if (user) {
66+
userCurrentScore = user.score
67+
tokensClaimed = user.tokens
68+
}
69+
70+
const score = {
71+
acquired: scoreAcquired,
72+
total: userCurrentScore + scoreAcquired
73+
}
74+
75+
const date = new Date(Date.now())
76+
const dateString = date.toISOString()
77+
const claimUser = {
78+
tag: tag,
79+
id: String(userId),
80+
claimedAt: dateString
81+
}
82+
83+
const updatedToken = {
84+
...token,
85+
remainingClaims: remainingClaims - 1,
86+
claimedBy: claimedBy.concat(claimUser)
87+
}
88+
89+
const updatedUser = {
90+
userId,
91+
tag,
92+
score: score.total,
93+
tokens: tokensClaimed.concat({
94+
code,
95+
value: scoreAcquired,
96+
claimedAt: dateString
97+
})
98+
}
99+
100+
const userClaimSuccess = await updateDatabaseUser(updatedUser)
101+
if (!userClaimSuccess) {
102+
response.json({
103+
status: 'error',
104+
message: 'Putz, deu ruim ao atualizar o usuário'
105+
})
106+
return
107+
}
108+
109+
const databaseUpdatedToken = await updateDatabaseToken(updatedToken)
110+
if (!databaseUpdatedToken) {
111+
response.json({
112+
status: 'error',
113+
message: 'Putz, deu ruim ao atualizar o token'
114+
})
115+
return
116+
}
117+
118+
response.json({
119+
status: 'success',
120+
message: `O código ${code} foi resgatado por ${userId}.`
121+
})
122+
return
123+
} catch (error) {
124+
next(error)
125+
console.log(error)
126+
}
127+
}
128+
129+
export default {
130+
claim
131+
}

src/index.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import dotenv from 'dotenv'
2+
dotenv.config()
3+
import express from 'express'
4+
import logger from 'morgan'
5+
import cors from 'cors'
6+
import token from './routes/token'
7+
import middlewares from './middewares'
8+
9+
const PORT = process.env.PORT || 8080
10+
11+
try {
12+
const app = express()
13+
14+
app.use(cors())
15+
app.use(logger('dev'))
16+
app.use(express.json())
17+
18+
app.use('/token', token)
19+
20+
app.use(middlewares.notFound)
21+
app.use(middlewares.errorHandler)
22+
23+
app.listen(PORT, () => {
24+
console.log(`Server is running on port ${PORT}.`)
25+
})
26+
} catch (error) {
27+
console.error(error)
28+
}

src/middewares/errorHandler.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Request, Response } from "express"
2+
3+
export function errorHandler (error: Error, request: Request, response: Response): void {
4+
response.status(505).json({
5+
message: error.message,
6+
stack: error.stack
7+
})
8+
}

src/middewares/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { errorHandler } from "./errorHandler";
2+
import { notFound } from "./notFound";
3+
4+
export default {
5+
errorHandler,
6+
notFound
7+
}

src/middewares/notFound.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Request, Response } from "express"
2+
3+
export function notFound (request: Request, response: Response): void {
4+
response.status(404).json({
5+
message: `Not Found - ${request.originalUrl}`
6+
})
7+
}

src/middewares/validateApiKey.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { NextFunction, Request, Response } from 'express'
2+
3+
export function validateApiKey (request: Request, response: Response, next: NextFunction): void {
4+
try {
5+
const apiKey = request.headers['x-apikey']
6+
if (apiKey !== process.env.APIKEY) {
7+
throw new Error('Wrong or missing apikey')
8+
}
9+
next()
10+
} catch (error) {
11+
response.json({
12+
status: 'error',
13+
message: error.message
14+
})
15+
}
16+
}

0 commit comments

Comments
 (0)