feat: integrate Drizzle ORM and SQLite for authentication
This commit is contained in:
12
drizzle.config.ts
Normal file
12
drizzle.config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
schema: './server/db/schema.ts',
|
||||
out: './server/db/migrations',
|
||||
dialect: 'sqlite',
|
||||
dbCredentials: {
|
||||
url: 'file:./server/db/local.db',
|
||||
},
|
||||
verbose: true,
|
||||
strict: true,
|
||||
});
|
@ -4,5 +4,8 @@ export default defineNuxtConfig({
|
||||
devtools: { enabled: true },
|
||||
modules: [
|
||||
'@nuxtjs/tailwindcss'
|
||||
]
|
||||
],
|
||||
runtimeConfig: {
|
||||
adminPassword: '', // NUXT_ADMIN_PASSWORD
|
||||
}
|
||||
})
|
||||
|
@ -10,11 +10,16 @@
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.9",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"nuxt": "^3.17.5",
|
||||
"vue": "^3.5.16",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/tailwindcss": "7.0.0-beta.0"
|
||||
"@nuxtjs/tailwindcss": "7.0.0-beta.0",
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"drizzle-kit": "^0.31.1"
|
||||
}
|
||||
}
|
||||
|
611
pnpm-lock.yaml
generated
611
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,21 +1,23 @@
|
||||
import { defineEventHandler, readBody, setResponseStatus } from 'h3';
|
||||
import { defineEventHandler, readBody, setResponseStatus, useRuntimeConfig } from 'h3';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { password } = body;
|
||||
const config = useRuntimeConfig(event);
|
||||
|
||||
if (!password) {
|
||||
setResponseStatus(event, 400);
|
||||
return { message: '请填写密码' };
|
||||
}
|
||||
|
||||
// TODO: Replace with a more secure admin authentication method
|
||||
if (password === 'adminpassword') {
|
||||
return {
|
||||
message: '管理员登录成功!',
|
||||
};
|
||||
} else {
|
||||
const adminPassword = config.adminPassword;
|
||||
|
||||
if (!adminPassword || password !== adminPassword) {
|
||||
setResponseStatus(event, 401);
|
||||
return { message: '密码错误' };
|
||||
}
|
||||
|
||||
return {
|
||||
message: '管理员登录成功!',
|
||||
};
|
||||
});
|
@ -1,4 +1,7 @@
|
||||
import { defineEventHandler, readBody, setResponseStatus } from 'h3';
|
||||
import { db, customers } from '~/server/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
@ -9,14 +12,31 @@ export default defineEventHandler(async (event) => {
|
||||
return { message: '请填写手机号和密码' };
|
||||
}
|
||||
|
||||
// TODO: Replace with database user lookup and password verification
|
||||
if (contact === '1234567890' && password === 'password') {
|
||||
return {
|
||||
message: '登录成功!',
|
||||
customerId: 'dummy-customer-id-123',
|
||||
};
|
||||
} else {
|
||||
try {
|
||||
const user = await db.query.customers.findFirst({
|
||||
where: eq(customers.contact, contact),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
setResponseStatus(event, 401);
|
||||
return { message: '手机号或密码错误' };
|
||||
}
|
||||
|
||||
const isPasswordValid = bcrypt.compareSync(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
setResponseStatus(event, 401);
|
||||
return { message: '手机号或密码错误' };
|
||||
}
|
||||
|
||||
return {
|
||||
message: '登录成功!',
|
||||
customerId: user.id,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
setResponseStatus(event, 500);
|
||||
return { message: '登录失败,请稍后重试' };
|
||||
}
|
||||
});
|
@ -1,21 +1,43 @@
|
||||
import { defineEventHandler, readBody, setResponseStatus } from 'h3';
|
||||
import { db, customers } from '~/server/db';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
|
||||
const { name, gender, contact, idCard, password } = body;
|
||||
|
||||
if (!name || !gender || !contact || !idCard || !password) {
|
||||
setResponseStatus(event, 400);
|
||||
return {
|
||||
message: '请填写完整信息',
|
||||
};
|
||||
return { message: '请填写完整信息' };
|
||||
}
|
||||
|
||||
// TODO: Add database logic to save the user
|
||||
console.log('Registering new user:', { name, gender, contact, idCard });
|
||||
try {
|
||||
const hashedPassword = bcrypt.hashSync(password, 10);
|
||||
|
||||
return {
|
||||
message: '注册成功!',
|
||||
};
|
||||
await db.insert(customers).values({
|
||||
name,
|
||||
gender,
|
||||
contact,
|
||||
idCard,
|
||||
password: hashedPassword,
|
||||
});
|
||||
|
||||
return { message: '注册成功!' };
|
||||
|
||||
} catch (error: any) {
|
||||
// Check for unique constraint violation
|
||||
if (error.message?.includes('UNIQUE constraint failed')) {
|
||||
setResponseStatus(event, 409); // Conflict
|
||||
if (error.message.includes('customers.contact')) {
|
||||
return { message: '该手机号已被注册' };
|
||||
}
|
||||
if (error.message.includes('customers.id_card')) {
|
||||
return { message: '该身份证号已被注册' };
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Registration error:', error);
|
||||
setResponseStatus(event, 500);
|
||||
return { message: '注册失败,请稍后重试' };
|
||||
}
|
||||
});
|
10
server/db/index.ts
Normal file
10
server/db/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { createClient } from '@libsql/client';
|
||||
import * as schema from './schema';
|
||||
|
||||
const client = createClient({
|
||||
url: 'file:./server/db/local.db',
|
||||
});
|
||||
|
||||
export const db = drizzle(client, { schema });
|
||||
export * from './schema';
|
BIN
server/db/local.db
Normal file
BIN
server/db/local.db
Normal file
Binary file not shown.
35
server/db/migrations/0000_gifted_agent_brand.sql
Normal file
35
server/db/migrations/0000_gifted_agent_brand.sql
Normal file
@ -0,0 +1,35 @@
|
||||
CREATE TABLE `customers` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`gender` text NOT NULL,
|
||||
`contact` text NOT NULL,
|
||||
`id_card` text NOT NULL,
|
||||
`password` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `customers_contact_unique` ON `customers` (`contact`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `customers_id_card_unique` ON `customers` (`id_card`);--> statement-breakpoint
|
||||
CREATE TABLE `reservations` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`customer_id` integer NOT NULL,
|
||||
`room_id` integer NOT NULL,
|
||||
`check_in_time` integer NOT NULL,
|
||||
`stay_days` integer NOT NULL,
|
||||
FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`room_id`) REFERENCES `rooms`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `room_types` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`type_name` text NOT NULL,
|
||||
`star_rating` integer NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `rooms` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`type_id` integer NOT NULL,
|
||||
`price` real NOT NULL,
|
||||
`feature` text,
|
||||
`available_count` integer DEFAULT 0 NOT NULL,
|
||||
FOREIGN KEY (`type_id`) REFERENCES `room_types`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
248
server/db/migrations/meta/0000_snapshot.json
Normal file
248
server/db/migrations/meta/0000_snapshot.json
Normal file
@ -0,0 +1,248 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "4e2b0752-859c-462f-81a8-d794f837ca5b",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"customers": {
|
||||
"name": "customers",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"gender": {
|
||||
"name": "gender",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"contact": {
|
||||
"name": "contact",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"id_card": {
|
||||
"name": "id_card",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"customers_contact_unique": {
|
||||
"name": "customers_contact_unique",
|
||||
"columns": [
|
||||
"contact"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"customers_id_card_unique": {
|
||||
"name": "customers_id_card_unique",
|
||||
"columns": [
|
||||
"id_card"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"reservations": {
|
||||
"name": "reservations",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"customer_id": {
|
||||
"name": "customer_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"room_id": {
|
||||
"name": "room_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"check_in_time": {
|
||||
"name": "check_in_time",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"stay_days": {
|
||||
"name": "stay_days",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"reservations_customer_id_customers_id_fk": {
|
||||
"name": "reservations_customer_id_customers_id_fk",
|
||||
"tableFrom": "reservations",
|
||||
"tableTo": "customers",
|
||||
"columnsFrom": [
|
||||
"customer_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"reservations_room_id_rooms_id_fk": {
|
||||
"name": "reservations_room_id_rooms_id_fk",
|
||||
"tableFrom": "reservations",
|
||||
"tableTo": "rooms",
|
||||
"columnsFrom": [
|
||||
"room_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"room_types": {
|
||||
"name": "room_types",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"type_name": {
|
||||
"name": "type_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"star_rating": {
|
||||
"name": "star_rating",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"rooms": {
|
||||
"name": "rooms",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"type_id": {
|
||||
"name": "type_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"feature": {
|
||||
"name": "feature",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"available_count": {
|
||||
"name": "available_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"rooms_type_id_room_types_id_fk": {
|
||||
"name": "rooms_type_id_room_types_id_fk",
|
||||
"tableFrom": "rooms",
|
||||
"tableTo": "room_types",
|
||||
"columnsFrom": [
|
||||
"type_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
13
server/db/migrations/meta/_journal.json
Normal file
13
server/db/migrations/meta/_journal.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1750234000729,
|
||||
"tag": "0000_gifted_agent_brand",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
32
server/db/schema.ts
Normal file
32
server/db/schema.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const customers = sqliteTable('customers', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
name: text('name').notNull(),
|
||||
gender: text('gender', { enum: ['male', 'female'] }).notNull(),
|
||||
contact: text('contact').notNull().unique(),
|
||||
idCard: text('id_card').notNull().unique(),
|
||||
password: text('password').notNull(),
|
||||
});
|
||||
|
||||
export const roomTypes = sqliteTable('room_types', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
typeName: text('type_name').notNull(),
|
||||
starRating: integer('star_rating').notNull(),
|
||||
});
|
||||
|
||||
export const rooms = sqliteTable('rooms', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
typeId: integer('type_id').notNull().references(() => roomTypes.id),
|
||||
price: real('price').notNull(),
|
||||
feature: text('feature'),
|
||||
availableCount: integer('available_count').notNull().default(0),
|
||||
});
|
||||
|
||||
export const reservations = sqliteTable('reservations', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
customerId: integer('customer_id').notNull().references(() => customers.id),
|
||||
roomId: integer('room_id').notNull().references(() => rooms.id),
|
||||
checkInTime: integer('check_in_time', { mode: 'timestamp' }).notNull(),
|
||||
stayDays: integer('stay_days').notNull(),
|
||||
});
|
Reference in New Issue
Block a user