Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7eca04d
Promote existing admin account via migration
subir-sh May 3, 2026
03acc4d
feat: skip detail sessions for period events
subir-sh May 3, 2026
f8fa55e
fix: apply consistent event ordering to day and month queries
subir-sh May 3, 2026
aa419d2
Merge pull request #113 from wafflestudio/codex/add-admin-account-cre…
subir-sh May 3, 2026
0f3c1d0
fix: restore createToken compatibility
subir-sh May 4, 2026
94ff464
fix: align month sort start priority with day
subir-sh May 4, 2026
3e086a9
Merge pull request #114 from wafflestudio/feat/admin
subir-sh May 4, 2026
2912a4a
road
aystoe May 4, 2026
3958f60
fix: also crawl mainContentHtml for period events
subir-sh May 4, 2026
e19ac02
fix: also add isPeriodEvent field for .json dump
subir-sh May 4, 2026
0d110aa
fix: preserve period event fallback during sync
subir-sh May 4, 2026
ede94f0
Merge pull request #119 from wafflestudio/feat/admin
subir-sh May 6, 2026
1902410
Merge branch 'develop' into feat/socialLogin
ohsuhyeon0119 May 6, 2026
339ef39
Merge pull request #117 from wafflestudio/feat/socialLogin
ohsuhyeon0119 May 6, 2026
d159d77
d
aystoe May 7, 2026
8d8feeb
Merge branch 'feat/socialLogin' of https://github.com/wafflestudio/ha…
aystoe May 7, 2026
48256ef
da
aystoe May 7, 2026
e5d7a26
Merge pull request #120 from wafflestudio/feat/socialLogin
aystoe May 7, 2026
0636826
feat(user): add deleteMe service method
May 7, 2026
45f10e6
feat: add delete me endpoint and clear refresh cookie
May 7, 2026
a69fe98
docs: update auth/user endpoint descriptions
May 7, 2026
d99e651
feat: add bug report create API and persistence
May 7, 2026
2f5d9c6
feat: add slack webhook notification for bug reports
May 7, 2026
40674d0
docs: hide logged-in user parameter in bug report API docs
May 7, 2026
6f1a721
fix: set created date on bug report inserts
May 7, 2026
3e61175
fix: delete appendLine
May 7, 2026
d03f682
Merge pull request #122 from wafflestudio/feat/user-page-side
ohsuhyeon0119 May 8, 2026
86e65a3
feat: add CREATE events
subir-sh May 8, 2026
6eb0a73
feat: lock only overriden event fields when synced after patch
subir-sh May 8, 2026
d93baf0
yaml bug resolve
aystoe May 8, 2026
8a1fe32
Merge pull request #123 from wafflestudio/bug/yaml
aystoe May 8, 2026
21a8821
feat: consider isPeriodEvent when returning event lists
subir-sh May 8, 2026
dc7d5f4
fix: exclude deleted events from bookmark count
subir-sh May 8, 2026
1da8d26
social login cookie bug
aystoe May 9, 2026
d814974
Merge pull request #125 from wafflestudio/bug/login
aystoe May 9, 2026
1404424
Merge pull request #124 from wafflestudio/feat/admin
aystoe May 9, 2026
08540ff
bug: database name
aystoe May 9, 2026
8c86c89
fix: version order change
May 9, 2026
6ed14ae
Merge pull request #127 from wafflestudio/fix/ver-conflict
ohsuhyeon0119 May 9, 2026
55e4ce5
fix: open cors 5173 and 5174 port
May 10, 2026
77b7506
Merge pull request #128 from wafflestudio/fix/cors
ohsuhyeon0119 May 10, 2026
f277914
fix: align month bucket with isPeriodEvent rule
subir-sh May 11, 2026
d78203f
Merge pull request #129 from wafflestudio/fix/retrieval
subir-sh May 11, 2026
703bf16
fix: add /api/v1 to admin page url
subir-sh May 12, 2026
882677a
err msg addition
aystoe May 12, 2026
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
1 change: 0 additions & 1 deletion .idea/sqldialects.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,11 @@ class ExtraSnuCrawler(
.filter { !it.title.isNullOrBlank() }
}

