Skip to content

Commit

Permalink
[Backend] Check markers nearby and inside korea (roughly)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alfex4936 committed Mar 1, 2024
1 parent 3096253 commit a8c0d08
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 13 deletions.
2 changes: 1 addition & 1 deletion backend/handlers/auth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func SendVerificationEmailHandler(c *fiber.Ctx) error {
// If GetUserByEmail does not return an error, it means the email is already in use
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Email already registered"})
} else if err != sql.ErrNoRows {
// Handle unexpected errors differently, perhaps with a 500 internal server error
// if db couldn't find a user, then it's valid. other errors are bad.
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "An unexpected error occurred"})
}

Expand Down
12 changes: 12 additions & 0 deletions backend/handlers/marker_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ func CreateMarkerWithPhotosHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid longitude"})
}

// Location Must Be Inside South Korea
yes := services.IsInSouthKorea(latitude, longitude)
if yes {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Operation not allowed within South Korea."})
}

// Checking if a Marker is Nearby
yes, _ = services.IsMarkerNearby(latitude, longitude)
if yes {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "There is a marker already nearby."})
}

// Set default description if it's empty or not provided
description := "설명 없음" // Default description
if descValues, exists := form.Value["description"]; exists && len(descValues[0]) > 0 {
Expand Down
1 change: 0 additions & 1 deletion backend/handlers/user_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

// DeleteUserHandler deletes the currently authenticated user
func DeleteUserHandler(c *fiber.Ctx) error {
// Retrieve the userID from the context, set by authentication middleware
userID, ok := c.Locals("userID").(int)
if !ok {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "User ID not found"})
Expand Down
87 changes: 83 additions & 4 deletions backend/services/marker_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ import (
"chulbong-kr/models"
)

/*
북한 제외
극동: 경상북도 울릉군의 독도(獨島)로 동경 131° 52′20“, → 131.87222222
극서: 전라남도 신안군의 소흑산도(小黑山島)로 동경 125° 04′, → 125.06666667
극북: 강원도 고성군 현내면 송현진으로 북위 38° 27′00, → 38.45000000
극남: 제주도 남제주군 마라도(馬羅島)로 북위 33° 06′00" → 33.10000000
섬 포함 우리나라의 중심점은 강원도 양구군 남면 도촌리 산48번지
북위 38도 03분 37.5초, 동경 128도 02분 2.5초 → 38.05138889, 128.03388889
섬을 제외하고 육지만을 놓고 한반도의 중심점을 계산하면 북한에 위치한 강원도 회양군 현리 인근
북위(lon): 38도 39분 00초, 동경(lat) 127도 28분 55초 → 33.10000000, 127.48194444
대한민국
도분초: 37° 34′ 8″ N, 126° 58′ 36″ E
소수점 좌표: 37.568889, 126.976667
*/
// South Korea's bounding box
const (
SouthKoreaMinLat = 33.0
SouthKoreaMaxLat = 38.615
SouthKoreaMinLong = 124.0
SouthKoreaMaxLong = 132.0
)

// Tsushima (Uni Island) bounding box
const (
TsushimaMinLat = 34.080
TsushimaMaxLat = 34.708
TsushimaMinLong = 129.164396
TsushimaMaxLong = 129.4938
)

// CreateMarker creates a new marker in the database after checking for nearby markers
func CreateMarker(markerDto *dto.MarkerRequest, userId int) (*models.Marker, error) {
// Start a transaction
Expand Down Expand Up @@ -312,12 +342,50 @@ func IsMarkerNearby(lat, long float64) (bool, error) {
if err != nil {
return false, err
}

// Channel to communicate results of the distance checks
resultChan := make(chan bool)
// Channel to signal the completion of all goroutines
doneChan := make(chan bool)

for _, m := range markers {
if math.Abs(distance(lat, long, m.Latitude, m.Longitude)-5) < 1 { // allow 1 meter error
return true, nil
}
go func(m models.Marker) {
// Perform the distance check
if math.Abs(distance(lat, long, m.Latitude, m.Longitude)-5) < 1 { // allow 1 meter error
resultChan <- true
} else {
resultChan <- false
}
}(m)
}
return false, nil

// Collect results
go func() {
nearby := false
for i := 0; i < len(markers); i++ {
if <-resultChan {
nearby = true
break // If any marker is nearby, no need to check further
}
}
doneChan <- nearby
}()

// Wait for the result
result := <-doneChan
return result, nil
}

// Haversine formula
func approximateDistance(lat1, long1, lat2, long2 float64) float64 {
const R = 6371000 // Radius of the Earth in meters
lat1Rad := lat1 * (math.Pi / 180)
lat2Rad := lat2 * (math.Pi / 180)
deltaLat := (lat2 - lat1) * (math.Pi / 180)
deltaLong := (long2 - long1) * (math.Pi / 180)
x := deltaLong * math.Cos((lat1Rad+lat2Rad)/2)
y := deltaLat
return math.Sqrt(x*x+y*y) * R
}

// distance calculates the distance between two geographic coordinates in meters
Expand All @@ -330,3 +398,14 @@ func distance(lat1, long1, lat2, long2 float64) float64 {
var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return 6371000 * c // Earth radius in meters
}

// IsInSouthKorea checks if given latitude and longitude are within South Korea (roughly)
func IsInSouthKorea(lat, long float64) bool {
// Check if within Tsushima (Uni Island) and return false if true
if lat >= TsushimaMinLat && lat <= TsushimaMaxLat && long >= TsushimaMinLong && long <= TsushimaMaxLong {
return false // The point is within Tsushima Island, not South Korea
}

// Check if within South Korea's bounding box
return lat >= SouthKoreaMinLat && lat <= SouthKoreaMaxLat && long >= SouthKoreaMinLong && long <= SouthKoreaMaxLong
}
68 changes: 67 additions & 1 deletion backend/services/marker_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ func TestDistance(t *testing.T) {
long2: 126.570712, // Approximately 5 meters away in longitude
expectedResult: 5, // Expecting the result to be close to 5 meters
},
{
name: "Very close distance 2", // actual 2.010888m distance
lat1: 37.8580352854713,
long1: 126.80789827370542,
lat2: 37.85803307018021,
long2: 126.80792100630472, // Approximately 5 meters away in longitude
expectedResult: 2, // Expecting the result to be close to 5 meters
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := distance(tt.lat1, tt.long1, tt.lat2, tt.long2)
result := approximateDistance(tt.lat1, tt.long1, tt.lat2, tt.long2)

// Log the distance calculated for this test case
t.Logf("Calculated distance for %q: %v meters", tt.name, result)
Expand All @@ -54,3 +62,61 @@ func TestDistance(t *testing.T) {
})
}
}

