Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.aichef.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;

@Slf4j
@Component
@RequiredArgsConstructor
public class DatabaseSchemaRepair {

private final JdbcTemplate jdbcTemplate;

@PostConstruct
public void ensureColumns() {
try {
jdbcTemplate.execute("ALTER TABLE meetings ADD COLUMN IF NOT EXISTS color VARCHAR(7)");
log.info("Schema repair check complete: meetings.color");
} catch (Exception e) {
log.warn("Schema repair failed for meetings.color: {}", e.getMessage());
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public record TelegramProperties(
@NotBlank String webhookSecret,
@NotBlank String webhookPath,
@NotBlank String apiBase,
String publicBaseUrl
String publicBaseUrl,
boolean useWebhook
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ public class TelegramWebhookRegistrar implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
String baseUrl = properties.publicBaseUrl();
log.info("Telegram config: botUsername={}, webhookPath={}, hasPublicBaseUrl={}, hasToken={}",
log.info("Telegram config: botUsername={}, webhookPath={}, hasPublicBaseUrl={}, hasToken={}, useWebhook={}",
properties.botUsername(),
properties.webhookPath(),
baseUrl != null && !baseUrl.isBlank(),
properties.botToken() != null && !properties.botToken().isBlank());
properties.botToken() != null && !properties.botToken().isBlank(),
properties.useWebhook());
telegramBotService.configureMiniAppEntryPoints();

if (!properties.useWebhook()) {
log.info("Webhook is disabled by TELEGRAM_USE_WEBHOOK=false. Deleting webhook and enabling polling.");
telegramBotService.deleteWebhook(false);
telegramBotService.logWebhookInfo();
return;
}

String webhookBaseUrl = resolveWebhookBaseUrl(baseUrl);
if (webhookBaseUrl == null) {
log.info("Webhook is disabled for current APP_PUBLIC_BASE_URL. Local mode: deleting webhook and enabling polling.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@

import com.aichef.domain.enums.Gender;
import com.aichef.domain.model.User;
import com.aichef.domain.model.UserGoogleConnection;
import com.aichef.repository.UserRepository;
import com.aichef.repository.UserGoogleConnectionRepository;
import com.aichef.service.MiniAppAuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.ZoneId;
import java.util.Optional;
import java.util.UUID;

Expand All @@ -23,6 +29,8 @@
public class MiniAppMeController {

private final MiniAppAuthService miniAppAuthService;
private final UserRepository userRepository;
private final UserGoogleConnectionRepository userGoogleConnectionRepository;

@GetMapping("/me")
public ResponseEntity<?> me(
Expand All @@ -40,7 +48,44 @@ public ResponseEntity<?> me(
Gender gender = user.getGender() == null ? Gender.UNKNOWN : user.getGender();
log.info("MiniApp profile loaded. userId={}, telegramId={}, gender={}",
user.getId(), user.getTelegramId(), gender);
return ResponseEntity.ok(new MeResponse(user.getId(), user.getTelegramId(), gender, toTitlePrefix(gender)));
UserGoogleConnection conn = userGoogleConnectionRepository.findByUser(user).orElse(null);
boolean googleConnected = conn != null
&& ((conn.getRefreshToken() != null && !conn.getRefreshToken().isBlank())
|| (conn.getAccessToken() != null && !conn.getAccessToken().isBlank()));
boolean icsConnected = conn != null && conn.getIcsToken() != null && !conn.getIcsToken().isBlank();
return ResponseEntity.ok(new MeResponse(
user.getId(),
user.getTelegramId(),
gender,
toTitlePrefix(gender),
user.getTimezone(),
new CalendarConnections(true, googleConnected, icsConnected)
));
}

@PatchMapping("/me/timezone")
public ResponseEntity<?> updateTimezone(
@RequestBody TimezoneUpdateRequest request,
@RequestHeader(value = "X-Telegram-Init-Data", required = false) String initData,
@RequestParam(value = "telegramId", required = false) Long telegramId
) {
Optional<User> userOpt = miniAppAuthService.resolveUser(initData, telegramId);
if (userOpt.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
}
String timezone = request == null || request.timezone() == null ? "" : request.timezone().trim();
if (timezone.isBlank()) {
return ResponseEntity.badRequest().body("timezone is required");
}
try {
ZoneId.of(timezone);
} catch (Exception e) {
return ResponseEntity.badRequest().body("Invalid timezone");
}
User user = userOpt.get();
user.setTimezone(timezone);
userRepository.save(user);
return ResponseEntity.ok(new TimezoneUpdateResponse(timezone));
}

private String toTitlePrefix(Gender gender) {
Expand All @@ -50,6 +95,26 @@ private String toTitlePrefix(Gender gender) {
return "Mr";
}

public record MeResponse(UUID id, Long telegramId, Gender gender, String titlePrefix) {
public record MeResponse(
UUID id,
Long telegramId,
Gender gender,
String titlePrefix,
String timezone,
CalendarConnections calendars
) {
}

public record CalendarConnections(
boolean internal,
boolean google,
boolean ical
) {
}

public record TimezoneUpdateRequest(String timezone) {
}

public record TimezoneUpdateResponse(String timezone) {
}
}
Loading