/**
* ✅ 상세 크롤링 (조건부 실행 지원)
* - init 모드에서 "모집마감" 스킵 같은 정책을 main에서 람다로 주입 가능
*/
fun enrichDetails(
events: List<ProgramEvent>,
ociUploadService: OciUploadService,
shouldFetch: (ProgramEvent) -> Boolean = { true }
shouldFetch: (ProgramEvent) -> Boolean = { true },
shouldUseDetailSessions: (ProgramEvent) -> Boolean = { true },
): List<ProgramEvent> {
return events.map { e ->
val dataSeq = e.dataSeq
Expand All @@ -88,7 +85,9 @@ class ExtraSnuCrawler(
cookieHeader = buildCookieHeader()
)

if (parsed.sessions.isEmpty()) { // fallback once
val useDetailSessions = shouldUseDetailSessions(e)

if (useDetailSessions && parsed.sessions.isEmpty()) {
val html2 = fetchDetailPageByPlaywright(dataSeq)
if (html2 != null) {
parsed = parseDetailData(
Expand All @@ -100,7 +99,11 @@ class ExtraSnuCrawler(
}

if (delayMsBetweenDetails > 0) Thread.sleep(delayMsBetweenDetails)
e.copy(detailSessions = parsed.sessions, mainContentHtml = parsed.mainContentHtml)

e.copy(
mainContentHtml = parsed.mainContentHtml,
detailSessions = if (useDetailSessions) parsed.sessions else emptyList()
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import com.team1.hangsha.batch.crawler.ProgramEvent
import com.team1.hangsha.common.upload.OciUploadService
import com.team1.hangsha.event.dto.core.CrawledDetailSession
import com.team1.hangsha.event.dto.core.CrawledProgramEvent
import com.team1.hangsha.event.model.EventPeriodPolicy
import com.team1.hangsha.event.service.EventSyncService
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDate
import kotlin.system.exitProcess

@Component
Expand Down Expand Up @@ -50,7 +52,12 @@ class ExtraSnuSyncRunner(
val events = if (!opt.withDetails) {
baseEvents
} else {
crawler.enrichDetails(baseEvents, ociUploadService) // { e -> e.status != "모집마감" } // @TODO: 위의 0001, 0002, ... 와 같이 매직 넘버라, ENUM화?
crawler.enrichDetails(
events = baseEvents,
ociUploadService = ociUploadService,
shouldFetch = { true },
shouldUseDetailSessions = { e -> !e.isPeriodEventFromList() }
)
}

// dumpOnly 여부와 상관없이 이미지 업로드는 항상 수행한다.
Expand Down Expand Up @@ -132,8 +139,27 @@ private data class BatchArgs(
}
}

private fun ProgramEvent.toCrawledProgramEvent(): CrawledProgramEvent =
CrawledProgramEvent(
private fun String?.toLocalDateOrNull(): LocalDate? =
this?.trim()
?.takeIf { it.isNotBlank() }
?.let { runCatching { LocalDate.parse(it) }.getOrNull() }

private fun ProgramEvent.isPeriodEventFromList(): Boolean {
val title = title?.trim().orEmpty()
val eventStart = activityStart.toLocalDateOrNull()?.atStartOfDay()
val eventEnd = activityEnd.toLocalDateOrNull()?.atTime(23, 59, 59)

return EventPeriodPolicy.isPeriodEvent(
title = title,
eventStart = eventStart,
eventEnd = eventEnd,
)
}

private fun ProgramEvent.toCrawledProgramEvent(): CrawledProgramEvent {
val isPeriodEvent = isPeriodEventFromList()

return CrawledProgramEvent(
dataSeq = dataSeq,
majorTypes = majorTypes,
title = title,
Expand All @@ -148,8 +174,14 @@ private fun ProgramEvent.toCrawledProgramEvent(): CrawledProgramEvent =
imageUrl = imageUrl,
tags = tags,
mainContentHtml = mainContentHtml,
detailSessions = detailSessions.map { it.toCrawledDetailSession() }
isPeriodEvent = isPeriodEvent,
detailSessions = if (isPeriodEvent) {
emptyList()
} else {
detailSessions.map { it.toCrawledDetailSession() }
}
)
}

private fun DetailSession.toCrawledDetailSession(): CrawledDetailSession =
CrawledDetailSession(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ data class CrawledProgramEvent(
val imageUrl: String? = null,
val tags: List<String> = emptyList(),
val mainContentHtml: String? = null,
val isPeriodEvent: Boolean? = null,
val detailSessions: List<CrawledDetailSession> = emptyList(),
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.team1.hangsha.event.dto.request

import java.time.LocalDateTime

data class EventCreateRequest(
val title: String,
val imageUrl: String? = null,
val operationMode: String? = null,

val tags: List<String>? = null,
val mainContentHtml: String? = null,

val statusId: Long? = null,
val eventTypeId: Long? = null,
val orgId: Long? = null,

val applyStart: LocalDateTime? = null,
val applyEnd: LocalDateTime? = null,
val eventStart: LocalDateTime? = null,
val eventEnd: LocalDateTime? = null,

val capacity: Int? = null,
val applyCount: Int? = null,

val organization: String? = null,
val location: String? = null,
val applyLink: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.team1.hangsha.event.dto.request

data class EventOverrideUpdateRequest(
val lockFields: List<String> = emptyList(),
val unlockFields: List<String> = emptyList(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.team1.hangsha.event.model

enum class AdminOverrideField {
TITLE,
IMAGE_URL,
OPERATION_MODE,
TAGS,
MAIN_CONTENT_HTML,
STATUS_ID,
EVENT_TYPE_ID,
ORG_ID,
APPLY_START,
APPLY_END,
EVENT_START,
EVENT_END,
CAPACITY,
APPLY_COUNT,
ORGANIZATION,
LOCATION,
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ data class Event(

val isPeriodEvent: Boolean = false,

val adminOverriddenFields: String? = null,
val adminDeleted: Boolean = false,

val capacity: Int? = null,
val applyCount: Int = 0,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ interface EventRepository : CrudRepository<Event, Long> {
"""
UPDATE events
SET status_id = :closedStatusId
WHERE status_id = :recruitingStatusId
WHERE admin_deleted = false
AND status_id = :recruitingStatusId
AND apply_end IS NOT NULL
AND apply_end < :now
AND (
admin_overridden_fields IS NULL
OR JSON_CONTAINS(admin_overridden_fields, JSON_QUOTE('statusId')) = 0
)
"""
)
fun closeExpiredRecruitingEvents(
Expand All @@ -43,6 +48,43 @@ interface EventRepository : CrudRepository<Event, Long> {
@Param("now") now: LocalDateTime,
): Int

@Query(
"""
select count(*) > 0
from events
where apply_link = :applyLink
and admin_deleted = true
"""
)
fun existsAdminDeletedByApplyLink(
@Param("applyLink") applyLink: String,
): Boolean

@Query(
"""
select *
from events
where id = :eventId
and admin_deleted = false
limit 1
"""
)
fun findVisibleById(
@Param("eventId") eventId: Long,
): Event?

@Modifying
@Query(
"""
update events
set admin_deleted = true
where id = :eventId
"""
)
fun softDeleteById(
@Param("eventId") eventId: Long,
): Int

@Modifying
@Query("DELETE FROM events")
fun deleteAllEventsRaw(): Int
Expand Down
Loading
Loading