PromucFlow_constructor/.cursor/docs/references/technical-details.md

699 lines
18 KiB
Markdown
Raw Normal View History

refactor: restructure .cursor directory for improved organization and clarity (#40196) # refactor: restructure .cursor directory for improved organization and clarity ## Description This PR refactors the `.cursor` directory to enhance organization, clarity, and maintainability. ### Problem The existing `.cursor` directory lacked clear organization, making it difficult to find specific files, understand their purpose, and add new components consistently. ### Solution A comprehensive restructuring: #### New Directory Structure ``` .cursor/ ├── settings.json # Main configuration file ├── docs/ # Documentation │ ├── guides/ # In-depth guides │ ├── references/ # Quick references │ └── practices/ # Best practices ├── rules/ # Rule definitions │ ├── commit/ # Commit-related rules │ ├── quality/ # Code quality rules │ ├── testing/ # Testing rules │ └── verification/ # Verification rules └── hooks/ # Git hooks and scripts ``` #### Key Changes 1. **Logical Categorization**: Organized files into clear categories based on purpose 2. **Improved Documentation**: Added comprehensive README files for each directory 3. **Standardized Naming**: Implemented consistent kebab-case naming convention 4. **Reference Updates**: Updated all internal references to point to new file locations ### Benefits - **Easier Navigation**: Clear categorization makes finding files intuitive - **Improved Understanding**: Comprehensive documentation explains purpose and usage - **Simplified Maintenance**: Logical structure makes updates and additions easier - **Better Onboarding**: New team members can quickly understand the system This refactoring sets a solid foundation for all Cursor AI-related configurations and rules, making it easier for the team to leverage Cursor's capabilities.
2025-04-11 06:34:33 +00:00
# Appsmith Technical Details
This document provides in-depth technical information about the Appsmith codebase, focusing on implementation details, design patterns, and technologies used. This information will help Cursor AI better understand the code at a deeper level.
## Technology Stack
### Frontend
- **Framework**: React 17+
- **State Management**: Redux with Redux-Saga
- **Language**: TypeScript 4+
- **Styling**: Styled Components with Tailwind CSS
- **Build Tool**: Webpack
- **Testing**: Jest, React Testing Library, Cypress
- **Form Management**: Formik
- **API Client**: Axios
- **UI Components**: Custom component library
### Backend
- **Framework**: Spring Boot 2.x
- **Language**: Java 11+
- **Database**: MongoDB
- **Reactive Programming**: Project Reactor
- **Security**: Spring Security
- **API Documentation**: Swagger/OpenAPI
- **Caching**: Redis
## Key Frontend Implementation Details
### State Management
The application uses Redux with a sophisticated structure:
```typescript
// Example action
export const fetchDatasources = (applicationId: string) => ({
type: ReduxActionTypes.FETCH_DATASOURCES,
payload: { applicationId },
});
// Example reducer
const datasourceReducer = (state = initialState, action: ReduxAction<any>) => {
switch (action.type) {
case ReduxActionTypes.FETCH_DATASOURCES_SUCCESS:
return { ...state, list: action.payload };
// ...
}
};
// Example saga
function* fetchDatasourcesSaga(action: ReduxAction<{ applicationId: string }>) {
try {
const response = yield call(DatasourcesApi.fetchDatasources, action.payload.applicationId);
yield put({
type: ReduxActionTypes.FETCH_DATASOURCES_SUCCESS,
payload: response.data,
});
} catch (error) {
yield put({
type: ReduxActionTypes.FETCH_DATASOURCES_ERROR,
payload: { error },
});
}
}
```
### Widget System
Widgets are the building blocks of the application UI. They follow a standard structure:
```typescript
export type WidgetProps = {
widgetId: string;
type: string;
widgetName: string;
parentId?: string;
renderMode: RenderMode;
version: number;
// ...other properties
};
export default class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
static getPropertyPaneConfig() {
return [
{
sectionName: "General",
children: [
{
propertyName: "text",
label: "Label",
controlType: "INPUT_TEXT",
// ...
},
// ...other properties
],
},
// ...other sections
];
}
getPageView() {
return (
<ButtonComponent
// ...props
onClick={this.handleClick}
/>
);
}
handleClick = () => {
if (this.props.onClick) {
super.executeAction({
triggerPropertyName: "onClick",
dynamicString: this.props.onClick,
event: {
type: EventType.ON_CLICK,
// ...
},
});
}
};
}
```
### Property Pane System
The property pane is dynamically generated based on the widget configuration:
```typescript
export const PropertyPaneView = (props: PropertyPaneViewProps) => {
const { config, panel } = props;
// Render property sections
return (
<PropertyPaneContainer>
{config.map((section) => (
<PropertySection key={section.sectionName} title={section.sectionName}>
{section.children.map((property) => (
<PropertyControl
key={property.propertyName}
propertyName={property.propertyName}
controlType={property.controlType}
// ...other props
/>
))}
</PropertySection>
))}
</PropertyPaneContainer>
);
};
```
### Data Binding
The app uses a JS evaluation engine to bind data to widgets:
```typescript
export function evaluateDynamicValue(
dynamicValue: string,
data: Record<string, unknown>,
): any {
// Set up execution environment
const scriptToEvaluate = `
function evaluation() {
const $ = ${JSON.stringify(data)};
try {
return ${dynamicValue};
} catch (e) {
return undefined;
}
}
evaluation();
`;
try {
return eval(scriptToEvaluate);
} catch (e) {
return undefined;
}
}
```
### API Integration
The API client is set up with Axios and handles authentication:
```typescript
const axiosInstance = axios.create({
baseURL: "/api/v1",
headers: {
"Content-Type": "application/json",
},
});
// Request interceptor for adding auth token
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem("AUTH_TOKEN");
if (token) {
// Basic validation - check if token is a valid JWT format
if (token.split('.').length === 3) {
config.headers.Authorization = `Bearer ${token}`;
} else {
// Handle invalid token - could log user out or refresh token
store.dispatch(refreshToken());
}
}
return config;
});
// Response interceptor for handling errors
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
// Handle unauthorized
store.dispatch(logoutUser());
}
return Promise.reject(error);
}
);
```
## Key Backend Implementation Details
### Repository Pattern
Mongo repositories use reactive programming:
```java
@Repository
public interface DatasourceRepository extends ReactiveMongoRepository<Datasource, String> {
Mono<Datasource> findByNameAndOrganizationId(String name, String organizationId);
Flux<Datasource> findAllByOrganizationId(String organizationId);
Mono<Long> countByNameAndOrganizationId(String name, String organizationId);
}
```
### Service Layer
Services handle business logic:
```java
@Service
@RequiredArgsConstructor
public class DatasourceServiceImpl implements DatasourceService {
private final DatasourceRepository repository;
private final PluginService pluginService;
@Override
public Mono<Datasource> create(Datasource datasource) {
return repository.save(datasource)
.flatMap(saved -> pluginService.getById(datasource.getPluginId())
.map(plugin -> {
saved.setPlugin(plugin);
return saved;
}));
}
// Other methods...
}
```
### Controller Layer
Controllers expose REST APIs:
```java
@RestController
@RequestMapping("/api/v1/datasources")
@RequiredArgsConstructor
public class DatasourceController {
private final DatasourceService service;
@PostMapping
public Mono<ResponseDTO<Datasource>> create(@RequestBody DatasourceDTO dto) {
Datasource datasource = new Datasource();
BeanUtils.copyProperties(dto, datasource);
return service.create(datasource)
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
}
// Other endpoints...
}
```
### Security Configuration
Spring Security setup:
```java
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.authorizeExchange()
.pathMatchers("/api/v1/public/**").permitAll()
.pathMatchers("/api/v1/auth/**").permitAll()
.anyExchange().authenticated()
.and()
.addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
// Other beans...
}
```
### Query Execution
Action execution is handled in a structured way:
```java
@Service
@RequiredArgsConstructor
public class ActionExecutionServiceImpl implements ActionExecutionService {
private final ActionExecutorFactory executorFactory;
@Override
public Mono<ActionExecutionResult> executeAction(ActionDTO action) {
ActionExecutor executor = executorFactory.getExecutor(action.getPluginType());
if (executor == null) {
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_PLUGIN_ACTION));
}
return executor.execute(action);
}
}
```
## Advanced Patterns
### Code Splitting
```typescript
// Lazy loading of components
const ApplicationPage = React.lazy(() => import("pages/Applications"));
const EditorPage = React.lazy(() => import("pages/Editor"));
// Router setup
const routes = [
{
path: "/applications",
component: ApplicationPage,
},
{
path: "/app/editor/:applicationId/:pageId",
component: EditorPage,
},
// ...
];
```
### Plugin System
The plugin system allows extensibility:
```java
public interface PluginExecutor<T, U> {
Mono<ActionExecutionResult> execute(T connection, U datasourceConfiguration, Object executeActionDTO);
Mono<T> datasourceCreate(U datasourceConfiguration);
void datasourceDestroy(T connection);
Set<String> getHintMessages(U datasourceConfiguration);
// ...
}
```
### Reactive Caching
```java
@Service
public class CacheableRepositoryHelper {
private final Map<String, Cache<String, Object>> cacheMap = new ConcurrentHashMap<>();
public <T> Mono<T> fetchFromCache(String cacheName, String key, Supplier<Mono<T>> fetchFunction) {
Cache<String, Object> cache = cacheMap.computeIfAbsent(cacheName, k ->
Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build());
Object cachedValue = cache.getIfPresent(key);
if (cachedValue != null) {
return Mono.just((T) cachedValue);
}
return fetchFunction.get()
.doOnNext(value -> cache.put(key, value));
}
}
```
### Action Collection System
Actions are grouped into collections for better organization:
```java
@Document
public class ActionCollection {
@Id
private String id;
private String name;
private String applicationId;
private String organizationId;
private String pageId;
private List<ActionDTO> actions;
private List<String> actionIds;
private String body;
// ...
}
```
## Common Code Patterns
### Error Handling
Frontend error handling:
```typescript
// Global error boundary
export class AppErrorBoundary extends React.Component<{}, { hasError: boolean }> {
constructor(props: {}) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
logError(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorPage />;
}
return this.props.children;
}
}
```
Backend error handling:
```java
@ExceptionHandler(AppsmithException.class)
public Mono<ResponseEntity<ResponseDTO<Object>>> handleAppsmithException(AppsmithException exception) {
log.error("Application error: {}", exception.getMessage(), exception);
ResponseDTO<Object> response = new ResponseDTO<>(
exception.getHttpStatus().value(),
null,
new ErrorDTO(exception.getAppErrorCode(), exception.getMessage())
);
return Mono.just(ResponseEntity
.status(exception.getHttpStatus())
.body(response));
}
```
### Validation
Frontend validation:
```typescript
const validateWidgetName = (widgetName: string) => {
const nameRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
if (!nameRegex.test(widgetName)) {
return "Widget name must start with a letter and can contain only letters, numbers, and underscore";
}
if (widgetName.length > 30) {
return "Widget name must be less than 30 characters";
}
return undefined;
};
```
Backend validation:
```java
@Validated
@Service
public class UserServiceImpl implements UserService {
@Override
public Mono<User> create(@Valid UserDTO userDTO) {
// Implementation
}
}
public class UserDTO {
@NotBlank(message = "Email is mandatory")
@Email(message = "Invalid email format")
private String email;
@NotBlank(message = "Password is mandatory")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
// Other fields...
}
```
### Internationalization
```typescript
// i18n setup
const i18n = createI18n({
locale: getBrowserLocale(),
messages: {
en: enMessages,
fr: frMessages,
// Other languages...
},
});
// Usage in components
const MyComponent = () => {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome.title')}</h1>
<p>{t('welcome.message')}</p>
</div>
);
};
```
## Enterprise-specific Features
### Audit Logging
```java
@Service
@ConditionalOnProperty(prefix = "appsmith", name = "audit.enabled", havingValue = "true")
public class AuditServiceImpl implements AuditService {
private final AuditRepository repository;
@Override
public Mono<AuditLog> log(String action, String resourceId, String resourceType, User user) {
AuditLog log = new AuditLog();
log.setAction(action);
log.setResourceId(resourceId);
log.setResourceType(resourceType);
log.setUserId(user.getId());
log.setUsername(user.getUsername());
log.setTimestamp(Instant.now());
return repository.save(log);
}
}
```
### Role-Based Access Control
```java
@Service
public class PermissionServiceImpl implements PermissionService {
@Override
public Mono<Boolean> hasPermission(User user, String resourceId, PermissionType permission) {
return userGroupRepository.findByUserIdAndOrganizationId(user.getId(), user.getCurrentOrganizationId())
.flatMap(userGroup -> {
if (userGroup.getRole() == UserRole.ORGANIZATION_ADMIN) {
return Mono.just(true);
}
return resourcePermissionRepository
.findByResourceIdAndPermission(resourceId, permission)
.any(resourcePermission -> resourcePermission.getUserGroupId().equals(userGroup.getId()));
});
}
}
```
### SSO Integration
```java
@Configuration
@ConditionalOnProperty(prefix = "appsmith.oauth2", name = "enabled", havingValue = "true")
public class OAuth2Config {
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> registrations = new ArrayList<>();
registrations.add(googleClientRegistration());
registrations.add(githubClientRegistration());
// Other providers...
return new InMemoryReactiveClientRegistrationRepository(registrations);
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId(googleClientId)
.clientSecret(googleClientSecret)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/api/v1/oauth2/callback/{registrationId}")
.scope("openid", "email", "profile")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
}
```
## Performance Optimization Techniques
### Frontend Optimizations
1. **Memoization**:
```typescript
const MemoizedComponent = React.memo(MyComponent);
// Using useMemo for computed values
const computedValue = useMemo(() => {
return expensiveComputation(a, b);
}, [a, b]);
// Using useCallback for functions
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
```
2. **Virtualization for Large Lists**:
```typescript
import { FixedSizeList } from 'react-window';
const MyList = ({ items }) => (
<FixedSizeList
height={500}
width={500}
itemCount={items.length}
itemSize={50}
>
{({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)}
</FixedSizeList>
);
```
### Backend Optimizations
1. **Batch Processing**:
```java
@Service
public class BatchImportServiceImpl implements BatchImportService {
@Override
public Mono<ImportResult> importEntities(List<Entity> entities) {
return Flux.fromIterable(entities)
.flatMap(this::validateEntity)
.collectList()
.flatMap(validatedEntities ->
repository.saveAll(validatedEntities)
.collectList()
.map(savedEntities -> new ImportResult(savedEntities.size(), null))
);
}
}
```
2. **Query Optimization**:
```java
// Using MongoDB indexes
@Document
public class Application {
// ...
@Indexed
private String name;
@Indexed
private String organizationId;
@CompoundIndex(def = "{'organizationId': 1, 'name': 1}", unique = true)
// ...
}
```
## This document should provide Cursor with a deeper understanding of the technical implementation details of Appsmith, allowing for more accurate and contextual assistance when working with the codebase.