func TestIsInSouthKorea(t *testing.T) {
// Define test cases
tests := []struct {
name string
latitude float64
longitude float64
want bool
}{
{"서울", 37.5665, 126.9780, true},
{"제주 카카오오름", 33.45049302403202, 126.57055468146439, true},
{"해운대", 35.1581232984585, 129.1598440928477, true}, // 해운대 해수욕장
{"포항", 36.08502506194445, 129.55140108962055, true}, // 포항
{"세종", 36.481550006080006, 127.28920084353089, true},
{"제주도", 33.4890, 126.4983, true},
{"우도", 33.51412972779723, 126.97244569597137, true},
{"마라도", 33.11294701534852, 126.2662987980748, true},
{"독도", 37.2426, 131.8597, true},
{"울릉도", 37.4845, 130.9057, true},
{"차귀도", 33.311273820042125, 126.14345298508049, true}, // 차귀도- 제주특별자치도 제주시 한경면 고산리
{"대강리", 38.61453830741445, 128.35799152766955, true}, // northernmost point, 강원특별자치도 고성군 현내면
{"백령도", 37.96909906079667, 124.609983839757, true}, // westernmost point, 코끼리바위, 인천 옹진군 백령면 연화리 1026-29
{"백령도2", 37.98488937628463, 124.68608584402796, true}, // 코끼리바위, 인천 옹진군 백령면 연화리 1026-29
{"철원", 38.31374456713513, 127.13423745903036, true}, // 강원특별자치도 철원군 철원읍 가단리 52
{"거제도", 34.54419719852532, 128.43864110479205, true}, // 거제도
{"광도", 34.269977354595504, 127.53055654653483, true},
{"가거도", 34.077014440034155, 125.11863713970902, true},
// false
{"이어도", 32.124463344828854, 125.18301360832207, false}, // southernmost point, 이어도. cannot build stuff.
{"Los Angeles", 34.0522, -118.2437, false},
{"Tokyo", 35.6895, 139.6917, false},
{"Beijing", 39.9042, 116.4074, false},
{"Uni Island", 34.707351308730146, 129.43478825264333, false},
{"Uni Island2", 34.43217756058352, 129.33997781093186, false},
{"Uni Island3", 34.636217082470296, 129.4828167691493, false},
{"Uni Island4", 34.29666974505072, 129.3871993238883, false},
{"Uni Island5", 34.0854739629158, 129.2154168085643, false},
{"Yantai (China)", 37.45460313491269, 122.43159543394779, false},
{"평양 (N.Korea)", 39.040122308158885, 125.75997459218848, false},
// {"Iki Island", 33.833510640897295, 129.6794423356137, false},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := IsInSouthKorea(tc.latitude, tc.longitude)
if got != tc.want {
t.Errorf("FAIL: %s - IsInSouthKorea(%f, %f) = %v; want %v", tc.name, tc.latitude, tc.longitude, got, tc.want)
} else {
// Provide clearer messages indicating the correctness of the test result
if got {
t.Logf("PASS: %s is correctly identified as inside South Korea.", tc.name)
} else {
t.Logf("PASS: %s is correctly identified as outside South Korea.", tc.name)
}
}
})
}
}
22 changes: 22 additions & 0 deletions backend/services/scheduler_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ import (
"github.com/robfig/cron/v3"
)

