mirror of
https://github.com/FranP-code/shortly.git
synced 2025-10-13 00:43:28 +00:00
first working prototype of shortly
This commit is contained in:
@@ -1,15 +1,10 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "standard",
|
||||
"overrides": [
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"semi": ["error", "never"],
|
||||
"quotes": [2, "single"],
|
||||
"no-unused-vars": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error"
|
||||
}
|
||||
}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
node_modules
|
||||
yarn.lock
|
||||
yarn.lock
|
||||
.env
|
||||
dist
|
||||
yarn-error.log
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -7,4 +7,11 @@ services:
|
||||
- .:/usr/src
|
||||
- /usr/src/node_modules
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- 3000:3000
|
||||
mongo:
|
||||
image: mongo
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGODB_LINK: ${MONGODB_LINK}
|
||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
|
||||
@@ -1,10 +0,0 @@
|
||||
{"forbidden-ids": [
|
||||
"settings",
|
||||
"register",
|
||||
"login",
|
||||
"admin",
|
||||
"franp",
|
||||
"github",
|
||||
"linkedin",
|
||||
"add"
|
||||
]}
|
||||
5
nodemon.json
Normal file
5
nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts, json",
|
||||
"exec": "ts-node ./src/app.ts"
|
||||
}
|
||||
73
package.json
73
package.json
@@ -1,33 +1,44 @@
|
||||
{
|
||||
"name": "url-shorter",
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/monatheoctocat/my_package.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "FranP-Code",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/monatheoctocat/my_package/issues"
|
||||
},
|
||||
"homepage": "https://github.com/monatheoctocat/my_package",
|
||||
"dependencies": {
|
||||
"@fastify/jwt": "^6.5.0",
|
||||
"fastify": "^4.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-n": "^15.0.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"nodemon": "^2.0.20"
|
||||
}
|
||||
"name": "shortly",
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node dist/app.js",
|
||||
"dev": "nodemon -L src/app.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/monatheoctocat/my_package.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "FranP-Code",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/monatheoctocat/my_package/issues"
|
||||
},
|
||||
"homepage": "https://github.com/monatheoctocat/my_package",
|
||||
"dependencies": {
|
||||
"@types/express": "^4.17.15",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"metadata-scraper": "^0.2.61",
|
||||
"mongoose": "5.13.14",
|
||||
"ulid": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^9.0.0",
|
||||
"@types/mongodb": "^4.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-n": "^15.0.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
20
src/app.ts
Normal file
20
src/app.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import express from 'express'
|
||||
require('dotenv').config()
|
||||
import databaseConnection from './models/dbConnection'
|
||||
import signUp from './routes/signUp/signUp'
|
||||
import getUser from './routes/getUser/getUser'
|
||||
import addUrl from './routes/addUrl/addUrl'
|
||||
import getUrl from './routes/get-url/getUrl'
|
||||
|
||||
const app = express()
|
||||
const port = process.env.PORT || 3000
|
||||
|
||||
databaseConnection()
|
||||
app.use('/get-user', getUser)
|
||||
app.use('/sign-up', signUp)
|
||||
app.use('/add-url', addUrl)
|
||||
app.use('/', getUrl)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`shortly on port ${port}`)
|
||||
})
|
||||
19
src/index.js
19
src/index.js
@@ -1,19 +0,0 @@
|
||||
const fastify = require('fastify')({ logger: true })
|
||||
|
||||
const addUrl = require('./routes/add-url')
|
||||
const index = require('./routes/index')
|
||||
const urlShortener = require('./routes/url-shortener')
|
||||
|
||||
fastify.register(index)
|
||||
fastify.register(urlShortener)
|
||||
fastify.register(addUrl)
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
await fastify.listen({ port: process.env.PORT || 3000, host: '0.0.0.0' })
|
||||
} catch (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
start()
|
||||
19
src/models/dbConnection.ts
Normal file
19
src/models/dbConnection.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
export default function databaseConnection() {
|
||||
mongoose
|
||||
.connect(
|
||||
`mongodb://root:${process.env.MONGO_INITDB_ROOT_PASSWORD}@${process.env.MONGODB_LINK}`,
|
||||
{
|
||||
serverSelectionTimeoutMS: 5000,
|
||||
}
|
||||
)
|
||||
.catch((err: any) => console.log(err))
|
||||
|
||||
mongoose.connection.once('open', async () => {
|
||||
console.log('DB connected')
|
||||
})
|
||||
mongoose.connection.once('error', (err: any) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
58
src/models/queries/Url.queries.ts
Normal file
58
src/models/queries/Url.queries.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/* eslint-disable quotes */
|
||||
import { decrypt } from '../../scripts/crypto'
|
||||
import generateId from '../../scripts/generateId'
|
||||
import isUrl from '../../scripts/isUrl'
|
||||
import removeEmptyProperties from '../../scripts/removeEmptyProperties'
|
||||
import { IUrl, UrlModel } from '../schemas/Url.schema'
|
||||
import { UserModel } from '../schemas/User.schema'
|
||||
|
||||
export async function createUrl(
|
||||
data: {
|
||||
id?: string
|
||||
url: string
|
||||
username?: string
|
||||
email?: string
|
||||
password: string
|
||||
} & ({ username: string } | { email: string })
|
||||
) {
|
||||
const { id, url, username, email, password } = data
|
||||
if (!isUrl(url)) {
|
||||
throw new Error('url invalid')
|
||||
}
|
||||
if (process.env.ALLOW_DUPLICATED_LINKS !== 'true') {
|
||||
const existingUrl = await UrlModel.findOne({
|
||||
url,
|
||||
})
|
||||
if (existingUrl) {
|
||||
return existingUrl
|
||||
}
|
||||
const user = await UserModel.findOne(
|
||||
removeEmptyProperties({ id, username, email })
|
||||
)
|
||||
if (!user) {
|
||||
throw new Error(JSON.stringify({ message: "user don't found" }))
|
||||
}
|
||||
if (
|
||||
decrypt({ content: user.password, iv: user.crypto.iv }) === password
|
||||
) {
|
||||
const newUrl: IUrl = {
|
||||
id: generateId(5),
|
||||
url,
|
||||
dateCreated: new Date(),
|
||||
uploadedByUser: user.id,
|
||||
}
|
||||
return UrlModel.create(newUrl)
|
||||
}
|
||||
} else {
|
||||
throw new Error(JSON.stringify({ message: "user don't found" }))
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUrlById(data: { id: string }) {
|
||||
const { id } = data
|
||||
const url = await UrlModel.findOne({ id })
|
||||
if (!url) {
|
||||
throw new Error('no url')
|
||||
}
|
||||
return url
|
||||
}
|
||||
111
src/models/queries/User.queries.ts
Normal file
111
src/models/queries/User.queries.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable quotes */
|
||||
import { IUser, UserModel } from '../schemas/User.schema'
|
||||
import { ulid } from 'ulid'
|
||||
import { decrypt, encrypt } from '../../scripts/crypto'
|
||||
import { UserRoles } from '../../utils/constants'
|
||||
import removeEmptyProperties from '../../scripts/removeEmptyProperties'
|
||||
|
||||
export async function getUserData(
|
||||
data: {
|
||||
userId?: number
|
||||
username?: string
|
||||
email?: string
|
||||
} & ({ username: string } | { email: string })
|
||||
) {
|
||||
return UserModel.find(removeEmptyProperties(data))
|
||||
}
|
||||
|
||||
export async function getUserDataWithId(
|
||||
data: {
|
||||
username?: string
|
||||
email?: string
|
||||
password?: string
|
||||
} & ({ username: string } | { email: string })
|
||||
) {
|
||||
const { username, email, password } = data
|
||||
const user = await UserModel.findOne(
|
||||
removeEmptyProperties({ username, email })
|
||||
)
|
||||
if (!user) {
|
||||
throw new Error(JSON.stringify({ message: "user don't found" }))
|
||||
}
|
||||
if (
|
||||
decrypt({
|
||||
content: user.password,
|
||||
iv: user.crypto.iv,
|
||||
}) === password
|
||||
) {
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
}
|
||||
} else {
|
||||
throw new Error(JSON.stringify({ message: "password don't match" }))
|
||||
}
|
||||
}
|
||||
|
||||
export async function createUser(data: {
|
||||
email: string
|
||||
password: string
|
||||
username: string
|
||||
role: UserRoles
|
||||
sponsorUid: string
|
||||
firstUserPassword?: string
|
||||
}) {
|
||||
const { email, password, username, role, sponsorUid } = data
|
||||
const { iv, content: encryptedPassword } = encrypt(password)
|
||||
const ulidSeed = process.env.ULID_SEED && parseInt(process.env.ULID_SEED)
|
||||
const newUser: IUser = {
|
||||
id: typeof ulidSeed === 'number' ? ulid(ulidSeed) : ulid(),
|
||||
dateCreated: new Date(),
|
||||
email,
|
||||
password: encryptedPassword,
|
||||
role,
|
||||
username,
|
||||
crypto: { iv },
|
||||
}
|
||||
const existingUsername = await UserModel.countDocuments({ username })
|
||||
const existingEmail = await UserModel.countDocuments({ email })
|
||||
if (existingUsername || existingEmail) {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
message: 'fields duplicated',
|
||||
fieldsDuplicated: [
|
||||
existingUsername && 'username',
|
||||
existingEmail && 'email',
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
const firstUserCreated = !(await UserModel.count())
|
||||
if (firstUserCreated) {
|
||||
if (data.firstUserPassword !== process.env.FIRST_USER_PASSWORD) {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
message: 'wrong first user password',
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
console.log(sponsorUid)
|
||||
if (!sponsorUid) {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
message: 'no sponsor user param',
|
||||
})
|
||||
)
|
||||
}
|
||||
const sponsorUser = await UserModel.findOne({ id: sponsorUid })
|
||||
if (!sponsorUser) {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
message: 'no sponsor user',
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
const userModelCreation = new UserModel(newUser)
|
||||
await userModelCreation.save()
|
||||
}
|
||||
34
src/models/schemas/Url.schema.ts
Normal file
34
src/models/schemas/Url.schema.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { model, Model, Schema } from 'mongoose'
|
||||
|
||||
export interface IUrl {
|
||||
id: string
|
||||
url: string
|
||||
dateCreated: Date
|
||||
uploadedByUser: string
|
||||
}
|
||||
|
||||
export const urlSchema = new Schema<IUrl>(
|
||||
{
|
||||
id: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
dateCreated: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
required: true,
|
||||
},
|
||||
uploadedByUser: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{ collection: 'url', timestamps: true }
|
||||
)
|
||||
|
||||
export const UrlModel: Model<IUrl> = model('Url', urlSchema)
|
||||
52
src/models/schemas/User.schema.ts
Normal file
52
src/models/schemas/User.schema.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Schema, model, Model } from 'mongoose'
|
||||
import { Crypto, UserRoles } from '../../utils/constants'
|
||||
export interface IUser {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
dateCreated: Date
|
||||
role: UserRoles
|
||||
crypto: Crypto
|
||||
}
|
||||
|
||||
export const userSchema = new Schema<IUser>(
|
||||
{
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
lowercase: true,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
dateCreated: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
required: true,
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
crypto: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{ collection: 'user', timestamps: true }
|
||||
)
|
||||
|
||||
export const UserModel: Model<IUser> = model('User', userSchema)
|
||||
18
src/pages/getUrl/generateGetUrlPage.ts
Normal file
18
src/pages/getUrl/generateGetUrlPage.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export default function (metaData: any) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${metaData.title}</title>
|
||||
<meta name="description" content="${metaData.description}">
|
||||
<meta property="og:image" content="${metaData.image}">
|
||||
<meta property="og:url" content="${metaData.url}">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.location.href = "${metaData.url}";
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
module.exports = async function (fastify, options) {
|
||||
fastify.register(require('@fastify/jwt'), {
|
||||
secret: 'supersecret123'
|
||||
})
|
||||
|
||||
fastify.post('/add', (req, reply) => {
|
||||
// some code
|
||||
const token = fastify.jwt.sign({ abc: 123 })
|
||||
console.log(token)
|
||||
reply.send({ token })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
}
|
||||
33
src/routes/addUrl/addUrl.ts
Normal file
33
src/routes/addUrl/addUrl.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Router, Request, Response } from 'express'
|
||||
import { createUrl } from '../../models/queries/Url.queries'
|
||||
import checkMissingData from '../../scripts/checkMissingData'
|
||||
import checkUsernameOrEmail from '../../scripts/checkUsernameOrEmail'
|
||||
import mongoErrorService from '../../services/mongoErrorService'
|
||||
const router = Router()
|
||||
|
||||
router.post('/', async (req: Request, res: Response) => {
|
||||
const values = ['url', 'password']
|
||||
const { url, email, username, password } = req.headers
|
||||
try {
|
||||
checkMissingData(values, req)
|
||||
checkUsernameOrEmail(<string>username, <string>email, res)
|
||||
const createdUrl = await createUrl({
|
||||
email: <string>email,
|
||||
password: <string>password,
|
||||
url: <string>url,
|
||||
username: <string>username,
|
||||
})
|
||||
res.status(200).json({
|
||||
message: 'url created',
|
||||
url: createdUrl,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(400).json({
|
||||
error: mongoErrorService(error),
|
||||
})
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
export default router
|
||||
12
src/routes/get-url/forbidden-ids.json
Normal file
12
src/routes/get-url/forbidden-ids.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"ids": [
|
||||
"settings",
|
||||
"register",
|
||||
"login",
|
||||
"admin",
|
||||
"franp",
|
||||
"github",
|
||||
"linkedin",
|
||||
"add"
|
||||
]
|
||||
}
|
||||
30
src/routes/get-url/getUrl.ts
Normal file
30
src/routes/get-url/getUrl.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Router, Request, Response } from 'express'
|
||||
import getMetaData from 'metadata-scraper'
|
||||
import { getUrlById } from '../../models/queries/Url.queries'
|
||||
import generateGetUrlPage from '../../pages/getUrl/generateGetUrlPage'
|
||||
import mongoErrorService from '../../services/mongoErrorService'
|
||||
import { ids as forbiddenIds } from './forbidden-ids.json'
|
||||
const router = Router()
|
||||
|
||||
router.get('/:urlId', async (req: Request, res: Response) => {
|
||||
const { urlId } = req.params
|
||||
if (!urlId || forbiddenIds.includes(urlId)) {
|
||||
res.status(400).json({
|
||||
message: 'forbidden id',
|
||||
})
|
||||
}
|
||||
try {
|
||||
const { url } = await getUrlById({
|
||||
id: urlId,
|
||||
})
|
||||
const metaData = await getMetaData(url)
|
||||
res.send(generateGetUrlPage(metaData))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(400).json({
|
||||
error: mongoErrorService(error),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
30
src/routes/getUser/getUser.ts
Normal file
30
src/routes/getUser/getUser.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Router, Request, Response } from 'express'
|
||||
import { getUserDataWithId } from '../../models/queries/User.queries'
|
||||
import checkMissingData from '../../scripts/checkMissingData'
|
||||
import checkUsernameOrEmail from '../../scripts/checkUsernameOrEmail'
|
||||
import mongoErrorService from '../../services/mongoErrorService'
|
||||
const router = Router()
|
||||
|
||||
router.get('/', async (req: Request, res: Response) => {
|
||||
const values = ['password']
|
||||
const { email, username, password } = req.headers
|
||||
try {
|
||||
checkMissingData(values, req)
|
||||
checkUsernameOrEmail(<string>username, <string>email, res)
|
||||
const user = await getUserDataWithId({
|
||||
email: <string>email,
|
||||
username: <string>username,
|
||||
password: <string>password,
|
||||
})
|
||||
res.status(201).json({
|
||||
message: 'user found',
|
||||
user,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(400).json({
|
||||
error: mongoErrorService(error),
|
||||
})
|
||||
}
|
||||
})
|
||||
export default router
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = async function (fastify, options) {
|
||||
fastify.get('/', async (request, reply) => {
|
||||
return { test: 'testValue' }
|
||||
})
|
||||
}
|
||||
39
src/routes/signUp/signUp.ts
Normal file
39
src/routes/signUp/signUp.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Router, Request, Response } from 'express'
|
||||
import { createUser } from '../../models/queries/User.queries'
|
||||
import checkMissingData from '../../scripts/checkMissingData'
|
||||
import mongoErrorService from '../../services/mongoErrorService'
|
||||
import { UserRoles } from '../../utils/constants'
|
||||
const router = Router()
|
||||
|
||||
router.post('/', async (req: Request, res: Response) => {
|
||||
const values = ['email', 'password', 'username', 'role']
|
||||
const {
|
||||
email,
|
||||
first_user_password,
|
||||
password,
|
||||
role,
|
||||
sponsor_uid,
|
||||
username,
|
||||
} = req.headers
|
||||
try {
|
||||
checkMissingData(values, req)
|
||||
await createUser({
|
||||
email: <string>email,
|
||||
password: <string>password,
|
||||
username: <string>username,
|
||||
role: <UserRoles>role,
|
||||
sponsorUid: <string>sponsor_uid,
|
||||
firstUserPassword: <string>first_user_password,
|
||||
})
|
||||
res.status(201).json({
|
||||
message: 'user added',
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(400).json({
|
||||
error: mongoErrorService(error),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = async function (fastify, options) {
|
||||
fastify.get('/:id', async (request, reply) => {
|
||||
const { id } = request.params
|
||||
return { test: id }
|
||||
})
|
||||
}
|
||||
19
src/scripts/checkMissingData.ts
Normal file
19
src/scripts/checkMissingData.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Request } from 'express'
|
||||
|
||||
export default function (values: string[], req: Request) {
|
||||
const missingData = values.reduce((arr: string[], valueName) => {
|
||||
if (!req.headers[valueName]) {
|
||||
return [...arr, valueName]
|
||||
} else {
|
||||
return arr
|
||||
}
|
||||
}, [])
|
||||
if (missingData.length) {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
mesasage: 'no data from user',
|
||||
properties: missingData,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
10
src/scripts/checkUsernameOrEmail.ts
Normal file
10
src/scripts/checkUsernameOrEmail.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Response } from 'express'
|
||||
|
||||
export default function (username: string, email: string, res: Response) {
|
||||
if (![username, email].filter((value) => value).length) {
|
||||
res.status(400).json({
|
||||
message: 'not username or email',
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
27
src/scripts/crypto.ts
Normal file
27
src/scripts/crypto.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import crypto from 'crypto'
|
||||
import { CryptoWithContent } from '../utils/constants'
|
||||
|
||||
const algorithm = 'aes-256-ctr'
|
||||
const secretKey = <string>process.env.ENCRYPT_KEY
|
||||
const iv = crypto.randomBytes(16)
|
||||
export const encrypt = (text: string) => {
|
||||
const cipher = crypto.createCipheriv(algorithm, secretKey, iv)
|
||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()])
|
||||
return {
|
||||
iv: iv.toString('hex'),
|
||||
content: encrypted.toString('hex'),
|
||||
}
|
||||
}
|
||||
|
||||
export const decrypt = (hash: CryptoWithContent) => {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
algorithm,
|
||||
secretKey,
|
||||
Buffer.from(hash.iv, 'hex')
|
||||
)
|
||||
const decrpyted = Buffer.concat([
|
||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||
decipher.final(),
|
||||
])
|
||||
return decrpyted.toString()
|
||||
}
|
||||
12
src/scripts/generateId.ts
Normal file
12
src/scripts/generateId.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { charactersForIdGeneration } from '../utils/constants'
|
||||
|
||||
export default function (length: number) {
|
||||
let result = ''
|
||||
const charactersLength = charactersForIdGeneration.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += charactersForIdGeneration.charAt(
|
||||
Math.floor(Math.random() * charactersLength)
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
5
src/scripts/isUrl.ts
Normal file
5
src/scripts/isUrl.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { isUrlRegex } from '../utils/constants'
|
||||
|
||||
export default function (url: string) {
|
||||
return !!isUrlRegex.test(url)
|
||||
}
|
||||
5
src/scripts/removeEmptyProperties.ts
Normal file
5
src/scripts/removeEmptyProperties.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function (obj: Object) {
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const nonEmptyEntries = Object.entries(obj).filter(([key, value]) => value)
|
||||
return Object.fromEntries(nonEmptyEntries)
|
||||
}
|
||||
23
src/services/mongoErrorService.ts
Normal file
23
src/services/mongoErrorService.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { MongoServerError } from 'mongodb'
|
||||
|
||||
export default function mongoErrorService(error: any) {
|
||||
let responseMessage: any
|
||||
if (error instanceof MongoServerError) {
|
||||
switch (error.code) {
|
||||
case 1:
|
||||
responseMessage = '1'
|
||||
break
|
||||
|
||||
default:
|
||||
responseMessage = error.message
|
||||
break
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
responseMessage = JSON.parse(error.message)
|
||||
} catch {
|
||||
responseMessage = error
|
||||
}
|
||||
}
|
||||
return responseMessage
|
||||
}
|
||||
0
src/services/userService.ts
Normal file
0
src/services/userService.ts
Normal file
13
src/utils/constants.ts
Normal file
13
src/utils/constants.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type Crypto = {
|
||||
iv: string
|
||||
}
|
||||
export type CryptoWithContent = {
|
||||
iv: string
|
||||
content: string
|
||||
}
|
||||
export type UserRoles = 'admin' | 'user'
|
||||
export const charactersForIdGeneration =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-()!_><'
|
||||
export const isUrlRegex = new RegExp(
|
||||
'^(http[s]?:\\/\\/(www\\.)?|ftp:\\/\\/(www\\.)?|www\\.){1}([0-9A-Za-z-\\.@:%_+~#=]+)+((\\.[a-zA-Z]{2,3})+)(/(.)*)?(\\?(.)*)?'
|
||||
)
|
||||
103
tsconfig.json
Normal file
103
tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs" /* Specify what module code is generated. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
// "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
"resolveJsonModule": true /* Enable importing .json files. */,
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user