Prettier lint

This commit is contained in:
Douglas Barone 2023-06-28 13:21:42 -04:00
parent 54948c1795
commit a425a59cc7
45 changed files with 423 additions and 394 deletions

2
.gitignore vendored
View File

@ -150,4 +150,4 @@ dev.db
db/
# Frontend build
public/
/public/

View File

@ -1,5 +1,6 @@
{
"tabWidth": 2,
"useTabs": false,
"htmlWhitespaceSensitivity": "ignore"
"htmlWhitespaceSensitivity": "ignore",
"semi": false
}

2
globals.d.ts vendored
View File

@ -1 +1 @@
declare module 'net-snmp'
declare module "net-snmp"

16
package-lock.json generated
View File

@ -31,6 +31,7 @@
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^20.3.1",
"nodemon": "^2.0.22",
"prettier": "^2.8.8",
"prisma": "^4.15.0",
"rimraf": "^5.0.1",
"ts-node": "^10.9.1",
@ -3927,6 +3928,21 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prisma": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz",

View File

@ -16,7 +16,8 @@
"dev": "concurrently --kill-others -n Server,Web \"npm run dev:server\" \"npm run dev:web\"",
"dev:server": "nodemon --ext js,ts,mts,mjs,json,prisma --exclude ./web/* --exec \"tsx src/index.ts\"",
"dev:web": "npm run dev -w web",
"devLegacy": "NODE_OPTIONS=\"--loader ts-node/esm\" node ./src/index.ts"
"devLegacy": "NODE_OPTIONS=\"--loader ts-node/esm\" node ./src/index.ts",
"prettier": "prettier --write \"src/**/*.{js,ts,tsx,json,md}\" \"web/**/*.{js,ts,tsx,json,md}\""
},
"prisma": {
"seed": "tsx prisma/seed.ts"
@ -29,6 +30,7 @@
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^20.3.1",
"nodemon": "^2.0.22",
"prettier": "^2.8.8",
"prisma": "^4.15.0",
"rimraf": "^5.0.1",
"ts-node": "^10.9.1",

View File

@ -1,116 +1,116 @@
import { PrismaClient } from '@prisma/client'
import { PrismaClient } from "@prisma/client"
export const prisma = new PrismaClient()
async function main() {
console.log('Seeding printers...')
console.log("Seeding printers...")
console.log('Seeding subnets...')
console.log("Seeding subnets...")
await prisma.network.createMany({
data: [
{
shortName: 'RT1',
name: 'Reitoria',
cidr: '10.0.0.0/21'
shortName: "RT1",
name: "Reitoria",
cidr: "10.0.0.0/21",
},
{
shortName: 'RT2',
name: 'Reitoria 2',
cidr: '10.1.0.0/21'
shortName: "RT2",
name: "Reitoria 2",
cidr: "10.1.0.0/21",
},
{
shortName: 'AQ',
name: 'Aquidauana',
cidr: '10.2.0.0/21'
shortName: "AQ",
name: "Aquidauana",
cidr: "10.2.0.0/21",
},
{
shortName: 'CG',
name: 'Campo Grande',
cidr: '10.3.0.0/21'
shortName: "CG",
name: "Campo Grande",
cidr: "10.3.0.0/21",
},
{
shortName: 'CB',
name: 'Corumbá',
cidr: '10.4.0.0/21'
shortName: "CB",
name: "Corumbá",
cidr: "10.4.0.0/21",
},
{
shortName: 'CX',
name: 'Coxim',
cidr: '10.5.0.0/21'
shortName: "CX",
name: "Coxim",
cidr: "10.5.0.0/21",
},
{
shortName: 'NA',
name: 'Nova Andradina',
cidr: '10.6.0.0/21'
shortName: "NA",
name: "Nova Andradina",
cidr: "10.6.0.0/21",
},
{
shortName: 'PP',
name: 'Ponta Porã',
cidr: '10.7.0.0/21'
shortName: "PP",
name: "Ponta Porã",
cidr: "10.7.0.0/21",
},
{
shortName: 'TL',
name: 'Três Lagoas',
cidr: '10.8.0.0/21'
shortName: "TL",
name: "Três Lagoas",
cidr: "10.8.0.0/21",
},
{
shortName: 'JD',
name: 'Jardim',
cidr: '10.9.0.0/21'
shortName: "JD",
name: "Jardim",
cidr: "10.9.0.0/21",
},
{
shortName: 'NV',
name: 'Naviraí',
cidr: '10.10.0.0/21'
shortName: "NV",
name: "Naviraí",
cidr: "10.10.0.0/21",
},
{
shortName: 'DR',
name: 'Dourados',
cidr: '10.11.0.0/21'
}
shortName: "DR",
name: "Dourados",
cidr: "10.11.0.0/21",
},
],
skipDuplicates: true
skipDuplicates: true,
})
await prisma.printer.createMany({
data: [
{
friendlyName: 'P04',
ip: '10.7.0.134',
model: 'ECOSYS M3655idn',
serialNumber: 'R4P1478461',
networkId: 8
friendlyName: "P04",
ip: "10.7.0.134",
model: "ECOSYS M3655idn",
serialNumber: "R4P1478461",
networkId: 8,
},
{
friendlyName: 'P05',
ip: '10.7.0.135',
model: 'ECOSYS M2040dn',
serialNumber: 'VR91483974',
networkId: 8
friendlyName: "P05",
ip: "10.7.0.135",
model: "ECOSYS M2040dn",
serialNumber: "VR91483974",
networkId: 8,
},
{
friendlyName: 'P06',
ip: '10.7.0.136',
model: 'ECOSYS M2040dn',
serialNumber: 'VR91586433',
networkId: 8
friendlyName: "P06",
ip: "10.7.0.136",
model: "ECOSYS M2040dn",
serialNumber: "VR91586433",
networkId: 8,
},
{
friendlyName: 'P07',
ip: '10.7.0.137',
model: 'ECOSYS M2040dn',
serialNumber: 'VR91586432',
networkId: 8
friendlyName: "P07",
ip: "10.7.0.137",
model: "ECOSYS M2040dn",
serialNumber: "VR91586432",
networkId: 8,
},
{
friendlyName: 'P08',
ip: '10.7.0.138',
model: 'ECOSYS P6235cdn',
serialNumber: 'RCG0304510',
networkId: 8
}
]
friendlyName: "P08",
ip: "10.7.0.138",
model: "ECOSYS P6235cdn",
serialNumber: "RCG0304510",
networkId: 8,
},
],
})
}
@ -118,7 +118,7 @@ main()
.then(() => {
prisma.$disconnect()
})
.catch(error => {
.catch((error) => {
console.error(error)
prisma.$disconnect()
process.exit(1)

View File

@ -1,7 +1,8 @@
import { Request, Response, Router } from 'express'
import { AuthenticationService } from '../services/AuthenticationService.js'
import { InvalidCredentialsError } from 'ldapts'
import { User } from '@prisma/client'
import { Request, Response, Router } from "express"
import { AuthenticationService } from "../services/AuthenticationService.js"
import { InvalidCredentialsError } from "ldapts"
import { User } from "@prisma/client"
import { authMiddleware } from "../middlewares/authMiddleware.js"
const router = Router()
@ -10,7 +11,7 @@ class LoginController {
const { username, password } = req.body
if (!username || !password) {
res.status(400).json({ error: 'Usuário e senha devem ser informados!' })
res.status(400).json({ error: "Usuário e senha devem ser informados!" })
return
}
@ -19,7 +20,7 @@ class LoginController {
res.json({ token })
} catch (error: any) {
if (error instanceof InvalidCredentialsError) {
res.status(401).json({ error: 'Usuário ou senha inválidos' })
res.status(401).json({ error: "Usuário ou senha inválidos" })
return
}
res.status(401).json({ error: error.message })
@ -31,7 +32,7 @@ class LoginController {
}
}
router.post('/', LoginController.login)
router.get('/me', LoginController.me)
router.post("/", LoginController.login)
router.get("/me", authMiddleware, LoginController.me)
export default router

View File

@ -1,9 +1,9 @@
import { Request, Response, Router } from 'express'
import { Request, Response, Router } from "express"
import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
import { prisma } from '../prisma.js'
import { hasRolesMiddleware } from "../middlewares/hasRolesMiddleware.js"
import { prisma } from "../prisma.js"
import { distributedCopy } from '../utils/distributedCopy.js'
import { distributedCopy } from "../utils/distributedCopy.js"
const router = Router()
@ -13,7 +13,7 @@ class PrinterController {
if (!campus) {
const printers = await prisma.printer.findMany({
include: { network: true }
include: { network: true },
})
return res.json(printers)
}
@ -21,10 +21,10 @@ class PrinterController {
const printers = await prisma.printer.findMany({
where: {
network: {
shortName: String(campus)
}
shortName: String(campus),
},
},
include: { network: true }
include: { network: true },
})
return res.json(printers)
@ -42,23 +42,23 @@ class PrinterController {
status: {
where: {
timestamp: {
gte
}
gte,
},
},
orderBy: {
timestamp: 'desc'
}
}
}
timestamp: "desc",
},
},
},
})
if (printer)
res.json({
...printer,
status: distributedCopy(printer.status, Number(take))
status: distributedCopy(printer.status, Number(take)),
})
else res.status(400).json({ error: 'Printer not found' })
else res.status(400).json({ error: "Printer not found" })
}
static async edit(req: Request, res: Response) {
@ -67,18 +67,18 @@ class PrinterController {
// Verify if printer exists
const printerExists = await prisma.printer.findUnique({
where: { id: Number(id) }
where: { id: Number(id) },
})
if (printerExists) {
const printer = await prisma.printer.update({
where: { id: Number(id) },
data: { friendlyName }
data: { friendlyName },
})
res.json(printer)
} else {
res.status(400).json({ error: 'Printer not found' })
res.status(400).json({ error: "Printer not found" })
}
}
@ -87,15 +87,15 @@ class PrinterController {
await prisma.printer.delete({ where: { id: Number(id) } })
res.json({ message: 'Printer deleted' })
res.json({ message: "Printer deleted" })
}
}
router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR']))
router.use(hasRolesMiddleware(["ADMIN", "INSPECTOR"]))
router.get('/', PrinterController.index)
router.get('/:id', PrinterController.show)
router.put('/:id', PrinterController.edit)
router.delete('/:id', PrinterController.delete)
router.get("/", PrinterController.index)
router.get("/:id", PrinterController.show)
router.put("/:id", PrinterController.edit)
router.delete("/:id", PrinterController.delete)
export default router

