diff --git a/apiDocs/lstV2/Auth/Login.bru b/apiDocs/lstV2/Auth/Login.bru index ad90de2..71070ea 100644 --- a/apiDocs/lstV2/Auth/Login.bru +++ b/apiDocs/lstV2/Auth/Login.bru @@ -12,7 +12,7 @@ post { body:json { { - "username":"admin", + "username": "admin", "password": "password123" } } diff --git a/apiDocs/lstV2/Auth/Register.bru b/apiDocs/lstV2/Auth/Register.bru new file mode 100644 index 0000000..5eb4361 --- /dev/null +++ b/apiDocs/lstV2/Auth/Register.bru @@ -0,0 +1,20 @@ +meta { + name: Register + type: http + seq: 4 +} + +post { + url: http://localhost:3000/api/auth/register + body: json + auth: none +} + +body:json { + { + "username":"matthes02", + "email": "blake@alpla.com", + "password": "Vsd!134" + + } +} diff --git a/apiDocs/lstV2/api/Api Hit.bru b/apiDocs/lstV2/api/Api Hit.bru new file mode 100644 index 0000000..001ca59 --- /dev/null +++ b/apiDocs/lstV2/api/Api Hit.bru @@ -0,0 +1,18 @@ +meta { + name: Api Hit + type: http + seq: 1 +} + +post { + url: http://localhost:3000/api/hits + body: json + auth: none +} + +body:json { + { + "name": "something", + "arrayData": "{someghjdsfsd}" + } +} diff --git a/server/database/drizzle.config.ts b/drizzle.config.ts similarity index 51% rename from server/database/drizzle.config.ts rename to drizzle.config.ts index 97aa44a..78d5da9 100644 --- a/server/database/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,8 +1,10 @@ import {defineConfig} from "drizzle-kit"; +const database = process.env.DATABASE_URL || ""; export default defineConfig({ dialect: "postgresql", // 'mysql' | 'sqlite' | 'turso' - schema: "./schema", + schema: "./server/database/schema/", + out: "./server/database/migrations", dbCredentials: { - url: "postgresql://postgres:nova0511@localhost:5432/lst_db", + url: database, }, }); diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index f70e228..e62c3a7 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -50,7 +50,7 @@ function RouteComponent() { const onSubmitLogin = async (value: z.infer) => { // Do something with form data - + console.log(value); // first update the rememberMe incase it was selected if (value.rememberMe) { localStorage.setItem("rememberMe", value.rememberMe.toString()); @@ -69,17 +69,13 @@ function RouteComponent() { body: JSON.stringify({username: value.username, password: value.password}), }); - // if (!response.ok) { - // throw new Error("Invalid credentials"); - // } - const data = await response.json(); // Store token in localStorage // localStorage.setItem("auth_token", data.data.token); - setSession(data.data.user, data.data.token); - toast.success(`You are logged in as ${data.data.user.username}`); + setSession(data.user, data.token); + toast.success(`You are logged in as ${data.user.username}`); router.navigate({to: "/"}); } catch (err) { toast.error("Invalid credentials"); diff --git a/package.json b/package.json index 9e4fd33..184db1f 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "description": "", "main": "index.ts", "scripts": { - "dev": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'cd server && bun run dev' 'cd frontend && bun run dev'", - "dev:server": "bun --env-file .env --watch server/index.ts", - "dev:ocme": "bun --env-file .env --watch ocme/index.ts", - "dev:frontend": "cd frontend && bunx --bun vite", + "dev": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'bun --watch server/index.ts' 'cd frontend && bunx --bun vite'", + "dev:server": "bun --watch server/index.ts", + "dev:ocme": "bun --watch ocme/index.ts", + "dev:front": "cd frontend && bunx --bun vite", "build:server": "cd apps/server && bun build index.js --outdir ../../dist/server", "build:ocme": "rimraf dist/ocme && cd apps/ocme && bun build index.js --outdir ../../dist/ocme", "build:front": "cd frontend && rimraf frontend/dist && bun run build", @@ -15,7 +15,8 @@ "commit": "cz", "clean": "rimraf dist/server", "deploy": "standard-version --conventional-commits", - "ui:add": "cd frontend && bun shadcn add " + "ui:add": "cd frontend && bun shadcn add ", + "db:dev": "bun drizzle-kit generate && bun drizzle-kit migrate" }, "keywords": [], "author": "", @@ -23,7 +24,7 @@ "dependencies": { "@dotenvx/dotenvx": "^1.35.0", "@hono/zod-openapi": "^0.18.4", - "@scalar/hono-api-reference": "^0.5.174", + "@scalar/hono-api-reference": "^0.5.175", "@types/bun": "^1.2.2", "axios": "^1.7.9", "bcrypt": "^5.1.1", @@ -35,12 +36,17 @@ "hono": "^4.7.1", "http-proxy-middleware": "^3.0.3", "jsonwebtoken": "^9.0.2", + "pg": "^8.13.3", + "postgres": "^3.4.5", "zod": "^3.24.2" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/js-cookie": "^3.0.6", + "@types/pg": "^8.11.11", "concurrently": "^9.1.2", "cz-conventional-changelog": "^3.3.0", + "drizzle-kit": "^0.30.4", "rimraf": "^6.0.1", "standard-version": "^9.5.0", "typescript": "~5.7.3" diff --git a/server/database/.gitignore b/server/database/.gitignore deleted file mode 100644 index 9b1ee42..0000000 --- a/server/database/.gitignore +++ /dev/null @@ -1,175 +0,0 @@ -# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore - -# Logs - -logs -_.log -npm-debug.log_ -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Caches - -.cache - -# Diagnostic reports (https://nodejs.org/api/report.html) - -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# Runtime data - -pids -_.pid -_.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover - -lib-cov - -# Coverage directory used by tools like istanbul - -coverage -*.lcov - -# nyc test coverage - -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) - -.grunt - -# Bower dependency directory (https://bower.io/) - -bower_components - -# node-waf configuration - -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) - -build/Release - -# Dependency directories - -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) - -web_modules/ - -# TypeScript cache - -*.tsbuildinfo - -# Optional npm cache directory - -.npm - -# Optional eslint cache - -.eslintcache - -# Optional stylelint cache - -.stylelintcache - -# Microbundle cache - -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history - -.node_repl_history - -# Output of 'npm pack' - -*.tgz - -# Yarn Integrity file - -.yarn-integrity - -# dotenv environment variable files - -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) - -.parcel-cache - -# Next.js build output - -.next -out - -# Nuxt.js build / generate output - -.nuxt -dist - -# Gatsby files - -# Comment in the public line in if your project uses Gatsby and not Next.js - -# https://nextjs.org/blog/next-9-1#public-directory-support - -# public - -# vuepress build output - -.vuepress/dist - -# vuepress v2.x temp and cache directory - -.temp - -# Docusaurus cache and generated files - -.docusaurus - -# Serverless directories - -.serverless/ - -# FuseBox cache - -.fusebox/ - -# DynamoDB Local files - -.dynamodb/ - -# TernJS port file - -.tern-port - -# Stores VSCode versions used for testing VSCode extensions - -.vscode-test - -# yarn v2 - -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store diff --git a/server/database/README.md b/server/database/README.md deleted file mode 100644 index 2739490..0000000 --- a/server/database/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# database - -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run index.ts -``` - -This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/server/database/dbClient.ts b/server/database/dbClient.ts new file mode 100644 index 0000000..f4daad5 --- /dev/null +++ b/server/database/dbClient.ts @@ -0,0 +1,7 @@ +import {drizzle} from "drizzle-orm/postgres-js"; +import postgres from "postgres"; +const database = process.env.DATABASE_URL || ""; + +const queryClient = postgres(database); + +export const db = drizzle({client: queryClient}); diff --git a/server/database/drizzle/0000_stormy_thunderbolt.sql b/server/database/drizzle/0000_stormy_thunderbolt.sql deleted file mode 100644 index abd53fd..0000000 --- a/server/database/drizzle/0000_stormy_thunderbolt.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE "users" ( - "id" serial PRIMARY KEY NOT NULL, - "user_id" text NOT NULL, - "title" text NOT NULL, - "passwordToken" text NOT NULL, - "passwordTokenExpires" timestamp, - "active" boolean DEFAULT true NOT NULL, - "pingcode" numeric, - "add_Date" timestamp DEFAULT now(), - "add_User" text DEFAULT 'LST_System' NOT NULL, - "upd_date" timestamp DEFAULT now() -); diff --git a/server/database/drizzle/meta/_journal.json b/server/database/drizzle/meta/_journal.json deleted file mode 100644 index 0fd12dd..0000000 --- a/server/database/drizzle/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1739914245651, - "tag": "0000_stormy_thunderbolt", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/server/database/index.ts b/server/database/index.ts deleted file mode 100644 index c9d8c34..0000000 --- a/server/database/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {drizzle} from "drizzle-orm/postgres-js"; -import postgres from "postgres"; -import "../../load-env"; - -const queryClient = postgres("postgresql://postgres:nova0511@localhost:5432/lst_db"); - -export const db = drizzle(queryClient); diff --git a/server/database/migrations/0000_typical_frightful_four.sql b/server/database/migrations/0000_typical_frightful_four.sql new file mode 100644 index 0000000..9f25baa --- /dev/null +++ b/server/database/migrations/0000_typical_frightful_four.sql @@ -0,0 +1,15 @@ +CREATE TABLE "users" ( + "user_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "username" text NOT NULL, + "email" text NOT NULL, + "password" text NOT NULL, + "passwordToken" text, + "passwordTokenExpires" timestamp, + "active" boolean DEFAULT true NOT NULL, + "pingcode" numeric, + "lastLogin" timestamp DEFAULT now(), + "add_User" text DEFAULT 'LST_System' NOT NULL, + "add_Date" timestamp DEFAULT now(), + "upd_User" text DEFAULT 'LST_System' NOT NULL, + "upd_date" timestamp DEFAULT now() +); diff --git a/server/database/migrations/0001_sharp_pet_avengers.sql b/server/database/migrations/0001_sharp_pet_avengers.sql new file mode 100644 index 0000000..df74bc9 --- /dev/null +++ b/server/database/migrations/0001_sharp_pet_avengers.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX "username" ON "users" USING btree ("username"); \ No newline at end of file diff --git a/server/database/drizzle/meta/0000_snapshot.json b/server/database/migrations/meta/0000_snapshot.json similarity index 73% rename from server/database/drizzle/meta/0000_snapshot.json rename to server/database/migrations/meta/0000_snapshot.json index cc6198d..447785c 100644 --- a/server/database/drizzle/meta/0000_snapshot.json +++ b/server/database/migrations/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "d0e2effa-c6ac-4f81-b546-ef6b10037eca", + "id": "d1b4a8a6-caa3-4c45-a3a1-cdfc99ca7bea", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -8,20 +8,27 @@ "name": "users", "schema": "", "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, "user_id": { "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", "type": "text", "primaryKey": false, "notNull": true }, - "title": { - "name": "title", + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", "type": "text", "primaryKey": false, "notNull": true @@ -30,7 +37,7 @@ "name": "passwordToken", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "passwordTokenExpires": { "name": "passwordTokenExpires", @@ -51,8 +58,8 @@ "primaryKey": false, "notNull": false }, - "add_Date": { - "name": "add_Date", + "lastLogin": { + "name": "lastLogin", "type": "timestamp", "primaryKey": false, "notNull": false, @@ -65,6 +72,20 @@ "notNull": true, "default": "'LST_System'" }, + "add_Date": { + "name": "add_Date", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "upd_User": { + "name": "upd_User", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'LST_System'" + }, "upd_date": { "name": "upd_date", "type": "timestamp", diff --git a/server/database/migrations/meta/0001_snapshot.json b/server/database/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..5a57de9 --- /dev/null +++ b/server/database/migrations/meta/0001_snapshot.json @@ -0,0 +1,133 @@ +{ + "id": "d6c99236-0eea-49f1-817b-13139c0f42f5", + "prevId": "d1b4a8a6-caa3-4c45-a3a1-cdfc99ca7bea", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.users": { + "name": "users", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "passwordToken": { + "name": "passwordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "passwordTokenExpires": { + "name": "passwordTokenExpires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "pingcode": { + "name": "pingcode", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "lastLogin": { + "name": "lastLogin", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "add_User": { + "name": "add_User", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'LST_System'" + }, + "add_Date": { + "name": "add_Date", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "upd_User": { + "name": "upd_User", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'LST_System'" + }, + "upd_date": { + "name": "upd_date", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "username": { + "name": "username", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/database/migrations/meta/_journal.json b/server/database/migrations/meta/_journal.json new file mode 100644 index 0000000..132eb27 --- /dev/null +++ b/server/database/migrations/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1740160921910, + "tag": "0000_typical_frightful_four", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1740161259149, + "tag": "0001_sharp_pet_avengers", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/server/database/package.json b/server/database/package.json deleted file mode 100644 index e6f9fb6..0000000 --- a/server/database/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "database", - "module": "index.ts", - "type": "module", - "devDependencies": { - "@types/bun": "latest", - "@types/pg": "^8.11.11", - "drizzle-kit": "^0.30.4", - "tsx": "^4.19.2" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "dependencies": { - "drizzle-orm": "^0.39.3", - "drizzle-zod": "^0.7.0", - "pg": "^8.13.3", - "postgres": "^3.4.5" - } -} \ No newline at end of file diff --git a/server/database/schema/users.ts b/server/database/schema/users.ts index b2dfa03..3d0f0d7 100644 --- a/server/database/schema/users.ts +++ b/server/database/schema/users.ts @@ -1,27 +1,35 @@ -import {text, pgTable, serial, numeric, index, timestamp, boolean} from "drizzle-orm/pg-core"; +import {text, pgTable, numeric, index, timestamp, boolean, uuid, uniqueIndex} from "drizzle-orm/pg-core"; import {createInsertSchema, createSelectSchema} from "drizzle-zod"; import {z} from "zod"; -export const users = pgTable("users", { - user_id: serial("id").primaryKey(), - username: text("user_id").notNull(), - email: text("title").notNull(), - passwordToken: text("passwordToken").notNull(), - passwordTokenExpires: timestamp("passwordTokenExpires"), - acitve: boolean("active").default(true).notNull(), - pinCode: numeric("pingcode"), - lastLogin: timestamp("add_Date").defaultNow(), - add_User: text("add_User").default("LST_System").notNull(), - add_Date: timestamp("add_Date").defaultNow(), - upd_user: text("add_User").default("LST_System").notNull(), - upd_date: timestamp("upd_date").defaultNow(), -}); +export const users = pgTable( + "users", + { + user_id: uuid("user_id").defaultRandom().primaryKey(), + username: text("username").notNull(), + email: text("email").notNull(), + password: text("password").notNull(), + passwordToken: text("passwordToken"), + passwordTokenExpires: timestamp("passwordTokenExpires"), + acitve: boolean("active").default(true).notNull(), + pinCode: numeric("pingcode"), + lastLogin: timestamp("lastLogin").defaultNow(), + add_User: text("add_User").default("LST_System").notNull(), + add_Date: timestamp("add_Date").defaultNow(), + upd_user: text("upd_User").default("LST_System").notNull(), + upd_date: timestamp("upd_date").defaultNow(), + }, + (table) => [ + // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), + uniqueIndex("username").on(table.username), + ] +); // Schema for inserting a user - can be used to validate API requests export const insertUsersSchema = createInsertSchema(users, { username: z.string().min(3, {message: "Username must be at least 3 characters"}), email: z.string().email({message: "Invalid email"}), - passwordToken: z.string().min(8, {message: "Password must be at least 8 characters"}), + password: z.string().min(8, {message: "Password must be at least 8 characters"}), }); // Schema for selecting a Expenses - can be used to validate API responses export const selectExpensesSchema = createSelectSchema(users); diff --git a/server/database/tsconfig.json b/server/database/tsconfig.json deleted file mode 100644 index 238655f..0000000 --- a/server/database/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - // Enable latest features - "lib": ["ESNext", "DOM"], - "target": "ESNext", - "module": "ESNext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } -} diff --git a/server/package.json b/server/package.json deleted file mode 100644 index 0accc8e..0000000 --- a/server/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "lstv2-server", - "version": "1.0.0", - "description": "", - "private": true, - "scripts": { - "dev": "bun --env-file ../.env --watch ./index.ts", - "build": "bun build ./index.ts" - }, - "devDependencies": { - "typescript": "^5.7.3" - }, - "dependencies": { - "@scalar/hono-api-reference": "^0.5.174" - } -} diff --git a/server/src/app.ts b/server/src/app.ts index 7acae4c..e1e7923 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -6,7 +6,8 @@ import {OpenAPIHono} from "@hono/zod-openapi"; //routes import auth from "./services/auth/authService"; -import scalar from "./route/scalar"; +import scalar from "./services/general/route/scalar"; +import apiHits from "./services/general/route/apitHits"; // services import {ocmeService} from "./services/ocme/ocmeServer"; @@ -38,10 +39,10 @@ app.all("/ocme/*", async (c) => { return ocmeService(c); }); -const routes = [scalar, auth] as const; +const routes = [scalar, auth, apiHits] as const; routes.forEach((route) => { - app.route("/", route); + app.route("/api/", route); }); //app.basePath("/api/auth").route("/login", login).route("/session", session).route("/register", register); diff --git a/server/src/globalUtils/apitHits.ts b/server/src/globalUtils/apitHits.ts new file mode 100644 index 0000000..3fe624e --- /dev/null +++ b/server/src/globalUtils/apitHits.ts @@ -0,0 +1,39 @@ +import {z, ZodError} from "zod"; +import {Context} from "hono"; +// Define the request body schema +const requestSchema = z.object({ + ip: z.string().optional(), + endpoint: z.string(), + action: z.string().optional(), + stats: z.string().optional(), +}); + +type ApiHitData = z.infer; + +export const apiHit = async ( + c: Context, + data: unknown +): Promise<{success: boolean; data?: ApiHitData; errors?: any[]}> => { + // console.log(data); + try { + // Extract IP from request headers or connection info + const forwarded = c.req.header("host"); + + //console.log(forwarded); + // Validate the data + const validatedData = requestSchema.parse(data); + + // Proceed with the validated data + // console.log("Validated Data:", validatedData); + return {success: true, data: validatedData}; + } catch (error) { + // Explicitly check if the error is an instance of ZodError + if (error instanceof ZodError) { + // console.log({success: false, errors: error.errors}); + return {success: false, errors: error.errors}; + } + + // Catch other unexpected errors + return {success: false, errors: [{message: "An unknown error occurred"}]}; + } +}; diff --git a/server/src/services/auth/authService.ts b/server/src/services/auth/authService.ts index 897c4ec..30c6fe3 100644 --- a/server/src/services/auth/authService.ts +++ b/server/src/services/auth/authService.ts @@ -5,8 +5,8 @@ import register from "./routes/register"; import session from "./routes/session"; const app = new OpenAPIHono(); -app.route("api/auth/login", login); -app.route("api/auth//register", register); -app.route("api/auth/session", session); +app.route("auth/login", login); +app.route("auth/register", register); +app.route("auth/session", session); export default app; diff --git a/server/src/services/auth/routes/login.ts b/server/src/services/auth/routes/login.ts index 25c049e..0a42154 100644 --- a/server/src/services/auth/routes/login.ts +++ b/server/src/services/auth/routes/login.ts @@ -5,38 +5,38 @@ const app = new OpenAPIHono(); const UserSchema = z .object({ - username: z.string().min(3).openapi({example: "smith002"}), + username: z.string().optional().openapi({example: "smith002"}), + //email: z.string().optional().openapi({example: "s.smith@example.com"}), password: z.string().openapi({example: "password123"}), }) .openapi("User"); -// Define the response schema for the login endpoint -const LoginResponseSchema = z -.object({ - message: z.string().openapi({example: "Login successful"}), - user: z.object({ - username: z.string().openapi({example: "smith002"}), - // Add other user fields as needed - }), -}) -.openapi("LoginResponse"); - const route = createRoute({ tags: ["Auth"], summary: "Login as user", description: "Login as a user to get a JWT token", method: "post", path: "/", - request: {body: {content: {"application/json": {schema: UserSchema}}}}, + request: { + body: { + content: { + "application/json": {schema: UserSchema}, + }, + }, + }, responses: { 200: { content: { "application/json": { - schema: LoginResponseSchema, + schema: z.object({ + success: z.boolean().openapi({example: true}), + message: z.string().openapi({example: "Logged in"}), + }), }, }, - description: "Login successful", + description: "Response message", }, + 400: { content: { "application/json": { @@ -52,37 +52,38 @@ const route = createRoute({ content: { "application/json": { schema: z.object({ - message: z.string().openapi({example: "Invalid credentials"}), + success: z.boolean().openapi({example: false}), + message: z.string().openapi({example: "Username and password required"}), }), }, }, - description: "Unauthorized", + description: "Bad request", }, }, }); app.openapi(route, async (c) => { - let body: {username: string; password: string}; + const {username, password, email} = await c.req.json(); - console.log(`Trying to login`); - try { - body = await c.req.json(); - } catch (error) { - return c.json({success: false, message: "Username and password required"}, 400); + if (!username || !password || !email) { + return c.json( + { + success: false, + message: "Username and password are required", + }, + 400 + ); } - if (!body?.username || !body?.password) { - return c.json({success: false, message: "Username and password required"}, 400); - } try { - const {token, user} = login(body.username, body.password); + const {token, user} = login(username.toLowerCase(), password); // Set the JWT as an HTTP-only cookie - // c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Path=/; SameSite=None; Max-Age=3600`); + //c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Secure; Path=/; SameSite=None; Max-Age=3600`); - return c.json({message: "Login successful", data: {token, user}}); + return c.json({success: true, message: "Login successful", user, token}, 200); } catch (err) { - return c.json({message: err instanceof Error ? err.message : "Invalid credentials"}, 401); + return c.json({success: false, message: "Incorrect Credentials"}, 401); } }); diff --git a/server/src/services/auth/routes/register.ts b/server/src/services/auth/routes/register.ts index 8f2ec3d..79971c2 100644 --- a/server/src/services/auth/routes/register.ts +++ b/server/src/services/auth/routes/register.ts @@ -1,14 +1,31 @@ import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; +import {db} from "../../../../database/dbClient"; +import {users} from "../../../../database/schema/users"; +import {apiHit} from "../../../globalUtils/apitHits"; +import {createPassword} from "../utils/createPassword"; +import {eq} from "drizzle-orm"; const app = new OpenAPIHono(); -const UserSchema = z -.object({ - id: z.string().openapi({example: "123"}), - name: z.string().min(3).openapi({example: "John Doe"}), - age: z.number().openapi({example: 42}), -}) -.openapi("User"); +const UserSchema = z.object({ + username: z + .string() + .regex(/^[a-zA-Z0-9_]{3,30}$/) + .openapi({example: "smith034"}), + email: z.string().email().openapi({example: "smith@example.com"}), + password: z + .string() + .min(6, {message: "Passwords must be longer than 3 characters"}) + .regex(/[A-Z]/, {message: "Password must contain at least one uppercase letter"}) + .regex(/[\W_]/, {message: "Password must contain at least one special character"}) + .openapi({example: "Password1!"}), +}); + +type User = z.infer; + +const responseSchema = z.object({ + message: z.string().optional().openapi({example: "User Created"}), +}); app.openapi( createRoute({ @@ -16,17 +33,88 @@ app.openapi( summary: "Register a new user", method: "post", path: "/", - request: {params: UserSchema}, + request: { + body: { + content: { + "application/json": {schema: UserSchema}, + }, + }, + }, responses: { 200: { - content: {"application/json": {schema: UserSchema}}, + content: {"application/json": {schema: responseSchema}}, + description: "Retrieve the user", + }, + 400: { + content: { + "application/json": { + schema: z.object({ + success: z.boolean().openapi({example: false}), + message: z.string().openapi({example: "Invalid credentials passed"}), + }), + }, + }, description: "Retrieve the user", }, }, }), - (c) => { - const {id} = c.req.valid("param"); - return c.json({id, age: 20, name: "Ultra-man"}); + async (c) => { + // apit hit + apiHit(c, {endpoint: "api/auth/register"}); + let {username, email, password} = await c.req.json(); + + if (!username || !email || !password) { + return c.json({success: false, message: "Credentials missing"}, 400); + } + + // some usernames that should be ignored + const badActors = ["admin", "root"]; + if (badActors.includes(username)) { + return c.json( + { + success: false, + message: `${username} is not a valid name to be registerd please try again`, + }, + 400 + ); + } + + // make sure the user dose not already exist in the system + const userCheck = await db.select().from(users).where(eq(users.username, username)); + + if (userCheck.length === 1) { + return c.json( + { + success: false, + message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`, + }, + 400 + ); + } + + // make sure we only send over a username that is all lowercase + username = username.toLowerCase(); + + // get the good kinda password + password = await createPassword(password); + + try { + const user = await db + .insert(users) + .values({username, email, password}) + .returning({user: users.username, email: users.email}); + + return c.json({message: "User Registered", user}, 200); + } catch (error) { + console.log(error); + return c.json( + { + success: false, + message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`, + }, + 400 + ); + } } ); diff --git a/server/src/services/auth/lib/createPassword.ts b/server/src/services/auth/utils/createPassword.ts similarity index 55% rename from server/src/services/auth/lib/createPassword.ts rename to server/src/services/auth/utils/createPassword.ts index a529c8e..3f2fd75 100644 --- a/server/src/services/auth/lib/createPassword.ts +++ b/server/src/services/auth/utils/createPassword.ts @@ -1,14 +1,14 @@ import bcrypt from "bcrypt"; -export const passwordUpdate = (password: string) => { +export const createPassword = async (password: string) => { // encypt password - let pass: string = process.env.SECRET; - let salt: string = process.env.SALTING; + let pass: string | undefined = process.env.SECRET; + let salt: string | undefined = process.env.SALTING; if (!pass || !salt) { pass = "error"; } else { - pass = bcrypt.hashSync(process.env.SECRET + password, parseInt(process.env.SALTING)); + pass = bcrypt.hashSync(process.env.SECRET + password, parseInt(salt)); pass = btoa(pass); } diff --git a/server/src/route/apiDoc.ts b/server/src/services/general/route/apiDoc.ts similarity index 92% rename from server/src/route/apiDoc.ts rename to server/src/services/general/route/apiDoc.ts index e72bcd0..923990d 100644 --- a/server/src/route/apiDoc.ts +++ b/server/src/services/general/route/apiDoc.ts @@ -2,7 +2,7 @@ import {OpenAPIHono} from "@hono/zod-openapi"; const app = new OpenAPIHono(); // the doc endpoint -app.doc("/api", { +app.doc("/", { openapi: "3.0.0", info: { version: "1.0.0", diff --git a/server/src/services/general/route/apitHits.ts b/server/src/services/general/route/apitHits.ts new file mode 100644 index 0000000..3d45a80 --- /dev/null +++ b/server/src/services/general/route/apitHits.ts @@ -0,0 +1,53 @@ +import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; +import {apiHit} from "../../../globalUtils/apitHits"; + +const app = new OpenAPIHono(); + +// Define the request body schema +const requestSchema = z.object({ + ip: z.string().optional(), + endpoint: z.string().optional(), + action: z.string().optional(), + stats: z.string().optional(), +}); + +// Define the response schema +const responseSchema = z.object({ + message: z.string(), +}); + +app.openapi( + createRoute({ + tags: ["api"], + summary: "Tracks the API posts and how often", + method: "post", + path: "/hits", + request: { + body: { + content: { + "application/json": {schema: requestSchema}, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": {schema: responseSchema}, + }, + description: "Response message", + }, + }, + }), + async (c) => { + const data = await c.req.json(); + + apiHit(data); + + // Return response with the received data + return c.json({ + message: `Received name: ${data.name}, arrayData: ${data.arrayData}`, + }); + } +); + +export default app; diff --git a/server/src/route/scalar.ts b/server/src/services/general/route/scalar.ts similarity index 96% rename from server/src/route/scalar.ts rename to server/src/services/general/route/scalar.ts index d45fd51..8c403ee 100644 --- a/server/src/route/scalar.ts +++ b/server/src/services/general/route/scalar.ts @@ -4,7 +4,7 @@ import {apiReference} from "@scalar/hono-api-reference"; const app = new OpenAPIHono(); app.get( - "/api/docs", + "/docs", apiReference({ theme: "kepler", layout: "classic", @@ -56,7 +56,7 @@ app.get( description: "Production", }, { - url: "http://localhost:4000", + url: "http://localhost:3000", description: "dev server", }, ], diff --git a/tsconfig.json b/tsconfig.json index f1152c3..77d0c7e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,5 @@ ], "outDir": "dist" }, - "include": ["./server/src", "./server/index.ts"] + "include": ["./server/src", "./server/index.ts", "./server/database"] }