diff --git a/src/main/java/com/involutionhell/backend/community/controller/SharedLinkInternalController.java b/src/main/java/com/involutionhell/backend/community/controller/SharedLinkInternalController.java index 276701a..095eb11 100644 --- a/src/main/java/com/involutionhell/backend/community/controller/SharedLinkInternalController.java +++ b/src/main/java/com/involutionhell/backend/community/controller/SharedLinkInternalController.java @@ -28,9 +28,12 @@ * 机器人桥接渠道的提交接口。 * * 设计目标: - * - 部署在 127.0.0.1:8080,不走 Caddy(Caddyfile 不代理 /internal 路径) - * - 仅接受带正确 X-Internal-Key 的请求,密钥通过 env INTERNAL_API_KEY 注入 - * - 用 SharedLinkService.submitInternal,跳过 24h 限频,固定挂到 discord-bridge 系统账号 + * - ChatBot 走 127.0.0.1:8080 直连;但 api.involutionhell.com 的 Caddy 兜底和前端 + * /api/community/links/:path* rewrite 都会转发到这里,公网可达,唯一的门是下面的 key + * - 仅接受带正确 X-Internal-Key 的请求,密钥通过 env INTERNAL_API_KEY 注入。 + * 持 key 者能指定 discordUserId,即决定链接挂在哪个已绑定 Discord 的账号名下 + * - 用 SharedLinkService.submitInternal,跳过 24h 限频;分享人 Discord 已绑定站内账号时 + * 记到本人名下,否则挂 discord-bridge 系统账号 * * 路径选 /api/community/links/internal 而非独立 /internal/... 是为了: * - 沿用已有 SharedLinkController 的业务命名,读代码更顺手 @@ -105,10 +108,17 @@ public ResponseEntity> submit( return ResponseEntity.badRequest() .body(new ApiResponse<>(false, "url is required", null)); } + // Discord 用户 id 是 snowflake(纯数字,最长 20 位);不传或空串按没有处理 + String discordUserId = req.discordUserId() == null || req.discordUserId().isBlank() + ? null : req.discordUserId().trim(); + if (discordUserId != null && !discordUserId.matches("\\d{1,20}")) { + return ResponseEntity.badRequest() + .body(new ApiResponse<>(false, "invalid discordUserId", null)); + } try { SharedLink saved = service.submitInternal( - req.submitterLabel(), req.url(), req.recommendation()); + req.submitterLabel(), req.url(), req.recommendation(), discordUserId); return ResponseEntity.ok(ApiResponse.ok(SharedLinkView.from(saved))); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest() diff --git a/src/main/java/com/involutionhell/backend/community/dto/InternalShareRequest.java b/src/main/java/com/involutionhell/backend/community/dto/InternalShareRequest.java index 0072343..6a7affb 100644 --- a/src/main/java/com/involutionhell/backend/community/dto/InternalShareRequest.java +++ b/src/main/java/com/involutionhell/backend/community/dto/InternalShareRequest.java @@ -6,6 +6,8 @@ * submitterLabel: 原始分享人名(Discord 昵称),由桥接服务填写;展示在 recommendation 里 * url: 原始 URL(规范化交由后端 UrlNormalizer) * recommendation: 可选的推荐语(机器人自动渠道通常为空) + * discordUserId: 可选,分享人的 Discord 用户 id(纯数字)。已绑定到站内账号时链接记到本人名下 */ -public record InternalShareRequest(String url, String recommendation, String submitterLabel) { +public record InternalShareRequest(String url, String recommendation, String submitterLabel, + String discordUserId) { } diff --git a/src/main/java/com/involutionhell/backend/community/model/SharedLink.java b/src/main/java/com/involutionhell/backend/community/model/SharedLink.java index bf920dd..186d780 100644 --- a/src/main/java/com/involutionhell/backend/community/model/SharedLink.java +++ b/src/main/java/com/involutionhell/backend/community/model/SharedLink.java @@ -17,6 +17,7 @@ * - status:见 SharedLinkStatus * - ogFetchError:OG 抓取失败时记录原因,前端降级展示仍保留卡片 * - archivedAt / archivedReason:原文失效(HEAD 探活连续 2 次失败)后写入 + * - discordUserId:机器人渠道的分享人 Discord id,网页提交为 null */ public record SharedLink( Long id, @@ -37,6 +38,7 @@ public record SharedLink( Instant archivedAt, String archivedReason, Instant createdAt, - Instant updatedAt + Instant updatedAt, + String discordUserId ) { } diff --git a/src/main/java/com/involutionhell/backend/community/repository/JdbcSharedLinkRepository.java b/src/main/java/com/involutionhell/backend/community/repository/JdbcSharedLinkRepository.java index 25604d9..04a42c3 100644 --- a/src/main/java/com/involutionhell/backend/community/repository/JdbcSharedLinkRepository.java +++ b/src/main/java/com/involutionhell/backend/community/repository/JdbcSharedLinkRepository.java @@ -59,7 +59,8 @@ public JdbcSharedLinkRepository(JdbcTemplate jdbc, ObjectMapper objectMapper) { toInstant(rs.getTimestamp("archived_at")), rs.getString("archived_reason"), toInstant(rs.getTimestamp("created_at")), - toInstant(rs.getTimestamp("updated_at")) + toInstant(rs.getTimestamp("updated_at")), + rs.getString("discord_user_id") ); @Override @@ -68,8 +69,8 @@ public SharedLink insert(SharedLink draft) { // flags 列 Postgres 侧是 JSONB,H2 测试侧是 VARCHAR。用 setObject(Types.OTHER) + JSON 字符串 // 的方式让两种方言都能吃进去(与 JdbcEventRepository.speakers 的做法保持一致)。 String sql = "INSERT INTO shared_links " - + "(submitter_id, url, url_hash, host, recommendation, status, flags) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)"; + + "(submitter_id, url, url_hash, host, recommendation, status, flags, discord_user_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; jdbc.update(conn -> { PreparedStatement ps = conn.prepareStatement(sql, new String[]{"id"}); ps.setLong(1, draft.submitterId()); @@ -80,6 +81,7 @@ public SharedLink insert(SharedLink draft) { else ps.setString(5, draft.recommendation()); ps.setString(6, draft.status() != null ? draft.status() : SharedLinkStatus.PENDING); ps.setObject(7, serializeFlags(draft.flags()), Types.OTHER); + ps.setString(8, draft.discordUserId()); return ps; }, kh); Long id = kh.getKey() != null ? kh.getKey().longValue() : null; @@ -203,8 +205,10 @@ public int incrementReportCount(Long id) { @Override public int countBySubmitterSince(Long submitterId, Instant since) { + // 只数网页提交:机器人渠道本身不限频,已绑定 Discord 的人在群里分享不该占网页配额 Integer n = jdbc.queryForObject( - "SELECT COUNT(*) FROM shared_links WHERE submitter_id = ? AND created_at >= ?", + "SELECT COUNT(*) FROM shared_links WHERE submitter_id = ? AND created_at >= ? " + + "AND discord_user_id IS NULL", Integer.class, submitterId, Timestamp.from(since)); return n != null ? n : 0; } diff --git a/src/main/java/com/involutionhell/backend/community/repository/SharedLinkRepository.java b/src/main/java/com/involutionhell/backend/community/repository/SharedLinkRepository.java index b788679..6babbe7 100644 --- a/src/main/java/com/involutionhell/backend/community/repository/SharedLinkRepository.java +++ b/src/main/java/com/involutionhell/backend/community/repository/SharedLinkRepository.java @@ -53,7 +53,7 @@ void updateEnrichment(Long id, /** +1 report_count 并返回新值。3 及以上由 Service 层决定是否转 PENDING_MANUAL。 */ int incrementReportCount(Long id); - /** 限频判定:某用户在 since 之后提交了几条。 */ + /** 限频判定:某用户在 since 之后从网页提交了几条(不含机器人渠道)。 */ int countBySubmitterSince(Long submitterId, java.time.Instant since); /** M9 失效探活:拉所有 APPROVED 的 (id, url),用于 HEAD 探活。只拿需要的两列。 */ diff --git a/src/main/java/com/involutionhell/backend/community/service/SharedLinkService.java b/src/main/java/com/involutionhell/backend/community/service/SharedLinkService.java index e7ad976..51c313e 100644 --- a/src/main/java/com/involutionhell/backend/community/service/SharedLinkService.java +++ b/src/main/java/com/involutionhell/backend/community/service/SharedLinkService.java @@ -9,6 +9,7 @@ import com.involutionhell.backend.community.util.UrlNormalizer; import com.involutionhell.backend.usercenter.model.UserAccount; import com.involutionhell.backend.usercenter.service.UserCenterService; +import com.involutionhell.backend.usercenter.service.UserIdentityService; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +40,7 @@ public class SharedLinkService { private static final Logger log = LoggerFactory.getLogger(SharedLinkService.class); - /** 每用户每 24h 最多提交 N 条。滚动时间窗,不是自然日。 */ + /** 每用户每 24h 最多从网页提交 N 条(机器人渠道不计)。滚动时间窗,不是自然日。 */ static final int DAILY_SUBMIT_LIMIT = 5; /** 3 条独立举报自动下架。 */ @@ -55,6 +56,7 @@ public class SharedLinkService { * community 模块不应该直接摸 usercenter 的仓储层,跨模块访问只经过 service。 */ private final UserCenterService userCenterService; + private final UserIdentityService userIdentityService; /** * 缓存 discord-bridge 账号 id,避免每次 submitInternal 都查一次库。 @@ -74,10 +76,12 @@ public class SharedLinkService { public SharedLinkService(SharedLinkRepository linkRepo, LinkReportRepository reportRepo, - UserCenterService userCenterService) { + UserCenterService userCenterService, + UserIdentityService userIdentityService) { this.linkRepo = linkRepo; this.reportRepo = reportRepo; this.userCenterService = userCenterService; + this.userIdentityService = userIdentityService; } @Autowired @@ -175,7 +179,8 @@ public SharedLink submit(Long submitterId, String rawUrl, String recommendation) SharedLinkStatus.PENDING, 0, // report_count null, null, // archived - null, null // created/updated + null, null, // created/updated + null // discord_user_id:网页提交没有 ); SharedLink saved = linkRepo.insert(draft); log.info("shared-link submitted: id={} submitter={} host={}", @@ -194,16 +199,21 @@ public SharedLink submit(Long submitterId, String rawUrl, String recommendation) * * 与 submit() 的差异: * - 不做 24h 限频(机器人是整群一把提,限频会把合法流量卡死) - * - 不关心前端登录态:submitter 固定取 seed 的 discord-bridge 账号 + * - 不关心前端登录态:分享人的 Discord id 已绑定到可用账号时记到本人名下, + * 否则(没传 id / 没绑定 / 账号禁用)挂 seed 的 discord-bridge 账号 + * - discordUserId 原样落库,没绑定的人日后绑定了还能按 id 改挂 * - recommendation 前缀自动打「来自 Discord @{label}」,前端展示能看到真实分享人 * * 与 submit() 一致的: * - 同样走 UrlNormalizer、去重、OG + DeepSeek 异步富化 * - 同样的状态机(PENDING → APPROVED/PENDING_MANUAL/FLAGGED) * - 同样的 DuplicateKeyException 语义 + * + * @param discordUserId 分享人的 Discord 用户 id(纯数字),可为 null(如 MCP 渠道) */ @Transactional(rollbackFor = Exception.class) - public SharedLink submitInternal(String submitterLabel, String rawUrl, String recommendation) { + public SharedLink submitInternal(String submitterLabel, String rawUrl, String recommendation, + String discordUserId) { UrlNormalizer.Normalized norm = UrlNormalizer.normalize(rawUrl); Long resolvedBridgeId = bridgeId; @@ -215,6 +225,9 @@ public SharedLink submitInternal(String submitterLabel, String rawUrl, String re "discord-bridge 账号不存在,检查 schema.sql 是否已执行 seed")); this.bridgeId = resolvedBridgeId; } + Long submitterId = discordUserId == null + ? resolvedBridgeId + : userIdentityService.findEnabledUserId("discord", discordUserId).orElse(resolvedBridgeId); String urlHash = UrlNormalizer.sha256Hex(norm.canonicalUrl()); @@ -229,7 +242,7 @@ public SharedLink submitInternal(String submitterLabel, String rawUrl, String re SharedLink draft = new SharedLink( null, - resolvedBridgeId, + submitterId, norm.canonicalUrl(), urlHash, norm.host(), @@ -241,11 +254,12 @@ public SharedLink submitInternal(String submitterLabel, String rawUrl, String re SharedLinkStatus.PENDING, 0, null, null, - null, null + null, null, + discordUserId ); SharedLink saved = linkRepo.insert(draft); - log.info("shared-link submitted via bridge: id={} label={} host={}", - saved.id(), submitterLabel, saved.host()); + log.info("shared-link submitted via bridge: id={} label={} discordUserId={} submitter={} host={}", + saved.id(), submitterLabel, discordUserId, submitterId, saved.host()); if (enrichmentWorker != null) { enrichmentWorker.enrich(saved.id()); diff --git a/src/main/java/com/involutionhell/backend/usercenter/service/UserIdentityService.java b/src/main/java/com/involutionhell/backend/usercenter/service/UserIdentityService.java index 4930de0..5df76dd 100644 --- a/src/main/java/com/involutionhell/backend/usercenter/service/UserIdentityService.java +++ b/src/main/java/com/involutionhell/backend/usercenter/service/UserIdentityService.java @@ -1,11 +1,13 @@ package com.involutionhell.backend.usercenter.service; import com.involutionhell.backend.usercenter.dto.LinkedIdentityView; +import com.involutionhell.backend.usercenter.model.UserAccount; import com.involutionhell.backend.usercenter.model.UserIdentity; import com.involutionhell.backend.usercenter.repository.UserAccountRepository; import com.involutionhell.backend.usercenter.repository.UserIdentityRepository; import java.util.List; import java.util.Locale; +import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,6 +32,17 @@ public List listForUser(long userId) { .toList(); } + /** + * provider 身份 → 已启用账号的 id。没绑定或账号被禁用返回 empty。 + * 给机器人渠道把 Discord 分享归到本人名下用,不涉及登录。 + */ + public Optional findEnabledUserId(String provider, String providerUserId) { + return userIdentityRepository.findByProviderAndProviderUserId(provider, providerUserId) + .flatMap(i -> userAccountRepository.findById(i.userId())) + .filter(UserAccount::enabled) + .map(UserAccount::id); + } + /** * 解绑指定 provider 身份。返回解绑后剩余身份列表。 * diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index c8377ce..6647f43 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -35,8 +35,9 @@ ON CONFLICT (username) DO NOTHING; -- 双重禁用: -- 1. password_hash='!' —— 非 sha256 合法十六进制,口令校验永远失败 -- 2. enabled=FALSE —— 未来若有人重置密码 / 误改口令,enabled 仍挡住登录 --- 该账号仅被 SharedLinkService.submitInternal 取 submitter_id 用(FK 不 care enabled), --- 真实提交人名放在 recommendation 里("来自 Discord @xxx")。 +-- 该账号仅被 SharedLinkService.submitInternal 取 submitter_id 用(FK 不 care enabled): +-- 分享人的 Discord 没绑定站内账号时挂到这里,Discord id 记在 shared_links.discord_user_id, +-- 昵称放在 recommendation 里("来自 Discord @xxx")。 -- -- 注意:这里用 DO UPDATE 而非 DO NOTHING,是因为老版本 seed 落过 enabled=TRUE, -- 每次启动 reconcile 回 FALSE,可阻断历史遗留记录再次被误启用。 @@ -249,6 +250,7 @@ CREATE TABLE IF NOT EXISTS shared_links ( admin_note TEXT, -- 管理员人工审核备注(reject 原因 / approve 评语) probe_fail_count INT NOT NULL DEFAULT 0, -- M9 失效探活连续失败计数,>=2 → ARCHIVED probe_last_at TIMESTAMPTZ, -- 最近一次 HEAD 探活时间,用于跳过刚跑过的 + discord_user_id VARCHAR(32), -- 机器人渠道的分享人 Discord id;网页提交为 NULL created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); @@ -259,6 +261,10 @@ ALTER TABLE shared_links ADD COLUMN IF NOT EXISTS probe_last_at TIMESTAMPTZ; -- admin_note:管理员 approve/reject 时的备注(reject 原因、approve 评语等) -- 与 archived_reason 区分:后者是系统归档原因(link_dead 等),前者是人工审核原因 ALTER TABLE shared_links ADD COLUMN IF NOT EXISTS admin_note TEXT; +-- discord_user_id:机器人渠道记下分享人的 Discord id(昵称会改、会重名,id 不会)。 +-- 提交时已绑定 Discord 的账号直接记为 submitter;没绑定的仍挂 discord-bridge, +-- 留着 id 以便日后对方绑定后按 id 改挂。 +ALTER TABLE shared_links ADD COLUMN IF NOT EXISTS discord_user_id VARCHAR(32); CREATE INDEX IF NOT EXISTS idx_shared_links_status_created ON shared_links(status, created_at DESC); CREATE INDEX IF NOT EXISTS idx_shared_links_category ON shared_links(category) WHERE status = 'APPROVED'; diff --git a/src/test/java/com/involutionhell/backend/community/controller/SharedLinkInternalControllerIntegrationTests.java b/src/test/java/com/involutionhell/backend/community/controller/SharedLinkInternalControllerIntegrationTests.java index bc5381a..e78e49f 100644 --- a/src/test/java/com/involutionhell/backend/community/controller/SharedLinkInternalControllerIntegrationTests.java +++ b/src/test/java/com/involutionhell/backend/community/controller/SharedLinkInternalControllerIntegrationTests.java @@ -1,12 +1,18 @@ package com.involutionhell.backend.community.controller; +import com.involutionhell.backend.community.service.SharedLinkService; import com.involutionhell.backend.support.AbstractWebIntegrationTest; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.ResultActions; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -127,6 +133,118 @@ void submit_missingUrl_returns400() throws Exception { .andExpect(jsonPath("$.message").value("url is required")); } + // ── 分享人归属(discordUserId)────────────────────────────────────────── + // H2 库在整个测试进程内共享,用户名 / Discord id / URL 都取本类专用的值,避免撞别的测试 + + @Autowired + private JdbcTemplate jdbc; + + @Autowired + private SharedLinkService sharedLinkService; + + /** 别的测试会数用户总数,建的用户必须删掉(身份、链接随 FK 级联) */ + @AfterEach + void cleanUpAttributionFixtures() { + jdbc.update("DELETE FROM shared_links WHERE url LIKE 'https://arxiv.org/abs/2610.%'"); + jdbc.update("DELETE FROM user_accounts WHERE username LIKE 'share-attr-%'"); + } + + private long createUser(String username, boolean enabled) { + jdbc.update("INSERT INTO user_accounts (username, password_hash, display_name, enabled, roles, permissions) " + + "VALUES (?, '!', ?, ?, 'user', '')", username, username, enabled); + return jdbc.queryForObject("SELECT id FROM user_accounts WHERE username = ?", Long.class, username); + } + + private void linkDiscord(long userId, String discordUserId) { + jdbc.update("INSERT INTO user_identities (user_id, provider, provider_user_id) VALUES (?, 'discord', ?)", + userId, discordUserId); + } + + private long bridgeId() { + return jdbc.queryForObject("SELECT id FROM user_accounts WHERE username = 'discord-bridge'", Long.class); + } + + private String storedDiscordUserId(String url) { + return jdbc.queryForObject("SELECT discord_user_id FROM shared_links WHERE url = ?", String.class, url); + } + + private ResultActions submitAs(String url, String discordUserId) throws Exception { + String idField = discordUserId == null ? "" : ",\"discordUserId\":\"" + discordUserId + "\""; + return mockMvc.perform(post("/api/community/links/internal") + .header(KEY_HEADER, KEY) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"url\":\"" + url + "\",\"submitterLabel\":\"someone\"" + idField + "}")); + } + + @Test + @DisplayName("已绑定 Discord 的分享人 → 链接记到本人名下,Discord id 落库") + void submit_linkedDiscord_attributesToAccount() throws Exception { + long userId = createUser("share-attr-linked", true); + linkDiscord(userId, "900000000000000001"); + String url = "https://arxiv.org/abs/2610.10001"; + + submitAs(url, "900000000000000001") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.submitterId").value(userId)) + .andExpect(jsonPath("$.data.recommendation").value("来自 Discord @someone")); + assertThat(storedDiscordUserId(url)).isEqualTo("900000000000000001"); + } + + @Test + @DisplayName("Discord id 没绑定任何账号 → 挂 discord-bridge,但 id 照样落库") + void submit_unlinkedDiscord_fallsBackToBridgeAndKeepsId() throws Exception { + String url = "https://arxiv.org/abs/2610.10002"; + submitAs(url, "900000000000000002") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.submitterId").value(bridgeId())); + assertThat(storedDiscordUserId(url)).isEqualTo("900000000000000002"); + } + + @Test + @DisplayName("绑定的账号已禁用 → 挂 discord-bridge") + void submit_disabledAccount_fallsBackToBridge() throws Exception { + long userId = createUser("share-attr-disabled", false); + linkDiscord(userId, "900000000000000003"); + submitAs("https://arxiv.org/abs/2610.10003", "900000000000000003") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.submitterId").value(bridgeId())); + } + + @Test + @DisplayName("不传 discordUserId(如 MCP 渠道)→ 挂 discord-bridge,id 为空") + void submit_withoutDiscordId_staysOnBridge() throws Exception { + String url = "https://arxiv.org/abs/2610.10004"; + submitAs(url, null) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.submitterId").value(bridgeId())); + assertThat(storedDiscordUserId(url)).isNull(); + } + + @Test + @DisplayName("discordUserId 不是纯数字 → 400,不落库") + void submit_invalidDiscordId_returns400() throws Exception { + String url = "https://arxiv.org/abs/2610.10005"; + submitAs(url, "12ab") + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("invalid discordUserId")); + assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM shared_links WHERE url = ?", Integer.class, url)) + .isZero(); + } + + @Test + @DisplayName("在 Discord 里分享的条数不占网页提交的每日配额") + void bridgeShares_doNotCountTowardWebDailyLimit() throws Exception { + long userId = createUser("share-attr-quota", true); + linkDiscord(userId, "900000000000000006"); + for (int i = 0; i < 6; i++) { + submitAs("https://arxiv.org/abs/2610.1010" + i, "900000000000000006") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.submitterId").value(userId)); + } + assertThat(sharedLinkService.submit(userId, "https://arxiv.org/abs/2610.10200", null).submitterId()) + .isEqualTo(userId); + } + // ── summary 接口 ──────────────────────────────────────────────────────── @Test @DisplayName("GET /internal/summary 缺 key → 403") diff --git a/src/test/java/com/involutionhell/backend/community/service/SharedLinkEnrichmentWorkerTests.java b/src/test/java/com/involutionhell/backend/community/service/SharedLinkEnrichmentWorkerTests.java index fe6e679..2f9dd88 100644 --- a/src/test/java/com/involutionhell/backend/community/service/SharedLinkEnrichmentWorkerTests.java +++ b/src/test/java/com/involutionhell/backend/community/service/SharedLinkEnrichmentWorkerTests.java @@ -77,7 +77,8 @@ private SharedLink stubLink(Long id, String url, String host) { null, null, null, null, null, null, Map.of(), SharedLinkStatus.PENDING, 0, null, null, - Instant.now(), Instant.now() + Instant.now(), Instant.now(), + null ); } diff --git a/src/test/resources/test-schema.sql b/src/test/resources/test-schema.sql index d5bfa27..3b38716 100644 --- a/src/test/resources/test-schema.sql +++ b/src/test/resources/test-schema.sql @@ -127,6 +127,7 @@ CREATE TABLE IF NOT EXISTS shared_links ( admin_note TEXT, probe_fail_count INT NOT NULL DEFAULT 0, probe_last_at TIMESTAMP, + discord_user_id VARCHAR(32), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (submitter_id) REFERENCES user_accounts(id) ON DELETE CASCADE