Updating types
This commit is contained in:
parent
168de8a2b9
commit
f7e41c710c
@ -4,12 +4,12 @@ import { ThemeProvider } from '@mui/material/styles';
|
|||||||
|
|
||||||
import { backstoryTheme } from './BackstoryTheme';
|
import { backstoryTheme } from './BackstoryTheme';
|
||||||
|
|
||||||
import { SeverityType } from './components/Snack';
|
import { SeverityType } from 'components/Snack';
|
||||||
import { Query } from './types/types';
|
import { Query } from 'types/types';
|
||||||
import { ConversationHandle } from './components/Conversation';
|
import { ConversationHandle } from 'components/Conversation';
|
||||||
import { UserProvider } from './components/UserContext';
|
import { UserProvider } from 'components/UserContext';
|
||||||
import { UserRoute } from './routes/UserRoute';
|
import { UserRoute } from 'routes/UserRoute';
|
||||||
import { BackstoryLayout } from './components/BackstoryLayout';
|
import { BackstoryLayout } from 'components/layout/BackstoryLayout';
|
||||||
|
|
||||||
import './BackstoryApp.css';
|
import './BackstoryApp.css';
|
||||||
import '@fontsource/roboto/300.css';
|
import '@fontsource/roboto/300.css';
|
||||||
@ -17,7 +17,7 @@ import '@fontsource/roboto/400.css';
|
|||||||
import '@fontsource/roboto/500.css';
|
import '@fontsource/roboto/500.css';
|
||||||
import '@fontsource/roboto/700.css';
|
import '@fontsource/roboto/700.css';
|
||||||
|
|
||||||
import { connectionBase } from './Global';
|
import { connectionBase } from './utils/Global';
|
||||||
|
|
||||||
// Cookie handling functions
|
// Cookie handling functions
|
||||||
const getCookie = (name: string) => {
|
const getCookie = (name: string) => {
|
||||||
|
112
frontend/src/README.md
Normal file
112
frontend/src/README.md
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# Disk structure
|
||||||
|
|
||||||
|
Below is the general directory structure for the Backstory platform, prioritizing maintainability and developer experience:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/ # Reusable UI components
|
||||||
|
│ ├── common/ # Generic components (Button, Modal, etc.)
|
||||||
|
│ ├── forms/ # Form-related components
|
||||||
|
│ ├── layout/ # Layout components (Header, Sidebar, etc.)
|
||||||
|
│ └── ui/ # MUI customizations and themed components
|
||||||
|
├── pages/ # Page-level components (route components)
|
||||||
|
│ ├── auth/
|
||||||
|
│ ├── dashboard/
|
||||||
|
│ ├── profile/
|
||||||
|
│ └── settings/
|
||||||
|
├── features/ # Feature-specific modules
|
||||||
|
│ ├── authentication/
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ ├── hooks/
|
||||||
|
│ │ ├── services/
|
||||||
|
│ │ └── types/
|
||||||
|
│ ├── user-management/
|
||||||
|
│ └── analytics/
|
||||||
|
├── hooks/ # Custom React hooks
|
||||||
|
│ ├── api/ # API-related hooks
|
||||||
|
│ ├── ui/ # UI state hooks
|
||||||
|
│ └── utils/ # Utility hooks
|
||||||
|
├── services/ # API calls and external services
|
||||||
|
│ ├── api/
|
||||||
|
│ ├── auth/
|
||||||
|
│ └── storage/
|
||||||
|
├── store/ # State management (Redux/Zustand/Context)
|
||||||
|
│ ├── slices/ # If using Redux Toolkit
|
||||||
|
│ ├── providers/ # Context providers
|
||||||
|
│ └── types/
|
||||||
|
├── utils/ # Pure utility functions
|
||||||
|
│ ├── constants/
|
||||||
|
│ ├── helpers/
|
||||||
|
│ └── validators/
|
||||||
|
├── styles/ # Global styles and theme
|
||||||
|
│ ├── theme/ # MUI theme customization
|
||||||
|
│ ├── globals.css
|
||||||
|
│ └── variables.css
|
||||||
|
├── types/ # TypeScript type definitions
|
||||||
|
│ ├── api/
|
||||||
|
│ ├── common/
|
||||||
|
│ └── components/
|
||||||
|
├── assets/ # Static assets
|
||||||
|
│ ├── images/
|
||||||
|
│ ├── icons/
|
||||||
|
│ └── fonts/
|
||||||
|
├── config/ # Configuration files
|
||||||
|
│ ├── env.ts
|
||||||
|
│ ├── routes.ts
|
||||||
|
│ └── constants.ts
|
||||||
|
└── __tests__/ # Test files mirroring src structure
|
||||||
|
├── components/
|
||||||
|
├── pages/
|
||||||
|
└── utils/
|
||||||
|
```
|
||||||
|
|
||||||
|
# Key organizational principles:
|
||||||
|
|
||||||
|
1. Feature-Based Architecture
|
||||||
|
The features/ directory groups related functionality together, making it easy to find everything related to a specific feature in one place.
|
||||||
|
|
||||||
|
2. Clear Separation of Concerns
|
||||||
|
|
||||||
|
```
|
||||||
|
components/ - Pure UI components
|
||||||
|
pages/ - Route-level components
|
||||||
|
services/ - Data fetching and external APIs
|
||||||
|
hooks/ - Reusable logic
|
||||||
|
utils/ - Pure functions
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Scalable Component Organization
|
||||||
|
Components are organized by purpose rather than alphabetically, with subcategories that make sense as the app grows.
|
||||||
|
|
||||||
|
4. Centralized Configuration
|
||||||
|
All app configuration lives in config/, making it easy to manage environment variables, routes, and constants.
|
||||||
|
5. Type Safety First
|
||||||
|
Dedicated types/ directory with clear categorization helps maintain type definitions as the app scales.
|
||||||
|
|
||||||
|
# Naming Conventions
|
||||||
|
|
||||||
|
* Use PascalCase for components (UserProfile.tsx)
|
||||||
|
* Use camelCase for utilities and hooks (formatDate.ts, useLocalStorage.ts)
|
||||||
|
* Use kebab-case for directories (user-management/)
|
||||||
|
|
||||||
|
# Index Files
|
||||||
|
Create index.ts files in major directories to enable clean imports:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// components/common/index.ts
|
||||||
|
export { Button } from './Button';
|
||||||
|
export { Modal } from './Modal';
|
||||||
|
|
||||||
|
// Import usage
|
||||||
|
import { Button, Modal } from '@/components/common';
|
||||||
|
```
|
||||||
|
|
||||||
|
# Path Aliases
|
||||||
|
Configure path aliases in your build tool:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Instead of: ../../../../components/common/Button
|
||||||
|
import { Button } from '@/components/common/Button';
|
||||||
|
```
|
||||||
|
|
||||||
|
This structure scales well while keeping related code co-located and maintaining clear boundaries between different types of functionality.
|
@ -24,6 +24,8 @@ import PhoneInput from 'react-phone-number-input';
|
|||||||
import { E164Number } from 'libphonenumber-js/core';
|
import { E164Number } from 'libphonenumber-js/core';
|
||||||
import './PhoneInput.css';
|
import './PhoneInput.css';
|
||||||
|
|
||||||
|
import { ApiClient } from 'types/api-client';
|
||||||
|
|
||||||
// Import conversion utilities
|
// Import conversion utilities
|
||||||
import {
|
import {
|
||||||
formatApiRequest,
|
formatApiRequest,
|
||||||
@ -36,9 +38,8 @@ import {
|
|||||||
} from './types/conversion';
|
} from './types/conversion';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthResponse, BaseUser, Guest
|
AuthResponse, BaseUser, Guest, Candidate
|
||||||
} from './types/types'
|
} from './types/types'
|
||||||
import { connectionBase } from 'Global';
|
|
||||||
|
|
||||||
interface LoginRequest {
|
interface LoginRequest {
|
||||||
login: string;
|
login: string;
|
||||||
@ -54,9 +55,8 @@ interface RegisterRequest {
|
|||||||
phone?: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE_URL = `${connectionBase}/api/1.0`;
|
|
||||||
|
|
||||||
const BackstoryTestApp: React.FC = () => {
|
const BackstoryTestApp: React.FC = () => {
|
||||||
|
const apiClient = new ApiClient();
|
||||||
const [currentUser, setCurrentUser] = useState<BaseUser | null>(null);
|
const [currentUser, setCurrentUser] = useState<BaseUser | null>(null);
|
||||||
const [guestSession, setGuestSession] = useState<Guest | null>(null);
|
const [guestSession, setGuestSession] = useState<Guest | null>(null);
|
||||||
const [tabValue, setTabValue] = useState(0);
|
const [tabValue, setTabValue] = useState(0);
|
||||||
@ -139,24 +139,7 @@ const BackstoryTestApp: React.FC = () => {
|
|||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Format request data for API (camelCase to snake_case)
|
const authResponse = await apiClient.login(loginForm.login, loginForm.password)
|
||||||
const requestData = formatApiRequest({
|
|
||||||
login: loginForm.login,
|
|
||||||
password: loginForm.password
|
|
||||||
});
|
|
||||||
|
|
||||||
debugConversion(requestData, 'Login Request');
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestData)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use conversion utility to handle response
|
|
||||||
const authResponse = await handleApiResponse<AuthResponse>(response);
|
|
||||||
|
|
||||||
debugConversion(authResponse, 'Login Response');
|
debugConversion(authResponse, 'Login Response');
|
||||||
|
|
||||||
@ -186,7 +169,7 @@ const BackstoryTestApp: React.FC = () => {
|
|||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const candidateData = {
|
const candidate: Candidate = {
|
||||||
username: registerForm.username,
|
username: registerForm.username,
|
||||||
email: registerForm.email,
|
email: registerForm.email,
|
||||||
firstName: registerForm.firstName,
|
firstName: registerForm.firstName,
|
||||||
@ -210,21 +193,7 @@ const BackstoryTestApp: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format request data for API (camelCase to snake_case, dates to ISO strings)
|
const result = await apiClient.createCandidate(candidate);
|
||||||
const requestData = formatApiRequest(candidateData);
|
|
||||||
|
|
||||||
debugConversion(requestData, 'Registration Request');
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/candidates`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestData)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use conversion utility to handle response
|
|
||||||
const result = await handleApiResponse<any>(response);
|
|
||||||
|
|
||||||
debugConversion(result, 'Registration Response');
|
debugConversion(result, 'Registration Response');
|
||||||
|
|
||||||
|
@ -9,13 +9,13 @@ import { SxProps, Theme } from '@mui/material';
|
|||||||
import PropagateLoader from "react-spinners/PropagateLoader";
|
import PropagateLoader from "react-spinners/PropagateLoader";
|
||||||
|
|
||||||
import { Message, MessageList, BackstoryMessage, MessageRoles } from './Message';
|
import { Message, MessageList, BackstoryMessage, MessageRoles } from './Message';
|
||||||
import { DeleteConfirmation } from './DeleteConfirmation';
|
import { DeleteConfirmation } from 'components/DeleteConfirmation';
|
||||||
import { Query } from '../types/types';
|
import { Query } from 'types/types';
|
||||||
import { BackstoryTextField, BackstoryTextFieldRef } from './BackstoryTextField';
|
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
||||||
import { BackstoryElementProps } from './BackstoryTab';
|
import { BackstoryElementProps } from './BackstoryTab';
|
||||||
import { connectionBase } from '../Global';
|
import { connectionBase } from 'utils/Global';
|
||||||
import { useUser } from "./UserContext";
|
import { useUser } from "components/UserContext";
|
||||||
import { streamQueryResponse, StreamQueryController } from './streamQueryResponse';
|
import { streamQueryResponse, StreamQueryController } from 'services/streamQueryResponse';
|
||||||
|
|
||||||
import './Conversation.css';
|
import './Conversation.css';
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@ import React, { useEffect, useState, useRef } from 'react';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||||
import { Quote } from 'components/Quote';
|
import { Quote } from 'components/Quote';
|
||||||
import { streamQueryResponse, StreamQueryController } from './streamQueryResponse';
|
import { streamQueryResponse, StreamQueryController } from 'services/streamQueryResponse';
|
||||||
import { connectionBase } from 'Global';
|
import { connectionBase } from 'utils/Global';
|
||||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
import { BackstoryElementProps } from 'components/BackstoryTab';
|
||||||
import { useUser } from './UserContext';
|
import { useUser } from 'components/UserContext';
|
||||||
|
|
||||||
interface GenerateImageProps extends BackstoryElementProps {
|
interface GenerateImageProps extends BackstoryElementProps {
|
||||||
prompt: string
|
prompt: string
|
||||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|||||||
import mermaid, { MermaidConfig } from 'mermaid';
|
import mermaid, { MermaidConfig } from 'mermaid';
|
||||||
import { SxProps } from '@mui/material/styles';
|
import { SxProps } from '@mui/material/styles';
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { useResizeObserverAndMutationObserver } from './useAutoScrollToBottom';
|
import { useResizeObserverAndMutationObserver } from '../hooks/useAutoScrollToBottom';
|
||||||
|
|
||||||
const defaultMermaidConfig : MermaidConfig = {
|
const defaultMermaidConfig : MermaidConfig = {
|
||||||
startOnLoad: true,
|
startOnLoad: true,
|
||||||
|
@ -76,7 +76,6 @@ type BackstoryMessage = {
|
|||||||
expandable?: boolean,
|
expandable?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
interface ChatBubbleProps {
|
interface ChatBubbleProps {
|
||||||
role: MessageRoles,
|
role: MessageRoles,
|
||||||
isInfo?: boolean;
|
isInfo?: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { SxProps, Theme } from '@mui/material';
|
import { SxProps, Theme } from '@mui/material';
|
||||||
import { RefObject, useRef } from 'react';
|
import { RefObject, useRef } from 'react';
|
||||||
import { useAutoScrollToBottom } from './useAutoScrollToBottom';
|
import { useAutoScrollToBottom } from '../hooks/useAutoScrollToBottom';
|
||||||
|
|
||||||
interface ScrollableProps {
|
interface ScrollableProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||||
import { SetSnackType } from './Snack';
|
import { SetSnackType } from './Snack';
|
||||||
import { connectionBase } from '../Global';
|
import { connectionBase } from '../utils/Global';
|
||||||
import { User } from '../types/types';
|
import { User } from '../types/types';
|
||||||
|
|
||||||
type UserContextType = {
|
type UserContextType = {
|
||||||
|
@ -17,7 +17,7 @@ import TableContainer from '@mui/material/TableContainer';
|
|||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
|
|
||||||
import { Scrollable } from './Scrollable';
|
import { Scrollable } from './Scrollable';
|
||||||
import { connectionBase } from '../Global';
|
import { connectionBase } from '../utils/Global';
|
||||||
|
|
||||||
import './VectorVisualizer.css';
|
import './VectorVisualizer.css';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from './BackstoryTab';
|
||||||
|
@ -17,7 +17,7 @@ import { Footer } from 'components/layout/Footer';
|
|||||||
import { Snack, SetSnackType } from 'components/Snack';
|
import { Snack, SetSnackType } from 'components/Snack';
|
||||||
import { useUser } from 'components/UserContext';
|
import { useUser } from 'components/UserContext';
|
||||||
import { User } from 'types/types';
|
import { User } from 'types/types';
|
||||||
import { getBackstoryDynamicRoutes } from 'components/BackstoryRoutes';
|
import { getBackstoryDynamicRoutes } from 'components/layout/BackstoryRoutes';
|
||||||
import { LoadingComponent } from "components/LoadingComponent";
|
import { LoadingComponent } from "components/LoadingComponent";
|
||||||
|
|
||||||
type NavigationLinkType = {
|
type NavigationLinkType = {
|
||||||
@ -73,9 +73,9 @@ const getNavigationLinks = (user: User | null): NavigationLinkType[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (user.userType) {
|
switch (user.userType) {
|
||||||
case 'UserType.CANDIDATE':
|
case 'candidate':
|
||||||
return CandidateNavItems;
|
return CandidateNavItems;
|
||||||
case 'UserType.EMPLOYER':
|
case 'employer':
|
||||||
return EmployerNavItems;
|
return EmployerNavItems;
|
||||||
default:
|
default:
|
||||||
return DefaultNavItems;
|
return DefaultNavItems;
|
||||||
|
@ -2,8 +2,8 @@ import React, { Ref, ReactNode } from "react";
|
|||||||
import { Route } from "react-router-dom";
|
import { Route } from "react-router-dom";
|
||||||
import { Typography } from '@mui/material';
|
import { Typography } from '@mui/material';
|
||||||
|
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from '../BackstoryTab';
|
||||||
import { ConversationHandle } from './Conversation';
|
import { ConversationHandle } from '../Conversation';
|
||||||
import { User } from 'types/types';
|
import { User } from 'types/types';
|
||||||
|
|
||||||
import { ChatPage } from 'pages/ChatPage';
|
import { ChatPage } from 'pages/ChatPage';
|
||||||
@ -59,7 +59,7 @@ const getBackstoryDynamicRoutes = (props: BackstoryDynamicRoutesProps, user?: Us
|
|||||||
|
|
||||||
routes.push(<Route key={`${index++}`} path="/logout" element={<LogoutPage />} />);
|
routes.push(<Route key={`${index++}`} path="/logout" element={<LogoutPage />} />);
|
||||||
|
|
||||||
if (user.userType === "UserType.CANDIDATE") {
|
if (user.userType === 'candidate') {
|
||||||
routes.splice(-1, 0, ...[
|
routes.splice(-1, 0, ...[
|
||||||
<Route key={`${index++}`} path="/profile" element={<ProfilePage />} />,
|
<Route key={`${index++}`} path="/profile" element={<ProfilePage />} />,
|
||||||
<Route key={`${index++}`} path="/backstory" element={<BackstoryPage />} />,
|
<Route key={`${index++}`} path="/backstory" element={<BackstoryPage />} />,
|
||||||
@ -68,7 +68,7 @@ const getBackstoryDynamicRoutes = (props: BackstoryDynamicRoutesProps, user?: Us
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.userType === "UserType.EMPLOYER") {
|
if (user.userType === 'employer') {
|
||||||
routes.splice(-1, 0, ...[
|
routes.splice(-1, 0, ...[
|
||||||
<Route key={`${index++}`} path="/search" element={<SearchPage />} />,
|
<Route key={`${index++}`} path="/search" element={<SearchPage />} />,
|
||||||
<Route key={`${index++}`} path="/saved" element={<SavedPage />} />,
|
<Route key={`${index++}`} path="/saved" element={<SavedPage />} />,
|
@ -4,7 +4,7 @@ import { ThemeProvider } from '@mui/material/styles';
|
|||||||
import { backstoryTheme } from './BackstoryTheme';
|
import { backstoryTheme } from './BackstoryTheme';
|
||||||
import { BrowserRouter as Router } from "react-router-dom";
|
import { BrowserRouter as Router } from "react-router-dom";
|
||||||
import { BackstoryApp } from './BackstoryApp';
|
import { BackstoryApp } from './BackstoryApp';
|
||||||
// import { BackstoryTestApp } from 'TestApp';
|
import { BackstoryTestApp } from 'TestApp';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ root.render(
|
|||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeProvider theme={backstoryTheme}>
|
<ThemeProvider theme={backstoryTheme}>
|
||||||
<Router>
|
<Router>
|
||||||
<BackstoryApp />
|
{/* <BackstoryApp /> */}
|
||||||
{/* <BackstoryTestApp /> */}
|
<BackstoryTestApp />
|
||||||
</Router>
|
</Router>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
@ -5,7 +5,7 @@ import Box from '@mui/material/Box';
|
|||||||
|
|
||||||
import { BackstoryPageProps } from '../components/BackstoryTab';
|
import { BackstoryPageProps } from '../components/BackstoryTab';
|
||||||
import { CandidateInfo } from 'components/CandidateInfo';
|
import { CandidateInfo } from 'components/CandidateInfo';
|
||||||
import { connectionBase } from '../Global';
|
import { connectionBase } from '../utils/Global';
|
||||||
import { Candidate } from "../types/types";
|
import { Candidate } from "../types/types";
|
||||||
|
|
||||||
const CandidateListingPage = (props: BackstoryPageProps) => {
|
const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||||
|
@ -17,7 +17,7 @@ const ChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((props: Back
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const [questions, setQuestions] = useState<React.ReactElement[]>([]);
|
const [questions, setQuestions] = useState<React.ReactElement[]>([]);
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const candidate: Candidate | null = (user && user.userType === "UserType.CANDIDATE") ? user as Candidate : null;
|
const candidate: Candidate | null = (user && user.userType === 'candidate') ? user as Candidate : null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!candidate) {
|
if (!candidate) {
|
||||||
|
@ -14,7 +14,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
// import ResetIcon from '@mui/icons-material/History';
|
// import ResetIcon from '@mui/icons-material/History';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
|
||||||
import { connectionBase } from '../Global';
|
import { connectionBase } from '../utils/Global';
|
||||||
import { BackstoryPageProps } from '../components/BackstoryTab';
|
import { BackstoryPageProps } from '../components/BackstoryTab';
|
||||||
|
|
||||||
interface ServerTunables {
|
interface ServerTunables {
|
||||||
|
@ -8,15 +8,17 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import SendIcon from '@mui/icons-material/Send';
|
import SendIcon from '@mui/icons-material/Send';
|
||||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||||
|
import { jsonrepair } from 'jsonrepair';
|
||||||
|
|
||||||
|
|
||||||
import { CandidateInfo } from '../components/CandidateInfo';
|
import { CandidateInfo } from '../components/CandidateInfo';
|
||||||
import { Query } from '../types/types'
|
import { Query } from '../types/types'
|
||||||
import { Quote } from 'components/Quote';
|
import { Quote } from 'components/Quote';
|
||||||
import { streamQueryResponse, StreamQueryController } from '../components/streamQueryResponse';
|
import { streamQueryResponse, StreamQueryController } from 'services/streamQueryResponse';
|
||||||
import { connectionBase } from 'Global';
|
import { connectionBase } from 'utils/Global';
|
||||||
import { Candidate } from '../types/types';
|
import { Candidate } from '../types/types';
|
||||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
import { BackstoryElementProps } from 'components/BackstoryTab';
|
||||||
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
||||||
import { jsonrepair } from 'jsonrepair';
|
|
||||||
import { StyledMarkdown } from 'components/StyledMarkdown';
|
import { StyledMarkdown } from 'components/StyledMarkdown';
|
||||||
import { Scrollable } from '../components/Scrollable';
|
import { Scrollable } from '../components/Scrollable';
|
||||||
import { Pulse } from 'components/Pulse';
|
import { Pulse } from 'components/Pulse';
|
||||||
|
@ -3,7 +3,7 @@ import { useParams, useNavigate } from "react-router-dom";
|
|||||||
import { useUser } from "../components/UserContext";
|
import { useUser } from "../components/UserContext";
|
||||||
import { User } from "../types/types";
|
import { User } from "../types/types";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { connectionBase } from "../Global";
|
import { connectionBase } from "../utils/Global";
|
||||||
import { SetSnackType } from '../components/Snack';
|
import { SetSnackType } from '../components/Snack';
|
||||||
import { LoadingComponent } from "../components/LoadingComponent";
|
import { LoadingComponent } from "../components/LoadingComponent";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BackstoryMessage } from './Message';
|
import { BackstoryMessage } from 'components/Message';
|
||||||
import { Query } from '../types/types';
|
import { Query } from 'types/types';
|
||||||
import { jsonrepair } from 'jsonrepair';
|
import { jsonrepair } from 'jsonrepair';
|
||||||
|
|
||||||
type StreamQueryOptions = {
|
type StreamQueryOptions = {
|
@ -21,12 +21,17 @@ import {
|
|||||||
PaginatedRequest
|
PaginatedRequest
|
||||||
} from './conversion';
|
} from './conversion';
|
||||||
|
|
||||||
export class ApiClient {
|
class ApiClient {
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
private defaultHeaders: Record<string, string>;
|
private defaultHeaders: Record<string, string>;
|
||||||
|
|
||||||
constructor(baseUrl: string, authToken?: string) {
|
constructor(authToken?: string) {
|
||||||
this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
const loc = window.location;
|
||||||
|
if (!loc.host.match(/.*battle-linux.*/)) {
|
||||||
|
this.baseUrl = loc.protocol + "//" + loc.host + "/api/1.0";
|
||||||
|
} else {
|
||||||
|
this.baseUrl = loc.protocol + "//battle-linux.ketrenos.com:8912/api/1.0";
|
||||||
|
}
|
||||||
this.defaultHeaders = {
|
this.defaultHeaders = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...(authToken && { 'Authorization': `Bearer ${authToken}` })
|
...(authToken && { 'Authorization': `Bearer ${authToken}` })
|
||||||
@ -367,13 +372,6 @@ export class ApiClient {
|
|||||||
getBaseUrl(): string {
|
getBaseUrl(): string {
|
||||||
return this.baseUrl;
|
return this.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update base URL
|
|
||||||
*/
|
|
||||||
setBaseUrl(url: string): void {
|
|
||||||
this.baseUrl = url.replace(/\/$/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
@ -382,7 +380,7 @@ export class ApiClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
// Initialize API client
|
// Initialize API client
|
||||||
const apiClient = new ApiClient('https://api.yourjobplatform.com');
|
const apiClient = new ApiClient();
|
||||||
|
|
||||||
// Login and set auth token
|
// Login and set auth token
|
||||||
try {
|
try {
|
||||||
@ -573,4 +571,4 @@ function CandidateList() {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default ApiClient;
|
export { ApiClient };
|
@ -1,6 +1,6 @@
|
|||||||
// Generated TypeScript types from Pydantic models
|
// Generated TypeScript types from Pydantic models
|
||||||
// Source: src/models.py
|
// Source: src/models.py
|
||||||
// Generated on: 2025-05-27T23:44:38.806039
|
// Generated on: 2025-05-28T20:34:39.642452
|
||||||
// DO NOT EDIT MANUALLY - This file is auto-generated
|
// DO NOT EDIT MANUALLY - This file is auto-generated
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
@ -181,7 +181,7 @@ export interface Candidate {
|
|||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: "active" | "inactive" | "pending" | "banned";
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
userType?: "UserType.CANDIDATE";
|
userType?: "candidate";
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
@ -328,7 +328,7 @@ export interface Employer {
|
|||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: "active" | "inactive" | "pending" | "banned";
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
userType?: "UserType.EMPLOYER";
|
userType?: "employer";
|
||||||
companyName: string;
|
companyName: string;
|
||||||
industry: string;
|
industry: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -114,10 +114,18 @@ def python_type_to_typescript(python_type: Any) -> str:
|
|||||||
return f"Record<{key_type}, {value_type}>"
|
return f"Record<{key_type}, {value_type}>"
|
||||||
return "Record<string, any>"
|
return "Record<string, any>"
|
||||||
|
|
||||||
# Handle Literal types
|
# Handle Literal types - UPDATED SECTION
|
||||||
if hasattr(python_type, '__origin__') and str(python_type.__origin__).endswith('Literal'):
|
if hasattr(python_type, '__origin__') and str(python_type.__origin__).endswith('Literal'):
|
||||||
if args:
|
if args:
|
||||||
literal_values = [f'"{arg}"' if isinstance(arg, str) else str(arg) for arg in args]
|
literal_values = []
|
||||||
|
for arg in args:
|
||||||
|
if isinstance(arg, Enum):
|
||||||
|
# Handle enum values within literals
|
||||||
|
literal_values.append(f'"{arg.value}"')
|
||||||
|
elif isinstance(arg, str):
|
||||||
|
literal_values.append(f'"{arg}"')
|
||||||
|
else:
|
||||||
|
literal_values.append(str(arg))
|
||||||
return " | ".join(literal_values)
|
return " | ".join(literal_values)
|
||||||
|
|
||||||
# Handle Enum types
|
# Handle Enum types
|
||||||
@ -125,6 +133,10 @@ def python_type_to_typescript(python_type: Any) -> str:
|
|||||||
enum_values = [f'"{v.value}"' for v in python_type]
|
enum_values = [f'"{v.value}"' for v in python_type]
|
||||||
return " | ".join(enum_values)
|
return " | ".join(enum_values)
|
||||||
|
|
||||||
|
# Handle individual enum instances
|
||||||
|
if isinstance(python_type, Enum):
|
||||||
|
return f'"{python_type.value}"'
|
||||||
|
|
||||||
# Handle datetime
|
# Handle datetime
|
||||||
if python_type == datetime:
|
if python_type == datetime:
|
||||||
return "Date"
|
return "Date"
|
||||||
|
@ -1,461 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""
|
|
||||||
Enhanced Type Generator - Generate TypeScript types from Pydantic models
|
|
||||||
Now with command line parameters, pre-test validation, and TypeScript compilation
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import argparse
|
|
||||||
import subprocess
|
|
||||||
from typing import Any, Dict, List, Optional, Union, get_origin, get_args
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def run_command(command: str, description: str, cwd: str | None = None) -> bool:
|
|
||||||
"""Run a command and return success status"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
command,
|
|
||||||
shell=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
cwd=cwd
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
print(f"✅ {description}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"❌ {description} failed:")
|
|
||||||
if result.stderr.strip():
|
|
||||||
print(f" Error: {result.stderr.strip()}")
|
|
||||||
if result.stdout.strip():
|
|
||||||
print(f" Output: {result.stdout.strip()}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ {description} failed with exception: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def run_focused_test() -> bool:
|
|
||||||
"""Run the focused test to validate models before generating types"""
|
|
||||||
print("🧪 Running focused test to validate models...")
|
|
||||||
|
|
||||||
# Get the directory of the currently executing script
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
test_file_path = os.path.join(script_dir, "focused_test.py")
|
|
||||||
|
|
||||||
if not os.path.exists(test_file_path):
|
|
||||||
print("❌ focused_test.py not found - skipping model validation")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return run_command(f"python {test_file_path}", "Model validation")
|
|
||||||
|
|
||||||
def check_typescript_available() -> bool:
|
|
||||||
"""Check if TypeScript compiler is available"""
|
|
||||||
return run_command("npx tsc --version", "TypeScript version check")
|
|
||||||
|
|
||||||
# Add current directory to Python path so we can import models
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
sys.path.insert(0, current_dir)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pydantic import BaseModel # type: ignore
|
|
||||||
except ImportError as e:
|
|
||||||
print(f"Error importing pydantic: {e}")
|
|
||||||
print("Make sure pydantic is installed: pip install pydantic")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def python_type_to_typescript(python_type: Any) -> str:
|
|
||||||
"""Convert a Python type to TypeScript type string"""
|
|
||||||
|
|
||||||
# Handle None/null
|
|
||||||
if python_type is type(None):
|
|
||||||
return "null"
|
|
||||||
|
|
||||||
# Handle basic types
|
|
||||||
if python_type == str:
|
|
||||||
return "string"
|
|
||||||
elif python_type == int or python_type == float:
|
|
||||||
return "number"
|
|
||||||
elif python_type == bool:
|
|
||||||
return "boolean"
|
|
||||||
elif python_type == dict or python_type == Dict:
|
|
||||||
return "Record<string, any>"
|
|
||||||
elif python_type == list or python_type == List:
|
|
||||||
return "Array<any>"
|
|
||||||
|
|
||||||
# Handle typing generics
|
|
||||||
origin = get_origin(python_type)
|
|
||||||
args = get_args(python_type)
|
|
||||||
|
|
||||||
if origin is Union:
|
|
||||||
# Handle Optional (Union[T, None])
|
|
||||||
if len(args) == 2 and type(None) in args:
|
|
||||||
non_none_type = next(arg for arg in args if arg is not type(None))
|
|
||||||
return python_type_to_typescript(non_none_type)
|
|
||||||
|
|
||||||
# Handle other unions
|
|
||||||
union_types = [python_type_to_typescript(arg) for arg in args if arg is not type(None)]
|
|
||||||
return " | ".join(union_types)
|
|
||||||
|
|
||||||
elif origin is list or origin is List:
|
|
||||||
if args:
|
|
||||||
item_type = python_type_to_typescript(args[0])
|
|
||||||
return f"Array<{item_type}>"
|
|
||||||
return "Array<any>"
|
|
||||||
|
|
||||||
elif origin is dict or origin is Dict:
|
|
||||||
if len(args) == 2:
|
|
||||||
key_type = python_type_to_typescript(args[0])
|
|
||||||
value_type = python_type_to_typescript(args[1])
|
|
||||||
return f"Record<{key_type}, {value_type}>"
|
|
||||||
return "Record<string, any>"
|
|
||||||
|
|
||||||
# Handle Literal types
|
|
||||||
if hasattr(python_type, '__origin__') and str(python_type.__origin__).endswith('Literal'):
|
|
||||||
if args:
|
|
||||||
literal_values = [f'"{arg}"' if isinstance(arg, str) else str(arg) for arg in args]
|
|
||||||
return " | ".join(literal_values)
|
|
||||||
|
|
||||||
# Handle Enum types
|
|
||||||
if isinstance(python_type, type) and issubclass(python_type, Enum):
|
|
||||||
enum_values = [f'"{v.value}"' for v in python_type]
|
|
||||||
return " | ".join(enum_values)
|
|
||||||
|
|
||||||
# Handle datetime
|
|
||||||
if python_type == datetime:
|
|
||||||
return "Date"
|
|
||||||
|
|
||||||
# Handle Pydantic models
|
|
||||||
if isinstance(python_type, type) and issubclass(python_type, BaseModel):
|
|
||||||
return python_type.__name__
|
|
||||||
|
|
||||||
# Handle string representations
|
|
||||||
type_str = str(python_type)
|
|
||||||
if "EmailStr" in type_str:
|
|
||||||
return "string"
|
|
||||||
elif "HttpUrl" in type_str:
|
|
||||||
return "string"
|
|
||||||
elif "UUID" in type_str:
|
|
||||||
return "string"
|
|
||||||
|
|
||||||
# Default fallback
|
|
||||||
return "any"
|
|
||||||
|
|
||||||
def snake_to_camel(snake_str: str) -> str:
|
|
||||||
"""Convert snake_case to camelCase"""
|
|
||||||
components = snake_str.split('_')
|
|
||||||
return components[0] + ''.join(x.title() for x in components[1:])
|
|
||||||
|
|
||||||
def process_pydantic_model(model_class) -> Dict[str, Any]:
|
|
||||||
"""Process a Pydantic model and return TypeScript interface definition"""
|
|
||||||
interface_name = model_class.__name__
|
|
||||||
properties = []
|
|
||||||
|
|
||||||
# Get fields from the model
|
|
||||||
if hasattr(model_class, 'model_fields'):
|
|
||||||
# Pydantic v2
|
|
||||||
fields = model_class.model_fields
|
|
||||||
for field_name, field_info in fields.items():
|
|
||||||
ts_name = snake_to_camel(field_name)
|
|
||||||
|
|
||||||
# Check for alias
|
|
||||||
if hasattr(field_info, 'alias') and field_info.alias:
|
|
||||||
ts_name = field_info.alias
|
|
||||||
|
|
||||||
# Get type annotation
|
|
||||||
field_type = getattr(field_info, 'annotation', str)
|
|
||||||
ts_type = python_type_to_typescript(field_type)
|
|
||||||
|
|
||||||
# Check if optional
|
|
||||||
is_optional = False
|
|
||||||
if hasattr(field_info, 'is_required'):
|
|
||||||
is_optional = not field_info.is_required()
|
|
||||||
elif hasattr(field_info, 'default'):
|
|
||||||
is_optional = field_info.default is not None
|
|
||||||
|
|
||||||
properties.append({
|
|
||||||
'name': ts_name,
|
|
||||||
'type': ts_type,
|
|
||||||
'optional': is_optional
|
|
||||||
})
|
|
||||||
|
|
||||||
elif hasattr(model_class, '__fields__'):
|
|
||||||
# Pydantic v1
|
|
||||||
fields = model_class.__fields__
|
|
||||||
for field_name, field_info in fields.items():
|
|
||||||
ts_name = snake_to_camel(field_name)
|
|
||||||
|
|
||||||
if hasattr(field_info, 'alias') and field_info.alias:
|
|
||||||
ts_name = field_info.alias
|
|
||||||
|
|
||||||
field_type = getattr(field_info, 'annotation', getattr(field_info, 'type_', str))
|
|
||||||
ts_type = python_type_to_typescript(field_type)
|
|
||||||
|
|
||||||
is_optional = not getattr(field_info, 'required', True)
|
|
||||||
if hasattr(field_info, 'default') and field_info.default is not None:
|
|
||||||
is_optional = True
|
|
||||||
|
|
||||||
properties.append({
|
|
||||||
'name': ts_name,
|
|
||||||
'type': ts_type,
|
|
||||||
'optional': is_optional
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'name': interface_name,
|
|
||||||
'properties': properties
|
|
||||||
}
|
|
||||||
|
|
||||||
def process_enum(enum_class) -> Dict[str, Any]:
|
|
||||||
"""Process an Enum and return TypeScript type definition"""
|
|
||||||
enum_name = enum_class.__name__
|
|
||||||
values = [f'"{v.value}"' for v in enum_class]
|
|
||||||
if len(values) == 0:
|
|
||||||
raise ValueError(f"Enum class '{enum_name}' has no values.")
|
|
||||||
return {
|
|
||||||
'name': enum_name,
|
|
||||||
'values': " | ".join(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_typescript_interfaces(source_file: str):
|
|
||||||
"""Generate TypeScript interfaces from models"""
|
|
||||||
|
|
||||||
print(f"📖 Scanning {source_file} for Pydantic models and enums...")
|
|
||||||
|
|
||||||
# Import the models module dynamically
|
|
||||||
try:
|
|
||||||
import importlib.util
|
|
||||||
spec = importlib.util.spec_from_file_location("models", source_file)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Could not load module from {source_file}")
|
|
||||||
|
|
||||||
models_module = importlib.util.module_from_spec(spec)
|
|
||||||
sys.modules["models"] = models_module
|
|
||||||
spec.loader.exec_module(models_module)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error importing {source_file}: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
interfaces = []
|
|
||||||
enums = []
|
|
||||||
|
|
||||||
# Scan the models module
|
|
||||||
for name in dir(models_module):
|
|
||||||
obj = getattr(models_module, name)
|
|
||||||
|
|
||||||
# Skip private attributes
|
|
||||||
if name.startswith('_'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Check if it's a Pydantic model
|
|
||||||
if (isinstance(obj, type) and
|
|
||||||
issubclass(obj, BaseModel) and
|
|
||||||
obj != BaseModel):
|
|
||||||
|
|
||||||
interface = process_pydantic_model(obj)
|
|
||||||
interfaces.append(interface)
|
|
||||||
print(f" ✅ Found Pydantic model: {name}")
|
|
||||||
|
|
||||||
# Check if it's an Enum
|
|
||||||
elif (isinstance(obj, type) and
|
|
||||||
issubclass(obj, Enum)):
|
|
||||||
|
|
||||||
enum_def = process_enum(obj)
|
|
||||||
enums.append(enum_def)
|
|
||||||
print(f" ✅ Found enum: {name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ⚠️ Warning: Error processing {name}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"\n📊 Found {len(interfaces)} interfaces and {len(enums)} enums")
|
|
||||||
|
|
||||||
# Generate TypeScript content
|
|
||||||
ts_content = f"""// Generated TypeScript types from Pydantic models
|
|
||||||
// Source: {source_file}
|
|
||||||
// Generated on: {datetime.now().isoformat()}
|
|
||||||
// DO NOT EDIT MANUALLY - This file is auto-generated
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Add enums
|
|
||||||
if enums:
|
|
||||||
ts_content += "// ============================\n"
|
|
||||||
ts_content += "// Enums\n"
|
|
||||||
ts_content += "// ============================\n\n"
|
|
||||||
|
|
||||||
for enum_def in enums:
|
|
||||||
ts_content += f"export type {enum_def['name']} = {enum_def['values']};\n\n"
|
|
||||||
|
|
||||||
# Add interfaces
|
|
||||||
if interfaces:
|
|
||||||
ts_content += "// ============================\n"
|
|
||||||
ts_content += "// Interfaces\n"
|
|
||||||
ts_content += "// ============================\n\n"
|
|
||||||
|
|
||||||
for interface in interfaces:
|
|
||||||
ts_content += f"export interface {interface['name']} {{\n"
|
|
||||||
|
|
||||||
for prop in interface['properties']:
|
|
||||||
optional_marker = "?" if prop['optional'] else ""
|
|
||||||
ts_content += f" {prop['name']}{optional_marker}: {prop['type']};\n"
|
|
||||||
|
|
||||||
ts_content += "}\n\n"
|
|
||||||
|
|
||||||
# Add user union type if we have user types
|
|
||||||
user_interfaces = [i for i in interfaces if i['name'] in ['Candidate', 'Employer']]
|
|
||||||
if len(user_interfaces) >= 2:
|
|
||||||
ts_content += "// ============================\n"
|
|
||||||
ts_content += "// Union Types\n"
|
|
||||||
ts_content += "// ============================\n\n"
|
|
||||||
user_type_names = [i['name'] for i in user_interfaces]
|
|
||||||
ts_content += f"export type User = {' | '.join(user_type_names)};\n\n"
|
|
||||||
|
|
||||||
# Add export statement
|
|
||||||
ts_content += "// Export all types\n"
|
|
||||||
ts_content += "export type { };\n"
|
|
||||||
|
|
||||||
return ts_content
|
|
||||||
|
|
||||||
def compile_typescript(ts_file: str) -> bool:
|
|
||||||
"""Compile TypeScript file to check for syntax errors"""
|
|
||||||
print(f"🔧 Compiling TypeScript file to check syntax...")
|
|
||||||
|
|
||||||
# Check if TypeScript is available
|
|
||||||
if not check_typescript_available():
|
|
||||||
print("⚠️ TypeScript compiler not available - skipping compilation check")
|
|
||||||
print(" To install: npm install -g typescript")
|
|
||||||
return True # Don't fail if TS isn't available
|
|
||||||
|
|
||||||
# Run TypeScript compiler in check mode
|
|
||||||
return run_command(
|
|
||||||
f"npx tsc --noEmit --skipLibCheck {ts_file}",
|
|
||||||
"TypeScript syntax validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Main function with command line argument parsing"""
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Generate TypeScript types from Pydantic models',
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
epilog="""
|
|
||||||
Examples:
|
|
||||||
python generate_types.py # Use defaults
|
|
||||||
python generate_types.py --source models.py --output types.ts # Specify files
|
|
||||||
python generate_types.py --skip-test # Skip model validation
|
|
||||||
python generate_types.py --skip-compile # Skip TS compilation
|
|
||||||
python generate_types.py --source models.py --output types.ts --skip-test --skip-compile
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--source', '-s',
|
|
||||||
default='models.py',
|
|
||||||
help='Source Python file with Pydantic models (default: models.py)'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--output', '-o',
|
|
||||||
default='types.ts',
|
|
||||||
help='Output TypeScript file (default: types.ts)'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--skip-test',
|
|
||||||
action='store_true',
|
|
||||||
help='Skip running focused_test.py before generation'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--skip-compile',
|
|
||||||
action='store_true',
|
|
||||||
help='Skip TypeScript compilation check after generation'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--version', '-v',
|
|
||||||
action='version',
|
|
||||||
version='TypeScript Generator 2.0'
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
print("🚀 Enhanced TypeScript Type Generator")
|
|
||||||
print("=" * 50)
|
|
||||||
print(f"📁 Source file: {args.source}")
|
|
||||||
print(f"📁 Output file: {args.output}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Step 1: Validate source file exists
|
|
||||||
if not os.path.exists(args.source):
|
|
||||||
print(f"❌ Source file '{args.source}' not found")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Step 2: Run focused test (unless skipped)
|
|
||||||
if not args.skip_test:
|
|
||||||
if not run_focused_test():
|
|
||||||
print("❌ Model validation failed - aborting type generation")
|
|
||||||
sys.exit(1)
|
|
||||||
print()
|
|
||||||
else:
|
|
||||||
print("⏭️ Skipping model validation test")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Step 3: Generate TypeScript content
|
|
||||||
print("🔄 Generating TypeScript types...")
|
|
||||||
ts_content = generate_typescript_interfaces(args.source)
|
|
||||||
|
|
||||||
if ts_content is None:
|
|
||||||
print("❌ Failed to generate TypeScript content")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Step 4: Write to output file
|
|
||||||
with open(args.output, 'w') as f:
|
|
||||||
f.write(ts_content)
|
|
||||||
|
|
||||||
file_size = len(ts_content)
|
|
||||||
print(f"✅ TypeScript types generated: {args.output} ({file_size} characters)")
|
|
||||||
|
|
||||||
# Step 5: Compile TypeScript (unless skipped)
|
|
||||||
if not args.skip_compile:
|
|
||||||
print()
|
|
||||||
if not compile_typescript(args.output):
|
|
||||||
print("❌ TypeScript compilation failed - check the generated file")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print("⏭️ Skipping TypeScript compilation check")
|
|
||||||
|
|
||||||
# Step 6: Success summary
|
|
||||||
print(f"\n🎉 Type generation completed successfully!")
|
|
||||||
print("=" * 50)
|
|
||||||
print(f"✅ Generated {args.output} from {args.source}")
|
|
||||||
print(f"✅ File size: {file_size} characters")
|
|
||||||
if not args.skip_test:
|
|
||||||
print("✅ Model validation passed")
|
|
||||||
if not args.skip_compile:
|
|
||||||
print("✅ TypeScript syntax validated")
|
|
||||||
print(f"\n💡 Usage in your TypeScript project:")
|
|
||||||
print(f" import {{ Candidate, Employer, Job }} from './{Path(args.output).stem}';")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(f"\n⏹️ Type generation cancelled by user")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n❌ Error generating types: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
success = main()
|
|
||||||
sys.exit(0 if success else 1)
|
|
Loading…
x
Reference in New Issue
Block a user