View File

@ -1,7 +1,7 @@
import { Router, Request, Response } from 'express'
import { Router, Request, Response } from "express"
import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
import { PrinterDiscoveryService } from '../services/PrinterDiscoveryService.js'
import { hasRolesMiddleware } from "../middlewares/hasRolesMiddleware.js"
import { PrinterDiscoveryService } from "../services/PrinterDiscoveryService.js"
const router = Router()
@ -12,8 +12,8 @@ class PrinterDiscoveryController {
}
}
router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR']))
router.use(hasRolesMiddleware(["ADMIN", "INSPECTOR"]))
router.post('/', PrinterDiscoveryController.discovery)
router.post("/", PrinterDiscoveryController.discovery)
export default router

View File

@ -1,7 +1,7 @@
import { Router, Request, Response } from 'express'
import { prisma } from '../prisma.js'
import { PrinterStatusService } from '../services/PrinterStatusService.js'
import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
import { Router, Request, Response } from "express"
import { prisma } from "../prisma.js"
import { PrinterStatusService } from "../services/PrinterStatusService.js"
import { hasRolesMiddleware } from "../middlewares/hasRolesMiddleware.js"
const router = Router()
@ -9,16 +9,16 @@ class PrinterStatusController {
static async update(req: Request, res: Response) {
const printers = await prisma.printer.findMany()
printers.forEach(async printer => {
printers.forEach(async (printer) => {
new PrinterStatusService(printer)
})
res.json({ message: 'Updating printer status' })
res.json({ message: "Updating printer status" })
}
}
router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR']))
router.use(hasRolesMiddleware(["ADMIN", "INSPECTOR"]))
router.post('/update', PrinterStatusController.update)
router.post("/update", PrinterStatusController.update)
export default router

View File

@ -1,7 +1,7 @@
import 'dotenv/config'
import "dotenv/config"
import { app } from './server.js'
import { jobs } from './jobs.js'
import { app } from "./server.js"
import { jobs } from "./jobs.js"
const PORT = 8000
@ -9,7 +9,7 @@ const PORT = 8000
app.listen(PORT, () => {
console.log(
`Running in ${
process.env.NODE_ENV == 'production' ? 'PRODUCTION' : 'DEVELOPMENT'
process.env.NODE_ENV == "production" ? "PRODUCTION" : "DEVELOPMENT"
} mode. \nServer listening http://127.0.0.1:${PORT}`
)
})

View File

@ -1,20 +1,20 @@
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
import Bree from 'bree'
import * as path from "node:path"
import { fileURLToPath } from "node:url"
import Bree from "bree"
export const jobs = new Bree({
root: path.join(path.dirname(fileURLToPath(import.meta.url)), 'jobs'),
defaultExtension: process.env.NODE_ENV == 'production' ? 'js' : 'ts',
root: path.join(path.dirname(fileURLToPath(import.meta.url)), "jobs"),
defaultExtension: process.env.NODE_ENV == "production" ? "js" : "ts",
logger: false,
jobs: [
{
name: 'updatePrinterStatus',
interval: process.env.UPDATE_INTERVAL || '1m',
timeout: 0
name: "updatePrinterStatus",
interval: process.env.UPDATE_INTERVAL || "1m",
timeout: 0,
},
{
name: 'discoverPrinters',
cron: '0 */12 * * *'
}
]
name: "discoverPrinters",
cron: "0 */12 * * *",
},
],
})

