Skip to content

Commit b1f1a2d

Browse files
committed
release: Add whatsapp relay and inbox
1 parent b9219f6 commit b1f1a2d

22 files changed

Lines changed: 1258 additions & 26 deletions
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.flexcodelabs.flextuma.core.entities.whatsapp;
2+
3+
import com.flexcodelabs.flextuma.core.entities.base.Owner;
4+
import jakarta.persistence.Column;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.FetchType;
7+
import jakarta.persistence.JoinColumn;
8+
import jakarta.persistence.ManyToOne;
9+
import jakarta.persistence.Table;
10+
import jakarta.persistence.UniqueConstraint;
11+
import lombok.AllArgsConstructor;
12+
import lombok.Getter;
13+
import lombok.NoArgsConstructor;
14+
import lombok.Setter;
15+
16+
import java.time.LocalDateTime;
17+
18+
@Entity
19+
@Table(name = "whatsapp_inbox_message", uniqueConstraints = @UniqueConstraint(name = "uk_whatsapp_inbox_provider_message_id", columnNames = "provider_message_id"))
20+
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
21+
public class WhatsAppInboxMessage extends Owner {
22+
public static final String PLURAL = "whatsAppInboxMessages";
23+
public static final String NAME_PLURAL = "WhatsApp Inbox Messages";
24+
public static final String NAME_SINGULAR = "WhatsApp Inbox Message";
25+
public static final String READ = "READ_WHATSAPP_INBOX_MESSAGES";
26+
public static final String ADD = "ADD_WHATSAPP_INBOX_MESSAGES";
27+
public static final String DELETE = "DELETE_WHATSAPP_INBOX_MESSAGES";
28+
public static final String UPDATE = "UPDATE_WHATSAPP_INBOX_MESSAGES";
29+
30+
@ManyToOne(fetch = FetchType.LAZY)
31+
@JoinColumn(name = "config", nullable = false)
32+
private WhatsAppWebhookConfig config;
33+
34+
@Column(name = "from_number", nullable = false)
35+
private String fromNumber;
36+
37+
@Column(name = "contact_name")
38+
private String contactName;
39+
40+
@Column(name = "provider_message_id", nullable = false, unique = true)
41+
private String providerMessageId;
42+
43+
@Column(name = "message_type", nullable = false)
44+
private String messageType;
45+
46+
@Column(columnDefinition = "TEXT")
47+
private String content;
48+
49+
@Column(name = "received_at", nullable = false)
50+
private LocalDateTime receivedAt;
51+
52+
/** Unread when null. */
53+
@Column(name = "read_at")
54+
private LocalDateTime readAt;
55+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.flexcodelabs.flextuma.core.entities.whatsapp;
2+
3+
import com.flexcodelabs.flextuma.core.entities.base.Owner;
4+
import com.flexcodelabs.flextuma.core.enums.WhatsAppRelayStatus;
5+
import jakarta.persistence.Column;
6+
import jakarta.persistence.Entity;
7+
import jakarta.persistence.EnumType;
8+
import jakarta.persistence.Enumerated;
9+
import jakarta.persistence.FetchType;
10+
import jakarta.persistence.JoinColumn;
11+
import jakarta.persistence.ManyToOne;
12+
import jakarta.persistence.Table;
13+
import lombok.AllArgsConstructor;
14+
import lombok.Getter;
15+
import lombok.NoArgsConstructor;
16+
import lombok.Setter;
17+
18+
import java.time.LocalDateTime;
19+
20+
@Entity
21+
@Table(name = "whatsapp_relay_delivery")
22+
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
23+
public class WhatsAppRelayDelivery extends Owner {
24+
public static final String PLURAL = "whatsAppRelayDeliveries";
25+
public static final String NAME_PLURAL = "WhatsApp Relay Deliveries";
26+
public static final String NAME_SINGULAR = "WhatsApp Relay Delivery";
27+
public static final String READ = "READ_WHATSAPP_RELAY_DELIVERIES";
28+
public static final String ADD = "ADD_WHATSAPP_RELAY_DELIVERIES";
29+
public static final String DELETE = "DELETE_WHATSAPP_RELAY_DELIVERIES";
30+
public static final String UPDATE = "UPDATE_WHATSAPP_RELAY_DELIVERIES";
31+
32+
@ManyToOne(fetch = FetchType.LAZY)
33+
@JoinColumn(name = "config", nullable = false)
34+
private WhatsAppWebhookConfig config;
35+
36+
@Column(columnDefinition = "TEXT", nullable = false)
37+
private String payload;
38+
39+
@Column(nullable = false)
40+
@Enumerated(EnumType.STRING)
41+
private WhatsAppRelayStatus status = WhatsAppRelayStatus.PENDING;
42+
43+
@Column(nullable = false)
44+
private int retries = 0;
45+
46+
@Column(name = "last_attempt_at")
47+
private LocalDateTime lastAttemptAt;
48+
49+
@Column(name = "latency_ms")
50+
private Long latencyMs;
51+
52+
@Column(name = "last_error", columnDefinition = "TEXT")
53+
private String lastError;
54+
}

‎src/main/java/com/flexcodelabs/flextuma/core/entities/whatsapp/WhatsAppWebhookConfig.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ public class WhatsAppWebhookConfig extends Owner {
3333
@Column(name = "phone_number_id", nullable = false)
3434
private String phoneNumberId;
3535

36-
@NotBlank
37-
@Column(name = "callback_url", nullable = false, columnDefinition = "TEXT")
36+
@Column(name = "callback_url", columnDefinition = "TEXT")
3837
private String callbackUrl;
3938

4039
@NotBlank
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.flexcodelabs.flextuma.core.enums;
2+
3+
public enum WhatsAppRelayStatus {
4+
PENDING,
5+
PROCESSING,
6+
SUCCEEDED,
7+
FAILED
8+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.flexcodelabs.flextuma.core.helpers;
2+
3+
import javax.crypto.Mac;
4+
import javax.crypto.spec.SecretKeySpec;
5+
import java.nio.charset.StandardCharsets;
6+
7+
public final class HmacUtil {
8+
9+
private HmacUtil() {
10+
}
11+
12+
public static String sha256Hex(String payload, String secret) throws Exception {
13+
Mac mac = Mac.getInstance("HmacSHA256");
14+
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
15+
StringBuilder result = new StringBuilder();
16+
for (byte b : mac.doFinal(payload.getBytes(StandardCharsets.UTF_8))) {
17+
result.append(String.format("%02x", b));
18+
}
19+
return result.toString();
20+
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.flexcodelabs.flextuma.core.repositories;
2+
3+
import com.flexcodelabs.flextuma.core.entities.whatsapp.WhatsAppInboxMessage;
4+
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
5+
import org.springframework.stereotype.Repository;
6+
import java.util.UUID;
7+
8+
@Repository
9+
public interface WhatsAppInboxMessageRepository extends BaseRepository<WhatsAppInboxMessage, UUID>,
10+
JpaSpecificationExecutor<WhatsAppInboxMessage> {
11+
boolean existsByProviderMessageId(String providerMessageId);
12+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.flexcodelabs.flextuma.core.repositories;
2+
3+
import com.flexcodelabs.flextuma.core.entities.whatsapp.WhatsAppRelayDelivery;
4+
import com.flexcodelabs.flextuma.core.enums.WhatsAppRelayStatus;
5+
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6+
import org.springframework.data.jpa.repository.Modifying;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
9+
import org.springframework.stereotype.Repository;
10+
11+
import java.util.List;
12+
import java.util.UUID;
13+
14+
@Repository
15+
public interface WhatsAppRelayDeliveryRepository extends BaseRepository<WhatsAppRelayDelivery, UUID>,
16+
JpaSpecificationExecutor<WhatsAppRelayDelivery> {
17+
18+
List<WhatsAppRelayDelivery> findTop50ByStatusOrderByCreatedAsc(WhatsAppRelayStatus status);
19+
20+
@Modifying
21+
@Query("UPDATE WhatsAppRelayDelivery d SET d.status = :processing WHERE d.id = :id AND d.status = :pending")
22+
int claimPendingDelivery(@Param("id") UUID id,
23+
@Param("pending") WhatsAppRelayStatus pending,
24+
@Param("processing") WhatsAppRelayStatus processing);
25+
}

‎src/main/java/com/flexcodelabs/flextuma/core/services/DataSeederService.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ private void seedReadPrivileges() {
6969
"READ_WALLETS",
7070
"READ_SMS_LOGS",
7171
"READ_WHATSAPP_WEBHOOK_CONFIGS",
72+
"READ_WHATSAPP_INBOX_MESSAGES",
7273
"READ_SYSTEM_LOGS")
7374
.forEach(value -> {
7475
UUID id = UUID.nameUUIDFromBytes(value.getBytes(StandardCharsets.UTF_8));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.flexcodelabs.flextuma.modules.whatsapp.controllers;
2+
3+
import com.flexcodelabs.flextuma.core.controllers.BaseController;
4+
import com.flexcodelabs.flextuma.core.dtos.Pagination;
5+
import com.flexcodelabs.flextuma.core.entities.whatsapp.WhatsAppInboxMessage;
6+
import com.flexcodelabs.flextuma.modules.whatsapp.dtos.WhatsAppConversationDTO;
7+
import com.flexcodelabs.flextuma.modules.whatsapp.services.WhatsAppInboxMessageService;
8+
9+
import java.util.LinkedHashMap;
10+
import java.util.Map;
11+
import java.util.UUID;
12+
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.web.bind.annotation.GetMapping;
15+
import org.springframework.web.bind.annotation.PathVariable;
16+
import org.springframework.web.bind.annotation.PostMapping;
17+
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RequestParam;
19+
import org.springframework.web.bind.annotation.RestController;
20+
21+
@RestController
22+
@RequestMapping("/api/" + WhatsAppInboxMessage.PLURAL)
23+
public class WhatsAppInboxMessageController extends BaseController<WhatsAppInboxMessage, WhatsAppInboxMessageService> {
24+
25+
public WhatsAppInboxMessageController(WhatsAppInboxMessageService service) {
26+
super(service);
27+
}
28+
29+
@PostMapping("/{id}/read")
30+
public ResponseEntity<WhatsAppInboxMessage> markAsRead(@PathVariable UUID id) {
31+
return ResponseEntity.ok(service.markAsRead(id));
32+
}
33+
34+
@GetMapping("/conversations")
35+
public Map<String, Object> conversations(
36+
@RequestParam(defaultValue = "0") int page,
37+
@RequestParam(defaultValue = "25") int pageSize) {
38+
Pagination<WhatsAppConversationDTO> result = service.listConversations(page, pageSize);
39+
40+
Map<String, Object> response = new LinkedHashMap<>();
41+
response.put("page", result.getPage());
42+
response.put("total", result.getTotal());
43+
response.put("pageSize", result.getPageSize());
44+
response.put("conversations", result.getData());
45+
return response;
46+
}
47+
}

‎src/main/java/com/flexcodelabs/flextuma/modules/whatsapp/controllers/WhatsAppWebhookConfigController.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
import com.flexcodelabs.flextuma.core.controllers.BaseController;
44
import com.flexcodelabs.flextuma.core.entities.whatsapp.WhatsAppWebhookConfig;
5+
import com.flexcodelabs.flextuma.modules.whatsapp.dtos.WhatsAppWebhookOverviewDTO;
56
import com.flexcodelabs.flextuma.modules.whatsapp.services.WhatsAppWebhookConfigService;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.GetMapping;
69
import org.springframework.web.bind.annotation.RequestMapping;
710
import org.springframework.web.bind.annotation.RestController;
811

912
@RestController
1013
@RequestMapping("/api/" + WhatsAppWebhookConfig.PLURAL)
1114
public class WhatsAppWebhookConfigController extends BaseController<WhatsAppWebhookConfig, WhatsAppWebhookConfigService> {
1215
public WhatsAppWebhookConfigController(WhatsAppWebhookConfigService service) { super(service); }
16+
17+
@GetMapping("/overview")
18+
public ResponseEntity<WhatsAppWebhookOverviewDTO> overview() {
19+
return ResponseEntity.ok(service.getOverview());
20+
}
1321
}

0 commit comments

Comments
 (0)