Skip to content
Merged

Dev #11

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
c48d88d
style(frontend): polish dark/light theme styling, modals, and builder…
polichdev Sep 4, 2026
3ecd4e3
feat(ai): implement persistent chat sessions and message history API
polichdev Sep 4, 2026
f0dc320
feat(ai): integrate backend chat history and redesign AI page layout
polichdev Sep 4, 2026
143a233
feat(broadcast): add update tag endpoint
polichdev Sep 4, 2026
6659d54
feat(crm): add edit modals for user fields & tags, custom field selec…
polichdev Sep 4, 2026
c761b30
feat(security): integrate Cloudflare Turnstile bot verification and o…
polichdev Sep 5, 2026
96b038f
feat(auth): integrate Cloudflare Turnstile widget and brand scrollbar…
polichdev Sep 5, 2026
7aa9428
docs(auth): update README and API guides with Cloudflare Turnstile in…
polichdev Sep 5, 2026
7e9649c
refactor(backend): eliminate hardcoded values, magic numbers and DRY …
polichdev Sep 5, 2026
02eb5b0
refactor(backend): decompose god classes and separate layer responsib…
polichdev Sep 5, 2026
794f858
refactor(backend): apply strategy pattern to action executor and unif…
polichdev Sep 5, 2026
6b1414e
refactor(backend): optimize db access eliminate n-plus-one and secure…
polichdev Sep 5, 2026
f03cb55
refactor(backend): replace unmanaged threads with virtual threads and…
polichdev Sep 5, 2026
9c741e5
refactor(backend): enforce strict request validation DTOs and elimina…
polichdev Sep 5, 2026
5f61bf4
refactor(backend): align REST status codes timing attack defense and …
polichdev Sep 5, 2026
18df1d1
refactor(backend): eliminate swallowed exceptions comments hardcoded …
polichdev Sep 5, 2026
688f6d1
refactor(backend): align REST status codes and standardize response DTOs
polichdev Sep 5, 2026
2933dfb
refactor(backend): extract ValidationType enum and fix ignored catch …
polichdev Sep 5, 2026
3925ad9
refactor(backend): optimize db access with entity graphs and fix tran…
polichdev Sep 5, 2026
7a85c55
refactor(backend): fix memory leak in rate limiter and optimize per-b…
polichdev Sep 5, 2026
e4e814f
refactor(backend): harden security config actuator exposure and conne…
polichdev Sep 5, 2026
8fde9db
refactor(backend): decompose FlowEngineServiceImpl and deduplicate ex…
polichdev Sep 5, 2026
f46c421
refactor(backend): decompose CrmServiceImpl into specialized services…
polichdev Sep 5, 2026
b12476d
refactor(backend): apply strategy pattern to MessageNodeExecutor blocks
polichdev Sep 5, 2026
9a0d9ae
refactor(backend): harden error handling and eliminate remaining swal…
polichdev Sep 5, 2026
f3652a6
refactor(backend): introduce SupportAppealService and streamline Auth…
polichdev Sep 5, 2026
aef42b8
refactor(backend): eliminate inline fully-qualified class names acros…
polichdev Sep 5, 2026
2aeece8
refactor(backend): resolve audit issues, optimize db queries and fix …
polichdev Sep 6, 2026
c124fdf
refactor(backend): eliminate magic numbers, strings, and hardcoded AP…
polichdev Sep 6, 2026
3732673
refactor(core): resolve hardcode, magic numbers, and DRY violations
polichdev Sep 7, 2026
5c6b04f
refactor(architecture): decouple layers, modernize zustand stores, an…
polichdev Sep 7, 2026
2e537bb
refactor(components): decompose god components and eliminate prop dri…
polichdev Sep 7, 2026
2dbaa27
refactor(patterns): apply solid principles, eliminate any casts, and …
polichdev Sep 7, 2026
967e425
perf(rendering): implement search debouncing and component memoization
polichdev Sep 7, 2026
262cf56
fix(lifecycle): eliminate race conditions, microtask hacks, and multi…
polichdev Sep 7, 2026
72abf4d
refactor(data-fetching): implement query key factory and fix websocke…
polichdev Sep 7, 2026
fe21570
feat(resilience): introduce granular error boundaries and localized e…
polichdev Sep 7, 2026
c2703ee
refactor(forms): support dynamic schema localization and form validation
polichdev Sep 7, 2026
3efc898
refactor(core): resolve remaining design tokens, magic numbers, svg c…
polichdev Sep 7, 2026
b6e6efe
refactor(theme): migrate raw hex tailwind classes to semantic design …
polichdev Sep 7, 2026
b274f46
revert: undo raw hex replacement to preserve theme-switching CSS rule…
polichdev Sep 7, 2026
1f3f98c
fix(styles): remove unused color tokens from theme in index.css
polichdev Sep 7, 2026
c27a41a
fix(layout): ensure contacts and orders containers span full viewport…
polichdev Sep 7, 2026
298bad0
perf(bundle): split monolithic bundle into vendor chunks and lazy loa…
polichdev Sep 7, 2026
a151480
refactor(utils): centralize relative date formatting and eliminate du…
polichdev Sep 7, 2026
ab58554
refactor(admin): decompose AdminStatsPage into modular widgets
polichdev Sep 7, 2026
6cc3ae7
refactor(admin): decompose AdminBlogPage into modular components
polichdev Sep 7, 2026
b8eb092
refactor(automations): decompose AutomationsPage and centralize block…
polichdev Sep 7, 2026
22b98c7
refactor(frontend): clean codebase, optimize crm chat filters and fix…
polichdev Sep 7, 2026
19e1c01
refactor(frontend): resolve issues, decompose AdminChats and harden s…
polichdev Sep 8, 2026
a953705
fix(flow-builder): eliminate node re-render storm by scoping useConne…
polichdev Sep 8, 2026
a0c8894
fix(i18n): prevent full app unmount on language change in LanguagePro…
polichdev Sep 8, 2026
6afe997
perf(dashboard): optimize contact count retrieval in DashboardLayout …
polichdev Sep 8, 2026
baeb0e2
fix(lint): resolve 103 ESLint warnings including stale closures and m…
polichdev Sep 8, 2026
ce8d1d0
refactor(flow-builder): replace window CustomEvents with Zustand flow…
polichdev Sep 8, 2026
a40eee0
feat(notifications): implement unified toast system with RQ and Axios…
polichdev Sep 8, 2026
1aa1893
perf(broadcast): eliminate multi-bot polling storm in useBroadcastBui…
polichdev Sep 8, 2026
fd15623
refactor(forms): migrate critical forms to react-hook-form and Zod wi…
polichdev Sep 8, 2026
e3d437e
refactor(dashboard-stats): decompose DashboardStatsPage into charts a…
polichdev Sep 8, 2026
44848cf
refactor(user-fields): decompose UserFieldsPanel into modular compone…
polichdev Sep 8, 2026
bcf3501
refactor(blog-editor): decompose BlogEditorView into modular block ed…
polichdev Sep 8, 2026
d2dda63
style(design-system): centralize neo-brutalist tokens in Tailwind v4 …
polichdev Sep 8, 2026
38b344d
refactor(virtualization): align virtual list implementation and prune…
polichdev Sep 8, 2026
768db51
perf(chat): lazy load emoji picker to eliminate initial route navigat…
polichdev Sep 8, 2026
ee7b4f6
fix(i18n): subscribe owner pages to language changes and translate se…
polichdev Sep 8, 2026
33b70bb
fix(security): eliminate Google OAuth token leak and protect against …
polichdev Sep 8, 2026
fe887b0
fix(api): prevent double toast notifications on mutation errors
polichdev Sep 8, 2026
f552090
perf(chat): optimize message bubble re-renders and precompute button …
polichdev Sep 8, 2026
f9ed341
refactor(broadcast): decompose useBroadcastBuilder into specialized s…
polichdev Sep 8, 2026
03454a3
refactor(ui): unify duplicate ConfirmModal and AdminUserDetailModal c…
polichdev Sep 8, 2026
563285e
fix(lifecycle): prevent state update on unmounted wizards and add loc…
polichdev Sep 8, 2026
ed61984
chore(cleanup): backend cache eviction update and frontend style/test…
polichdev Sep 8, 2026
b8010da
docs(readme): expand tech stack badges with kafka, stripe, k6, websoc…
polichdev Sep 8, 2026
839559b
style(ui): unify button hover states, modal actions, and analytics ch…
polichdev Sep 9, 2026
94337a6
fix(chat): eliminate slow initial transition via idle preloading and …
polichdev Sep 9, 2026
28aaac1
chore(misc): Update README
polichdev Sep 9, 2026
ce70263
Fix formatting issues in README.md
polichdev Sep 9, 2026
ada0cdb
feat(workspace): expand team access across all owner bots and polish …
polichdev Sep 10, 2026
4a4aff9
feat(flow): implement Math, Leaderboard and Cooldown node executors w…
polichdev Sep 11, 2026
7769e78
feat(builder): add Math, Leaderboard, Cooldown nodes and categorized …
polichdev Sep 11, 2026
d993646
feat(flow): add published schema support and scheduler node execution
polichdev Sep 12, 2026
078390e
feat(builder): add Scheduler node, publish flow, and polish node UI
polichdev Sep 12, 2026
d75f161
feat(backend): implement query/interaction nodes, contact notificatio…
polichdev Sep 14, 2026
87ee661
feat(frontend): add query/interaction editors, rich action editor and…
polichdev Sep 14, 2026
62e8036
feat(bot-engine): implement subscription check node executor with typ…
polichdev Sep 15, 2026
bba8b1d
feat(flow-builder): add subscription check node UI
polichdev Sep 15, 2026
a3c0e73
feat(bot): implement moderation node executor and content filtering s…
polichdev Sep 16, 2026
33e5695
feat(flow-builder): add moderation node and rule configuration panel
polichdev Sep 16, 2026
8c8c047
feat(bot): implement join request auto-approval and group captcha mod…
polichdev Sep 16, 2026
c5a5453
feat(flow-builder): add join request node and group captcha moderatio…
polichdev Sep 16, 2026
a4256a3
refactor(backend): clean up inline fully-qualified imports and annota…
polichdev Sep 16, 2026
117e542
refactor(bot): extract unified user display name formatting across se…
polichdev Sep 16, 2026
ca008b1
refactor(common): replace hardcoded exception messages with i18n mess…
polichdev Sep 16, 2026
f7bfb30
perf(bot): remove synchronous external avatar fetching from subscribe…
polichdev Sep 16, 2026
aa90b7f
perf(admin): replace full-table in-memory scans with database count a…
polichdev Sep 16, 2026
7ad9623
refactor(bot): use managed task scheduler and eliminate unmanaged thr…
polichdev Sep 16, 2026
36152d5
refactor(bot): decompose flow engine coordinator and eliminate circul…
polichdev Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@
[![Spring Boot 4.0.6](https://img.shields.io/badge/Spring%20Boot-4.0.6-brightgreen.svg?logo=springboot&logoColor=white)](https://spring.io/projects/spring-boot)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791.svg?logo=postgresql&logoColor=white)](https://www.postgresql.org/)
[![Redis](https://img.shields.io/badge/Redis-7-DC382D.svg?logo=redis&logoColor=white)](https://redis.io/)
[![Apache Kafka](https://img.shields.io/badge/Apache_Kafka-7.6-231F20.svg?logo=apachekafka&logoColor=white)](https://kafka.apache.org/)
[![Liquibase](https://img.shields.io/badge/Liquibase-Migrations-006699.svg?logo=liquibase&logoColor=white)](https://www.liquibase.com/)
[![WebSockets](https://img.shields.io/badge/WebSockets-STOMP-010101.svg?logo=socketdotio&logoColor=white)](https://docs.spring.io/spring-framework/reference/web/websocket.html)
[![React 19](https://img.shields.io/badge/React-19-61dafb.svg?logo=react&logoColor=black)](https://react.dev/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Tailwind CSS v4](https://img.shields.io/badge/Tailwind_CSS-v4-38b2ac.svg?logo=tailwindcss&logoColor=white)](https://tailwindcss.com/)
[![TanStack Query](https://img.shields.io/badge/TanStack_Query-v5-FF4154.svg?logo=reactquery&logoColor=white)](https://tanstack.com/query)
[![Zod](https://img.shields.io/badge/Zod-v4-3E67B1.svg?logo=zod&logoColor=white)](https://zod.dev/)
[![AI Multi-Router](https://img.shields.io/badge/AI-Multi--Router-8E75B2.svg?logo=googlegemini&logoColor=white)](docs/backend/decisions/README.md#adr-002-multi-provider-ai-fallback-router-architecture)
[![Stripe API](https://img.shields.io/badge/Stripe-API-635BFF.svg?logo=stripe&logoColor=white)](https://stripe.com/)
[![Playwright](https://img.shields.io/badge/Playwright-E2E_Tests-2EAD33.svg?logo=playwright&logoColor=white)](https://playwright.dev/)
[![Grafana k6](https://img.shields.io/badge/Grafana_k6-Load_Testing-7D64FF.svg?logo=k6&logoColor=white)](https://k6.io/)

[![Docker](https://img.shields.io/badge/Docker-Multi--Stage-2496ED.svg?logo=docker&logoColor=white)](https://www.docker.com/)
[![Nginx](https://img.shields.io/badge/Nginx-Reverse_Proxy-009639.svg?logo=nginx&logoColor=white)](https://nginx.org/)
[![Testcontainers](https://img.shields.io/badge/Testcontainers-Integration_Tests-0B1C28.svg)](https://testcontainers.com/)
[![Telegram Bot API](https://img.shields.io/badge/Telegram-Bot_API-26A5E4.svg?logo=telegram&logoColor=white)](https://core.telegram.org/bots/api)
[![Cloudflare Turnstile](https://img.shields.io/badge/Cloudflare-Turnstile-F38020.svg?logo=cloudflare&logoColor=white)](https://www.cloudflare.com/products/turnstile/)

---

Expand All @@ -39,8 +48,6 @@ It serves as a full-scale technical showcase of building resilient, high-concurr
<img src="docs/images/preview.gif" alt="Launchly Platform Overview" width="100%" />
</p>

---

## Tech Stack

### Backend — `backend/`
Expand All @@ -53,6 +60,7 @@ It serves as a full-scale technical showcase of building resilient, high-concurr
- **Transactional Outbox & DLQ**: Resilient asynchronous event delivery with retry backoff and Dead Letter Queue
- **Distributed Idempotency Layer**: Redis-backed replay protection on all critical mutation endpoints
- **Dynamic Tier-Based Rate Limiter**: Token-bucket algorithm with RFC headers and role-based quotas
- **Cloudflare Turnstile Verify API**: Server-side bot verification and credential stuffing protection on auth endpoints
- **Enterprise Security Headers**: Strict HSTS, Content-Type Options, Frame Options, Referrer and Permissions policies
- **GDPR & SOC2 PII Masking**: Automatic redacting of emails, phone numbers, and secrets in runtime logs
- **JPA Deep Entity Graphs & JDBC Batching**: High-throughput statement batching (size=50) and N+1 query elimination
Expand All @@ -71,6 +79,7 @@ It serves as a full-scale technical showcase of building resilient, high-concurr
- **TanStack Query v5**: Server state caching, background synchronization, and optimistic mutations
- **Zustand v5**: Client-only synchronous state (auth session, canvas selection, network health)
- **React Hook Form + Zod v4**: Strict type-safe form validation and DTO transformations
- **Cloudflare Turnstile Widget**: Non-blocking bot protection with dynamic theme and flexible sizing
- **React Router v7**: Nested dashboard layouts and role-based route guards
- **STOMP & SockJS Client**: Real-time live chat subscriptions
- **TanStack Virtual & Custom Virtualizer**: Scalable windowing for high-volume conversation and contact lists
Expand All @@ -90,6 +99,7 @@ It serves as a full-scale technical showcase of building resilient, high-concurr

- **Visual Bot Constructor**: Drag-and-drop conversational graph builder supporting Message, Menu, Action, Condition, and AI nodes.
- **Omnichannel CRM & Live Inbox**: Two-way Telegram chat with live agent intervention, conversation search, and lead status pipelines.
- **Cloudflare Turnstile Bot & Spam Protection**: Smart non-interactive challenge verification securing authentication flows (registration and login) with zero friction.
- **Transactional Outbox & Integrations**: Guaranteed at-least-once delivery for webhooks (Hotmart, e-commerce, CRM leads) with automatic retry backoff and DLQ tracking.
- **Distributed Idempotency Protection**: Safe concurrent execution prevention for AI generation, campaign dispatches, team invites, and support appeals.
- **Dynamic Tier Rate Limiting**: Multi-tenant token-bucket rate limiting per plan tier (`ROLE_OWNER`, `ROLE_PRO`, `ROLE_ENTERPRISE`, `ROLE_ADMIN`).
Expand Down
11 changes: 10 additions & 1 deletion backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<java.version>21</java.version>
<mapstruct.version>1.6.3</mapstruct.version>
<jjwt.version>0.12.6</jjwt.version>
<testcontainers.version>1.20.4</testcontainers.version>
<testcontainers.version>1.20.6</testcontainers.version>
<sonar.projectKey>polchduikt_launchly</sonar.projectKey>
<sonar.organization>polchduikt</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
Expand Down Expand Up @@ -101,6 +101,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
Expand Down Expand Up @@ -273,6 +277,10 @@
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>

<build>
Expand All @@ -297,6 +305,7 @@
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -82,7 +83,7 @@ public ResponseEntity<Void> toggleAutomation(@Parameter(description = "Automatio
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> blockAutomation(
@Parameter(description = "Automation ID") @PathVariable Long automationId,
@RequestBody(required = false) AdminBlockRequest request) {
@Valid @RequestBody(required = false) AdminBlockRequest request) {
adminAutomationService.blockAutomation(automationId, request);
return ResponseEntity.ok().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -82,7 +83,7 @@ public ResponseEntity<Void> cancelBroadcast(@Parameter(description = "Broadcast
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public ResponseEntity<Void> blockBroadcast(
@Parameter(description = "Broadcast ID") @PathVariable Long broadcastId,
@RequestBody(required = false) AdminBlockRequest request) {
@Valid @RequestBody(required = false) AdminBlockRequest request) {
adminBroadcastService.blockBroadcast(broadcastId, request);
return ResponseEntity.ok().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@Tag(name = "Admin: Outbox & DLQ", description = "Dead letter queue inspection and event replay management")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.launchly.admin.controller;

import com.launchly.admin.dto.CreateMessageRequest;
import com.launchly.admin.dto.SupportMessageDto;
import com.launchly.admin.dto.SupportTicketDto;
import com.launchly.support.dto.CreateMessageRequest;
import com.launchly.support.dto.SupportMessageDto;
import com.launchly.support.dto.SupportTicketDto;
import com.launchly.support.enums.TicketStatus;
import com.launchly.admin.service.AdminSupportChatService;
import com.launchly.common.exception.ErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -12,6 +13,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -66,7 +68,7 @@ public ResponseEntity<SupportTicketDto> getTicketDetail(@Parameter(description =
@PostMapping("/{id}/messages")
public ResponseEntity<SupportMessageDto> addMessage(
@Parameter(description = "Ticket ID") @PathVariable Long id,
@RequestBody CreateMessageRequest request,
@Valid @RequestBody CreateMessageRequest request,
@AuthenticationPrincipal UserDetails userDetails) {
return ResponseEntity.ok(adminSupportChatService.addMessage(id, request.getText(), userDetails.getUsername()));
}
Expand All @@ -91,7 +93,7 @@ public ResponseEntity<SupportTicketDto> toggleFavorite(@Parameter(description =
@PatchMapping("/{id}/status")
public ResponseEntity<SupportTicketDto> updateStatus(
@Parameter(description = "Ticket ID") @PathVariable Long id,
@Parameter(description = "New status: OPEN, PENDING, CLOSED") @RequestParam(required = false) String status) {
@Parameter(description = "New status: OPEN, PENDING, CLOSED") @RequestParam(required = false) TicketStatus status) {
return ResponseEntity.ok(adminSupportChatService.updateStatus(id, status));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
@Index(name = "idx_user_audit_logs_user_date", columnList = "user_id, created_at"),
@Index(name = "idx_audit_logs_user_created", columnList = "user_id, created_at DESC")
})
@Data
@Getter
@Setter
@ToString(exclude = {"user"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserAuditLog {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@EqualsAndHashCode.Include
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
Expand Down
36 changes: 23 additions & 13 deletions backend/src/main/java/com/launchly/admin/mapper/AdminMapper.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package com.launchly.admin.mapper;

import com.launchly.admin.dto.AdminAutomationDto;
import com.launchly.admin.dto.AdminBroadcastDetailDto;
import com.launchly.admin.dto.AdminBroadcastDto;
import com.launchly.admin.dto.AdminUserDetailDto;
import com.launchly.admin.dto.AdminUserDto;
import com.launchly.admin.dto.UserActivityDto;
import com.launchly.admin.dto.UserAutomationSummaryDto;
import com.launchly.admin.dto.UserBroadcastSummaryDto;
import com.launchly.admin.entity.UserAuditLog;
import com.launchly.auth.entity.User;
import com.launchly.bot.entity.Bot;
import com.launchly.bot.entity.FlowSchema;
import com.launchly.broadcast.entity.BroadcastCampaign;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import org.springframework.data.domain.Page;

import java.time.LocalDateTime;
import java.util.List;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface AdminMapper {
Expand Down Expand Up @@ -44,7 +57,7 @@ public interface AdminMapper {
@Mapping(target = "blockedAt", source = "campaign.blockedAt")
@Mapping(target = "createdByEmail", expression = "java(creator != null && creator.getEmail() != null ? creator.getEmail() : defaultAuthor)")
@Mapping(target = "authorName", expression = "java(creator != null && creator.getName() != null ? creator.getName() : defaultAuthor)")
@Mapping(target = "createdAt", expression = "java(campaign.getCreatedAt() != null ? campaign.getCreatedAt() : java.time.LocalDateTime.now())")
@Mapping(target = "createdAt", expression = "java(campaign.getCreatedAt() != null ? campaign.getCreatedAt() : LocalDateTime.now())")
AdminBroadcastDto toBroadcastDto(BroadcastCampaign campaign, User creator, String defaultAuthor);

@Mapping(target = "id", source = "campaign.id")
Expand All @@ -55,17 +68,16 @@ public interface AdminMapper {
@Mapping(target = "sentCount", expression = "java(campaign.getSentCount() != null ? campaign.getSentCount() : 0)")
@Mapping(target = "failedCount", expression = "java(campaign.getFailedCount() != null ? campaign.getFailedCount() : 0)")
@Mapping(target = "totalCount", expression = "java(campaign.getTotalCount() != null ? campaign.getTotalCount() : 0)")
@Mapping(target = "status", expression = "java(campaign.isBlocked() ? \"BLOCKED\" : (campaign.getStatus() != null ? campaign.getStatus().name() : \"DRAFT\"))")
@Mapping(target = "blocked", source = "campaign.blocked")
@Mapping(target = "status", expression = "java(campaign.getStatus() != null ? campaign.getStatus().name() : \"DRAFT\")")
@Mapping(target = "blockReason", source = "campaign.blockReason")
@Mapping(target = "blockedAt", source = "campaign.blockedAt")
@Mapping(target = "createdByEmail", expression = "java(creator != null && creator.getEmail() != null ? creator.getEmail() : defaultAuthor)")
@Mapping(target = "authorName", expression = "java(creator != null && creator.getName() != null ? creator.getName() : defaultAuthor)")
@Mapping(target = "authorId", expression = "java(creator != null ? creator.getId() : null)")
@Mapping(target = "createdAt", expression = "java(campaign.getCreatedAt() != null ? campaign.getCreatedAt() : java.time.LocalDateTime.now())")
@Mapping(target = "createdAt", expression = "java(campaign.getCreatedAt() != null ? campaign.getCreatedAt() : LocalDateTime.now())")
@Mapping(target = "scheduledAt", source = "campaign.scheduledAt")
@Mapping(target = "activities", source = "activities")
com.launchly.admin.dto.AdminBroadcastDetailDto toBroadcastDetailDto(BroadcastCampaign campaign, User creator, String defaultAuthor, org.springframework.data.domain.Page<com.launchly.admin.dto.UserActivityDto> activities);
AdminBroadcastDetailDto toBroadcastDetailDto(BroadcastCampaign campaign, User creator, String defaultAuthor, Page<UserActivityDto> activities);

@Mapping(target = "id", source = "flow.id")
@Mapping(target = "name", expression = "java(bot != null && bot.getName() != null ? bot.getName() : \"Flow #\" + flow.getId())")
Expand All @@ -79,8 +91,8 @@ public interface AdminMapper {
@Mapping(target = "blockedAt", expression = "java(bot != null ? bot.getBlockedAt() : null)")
@Mapping(target = "triggerCount", source = "runsCount")
@Mapping(target = "errorCount", constant = "0L")
@Mapping(target = "lastExecutedAt", expression = "java(flow.getUpdatedAt() != null ? flow.getUpdatedAt() : java.time.LocalDateTime.now())")
com.launchly.admin.dto.AdminAutomationDto toAutomationDto(com.launchly.bot.entity.FlowSchema flow, com.launchly.bot.entity.Bot bot, User owner, String resolvedBotName, boolean isConnected, int runsCount);
@Mapping(target = "lastExecutedAt", expression = "java(flow.getUpdatedAt() != null ? flow.getUpdatedAt() : LocalDateTime.now())")
AdminAutomationDto toAutomationDto(FlowSchema flow, Bot bot, User owner, String resolvedBotName, boolean isConnected, int runsCount);

@Mapping(target = "id", source = "log.id")
@Mapping(target = "targetId", source = "log.targetId")
Expand All @@ -90,23 +102,23 @@ public interface AdminMapper {
@Mapping(target = "category", source = "log.category")
@Mapping(target = "badge", source = "log.badge")
@Mapping(target = "timestamp", source = "log.createdAt")
com.launchly.admin.dto.UserActivityDto toActivityDto(com.launchly.admin.entity.UserAuditLog log);
UserActivityDto toActivityDto(UserAuditLog log);

@Mapping(target = "id", source = "flow.id")
@Mapping(target = "name", expression = "java(flow.getBot() != null ? flow.getBot().getName() : \"Flow #\" + flow.getId())")
@Mapping(target = "botName", source = "resolvedBotName")
@Mapping(target = "active", expression = "java(flow.getBot() != null && flow.getBot().isActive() && isConnected)")
@Mapping(target = "triggerCount", source = "runs")
@Mapping(target = "triggerType", constant = "KEYWORD")
com.launchly.admin.dto.UserAutomationSummaryDto toAutomationSummaryDto(com.launchly.bot.entity.FlowSchema flow, String resolvedBotName, boolean isConnected, int runs);
UserAutomationSummaryDto toAutomationSummaryDto(FlowSchema flow, String resolvedBotName, boolean isConnected, int runs);

@Mapping(target = "id", source = "campaign.id")
@Mapping(target = "name", source = "campaign.name")
@Mapping(target = "botName", expression = "java(campaign.getBot() != null ? campaign.getBot().getName() : \"—\")")
@Mapping(target = "status", expression = "java(campaign.getStatus() != null ? campaign.getStatus().name() : \"DRAFT\")")
@Mapping(target = "sentCount", expression = "java(campaign.getSentCount() != null ? campaign.getSentCount() : 0)")
@Mapping(target = "createdAt", expression = "java(campaign.getCreatedAt() != null ? campaign.getCreatedAt().toString() : \"\")")
com.launchly.admin.dto.UserBroadcastSummaryDto toBroadcastSummaryDto(BroadcastCampaign campaign);
UserBroadcastSummaryDto toBroadcastSummaryDto(BroadcastCampaign campaign);

@Mapping(target = "id", source = "user.id")
@Mapping(target = "email", source = "user.email")
Expand All @@ -130,7 +142,5 @@ public interface AdminMapper {
@Mapping(target = "activities", source = "activities")
@Mapping(target = "automations", source = "automations")
@Mapping(target = "broadcasts", source = "broadcasts")
com.launchly.admin.dto.AdminUserDetailDto toUserDetailDto(User user, long botsCount, long automationsCount, long broadcastsCount, long contactsCount, String planName, java.time.LocalDateTime lastActivity, org.springframework.data.domain.Page<com.launchly.admin.dto.UserActivityDto> activities, java.util.List<com.launchly.admin.dto.UserAutomationSummaryDto> automations, java.util.List<com.launchly.admin.dto.UserBroadcastSummaryDto> broadcasts);
AdminUserDetailDto toUserDetailDto(User user, long botsCount, long automationsCount, long broadcastsCount, long contactsCount, String planName, LocalDateTime lastActivity, Page<UserActivityDto> activities, List<UserAutomationSummaryDto> automations, List<UserBroadcastSummaryDto> broadcasts);
}


Loading
Loading