View File

@ -1,4 +1,4 @@
import { PrinterDiscoveryService } from '../services/PrinterDiscoveryService.js'
import { PrinterDiscoveryService } from "../services/PrinterDiscoveryService.js"
async function discoverPrinters() {
console.log(`Discovering printers ${new Date().toLocaleString()}`)

View File

@ -1,11 +1,11 @@
import { prisma } from '../prisma.js'
import { PrinterStatusService } from '../services/PrinterStatusService.js'
import { prisma } from "../prisma.js"
import { PrinterStatusService } from "../services/PrinterStatusService.js"
function updatePrinterStatus() {
console.log(`Updating printers status ${new Date().toLocaleString()}`)
prisma.printer.findMany().then(printers => {
printers.forEach(async printer => {
prisma.printer.findMany().then((printers) => {
printers.forEach(async (printer) => {
new PrinterStatusService(printer)
})
})

View File

@ -1,4 +1,4 @@
import { Response, NextFunction, Request } from 'express'
import { Response, NextFunction, Request } from "express"
export async function authMiddleware(
req: Request,
@ -7,7 +7,7 @@ export async function authMiddleware(
) {
try {
if (!res.locals.user) {
res.status(401).json({ error: 'Must be logged in' })
res.status(401).json({ error: "Must be logged in" })
return
}

View File

@ -1,14 +1,14 @@
import { Response, NextFunction, Request } from 'express'
import { Response, NextFunction, Request } from "express"
import { Role } from '@prisma/client'
import { Role } from "@prisma/client"
export function hasRolesMiddleware(roles: Role[]) {
return function (req: Request, res: Response, next: NextFunction) {
try {
const userRoles = res.locals.user?.roles
if (roles.some(role => userRoles?.includes(role))) next()
else res.status(401).json({ error: 'Not authorized!' })
if (roles.some((role) => userRoles?.includes(role))) next()
else res.status(401).json({ error: "Not authorized!" })
} catch (error: any) {
res.status(401).json({ error: error.message })
}

View File

@ -1,4 +1,4 @@
import { Response, NextFunction, Request } from 'express'
import { Response, NextFunction, Request } from "express"
export async function loggerMiddleware(
req: Request,
@ -6,7 +6,7 @@ export async function loggerMiddleware(
next: NextFunction
) {
console.log(
'Request:',
"Request:",
new Date().toLocaleString(),
req.method,
req.url,

View File

@ -1,13 +1,13 @@
import { NextFunction, Request, Response } from 'express'
import { AuthenticationService } from '../services/AuthenticationService.js'
import { NextFunction, Request, Response } from "express"
import { AuthenticationService } from "../services/AuthenticationService.js"
function getToken(req: Request) {
const authHeader = req.headers.authorization as string
if (!authHeader) return null
const [type, token] = authHeader.split(' ')
if (type !== 'Bearer') throw new Error('Expected a Bearer token')
const [type, token] = authHeader.split(" ")
if (type !== "Bearer") throw new Error("Expected a Bearer token")
return token
}
@ -24,7 +24,7 @@ export async function populateUserMiddleware(
const user = await AuthenticationService.jwtAuth(token)
res.locals.user = user
} catch (error: any) {
return res.status(401).json({ error: error.message })
res.locals.user = null
}
}

View File

@ -1,3 +1,3 @@
import { PrismaClient } from '@prisma/client'
import { PrismaClient } from "@prisma/client"
export const prisma = new PrismaClient()

View File

@ -32,77 +32,79 @@ export type PrinterObjectIds = {
const objectIds: PrinterObjectIds[] = [
{
model: 'ECOSYS M3655idn',
model: "ECOSYS M3655idn",
objectIds: {
model: '1.3.6.1.2.1.25.3.2.1.3.1',
serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1',
counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1',
location: '1.3.6.1.2.1.1.6.0',
model: "1.3.6.1.2.1.25.3.2.1.3.1",
serialNumber: "1.3.6.1.2.1.43.5.1.1.17.1",
counter: "1.3.6.1.4.1.1347.43.10.1.1.12.1.1",
location: "1.3.6.1.2.1.1.6.0",
toners: {
black: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.1',
max: '1.3.6.1.2.1.43.11.1.1.8.1.1',
model: '1.3.6.1.2.1.43.11.1.1.6.1.1'
}
}
}
current: "1.3.6.1.2.1.43.11.1.1.9.1.1",
max: "1.3.6.1.2.1.43.11.1.1.8.1.1",
model: "1.3.6.1.2.1.43.11.1.1.6.1.1",
},
},
},
},
{
model: 'ECOSYS P6235cdn',
model: "ECOSYS P6235cdn",
objectIds: {
model: '1.3.6.1.2.1.25.3.2.1.3.1',
serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1',
counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1',
location: '1.3.6.1.2.1.1.6.0',
model: "1.3.6.1.2.1.25.3.2.1.3.1",
serialNumber: "1.3.6.1.2.1.43.5.1.1.17.1",
counter: "1.3.6.1.4.1.1347.43.10.1.1.12.1.1",
location: "1.3.6.1.2.1.1.6.0",
toners: {
black: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.4',
max: '1.3.6.1.2.1.43.11.1.1.8.1.4',
model: '1.3.6.1.2.1.43.11.1.1.6.1.4'
current: "1.3.6.1.2.1.43.11.1.1.9.1.4",
max: "1.3.6.1.2.1.43.11.1.1.8.1.4",
model: "1.3.6.1.2.1.43.11.1.1.6.1.4",
},
cyan: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.1',
max: '1.3.6.1.2.1.43.11.1.1.8.1.1',
model: '1.3.6.1.2.1.43.11.1.1.6.1.1'
current: "1.3.6.1.2.1.43.11.1.1.9.1.1",
max: "1.3.6.1.2.1.43.11.1.1.8.1.1",
model: "1.3.6.1.2.1.43.11.1.1.6.1.1",
},
magenta: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.2',
max: '1.3.6.1.2.1.43.11.1.1.8.1.2',
model: '1.3.6.1.2.1.43.11.1.1.6.1.2'
current: "1.3.6.1.2.1.43.11.1.1.9.1.2",
max: "1.3.6.1.2.1.43.11.1.1.8.1.2",
model: "1.3.6.1.2.1.43.11.1.1.6.1.2",
},
yellow: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.3',
max: '1.3.6.1.2.1.43.11.1.1.8.1.3',
model: '1.3.6.1.2.1.43.11.1.1.6.1.3'
}
}
}
current: "1.3.6.1.2.1.43.11.1.1.9.1.3",
max: "1.3.6.1.2.1.43.11.1.1.8.1.3",
model: "1.3.6.1.2.1.43.11.1.1.6.1.3",
},
},
},
},
{
model: 'ECOSYS M2040dn',
model: "ECOSYS M2040dn",
objectIds: {
model: '1.3.6.1.2.1.25.3.2.1.3.1',
serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1',
counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1',
location: '1.3.6.1.2.1.1.6.0',
model: "1.3.6.1.2.1.25.3.2.1.3.1",
serialNumber: "1.3.6.1.2.1.43.5.1.1.17.1",
counter: "1.3.6.1.4.1.1347.43.10.1.1.12.1.1",
location: "1.3.6.1.2.1.1.6.0",
toners: {
black: {
current: '1.3.6.1.2.1.43.11.1.1.9.1.1',
max: '1.3.6.1.2.1.43.11.1.1.8.1.1',
model: '1.3.6.1.2.1.43.11.1.1.6.1.1'
}
}
}
}
current: "1.3.6.1.2.1.43.11.1.1.9.1.1",
max: "1.3.6.1.2.1.43.11.1.1.8.1.1",
model: "1.3.6.1.2.1.43.11.1.1.6.1.1",
},
},
},
},
]
export class objectIdsRepository {
private constructor() {}
static getPrinterObjectIds(model: string): PrinterObjectIds {
const printerObjectIds = objectIds.find(printer => printer.model === model)
const printerObjectIds = objectIds.find(
(printer) => printer.model === model
)
if (!printerObjectIds) throw new Error('Model not found')
if (!printerObjectIds) throw new Error("Model not found")
return printerObjectIds
}

View File

@ -1,32 +1,32 @@
import express, { Request, Response } from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import express, { Request, Response } from "express"
import bodyParser from "body-parser"
import cors from "cors"
import { populateUserMiddleware } from './middlewares/populateUserMiddleware.js'
import { authMiddleware } from './middlewares/authMiddleware.js'
import { loggerMiddleware } from './middlewares/loggerMiddleware.js'
import { populateUserMiddleware } from "./middlewares/populateUserMiddleware.js"
import { authMiddleware } from "./middlewares/authMiddleware.js"
import { loggerMiddleware } from "./middlewares/loggerMiddleware.js"
import LoginRouter from './controllers/LoginController.js'
import PrinterRouter from './controllers/PrinterController.js'
import PrinterStatusRouter from './controllers/PrinterStatusController.js'
import PrinterDiscoveryRouter from './controllers/PrinterDiscoveryController.js'
import LoginRouter from "./controllers/LoginController.js"
import PrinterRouter from "./controllers/PrinterController.js"
import PrinterStatusRouter from "./controllers/PrinterStatusController.js"
import PrinterDiscoveryRouter from "./controllers/PrinterDiscoveryController.js"
export const app = express()
app.use(cors())
app.use(loggerMiddleware)
app.use('/api', bodyParser.json())
app.use("/api", bodyParser.json())
app.use('/api', populateUserMiddleware)
app.use("/api", populateUserMiddleware)
app.use('/api/login', LoginRouter)
app.use('/api/printer', PrinterRouter)
app.use('/api/printer-status', PrinterStatusRouter)
app.use('/api/discovery', PrinterDiscoveryRouter)
app.use("/api/login", LoginRouter)
app.use("/api/printer", PrinterRouter)
app.use("/api/printer-status", PrinterStatusRouter)
app.use("/api/discovery", PrinterDiscoveryRouter)
app.use('/', express.static('public'))
app.use("/", express.static("public"))
app.get('*', (req, res) => {
res.sendFile('index.html', { root: './public' })
app.get("*", (req, res) => {
res.sendFile("index.html", { root: "./public" })
})

View File

@ -1,10 +1,10 @@
import jwt from 'jsonwebtoken'
import { prisma } from '../prisma.js'
import { LdapService } from './LdapService.js'
import { UserService } from './UserService.js'
import { User } from '@prisma/client'
import jwt from "jsonwebtoken"
import { prisma } from "../prisma.js"
import { LdapService } from "./LdapService.js"
import { UserService } from "./UserService.js"
import { User } from "@prisma/client"
const JWT_SECRET = process.env.JWT_SECRET || 'secret'
const JWT_SECRET = process.env.JWT_SECRET || "secret"
export class AuthenticationService {
private constructor() {}
@ -17,7 +17,7 @@ export class AuthenticationService {
await UserService.importUser(username)
const token = jwt.sign({ username }, JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN || '30 days'
expiresIn: process.env.JWT_EXPIRES_IN || "30 days",
})
return `Bearer ${token}`
@ -28,7 +28,7 @@ export class AuthenticationService {
const { username } = jwt.verify(token, JWT_SECRET) as { username: string }
const user = await prisma.user.findUnique({
where: { username }
where: { username },
})
if (!user) return await UserService.importUser(username)

View File

@ -1,9 +1,9 @@
import { Client } from 'ldapts'
import { Client } from "ldapts"
const DOMAIN = process.env.AD_DOMAIN || 'IFMS'
const DN = process.env.AD_DN || 'DC=ifms,DC=edu,DC=br'
const BIND_USER = process.env.AD_BIND_USER || ''
const BIND_PASSWD = process.env.AD_BIND_PASSWORD || ''
const DOMAIN = process.env.AD_DOMAIN || "IFMS"
const DN = process.env.AD_DN || "DC=ifms,DC=edu,DC=br"
const BIND_USER = process.env.AD_BIND_USER || ""
const BIND_PASSWD = process.env.AD_BIND_PASSWORD || ""
interface LdapClientInterface extends Client {
authenticate(username: string, password: string): Promise<void>
@ -26,7 +26,7 @@ export class LdapService extends Client implements LdapClientInterface {
if (LdapService.instance) return LdapService.instance
super({
url: `ldap://${process.env.AD_HOST}`
url: `ldap://${process.env.AD_HOST}`,
})
LdapService.instance = this
@ -50,21 +50,21 @@ export class LdapService extends Client implements LdapClientInterface {
async getUser(username: string): Promise<LdapUser> {
return await this.adminBondOperation(async () => {
const { searchEntries } = await this.search(DN, {
scope: 'sub',
scope: "sub",
filter: `(sAMAccountName=${username})`,
attributes: [
'mail',
'sAMAccountName',
'displayName',
'thumbnailPhoto',
'dn',
'extensionAttribute1'
"mail",
"sAMAccountName",
"displayName",
"thumbnailPhoto",
"dn",
"extensionAttribute1",
],
explicitBufferAttributes: ['thumbnailPhoto']
explicitBufferAttributes: ["thumbnailPhoto"],
})
if (!searchEntries.length)
throw new Error('User not found on LDAP server.')
throw new Error("User not found on LDAP server.")
const {
sAMAccountName,
@ -72,7 +72,7 @@ export class LdapService extends Client implements LdapClientInterface {
mail,
thumbnailPhoto,
dn,
extensionAttribute1
extensionAttribute1,
} = searchEntries[0]
const ldapUser: LdapUser = {
@ -81,9 +81,9 @@ export class LdapService extends Client implements LdapClientInterface {
mail: mail.toString(),
thumbnailPhoto: `data:image/png;base64,${Buffer.from(
thumbnailPhoto as Buffer
).toString('base64')}`,
).toString("base64")}`,
groups: await this.getGroupsForUser(dn.toString()),
campus: extensionAttribute1?.toString().split('-')[0] || '--'
campus: extensionAttribute1?.toString().split("-")[0] || "--",
}
return ldapUser
@ -92,14 +92,14 @@ export class LdapService extends Client implements LdapClientInterface {
async getGroupsForUser(dn: string) {
const { searchEntries } = await this.search(DN, {
scope: 'sub',
scope: "sub",
filter: `(member:1.2.840.113556.1.4.1941:=${dn})`,
attributes: ['cn']
attributes: ["cn"],
})
if (!searchEntries.length) throw new Error('User not found on LDAP server.')
if (!searchEntries.length) throw new Error("User not found on LDAP server.")
return searchEntries.map(entry => entry.cn.toString())
return searchEntries.map((entry) => entry.cn.toString())
}
async authenticate(username: string, password: string) {

View File

@ -1,17 +1,17 @@
import snmp from 'net-snmp'
import netmask from 'netmask'
import { PrinterStatusService } from './PrinterStatusService.js'
import { prisma } from '../prisma.js'
import { Printer } from '@prisma/client'
import snmp from "net-snmp"
import netmask from "netmask"
import { PrinterStatusService } from "./PrinterStatusService.js"
import { prisma } from "../prisma.js"
import { Printer } from "@prisma/client"
export class PrinterDiscoveryService {
private static async isPrinter(ip: string) {
if (ip == '10.7.0.51') return false
if (ip == "10.7.0.51") return false
return new Promise((resolve, reject) => {
const session = snmp.createSession(ip, 'public', { timeout: 1000 })
const session = snmp.createSession(ip, "public", { timeout: 1000 })
const CHECK_OID = '1.3.6.1.2.1.1.1.0'
const CHECK_STRING = 'KYOCERA Document Solutions Printing System'
const CHECK_OID = "1.3.6.1.2.1.1.1.0"
const CHECK_STRING = "KYOCERA Document Solutions Printing System"
session.get(
[CHECK_OID],
@ -40,18 +40,18 @@ export class PrinterDiscoveryService {
try {
const block = new netmask.Netmask(cdir)
const BLACK_LISTED_IPS = ['10.7.1.1', '10.7.0.51']
const BLACK_LISTED_IPS = ["10.7.1.1", "10.7.0.51"]
block.forEach(ip => {
block.forEach((ip) => {
if (!BLACK_LISTED_IPS.includes(ip)) blockIPs.push(ip)
})
} catch (err) {
throw new Error('Invalid IP CIDR')
throw new Error("Invalid IP CIDR")
}
try {
await Promise.allSettled(
blockIPs.map(async ip => {
blockIPs.map(async (ip) => {
try {
if (await PrinterDiscoveryService.isPrinter(ip)) {
printers.push(ip)
@ -76,7 +76,7 @@ export class PrinterDiscoveryService {
const discoveredPrintersIPs: string[] = []
for (const network of networks) {
console.log('Discovering printers for network', network.cidr)
console.log("Discovering printers for network", network.cidr)
try {
const discoveredPrintersIPsForNetwork =
@ -90,11 +90,11 @@ export class PrinterDiscoveryService {
const printers = await prisma.printer.findMany()
const newPrintersIPs = discoveredPrintersIPs.filter(
ip => !printers.find(printer => printer.ip === ip)
(ip) => !printers.find((printer) => printer.ip === ip)
)
await Promise.allSettled(
newPrintersIPs.map(async ip => {
newPrintersIPs.map(async (ip) => {
const model = await PrinterStatusService.getPrinterModel(ip)
const serialNumber =
await PrinterStatusService.getPrinterSerialNumber(ip)
@ -102,7 +102,7 @@ export class PrinterDiscoveryService {
const printer = await prisma.printer.upsert({
where: { serialNumber },
create: { ip, model, networkId: network.id, serialNumber },
update: { ip, model, networkId: network.id }
update: { ip, model, networkId: network.id },
})
new PrinterStatusService(printer)

View File

@ -1,10 +1,10 @@
import snmp from 'net-snmp'
import { Printer } from '@prisma/client'
import { prisma } from '../prisma.js'
import snmp from "net-snmp"
import { Printer } from "@prisma/client"
import { prisma } from "../prisma.js"
import {
objectIdsRepository,
PrinterObjectIds
} from '../repositories/ObjectIDRepository.js'
PrinterObjectIds,
} from "../repositories/ObjectIDRepository.js"
type VarbindString = {
oid: string
@ -43,10 +43,10 @@ export type PrinterInfo = {
export class PrinterStatusService {
constructor(private printer: Printer) {
this.getPrinterSnmpStatus().then(async printerStatus => {
this.getPrinterSnmpStatus().then(async (printerStatus) => {
const lastStatus = await prisma.printerStatus.findFirst({
where: { printerId: this.printer.id },
orderBy: { timestamp: 'desc' }
orderBy: { timestamp: "desc" },
})
if (
@ -58,7 +58,7 @@ export class PrinterStatusService {
) {
await prisma.printerStatus.update({
where: { id: lastStatus.id },
data: { timestamp: new Date() }
data: { timestamp: new Date() },
})
} else {
console.log(
@ -80,10 +80,10 @@ export class PrinterStatusService {
tonerBlackLevel: printerStatus.toners.black.level,
tonerCyanLevel: printerStatus.toners.cyan?.level,
tonerMagentaLevel: printerStatus.toners.magenta?.level,
tonerYellowLevel: printerStatus.toners.yellow?.level
}
}
}
tonerYellowLevel: printerStatus.toners.yellow?.level,
},
},
},
})
}
})
@ -94,7 +94,7 @@ export class PrinterStatusService {
function extractObjValues(obj: any) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
if (typeof obj[key] === "object") {
extractObjValues(obj[key])
} else {
const oID = obj[key]
@ -124,9 +124,9 @@ export class PrinterStatusService {
static getPrinterModel(ip: string): Promise<string> {
return new Promise((resolve, reject) => {
const snmpSession = snmp.createSession(ip, 'public')
const snmpSession = snmp.createSession(ip, "public")
snmpSession.get(
['1.3.6.1.2.1.25.3.2.1.3.1'],
["1.3.6.1.2.1.25.3.2.1.3.1"],
(error: any, varbinds: any) => {
if (error) {
reject(error)
@ -141,9 +141,9 @@ export class PrinterStatusService {
static getPrinterSerialNumber(ip: string): Promise<string> {
return new Promise((resolve, reject) => {
const snmpSession = snmp.createSession(ip, 'public')
const snmpSession = snmp.createSession(ip, "public")
snmpSession.get(
['1.3.6.1.2.1.43.5.1.1.17.1'],
["1.3.6.1.2.1.43.5.1.1.17.1"],
(error: any, varbinds: any) => {
if (error) {
reject(error)
@ -158,7 +158,7 @@ export class PrinterStatusService {
async getPrinterSnmpStatus(): Promise<PrinterInfo> {
return new Promise((resolve, reject) => {
const session = snmp.createSession(this.printer.ip, 'public')
const session = snmp.createSession(this.printer.ip, "public")
const oIDsArray = this.objectIdsArray()
@ -178,8 +178,8 @@ export class PrinterStatusService {
current: string | undefined,
max: string | undefined
) {
if (typeof current === 'undefined' || typeof max === 'undefined')
throw new Error('current or max is undefined')
if (typeof current === "undefined" || typeof max === "undefined")
throw new Error("current or max is undefined")
return Math.floor((+current! / +max!) * 100)
}
@ -191,56 +191,60 @@ export class PrinterStatusService {
objectIdsRepository.getPrinterObjectIds(this.printer.model)
const printerInfo: PrinterInfo = {
counter: Number(snmpInfo.find(x => x.oid === objectIds.counter)?.value),
location: snmpInfo.find(x => x.oid === objectIds.location)
counter: Number(snmpInfo.find((x) => x.oid === objectIds.counter)?.value),
location: snmpInfo.find((x) => x.oid === objectIds.location)
?.value as string,
toners: {
black: {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.black.current)?.value,
snmpInfo.find(x => x.oid === objectIds.toners.black.max)?.value
snmpInfo.find((x) => x.oid === objectIds.toners.black.current)
?.value,
snmpInfo.find((x) => x.oid === objectIds.toners.black.max)?.value
),
model: snmpInfo.find(x => x.oid === objectIds.toners.black.model)
?.value as string
model: snmpInfo.find((x) => x.oid === objectIds.toners.black.model)
?.value as string,
},
cyan: objectIds.toners.cyan
? {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.cyan?.current)
snmpInfo.find((x) => x.oid === objectIds.toners.cyan?.current)
?.value,
snmpInfo.find(x => x.oid === objectIds.toners.cyan?.max)?.value
snmpInfo.find((x) => x.oid === objectIds.toners.cyan?.max)
?.value
),
model: snmpInfo.find(x => x.oid === objectIds.toners.cyan?.model)
?.value as string
model: snmpInfo.find(
(x) => x.oid === objectIds.toners.cyan?.model
)?.value as string,
}
: undefined,
magenta: objectIds.toners.magenta
? {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.magenta?.current)
?.value,
snmpInfo.find(x => x.oid === objectIds.toners.magenta?.max)
snmpInfo.find(
(x) => x.oid === objectIds.toners.magenta?.current
)?.value,
snmpInfo.find((x) => x.oid === objectIds.toners.magenta?.max)
?.value
),
model: snmpInfo.find(
x => x.oid === objectIds.toners.magenta?.model
)?.value as string
(x) => x.oid === objectIds.toners.magenta?.model
)?.value as string,
}
: undefined,
yellow: objectIds.toners.yellow
? {
level: this.calcTonerLevelPercentage(
snmpInfo.find(x => x.oid === objectIds.toners.yellow?.current)
snmpInfo.find((x) => x.oid === objectIds.toners.yellow?.current)
?.value,
snmpInfo.find(x => x.oid === objectIds.toners.yellow?.max)
snmpInfo.find((x) => x.oid === objectIds.toners.yellow?.max)
?.value
),
model: snmpInfo.find(
x => x.oid === objectIds.toners.yellow?.model
)?.value as string
(x) => x.oid === objectIds.toners.yellow?.model
)?.value as string,
}
: undefined
}
: undefined,
},
}
return printerInfo

View File

@ -1,10 +1,10 @@
import { User } from '@prisma/client'
import { LdapService } from './LdapService.js'
import { prisma } from '../prisma.js'
import { User } from "@prisma/client"
import { LdapService } from "./LdapService.js"
import { prisma } from "../prisma.js"
const ADMIN_GROUP = process.env.ADMIN_GROUP || 'PP-SERTI'
const INSPECTOR_GROUP = process.env.INSPECTOR_GROUP || 'Inspectors'
const USER_GROUP = process.env.USER_GROUP || 'G_SERVIDORES'
const ADMIN_GROUP = process.env.ADMIN_GROUP || "PP-SERTI"
const INSPECTOR_GROUP = process.env.INSPECTOR_GROUP || "Inspectors"
const USER_GROUP = process.env.USER_GROUP || "G_SERVIDORES"
export class UserService {
static async importUser(username: string) {
@ -12,27 +12,27 @@ export class UserService {
const ldapUser = await ldap.getUser(username)
if (!ldapUser) throw new Error('User not found!')
if (!ldapUser) throw new Error("User not found!")
const user: Omit<User, 'id' | 'createdAt' | 'updatedAt'> = {
const user: Omit<User, "id" | "createdAt" | "updatedAt"> = {
username: ldapUser.username,
displayName: ldapUser.displayName,
mail: ldapUser.mail,
thumbnailPhoto: ldapUser.thumbnailPhoto,
campus: ldapUser.campus,
roles: []
roles: [],
}
ldapUser.groups?.forEach(group => {
if (group === USER_GROUP) user.roles?.push('USER')
if (group === ADMIN_GROUP) user.roles?.push('ADMIN')
if (group === INSPECTOR_GROUP) user.roles?.push('INSPECTOR')
ldapUser.groups?.forEach((group) => {
if (group === USER_GROUP) user.roles?.push("USER")
if (group === ADMIN_GROUP) user.roles?.push("ADMIN")
if (group === INSPECTOR_GROUP) user.roles?.push("INSPECTOR")
})
return await prisma.user.upsert({
where: { username: ldapUser.username },
update: user,
create: user
create: user,
})
}
}

View File

@ -4,11 +4,11 @@ module.exports = {
node: true,
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
],
rules: {
'vue/multi-word-component-names': 'off',
"vue/multi-word-component-names": "off",
},
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@ -1,27 +1,27 @@
const BASE_URL = process.env.BASE_URL || "http://localhost:8000/api/";
const BASE_URL = process.env.BASE_URL || "http://localhost:8000/api/"
export async function api<T>(endpoint: string, options: any): Promise<T> {
const token = localStorage.getItem("token");
const token = localStorage.getItem("token")
if (token) {
options.headers = {
...options.headers,
Authorization: token,
};
}
}
options.headers = {
...options.headers,
"Content-Type": "application/json",
};
const response = await fetch(BASE_URL + endpoint, options);
const json = await response.json();
if (response.ok) {
return json;
}
throw new Error(json.error);
const response = await fetch(BASE_URL + endpoint, options)
const json = await response.json()
if (response.ok) {
return json
}
throw new Error(json.error)
}

View File

@ -1,13 +1,13 @@
const LOCAL_STORAGE_KEY = "token";
const LOCAL_STORAGE_KEY = "token"
export function saveJwtToken(token: string) {
localStorage.setItem(LOCAL_STORAGE_KEY, token);
localStorage.setItem(LOCAL_STORAGE_KEY, token)
}
export function getJwtToken() {
return localStorage.getItem(LOCAL_STORAGE_KEY);
return localStorage.getItem(LOCAL_STORAGE_KEY)
}
export function removeJwtToken() {
localStorage.removeItem(LOCAL_STORAGE_KEY);
localStorage.removeItem(LOCAL_STORAGE_KEY)
}

View File

@ -5,16 +5,16 @@
*/
// Components
import App from './App.vue'
import App from "./App.vue"
// Composables
import { createApp } from 'vue'
import { createApp } from "vue"
// Plugins
import { registerPlugins } from '@/plugins'
import { registerPlugins } from "@/plugins"
const app = createApp(App)
registerPlugins(app)
app.mount('#app')
app.mount("#app")

View File

@ -5,18 +5,15 @@
*/
// Plugins
import { loadFonts } from './webfontloader'
import vuetify from './vuetify'
import pinia from '../store'
import router from '../router'
import { loadFonts } from "./webfontloader"
import vuetify from "./vuetify"
import pinia from "../store"
import router from "../router"
// Types
import type { App } from 'vue'
import type { App } from "vue"
export function registerPlugins (app: App) {
export function registerPlugins(app: App) {
loadFonts()
app
.use(vuetify)
.use(router)
.use(pinia)
app.use(vuetify).use(router).use(pinia)
}

View File

@ -5,11 +5,11 @@
*/
// Styles
import "@mdi/font/css/materialdesignicons.css";
import "vuetify/styles";
import "@mdi/font/css/materialdesignicons.css"
import "vuetify/styles"
// Composables
import { createVuetify } from "vuetify";
import { createVuetify } from "vuetify"
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
@ -23,4 +23,4 @@ export default createVuetify({
},
},
},
});
})

View File

@ -4,12 +4,14 @@
* webfontloader documentation: https://github.com/typekit/webfontloader
*/
export async function loadFonts () {
const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader')
export async function loadFonts() {
const webFontLoader = await import(
/* webpackChunkName: "webfontloader" */ "webfontloader"
)
webFontLoader.load({
google: {
families: ['Roboto:100,300,400,500,700,900&display=swap'],
families: ["Roboto:100,300,400,500,700,900&display=swap"],
},
})
}

View File

@ -1,5 +1,5 @@
// Composables
import { createRouter, createWebHistory } from "vue-router";
import { createRouter, createWebHistory } from "vue-router"
const routes = [
{
@ -36,11 +36,11 @@ const routes = [
},
],
},
];
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
})
export default router;
export default router

View File

@ -1,28 +1,28 @@
// Utilities
import { defineStore } from "pinia";
import { api } from "@/api";
import { useRouter } from "vue-router";
import { User } from "@prisma/client";
import { defineStore } from "pinia"
import { api } from "@/api"
import { useRouter } from "vue-router"
import { Printer, User } from "@prisma/client"
const router = useRouter()
export const useAppStore = defineStore("app", {
state: () => ({
me: null as User | null,
printers: [],
printers: [] as Printer[],
}),
actions: {
async fetchPrinters() {
this.printers = await api("printer", { method: "GET" });
this.printers = await api<Printer[]>("printer", { method: "GET" })
},
async fetchMe() {
const router = useRouter();
try {
this.me = await api<User>("login/me", { method: "GET" });
this.me = await api<User>("login/me", { method: "GET" })
} catch (error) {
router.push({ name: "Login" });
router.push({ name: "Login" })
}
},
},
});
})

View File

@ -1,4 +1,4 @@
// Utilities
import { createPinia } from 'pinia'
import { createPinia } from "pinia"
export default createPinia()

View File

@ -1,6 +1,5 @@
<template>
<div>
<h1>Home</h1>
<printer-img model="ECOSYS P6235cdn" />
</div>
</template>

View File

@ -62,10 +62,21 @@
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { api } from "@/api";
import { saveJwtToken } from "@/auth";
import { getJwtToken, saveJwtToken } from "@/auth";
import { useRouter } from "vue-router";
import { useAppStore } from "@/store/app";
const router = useRouter();
const token = getJwtToken();
const { fetchMe } = useAppStore();
if (token) {
fetchMe();
router.replace({ name: "Home" });
}
const username = ref<string>("");
const password = ref<string>("");
@ -74,10 +85,6 @@ const errors = reactive<string[]>([]);
const loading = ref(false);
const router = useRouter();
const { fetchMe } = useAppStore();
async function login() {
errors.splice(0, errors.length);

View File

@ -1,7 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
declare module "*.vue" {
import type { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -14,10 +14,8 @@
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": [
"src/*"
]
}
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }],

View File

@ -1,10 +1,10 @@
// Plugins
import vue from "@vitejs/plugin-vue";
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
import vue from "@vitejs/plugin-vue"
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify"
// Utilities
import { defineConfig } from "vite";
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite"
import { fileURLToPath, URL } from "node:url"
// https://vitejs.dev/config/
export default defineConfig({
@ -31,4 +31,4 @@ export default defineConfig({
server: {
port: 3000,
},
});
})