mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 17:41:15 +00:00
fixinit10
This commit is contained in:
commit
e214f59fca
30 changed files with 9325 additions and 0 deletions
4
.dockerignore
Normal file
4
.dockerignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
.env
|
||||
node_modules/
|
||||
.vscode/
|
||||
client/
|
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
[*.js]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
17
.env.example
Normal file
17
.env.example
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Postgres config
|
||||
POSTGRES_DB=<placeholder>
|
||||
POSTGRES_USER=<placeholder>
|
||||
POSTGRES_PASSWORD=<placeholder>
|
||||
# POSTGRES CONTAINER NAME
|
||||
DB_HOST=postgres
|
||||
|
||||
# Use this instead if you want to use locally
|
||||
# DB_HOST=localhost
|
||||
|
||||
DATABASE_URL=postgresql://<placeholder>:<placeholder>@${DB_HOST}:5432/${POSTGRES_DB}?schema=
|
||||
|
||||
# Express
|
||||
SERVER_PORT=<placeholder>
|
||||
|
||||
# Security
|
||||
JWT_ACCESS_SECRET=<placeholder>
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
.git
|
||||
node_modules
|
||||
.env
|
||||
prisma/*.db
|
||||
.DS_Store
|
||||
prisma/migrations/dev
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
|
@ -0,0 +1,15 @@
|
|||
FROM node:16 as builder
|
||||
|
||||
# Create app dir
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
COPY prisma ./prisma/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["npm", "run", "prod:start"]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 CookieDasora
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Social Media - Unnamed
|
17
docker-compose.db.yml
Normal file
17
docker-compose.db.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:alpine
|
||||
restart: unless-stopped
|
||||
container_name: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
name: backend-db
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
|
@ -0,0 +1,30 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
api:
|
||||
container_name: api
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 8080:8080
|
||||
depends_on:
|
||||
- postgres
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
postgres:
|
||||
image: postgres:alpine
|
||||
restart: unless-stopped
|
||||
container_name: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
name: backend-db
|
8716
package-lock.json
generated
Normal file
8716
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
46
package.json
Normal file
46
package.json
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "social-media-app",
|
||||
"version": "0.0.1",
|
||||
"description": "A social media",
|
||||
"main": "src/server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:start": "nodemon src/server.js",
|
||||
"docker": "docker compose up -d",
|
||||
"docker:build": "docker build -t api . && docker compose up -d",
|
||||
"docker:db": "docker compose -f docker-compose.db.yml up -d",
|
||||
"docker:seed": "docker exec -it api npm run prisma:seed",
|
||||
"migrate:dev": "prisma migrate dev",
|
||||
"migrate:dev:create": "prisma migrate dev --create-only",
|
||||
"migrate:reset": "prisma migrate reset",
|
||||
"prisma:generate": "npx prisma generate",
|
||||
"prisma:seed": "prisma db seed",
|
||||
"prisma:studio": "npx prisma studio",
|
||||
"prod:start": "pm2-runtime start src/server.js",
|
||||
"test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/src/tests/*.spec.js"
|
||||
]
|
||||
},
|
||||
"author": "Cookie",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"pm2": "^5.3.0",
|
||||
"prisma": "^4.16.0",
|
||||
"standard": "^17.1.0",
|
||||
"supertest": "^6.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.16.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
"compression": "^1.7.4",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"validator": "^13.9.0"
|
||||
}
|
||||
}
|
11
prisma/Dockerfile
Normal file
11
prisma/Dockerfile
Normal file
|
@ -0,0 +1,11 @@
|
|||
FROM node:14
|
||||
RUN openssl version -v
|
||||
RUN uname -a
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm install -g prisma --unsafe-perm
|
||||
|
||||
ADD . ./prisma/
|
||||
|
||||
CMD ["prisma", "migrate", "up"]
|
5
prisma/client.js
Normal file
5
prisma/client.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default prisma
|
17
prisma/migrations/20230620191117_init/migration.sql
Normal file
17
prisma/migrations/20230620191117_init/migration.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"displayName" TEXT,
|
||||
"username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
20
prisma/schema.prisma
Normal file
20
prisma/schema.prisma
Normal file
|
@ -0,0 +1,20 @@
|
|||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
displayName String?
|
||||
username String @unique
|
||||
email String @unique
|
||||
password String
|
||||
createdAt DateTime @default(now())
|
||||
}
|
28
prisma/seed.js
Normal file
28
prisma/seed.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
async function main () {
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
username: 'cookie_'
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Seeding database...')
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
displayName: 'Cookie_',
|
||||
username: 'cookie_',
|
||||
email: 'cookie@cookie.com',
|
||||
password: '$2a$10$vBx5LkpZNMC3j1bHopUcp.ZcsMt5xfWlUJJGYIb4511aNccnHmMqi' // cookie
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => console.error(e))
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
20
src/app.js
Normal file
20
src/app.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import 'dotenv/config'
|
||||
|
||||
import express from 'express'
|
||||
import router from './routes.js'
|
||||
import compression from 'compression'
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(router)
|
||||
app.use(compression({ level: 9 }))
|
||||
|
||||
app.use((_req, res) => {
|
||||
res.status(404).json({
|
||||
error: 'Not found'
|
||||
})
|
||||
})
|
||||
|
||||
export default app
|
17
src/controllers/user-auth.js
Normal file
17
src/controllers/user-auth.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import userAuthService from '../services/user-auth.js'
|
||||
|
||||
async function userAuthController (req, res) {
|
||||
const { email, password } = req.body
|
||||
|
||||
const result = await userAuthService({ email, password })
|
||||
|
||||
if (result instanceof Error) {
|
||||
return res.status(400).json({
|
||||
error: result.message
|
||||
})
|
||||
}
|
||||
|
||||
res.json(result)
|
||||
}
|
||||
|
||||
export default userAuthController
|
15
src/controllers/user-info.js
Normal file
15
src/controllers/user-info.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import userInfoService from '../services/user-info.js'
|
||||
|
||||
async function userInfoController (req, res) {
|
||||
const result = await userInfoService(req)
|
||||
|
||||
if (result instanceof Error) {
|
||||
return res.status(400).json({
|
||||
error: result.message
|
||||
})
|
||||
}
|
||||
|
||||
res.json(result)
|
||||
}
|
||||
|
||||
export default userInfoController
|
17
src/controllers/user-signup.js
Normal file
17
src/controllers/user-signup.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import userSignupService from '../services/user-signup.js'
|
||||
|
||||
async function userSignupController (req, res) {
|
||||
const { username, email, password } = req.body
|
||||
|
||||
const result = await userSignupService({ username, email, password })
|
||||
|
||||
if (result instanceof Error) {
|
||||
return res.status(400).json({
|
||||
error: result.message
|
||||
})
|
||||
}
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
|
||||
export default userSignupController
|
38
src/middlewares/ensure-authenticated.js
Normal file
38
src/middlewares/ensure-authenticated.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import jsonwebtoken from 'jsonwebtoken'
|
||||
import prisma from '../../prisma/client.js'
|
||||
|
||||
function ensureAuthenticated (req, res, next) {
|
||||
if (!req.headers.authorization || req.headers.authorization.length === 0) {
|
||||
return res.status(401).json({
|
||||
error: 'Missing token'
|
||||
})
|
||||
}
|
||||
|
||||
const token = req.headers.authorization.split(' ')[1]
|
||||
|
||||
jsonwebtoken.verify(token, process.env.JWT_ACCESS_SECRET, async (err, decoded) => {
|
||||
if (err || !decoded) {
|
||||
return res.status(401).json({
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: decoded.id
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
error: 'Invalid user'
|
||||
})
|
||||
}
|
||||
|
||||
req.user = decoded
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
export default ensureAuthenticated
|
14
src/routes.js
Normal file
14
src/routes.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Router } from 'express'
|
||||
|
||||
import userSignupController from './controllers/user-signup.js'
|
||||
import userAuthController from './controllers/user-auth.js'
|
||||
import userInfoController from './controllers/user-info.js'
|
||||
import ensureAuthenticated from './middlewares/ensure-authenticated.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.post('/user/create', userSignupController)
|
||||
router.post('/user/auth', userAuthController)
|
||||
router.get('/user/info', ensureAuthenticated, userInfoController)
|
||||
|
||||
export default router
|
5
src/server.js
Normal file
5
src/server.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import app from './app.js'
|
||||
|
||||
app.listen(process.env.SERVER_PORT, () => {
|
||||
console.log(`Server is running @ ${process.env.SERVER_PORT}`)
|
||||
})
|
35
src/services/user-auth.js
Normal file
35
src/services/user-auth.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as bcrypt from 'bcrypt'
|
||||
import jsonwebtoken from 'jsonwebtoken'
|
||||
import prisma from '../../prisma/client.js'
|
||||
|
||||
async function userAuthService ({ email, password }) {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return new Error('User does not exists')
|
||||
}
|
||||
|
||||
if (email === undefined || password === undefined) {
|
||||
return new Error('Missing fields')
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(password, user.password)
|
||||
|
||||
if (validPassword === false) {
|
||||
return new Error('Invalid email or password')
|
||||
}
|
||||
|
||||
const { id } = user
|
||||
|
||||
const bearer = jsonwebtoken.sign({ id }, process.env.JWT_ACCESS_SECRET, { expiresIn: '1d' })
|
||||
|
||||
return {
|
||||
token: bearer
|
||||
}
|
||||
}
|
||||
|
||||
export default userAuthService
|
21
src/services/user-info.js
Normal file
21
src/services/user-info.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import prisma from '../../prisma/client.js'
|
||||
|
||||
async function userInfoService (req) {
|
||||
const userId = req.user.id
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true
|
||||
}
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export default userInfoService
|
40
src/services/user-signup.js
Normal file
40
src/services/user-signup.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import * as bcrypt from 'bcrypt'
|
||||
import prisma from '../../prisma/client.js'
|
||||
|
||||
async function userSignupService ({ username, email, password }) {
|
||||
if (!username || !email || !password) {
|
||||
return new Error('Missing fields')
|
||||
}
|
||||
|
||||
if (/^[a-zA-Z0-9_]{5,15}$/.test(username) === false) {
|
||||
return new Error('Username not allowed. Only alphanumerics characters (uppercase and lowercase words), underscore and it must be between 5 and 15 characters')
|
||||
}
|
||||
|
||||
if (await prisma.user.findFirst({ where: { username } })) {
|
||||
return new Error('Username already in use')
|
||||
}
|
||||
|
||||
if (await prisma.user.findFirst({ where: { email } })) {
|
||||
return new Error('Email already in use')
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(15)
|
||||
const hashedPassword = await bcrypt.hash(password, salt)
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password: hashedPassword
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
createdAt: true
|
||||
}
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export default userSignupService
|
51
src/tests/user-auth.spec.js
Normal file
51
src/tests/user-auth.spec.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import request from 'supertest'
|
||||
import prisma from '../../prisma/client.js'
|
||||
import app from '../app.js'
|
||||
|
||||
describe('POST /user/auth', () => {
|
||||
beforeAll(async () => {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: 'test',
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
username: 'test'
|
||||
}
|
||||
})
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
|
||||
it('should respond with a error if the user does not exists', async () => {
|
||||
const response = await request(app)
|
||||
.post('/user/auth')
|
||||
.send({
|
||||
email: 'mm@mm.com',
|
||||
password: 'aa'
|
||||
}).expect(400)
|
||||
expect(response.body).toHaveProperty('error')
|
||||
expect(response.body.error).toBe('User does not exists')
|
||||
})
|
||||
|
||||
it('should respond with a error if receive an invalid email or password', async () => {
|
||||
const response = await request(app)
|
||||
.post('/user/auth').send({
|
||||
email: 'test@test.com',
|
||||
password: 'haha'
|
||||
}).expect(400)
|
||||
expect(response.body).toHaveProperty('error')
|
||||
expect(response.body.error).toBe('Invalid email or password')
|
||||
})
|
||||
|
||||
it('should respond with a error if receive an empty body', async () => {
|
||||
const response = await request(app).post('/user/auth').send({}).expect(400)
|
||||
expect(response.body).toHaveProperty('error')
|
||||
expect(response.body.error).toBe('Missing fields')
|
||||
})
|
||||
})
|
7
src/tests/user-info.spec.js
Normal file
7
src/tests/user-info.spec.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import request from 'supertest'
|
||||
|
||||
describe('POST /user/info', () => {
|
||||
it('TODO', async () => {
|
||||
|
||||
})
|
||||
})
|
83
src/tests/user-signup.spec.js
Normal file
83
src/tests/user-signup.spec.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
import request from 'supertest'
|
||||
import prisma from '../../prisma/client.js'
|
||||
import app from '../app.js'
|
||||
|
||||
const mockUser = {
|
||||
username: 'username11',
|
||||
email: 'random@email.com',
|
||||
password: 'totallysafepass'
|
||||
}
|
||||
|
||||
describe('POST /user/create', () => {
|
||||
it('should respond with a 200 status code', async () => {
|
||||
const response = await request(app).post('/user/create').send(mockUser).expect(200)
|
||||
|
||||
expect(response.body).toHaveProperty('id')
|
||||
expect(response.body).toHaveProperty('username')
|
||||
expect(response.body).toHaveProperty('createdAt')
|
||||
})
|
||||
|
||||
it('should respond with a 400 status code if sent any invalid data', async () => {
|
||||
await request(app).post('/user/create').send({
|
||||
username: 'username12@',
|
||||
email: mockUser.email,
|
||||
password: mockUser.password
|
||||
}).expect(400)
|
||||
})
|
||||
|
||||
it('should respond with a 400 status code for an existing username', async () => {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: 'username12',
|
||||
email: 'user@email.com',
|
||||
password: 'reallystrongpass'
|
||||
}
|
||||
})
|
||||
|
||||
const response = await request(app).post('/user/create').send({
|
||||
username: 'username12',
|
||||
email: 'user1@email.com',
|
||||
password: 'reallystrongpass'
|
||||
}).expect(400)
|
||||
|
||||
expect(response.body).toHaveProperty('error')
|
||||
})
|
||||
|
||||
it('should respond with a 400 status code for an existing email', async () => {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: 'username13',
|
||||
email: 'user13@email.com',
|
||||
password: '1234'
|
||||
}
|
||||
})
|
||||
|
||||
const response = await request(app).post('/user/create').send({
|
||||
username: 'heythatscool',
|
||||
email: 'user13@email.com',
|
||||
password: '12345'
|
||||
}).expect(400)
|
||||
|
||||
expect(response.body).toHaveProperty('error')
|
||||
})
|
||||
|
||||
it('should respond with a 400 status code if receive an empty body', async () => {
|
||||
const response = await request(app).post('/user/create').send({}).expect(400)
|
||||
|
||||
expect(response.body).toHaveProperty('error')
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const usersToDelete = ['username11', 'username12', 'username13']
|
||||
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
username: {
|
||||
in: usersToDelete
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await prisma.$disconnect()
|
||||
})
|
Loading…
Reference in a new issue