690401:1326 fix secutities uuid
This commit is contained in:
@@ -12,5 +12,5 @@ services:
|
|||||||
# Override สำหรับ Database (Local Dev)
|
# Override สำหรับ Database (Local Dev)
|
||||||
mariadb:
|
mariadb:
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_ROOT_PASSWORD=Center#2025
|
- MYSQL_ROOT_PASSWORD=Center2025
|
||||||
- MYSQL_PASSWORD=Center2025
|
- MYSQL_PASSWORD=Center2025
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
-- Migration: Align Schema with Documentation
|
|
||||||
-- Version: 1733800000000
|
|
||||||
-- Date: 2025-12-10
|
|
||||||
-- Description: Add missing fields and fix column lengths to match schema v1.5.1
|
|
||||||
-- ==========================================================
|
|
||||||
-- Phase 1: Organizations Table Updates
|
|
||||||
-- ==========================================================
|
|
||||||
-- Add role_id column to organizations
|
|
||||||
ALTER TABLE organizations
|
|
||||||
ADD COLUMN role_id INT NULL COMMENT 'Reference to organization_roles table';
|
|
||||||
|
|
||||||
-- Add foreign key constraint
|
|
||||||
ALTER TABLE organizations
|
|
||||||
ADD CONSTRAINT fk_organizations_role FOREIGN KEY (role_id) REFERENCES organization_roles(id) ON DELETE
|
|
||||||
SET NULL;
|
|
||||||
|
|
||||||
-- Modify organization_name length from 200 to 255
|
|
||||||
ALTER TABLE organizations
|
|
||||||
MODIFY COLUMN organization_name VARCHAR(255) NOT NULL COMMENT 'Organization name';
|
|
||||||
|
|
||||||
-- ==========================================================
|
|
||||||
-- Phase 2: Users Table Updates (Security Fields)
|
|
||||||
-- ==========================================================
|
|
||||||
-- Add failed_attempts for login tracking
|
|
||||||
ALTER TABLE users
|
|
||||||
ADD COLUMN failed_attempts INT DEFAULT 0 COMMENT 'Number of failed login attempts';
|
|
||||||
|
|
||||||
-- Add locked_until for account lockout mechanism
|
|
||||||
ALTER TABLE users
|
|
||||||
ADD COLUMN locked_until DATETIME NULL COMMENT 'Account locked until this timestamp';
|
|
||||||
|
|
||||||
-- Add last_login_at for audit trail
|
|
||||||
ALTER TABLE users
|
|
||||||
ADD COLUMN last_login_at TIMESTAMP NULL COMMENT 'Last successful login timestamp';
|
|
||||||
|
|
||||||
-- ==========================================================
|
|
||||||
-- Phase 3: Roles Table Updates
|
|
||||||
-- ==========================================================
|
|
||||||
-- Modify role_name length from 50 to 100
|
|
||||||
ALTER TABLE roles
|
|
||||||
MODIFY COLUMN role_name VARCHAR(100) NOT NULL COMMENT 'Role name';
|
|
||||||
|
|
||||||
-- ==========================================================
|
|
||||||
-- Verification Queries
|
|
||||||
-- ==========================================================
|
|
||||||
-- Verify organizations table structure
|
|
||||||
SELECT COLUMN_NAME,
|
|
||||||
DATA_TYPE,
|
|
||||||
CHARACTER_MAXIMUM_LENGTH,
|
|
||||||
IS_NULLABLE,
|
|
||||||
COLUMN_COMMENT
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'organizations'
|
|
||||||
ORDER BY ORDINAL_POSITION;
|
|
||||||
|
|
||||||
-- Verify users table has new security fields
|
|
||||||
SELECT COLUMN_NAME,
|
|
||||||
DATA_TYPE,
|
|
||||||
COLUMN_DEFAULT,
|
|
||||||
IS_NULLABLE,
|
|
||||||
COLUMN_COMMENT
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'users'
|
|
||||||
AND COLUMN_NAME IN (
|
|
||||||
'failed_attempts',
|
|
||||||
'locked_until',
|
|
||||||
'last_login_at'
|
|
||||||
)
|
|
||||||
ORDER BY ORDINAL_POSITION;
|
|
||||||
|
|
||||||
-- Verify roles table role_name length
|
|
||||||
SELECT COLUMN_NAME,
|
|
||||||
DATA_TYPE,
|
|
||||||
CHARACTER_MAXIMUM_LENGTH
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'roles'
|
|
||||||
AND COLUMN_NAME = 'role_name';
|
|
||||||
|
|
||||||
-- ==========================================================
|
|
||||||
-- Rollback Script (Use if needed)
|
|
||||||
-- ==========================================================
|
|
||||||
/*
|
|
||||||
-- Rollback Phase 3: Roles
|
|
||||||
ALTER TABLE roles
|
|
||||||
MODIFY COLUMN role_name VARCHAR(50) NOT NULL;
|
|
||||||
|
|
||||||
-- Rollback Phase 2: Users
|
|
||||||
ALTER TABLE users
|
|
||||||
DROP COLUMN last_login_at,
|
|
||||||
DROP COLUMN locked_until,
|
|
||||||
DROP COLUMN failed_attempts;
|
|
||||||
|
|
||||||
-- Rollback Phase 1: Organizations
|
|
||||||
ALTER TABLE organizations
|
|
||||||
MODIFY COLUMN organization_name VARCHAR(200) NOT NULL;
|
|
||||||
|
|
||||||
ALTER TABLE organizations
|
|
||||||
DROP FOREIGN KEY fk_organizations_role;
|
|
||||||
|
|
||||||
ALTER TABLE organizations
|
|
||||||
DROP COLUMN role_id;
|
|
||||||
*/
|
|
||||||
@@ -57,7 +57,7 @@ import { MigrationModule } from './modules/migration/migration.module';
|
|||||||
// 1. Setup Config Module พร้อม Validation
|
// 1. Setup Config Module พร้อม Validation
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
envFilePath: '.env',
|
envFilePath: ['.env', '.env.local'],
|
||||||
load: [redisConfig],
|
load: [redisConfig],
|
||||||
validationSchema: envValidationSchema,
|
validationSchema: envValidationSchema,
|
||||||
validationOptions: {
|
validationOptions: {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ export const databaseConfig: TypeOrmModuleOptions = {
|
|||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: Number(process.env.DB_PORT || '3306'),
|
port: Number(process.env.DB_PORT || '3306'),
|
||||||
username: process.env.DB_USERNAME || 'root',
|
username: process.env.DB_USERNAME || 'admin',
|
||||||
password: process.env.DB_PASSWORD || 'Center#2025',
|
password: process.env.DB_PASSWORD || 'Center2025',
|
||||||
database: process.env.DB_DATABASE || 'lcbp3_dev',
|
database: process.env.DB_DATABASE || 'lcbp3_dev',
|
||||||
charset: 'utf8mb4',
|
charset: 'utf8mb4',
|
||||||
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export async function seedUsers(dataSource: DataSource) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const salt = await bcrypt.genSalt();
|
const salt = await bcrypt.genSalt();
|
||||||
const password = await bcrypt.hash('password123', salt); // Default password
|
const password = await bcrypt.hash('Center2025', salt); // Default password (ADR-019 aligned)
|
||||||
|
|
||||||
for (const u of usersData) {
|
for (const u of usersData) {
|
||||||
let user = await userRepo.findOneBy({ username: u.username });
|
let user = await userRepo.findOneBy({ username: u.username });
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ import { Can } from '@/components/common/can';
|
|||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
interface CorrespondencesPageProps {
|
interface CorrespondencesPageProps {
|
||||||
searchParams?: {
|
searchParams: Promise<{
|
||||||
type?: string;
|
type?: string;
|
||||||
};
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CorrespondencesPage({
|
export default async function CorrespondencesPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: CorrespondencesPageProps) {
|
}: CorrespondencesPageProps) {
|
||||||
const isRfaView = searchParams?.type?.toUpperCase() === 'RFA';
|
const params = await searchParams;
|
||||||
|
const isRfaView = params?.type?.toUpperCase() === 'RFA';
|
||||||
const heading = isRfaView ? 'RFAs (Request for Approval)' : 'Correspondences';
|
const heading = isRfaView ? 'RFAs (Request for Approval)' : 'Correspondences';
|
||||||
const description = isRfaView
|
const description = isRfaView
|
||||||
? 'Unified list view for RFA documents'
|
? 'Unified list view for RFA documents'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import QueryProvider from '@/providers/query-provider';
|
|||||||
import SessionProvider from '@/providers/session-provider'; // ✅ Import เข้ามา
|
import SessionProvider from '@/providers/session-provider'; // ✅ Import เข้ามา
|
||||||
import ThemeProvider from '@/providers/theme-provider';
|
import ThemeProvider from '@/providers/theme-provider';
|
||||||
import { Toaster } from '@/components/ui/sonner';
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
@@ -19,13 +20,15 @@ interface RootLayoutProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({ children }: RootLayoutProps) {
|
export default async function RootLayout({ children }: RootLayoutProps) {
|
||||||
|
const nonce = (await headers()).get('x-nonce') || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head />
|
<head />
|
||||||
<body className={cn('min-h-screen bg-background font-sans antialiased', inter.className)}>
|
<body className={cn('min-h-screen bg-background font-sans antialiased', inter.className)}>
|
||||||
<SessionProvider>
|
<SessionProvider nonce={nonce}>
|
||||||
<ThemeProvider>
|
<ThemeProvider nonce={nonce}>
|
||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useAuthStore } from '@/lib/stores/auth-store';
|
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface CanProps {
|
interface CanProps {
|
||||||
permission?: string;
|
permission?: string;
|
||||||
@@ -18,6 +18,15 @@ interface CanProps {
|
|||||||
|
|
||||||
export function Can({ permission, role, children, fallback = null }: CanProps) {
|
export function Can({ permission, role, children, fallback = null }: CanProps) {
|
||||||
const { hasPermission, hasRole } = useAuthStore();
|
const { hasPermission, hasRole } = useAuthStore();
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return <>{fallback}</>;
|
||||||
|
}
|
||||||
|
|
||||||
let allowed = true;
|
let allowed = true;
|
||||||
|
|
||||||
|
|||||||
@@ -162,13 +162,13 @@ export const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: backendData.user.user_id.toString(),
|
id: backendData.user.publicId, // ✅ Use publicId for session identity (ADR-019)
|
||||||
publicId: backendData.user.publicId, // ✅ Added (ADR-019 Waived for session)
|
publicId: backendData.user.publicId,
|
||||||
name: `${backendData.user.firstName ?? ''} ${backendData.user.lastName ?? ''}`.trim(),
|
name: `${backendData.user.firstName ?? ''} ${backendData.user.lastName ?? ''}`.trim(),
|
||||||
email: backendData.user.email,
|
email: backendData.user.email,
|
||||||
username: backendData.user.username,
|
username: backendData.user.username,
|
||||||
firstName: backendData.user.firstName, // ✅ Added
|
firstName: backendData.user.firstName,
|
||||||
lastName: backendData.user.lastName, // ✅ Added
|
lastName: backendData.user.lastName,
|
||||||
role: backendData.user.role || 'User',
|
role: backendData.user.role || 'User',
|
||||||
organizationId: backendData.user.primaryOrganizationId,
|
organizationId: backendData.user.primaryOrganizationId,
|
||||||
accessToken: backendData.access_token,
|
accessToken: backendData.access_token,
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { create } from 'zustand';
|
|||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string; // Internal stringified INT (for stability)
|
id: string; // publicId (ADR-019)
|
||||||
publicId?: string; // ADR-019: Public UUIDv7
|
publicId: string;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
|
|||||||
@@ -78,18 +78,6 @@ const nextConfig = {
|
|||||||
key: 'X-Content-Type-Options',
|
key: 'X-Content-Type-Options',
|
||||||
value: 'nosniff',
|
value: 'nosniff',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'Content-Security-Policy',
|
|
||||||
value: [
|
|
||||||
"default-src 'self'",
|
|
||||||
"script-src 'self' 'unsafe-eval'", // จำเป็นสำหรับ Workflow DSL Engine (new Function())
|
|
||||||
"style-src 'self' 'unsafe-inline'",
|
|
||||||
"img-src 'self' data: https:",
|
|
||||||
"font-src 'self'",
|
|
||||||
"connect-src 'self' ws: wss:",
|
|
||||||
"frame-src 'self'",
|
|
||||||
].join('; '),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,9 +4,15 @@
|
|||||||
import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react';
|
import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react';
|
||||||
import { AuthSync } from '@/components/auth/auth-sync';
|
import { AuthSync } from '@/components/auth/auth-sync';
|
||||||
|
|
||||||
export default function SessionProvider({ children }: { children: React.ReactNode }) {
|
export default function SessionProvider({
|
||||||
|
children,
|
||||||
|
nonce,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
nonce?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<NextAuthSessionProvider>
|
<NextAuthSessionProvider nonce={nonce}>
|
||||||
<AuthSync />
|
<AuthSync />
|
||||||
{children}
|
{children}
|
||||||
</NextAuthSessionProvider>
|
</NextAuthSessionProvider>
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
|||||||
|
|
||||||
export default function ThemeProvider({
|
export default function ThemeProvider({
|
||||||
children,
|
children,
|
||||||
|
nonce,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
nonce?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<NextThemesProvider
|
<NextThemesProvider
|
||||||
@@ -13,6 +15,7 @@ export default function ThemeProvider({
|
|||||||
defaultTheme="dark"
|
defaultTheme="dark"
|
||||||
enableSystem={false}
|
enableSystem={false}
|
||||||
themes={['light', 'dark']}
|
themes={['light', 'dark']}
|
||||||
|
nonce={nonce}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</NextThemesProvider>
|
</NextThemesProvider>
|
||||||
|
|||||||
+41
-1
@@ -43,7 +43,47 @@ export default auth((req) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.next(); // แก้ไขจาก null
|
// 5. Generate CSP with Nonce (Security Rule Tier 1)
|
||||||
|
// ใช้ Nonce Strategy เพื่ออนุญาต Inline Script เฉพาะที่ระบุตัวตนได้ ป้องกัน XSS
|
||||||
|
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
||||||
|
|
||||||
|
let connectSrcApi = 'http://localhost:3001';
|
||||||
|
if (process.env.NEXT_PUBLIC_API_URL) {
|
||||||
|
try {
|
||||||
|
connectSrcApi = new URL(process.env.NEXT_PUBLIC_API_URL).origin;
|
||||||
|
} catch {
|
||||||
|
connectSrcApi = process.env.NEXT_PUBLIC_API_URL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cspHeader = `
|
||||||
|
default-src 'self';
|
||||||
|
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval';
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
img-src 'self' blob: data: https:;
|
||||||
|
font-src 'self' data:;
|
||||||
|
connect-src 'self' ws: wss: ${connectSrcApi};
|
||||||
|
object-src 'none';
|
||||||
|
base-uri 'self';
|
||||||
|
form-action 'self';
|
||||||
|
frame-ancestors 'none';
|
||||||
|
upgrade-insecure-requests;
|
||||||
|
`
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const requestHeaders = new Headers(req.headers);
|
||||||
|
requestHeaders.set('x-nonce', nonce);
|
||||||
|
requestHeaders.set('Content-Security-Policy', cspHeader);
|
||||||
|
|
||||||
|
const response = NextResponse.next({
|
||||||
|
request: {
|
||||||
|
headers: requestHeaders,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
response.headers.set('Content-Security-Policy', cspHeader);
|
||||||
|
|
||||||
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
// กำหนดว่า Middleware นี้จะทำงานกับ Route ไหนบ้าง
|
// กำหนดว่า Middleware นี้จะทำงานกับ Route ไหนบ้าง
|
||||||
|
|||||||
Vendored
+12
-12
@@ -4,11 +4,11 @@ import _NextAuth, { DefaultSession } from 'next-auth';
|
|||||||
declare module 'next-auth' {
|
declare module 'next-auth' {
|
||||||
interface Session {
|
interface Session {
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string; // publicId (ADR-019)
|
||||||
publicId: string; // ✅ Added (ADR-019 Waived for session)
|
publicId: string;
|
||||||
username: string; // ✅ Added
|
username: string;
|
||||||
firstName: string; // ✅ Added
|
firstName: string;
|
||||||
lastName: string; // ✅ Added
|
lastName: string;
|
||||||
role: string;
|
role: string;
|
||||||
organizationId?: number;
|
organizationId?: number;
|
||||||
} & DefaultSession['user'];
|
} & DefaultSession['user'];
|
||||||
@@ -18,11 +18,11 @@ declare module 'next-auth' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string; // publicId (ADR-019)
|
||||||
publicId: string; // ✅ Added
|
publicId: string;
|
||||||
username: string; // ✅ Added
|
username: string;
|
||||||
firstName: string; // ✅ Added
|
firstName: string;
|
||||||
lastName: string; // ✅ Added
|
lastName: string;
|
||||||
role: string;
|
role: string;
|
||||||
organizationId?: number;
|
organizationId?: number;
|
||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
@@ -32,8 +32,8 @@ declare module 'next-auth' {
|
|||||||
|
|
||||||
declare module 'next-auth/jwt' {
|
declare module 'next-auth/jwt' {
|
||||||
interface JWT {
|
interface JWT {
|
||||||
id: string;
|
id: string; // publicId or username depending on auth flow
|
||||||
username: string; // ✅ Added
|
username: string;
|
||||||
role: string;
|
role: string;
|
||||||
organizationId?: number;
|
organizationId?: number;
|
||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
"start:mcp": "node ./scripts/start-mcp.js",
|
"start:mcp": "node ./scripts/start-mcp.js",
|
||||||
"dev:backend": "pnpm --filter backend start:dev",
|
"dev:backend": "pnpm --filter backend start:dev",
|
||||||
"dev:frontend": "pnpm --filter lcbp3-frontend dev",
|
"dev:frontend": "pnpm --filter lcbp3-frontend dev",
|
||||||
"dev": "pnpm run --parallel /dev|start:dev/",
|
"dev": "pnpm run --parallel \"/dev|start:dev/\"",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix"
|
"lint:fix": "eslint . --fix"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Status:** Accepted
|
**Status:** Accepted
|
||||||
**Date:** 2026-03-12
|
**Date:** 2026-03-12
|
||||||
**Version:** 1.8.1
|
**Version:** 1.8.2
|
||||||
**Decision Makers:** Development Team, Database Architect
|
**Decision Makers:** Development Team, Database Architect
|
||||||
**Related Documents:**
|
**Related Documents:**
|
||||||
|
|
||||||
@@ -369,26 +369,6 @@ ALTER TABLE notifications
|
|||||||
-- Using regular INDEX instead
|
-- Using regular INDEX instead
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rollback SQL
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Rollback: Remove UUID columns (Non-destructive reverse)
|
|
||||||
ALTER TABLE users DROP INDEX idx_users_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE organizations DROP INDEX idx_organizations_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE projects DROP INDEX idx_projects_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE contracts DROP INDEX idx_contracts_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE correspondences DROP INDEX idx_correspondences_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE correspondence_revisions DROP INDEX idx_correspondence_revisions_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE circulations DROP INDEX idx_circulations_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE shop_drawings DROP INDEX idx_shop_drawings_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE shop_drawing_revisions DROP INDEX idx_shop_drawing_revisions_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE contract_drawings DROP INDEX idx_contract_drawings_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE asbuilt_drawings DROP INDEX idx_asbuilt_drawings_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE asbuilt_drawing_revisions DROP INDEX idx_asbuilt_drawing_revisions_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE attachments DROP INDEX idx_attachments_uuid, DROP COLUMN uuid;
|
|
||||||
ALTER TABLE notifications DROP INDEX idx_notifications_uuid, DROP COLUMN uuid;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Storage Impact Analysis
|
## Storage Impact Analysis
|
||||||
@@ -530,17 +510,12 @@ type ProjectOption = {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Waivers & Exceptions
|
## 🔄 Change Log
|
||||||
|
|
||||||
### 1. AuthStore / Frontend Session User Identity
|
| Version | Date | Changes | Updated By |
|
||||||
|
| ------- | ---------- | ------------------------------------------------------------------- | ----------- |
|
||||||
**Date:** 2026-04-01
|
| 1.8.2 | 2026-04-01 | Removed Waiver: Session Identity to enforce strict `publicId` usage | Antigravity |
|
||||||
**Scope:** `frontend/lib/stores/auth-store.ts`, `frontend/lib/auth.ts`
|
| 1.8.1 | 2026-03-21 | Added Naming Convention Summary & Transition Strategy | Claude |
|
||||||
|
| 1.8.0 | 2026-03-12 | Initial Decision Outcome & Technical Spec | Human Dev |
|
||||||
**Decision:** ให้คงฟิลด์ `id` (stringified `user_id` INT) ไว้ใน `User` interface ของ `AuthStore` และ `NextAuth Session` เพื่อความเสถียรของระบบ Login ที่ใช้งานได้ดีอยู่แล้ว โดยให้เพิ่ม `publicId` เป็นฟิลด์เสริมแทนการ Replacement (Waive strict ADR-019 compliance for Session Identity only).
|
|
||||||
|
|
||||||
**Rationale:** ป้องกันความเสี่ยงในการเปลี่ยน Logic การจัดการ Session ที่อาจส่งผลกระทบต่อระบบ Authentication โดยรวม
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_สำหรับรายละเอียดการ Implement ดูที่ Implementation Plan ใน `05-07-hybrid-uuid-implementation-plan.md`_
|
_สำหรับรายละเอียดการ Implement ดูที่ Implementation Plan ใน `05-07-hybrid-uuid-implementation-plan.md`_
|
||||||
|
|||||||
Reference in New Issue
Block a user