func CronCleanUpPasswordTokens() {
c := cron.New()
_, err := c.AddFunc("@hourly", func() {
fmt.Println("Running cleanup job...")
if err := DeleteExpiredPasswordTokens(); err != nil {
// Log the error
fmt.Printf("Error deleting expired tokens: %v\n", err)
} else {
fmt.Println("Expired tokens cleanup executed successfully")
}
})
if err != nil {
// Handle the error
fmt.Printf("Error scheduling the token cleanup job: %v\n", err)
return
}
c.Start()

// Optionally, keep the scheduler running indefinitely
// select {}
}

func CronCleanUpToken() {
c := cron.New()
_, err := c.AddFunc("@daily", func() {
Expand Down
6 changes: 6 additions & 0 deletions backend/services/token_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ func DeleteExpiredTokens() error {
return err
}

func DeleteExpiredPasswordTokens() error {
query := `DELETE FROM PasswordTokens WHERE ExpiresAt < NOW()`
_, err := database.DB.Exec(query)
return err
}

func GenerateState() string {
b := make([]byte, 16)
rand.Read(b)
Expand Down
13 changes: 7 additions & 6 deletions backend/services/user_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ func GetUserByEmail(email string) (*models.User, error) {
// Execute the query
err := database.DB.Get(&user, query, email)
if err != nil {
if err == sql.ErrNoRows {
// No user found with the provided email
return nil, fmt.Errorf("no user found with email %s", email)
}
// An error occurred during the query execution
return nil, fmt.Errorf("error fetching user by email: %w", err)
return nil, err
// if err == sql.ErrNoRows {
// // No user found with the provided email
// return nil, fmt.Errorf("no user found with email %s", email)
// }
// // An error occurred during the query execution
// return nil, fmt.Errorf("error fetching user by email: %w", err)
}

return &user, nil
Expand Down

0 comments on commit a8c0d08

Please sign in to comment.