Initial project structure and core files
Add base project files including environment example, license, README, .gitignore, error classes, ESLint config, database modules, texture assets, repositories, routes, schemas, services, and server entry point. This establishes the foundational structure for a Yggdrasil-compatible REST API with modular error handling, database setup, and route organization.
This commit is contained in:
24
errors/DefaultError.js
Normal file
24
errors/DefaultError.js
Normal file
@@ -0,0 +1,24 @@
|
||||
class DefaultError extends Error {
|
||||
constructor(code, message, error) {
|
||||
super(message)
|
||||
this.code = code
|
||||
this.error = error
|
||||
this.message = message || "Internal Server Error"
|
||||
this.isOperational = true
|
||||
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const response = {
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
}
|
||||
if (this.error) {
|
||||
response.error = this.error
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DefaultError
|
||||
43
errors/ServiceError.js
Normal file
43
errors/ServiceError.js
Normal file
@@ -0,0 +1,43 @@
|
||||
class ServiceError extends Error {
|
||||
constructor(code, path, error, errorMessage, details = null) {
|
||||
super(errorMessage || error || "Accounts API Error")
|
||||
this.code = code
|
||||
this.path = path
|
||||
this.errorType = error
|
||||
this.errorMessage = errorMessage
|
||||
this.developerMessage = errorMessage
|
||||
this.details = details
|
||||
this.isOperational = true
|
||||
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const response = {}
|
||||
|
||||
if (this.path && this.path.trim() !== "") {
|
||||
response.path = this.path.replace(/:(\w+)/g, "<$1>")
|
||||
}
|
||||
|
||||
if (this.errorType && this.errorType.trim() !== "") {
|
||||
response.error = this.errorType
|
||||
response.errorType = this.errorType
|
||||
}
|
||||
|
||||
if (this.details) {
|
||||
response.details = this.details
|
||||
}
|
||||
|
||||
if (this.errorMessage && this.errorMessage.trim() !== "") {
|
||||
response.errorMessage = this.errorMessage
|
||||
}
|
||||
|
||||
if (this.developerMessage && this.developerMessage.trim() !== "") {
|
||||
response.developerMessage = this.developerMessage
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServiceError
|
||||
24
errors/SessionError.js
Normal file
24
errors/SessionError.js
Normal file
@@ -0,0 +1,24 @@
|
||||
class SessionError extends Error {
|
||||
constructor(statusCode, error, errorMessage, path) {
|
||||
super(errorMessage)
|
||||
this.path = path
|
||||
this.error = error
|
||||
this.statusCode = statusCode
|
||||
this.errorMessage = errorMessage
|
||||
this.isOperational = true
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const response = {
|
||||
path: this.path,
|
||||
errorMessage: this.errorMessage
|
||||
}
|
||||
if (this.error != undefined) {
|
||||
response.error = this.error
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SessionError
|
||||
119
errors/ValidationError.js
Normal file
119
errors/ValidationError.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const DefaultError = require("./DefaultError")
|
||||
const YggdrasilError = require("./YggdrasilError")
|
||||
const SessionError = require("./SessionError")
|
||||
const ServiceError = require("./ServiceError")
|
||||
const logger = require("../modules/logger")
|
||||
|
||||
class ValidationError extends DefaultError {
|
||||
constructor(result, config = {}, context = {}) {
|
||||
let formattedErrors = []
|
||||
if (result && result.error && Array.isArray(result.error.issues)) {
|
||||
formattedErrors = result.error.issues.flatMap(issue => {
|
||||
if (issue.code === "unrecognized_keys") {
|
||||
return issue.keys.map(key => ({
|
||||
field: [...issue.path, key].join("."),
|
||||
message: "Field not allowed"
|
||||
}))
|
||||
}
|
||||
|
||||
if (issue.code === "invalid_union") {
|
||||
return {
|
||||
field: issue.path.join("."),
|
||||
message: "Invalid input format (union mismatch)"
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
field: issue.path.join("."),
|
||||
message: issue.message
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (result instanceof Error) {
|
||||
formattedErrors = [{
|
||||
field: "global",
|
||||
message: result.message
|
||||
}]
|
||||
}
|
||||
else if (typeof result === "string") {
|
||||
formattedErrors = [{
|
||||
field: "global",
|
||||
message: result
|
||||
}]
|
||||
}
|
||||
else {
|
||||
formattedErrors = [{
|
||||
field: "unknown",
|
||||
message: "Unknown validation error"
|
||||
}]
|
||||
}
|
||||
|
||||
const message = config.message || "Validation failed"
|
||||
const statusCode = config.code || 400
|
||||
|
||||
super(statusCode, message, { errors: formattedErrors })
|
||||
|
||||
this.config = config
|
||||
this.formattedErrors = formattedErrors
|
||||
this.context = context
|
||||
|
||||
this.logError()
|
||||
}
|
||||
|
||||
logError() {
|
||||
const { method, path, ip } = this.context
|
||||
if (method && path) {
|
||||
logger.warn(
|
||||
`Validation failed for ${method} ${path} (${this.config.errorFormat || "Standard"}) ` +
|
||||
`<IP:${ip || "Unknown"}>`,
|
||||
["WEB", "yellow"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
if (this.config.errorFormat === "YggdrasilError") {
|
||||
const err = new YggdrasilError(
|
||||
this.code,
|
||||
this.config.errorName || "IllegalArgumentException",
|
||||
this.message,
|
||||
JSON.stringify(this.formattedErrors)
|
||||
)
|
||||
return err.serialize()
|
||||
}
|
||||
|
||||
if (this.config.errorFormat === "SessionError") {
|
||||
const err = new SessionError(
|
||||
this.code,
|
||||
this.config.errorName || "Forbidden",
|
||||
this.message,
|
||||
this.context.path || ""
|
||||
)
|
||||
return err.serialize()
|
||||
}
|
||||
|
||||
if (this.config.errorFormat === "ServiceError") {
|
||||
const err = new ServiceError(
|
||||
this.code,
|
||||
this.context.path || "",
|
||||
this.config.errorName || "ValidationException",
|
||||
this.message,
|
||||
this.formattedErrors
|
||||
)
|
||||
return err.serialize()
|
||||
}
|
||||
const response = {
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
errors: this.formattedErrors
|
||||
}
|
||||
|
||||
if (this.cause) {
|
||||
response.cause = this.cause
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ValidationError
|
||||
26
errors/YggdrasilError.js
Normal file
26
errors/YggdrasilError.js
Normal file
@@ -0,0 +1,26 @@
|
||||
class YggdrasilError extends Error {
|
||||
constructor(statusCode, error, errorMessage, cause) {
|
||||
super(errorMessage)
|
||||
this.statusCode = statusCode
|
||||
|
||||
this.error = error
|
||||
this.errorMessage = errorMessage
|
||||
this.cause = cause
|
||||
|
||||
this.isOperational = true
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const response = {
|
||||
error: this.error,
|
||||
errorMessage: this.errorMessage
|
||||
}
|
||||
if (this.cause) {
|
||||
response.cause = this.cause
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = YggdrasilError
|
||||
13
errors/errors.js
Normal file
13
errors/errors.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const DefaultError = require("./DefaultError")
|
||||
const ServiceError = require("./ServiceError")
|
||||
const SessionError = require("./SessionError")
|
||||
const YggdrasilError = require("./YggdrasilError")
|
||||
const ValidationError = require("./ValidationError")
|
||||
|
||||
module.exports = {
|
||||
DefaultError,
|
||||
SessionError,
|
||||
ServiceError,
|
||||
YggdrasilError,
|
||||
ValidationError
|
||||
}
|
||||
Reference in New Issue
Block a user