Skip to content

Commit 2ece9fc

Browse files
committed
fix scraper parser missing
1 parent d860546 commit 2ece9fc

File tree

7 files changed

+142
-41
lines changed

7 files changed

+142
-41
lines changed

backend/apps/events/models.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44

55
class Events(models.Model):
6-
# Human-readable event information
7-
id = models.BigAutoField(primary_key=True)
86
title = models.TextField(
97
null=True, blank=True, help_text="'Spring Career Fair 2024'"
108
)
@@ -103,8 +101,6 @@ class EventSubmission(models.Model):
103101
("rejected", "Rejected"),
104102
]
105103

106-
# Submission details
107-
id = models.BigAutoField(primary_key=True)
108104
submitted_by = models.CharField(
109105
max_length=255, help_text="Clerk user ID who submitted this event"
110106
)
@@ -135,7 +131,6 @@ class EventDates(models.Model):
135131
Stores individual occurrence dates for events.
136132
"""
137133

138-
id = models.BigAutoField(primary_key=True)
139134
event = models.ForeignKey(
140135
Events,
141136
on_delete=models.CASCADE,
@@ -175,7 +170,6 @@ class EventInterest(models.Model):
175170
Tracks user interest in events.
176171
Many-to-many relationship between users (Clerk user IDs) and events.
177172
"""
178-
id = models.BigAutoField(primary_key=True)
179173
event = models.ForeignKey(
180174
Events,
181175
on_delete=models.CASCADE,
@@ -202,3 +196,18 @@ def __str__(self):
202196
class IgnoredPost(models.Model):
203197
shortcode = models.CharField(max_length=32, unique=True)
204198
added_at = models.DateTimeField(auto_now_add=True)
199+
200+
201+
class Promotion(models.Model):
202+
# One promo window per event; extend it by spending more credits
203+
event = models.OneToOneField(Events, on_delete=models.CASCADE, related_name="promotion")
204+
starts_at = models.DateTimeField()
205+
ends_at = models.DateTimeField()
206+
207+
@property
208+
def active(self) -> bool:
209+
now = timezone.now()
210+
return self.starts_at <= now <= self.ends_at
211+
212+
def __str__(self):
213+
return f"Promotion({self.event.id}) {self.starts_at}{self.ends_at}"

backend/apps/events/views.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,42 @@ def delete_event(request, event_id):
959959

960960
except Exception as e:
961961
return Response({"message": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
962+
963+
964+
# Boost event
965+
966+
@api_view(["GET"])
967+
def boost_event_view(request, event_id):
968+
"""
969+
Body/query should include 'days' (int).
970+
Example: POST /events/123/boost?days=7
971+
"""
972+
clerk_user_id = get_clerk_user_id(request)
973+
if not clerk_user_id:
974+
return HttpResponseBadRequest("Unauthorized")
975+
976+
# Parse and validate integer days from query/body as you prefer
977+
try:
978+
days = int(request.GET.get("days") or request.POST.get("days"))
979+
except (TypeError, ValueError):
980+
return HttpResponseBadRequest("Missing/invalid 'days'")
981+
982+
# Optional: ensure only owner can boost their own event
983+
event = get_object_or_404(Event, pk=event_id)
984+
if event.owner_clerk_user_id != clerk_user_id:
985+
return HttpResponseBadRequest("You can only boost your own events")
986+
987+
try:
988+
promo = boost_event(clerk_user_id, event_id, days)
989+
return JsonResponse({
990+
"ok": True,
991+
"promotion": {
992+
"starts_at": promo.starts_at.isoformat(),
993+
"ends_at": promo.ends_at.isoformat(),
994+
"active": promo.active,
995+
}
996+
})
997+
except InsufficientCredits as e:
998+
return HttpResponseBadRequest(str(e))
999+
except ValueError as e:
1000+
return HttpResponseBadRequest(str(e))

backend/config/settings/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"apps.clubs",
5252
"apps.promotions",
5353
"apps.newsletter",
54+
"apps.payments",
5455
]
5556

5657
MIDDLEWARE = [

backend/scraping/instagram_feed.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from django.utils import timezone
2222
from dotenv import load_dotenv
2323
from instaloader import Instaloader
24+
from dateutil import parser
2425

2526
from apps.clubs.models import Clubs
2627
from apps.events.models import Events, IgnoredPost, EventDates
@@ -57,6 +58,7 @@
5758
SUPABASE_DB_URL = os.getenv("SUPABASE_DB_URL")
5859

5960

61+
def is_duplicate_event(event_data):
6062
def is_duplicate_event(event_data):
6163
"""Check for duplicate events using title, occurrences, location, and description."""
6264

@@ -226,7 +228,9 @@ def insert_event_to_db(event_data, ig_handle, source_url):
226228
school = event_data.get("school", "")
227229
categories = event_data.get("categories", [])
228230
occurrences = event_data.get("occurrences")
231+
occurrences = event_data.get("occurrences")
229232

233+
if not occurrences:
230234
if not occurrences:
231235
logger.warning(f"Event '{title}' missing occurrences; skipping insert")
232236
return "missing_occurrence"
@@ -235,6 +239,7 @@ def insert_event_to_db(event_data, ig_handle, source_url):
235239
logger.warning(f"Event '{title}' missing categories, assigning 'Uncategorized'")
236240
categories = ["Uncategorized"]
237241

242+
if is_duplicate_event(event_data):
238243
if is_duplicate_event(event_data):
239244
return "duplicate"
240245

@@ -421,12 +426,14 @@ def check_post_limit():
421426
event_data.get("title")
422427
and event_data.get("location")
423428
and event_data.get("occurrences")
429+
and event_data.get("occurrences")
424430
):
425431
missing_fields = []
426432
if not event_data.get("title"):
427433
missing_fields.append("title")
428434
if not event_data.get("location"):
429435
missing_fields.append("location")
436+
if not event_data.get("occurrences"):
430437
if not event_data.get("occurrences"):
431438
missing_fields.append("occurrences")
432439
logger.warning(
@@ -435,6 +442,7 @@ def check_post_limit():
435442
added_to_db = "missing_fields"
436443
continue
437444

445+
first_occurrence = event_data.get("occurrences")[0]
438446
first_occurrence = event_data.get("occurrences")[0]
439447
dtstart_utc = first_occurrence.get("start_utc")
440448
now = timezone.now()

frontend/src/features/events/hooks/useEventInterest.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export function useMyInterestedEvents() {
1717
},
1818
enabled: isSignedIn,
1919
staleTime: 1000 * 60 * 5, // Cache for 5 minutes
20+
meta: {
21+
skipErrorToast: true,
22+
},
2023
});
2124
}
2225

frontend/src/features/events/hooks/useEvents.ts

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,51 @@ export function useEvents() {
2020
const addedAt = searchParams.get("added_at") || "";
2121
const showInterested = searchParams.get("interested") === "true";
2222
const view = searchParams.get("view") || "grid";
23-
23+
2424
const { data: interestedEventIds } = useMyInterestedEvents();
2525

26-
const {
26+
const {
2727
data,
28-
isLoading,
28+
isLoading,
2929
error,
3030
fetchNextPage,
3131
hasNextPage,
3232
isFetchingNextPage,
33-
} = useInfiniteQuery<EventsResponse, Error, any, string[], string | undefined>({
34-
queryKey: ["events", searchTerm, categories, dtstart_utc, addedAt, view, showInterested ? "interested" : ""],
33+
} = useInfiniteQuery<
34+
EventsResponse,
35+
Error,
36+
any,
37+
string[],
38+
string | undefined
39+
>({
40+
queryKey: [
41+
"events",
42+
searchTerm,
43+
categories,
44+
dtstart_utc,
45+
addedAt,
46+
view,
47+
showInterested ? "interested" : "",
48+
],
3549
queryFn: async ({ pageParam }: { pageParam: string | undefined }) => {
3650
const queryParams: Record<string, any> = {};
37-
51+
3852
if (pageParam) {
3953
queryParams.cursor = pageParam;
4054
}
41-
55+
4256
if (searchTerm) {
4357
queryParams.search = searchTerm;
4458
}
45-
59+
4660
if (categories) {
4761
queryParams.categories = categories;
4862
}
49-
63+
5064
if (dtstart_utc) {
5165
queryParams.dtstart_utc = dtstart_utc;
5266
}
53-
67+
5468
if (addedAt) {
5569
queryParams.added_at = addedAt;
5670
}
@@ -71,7 +85,7 @@ export function useEvents() {
7185
initialPageParam: undefined as string | undefined,
7286
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
7387
refetchOnWindowFocus: false,
74-
enabled: true,
88+
enabled: true,
7589
});
7690

7791
// Flatten all pages into a single array of events
@@ -81,7 +95,8 @@ export function useEvents() {
8195
}, [data]);
8296

8397
// Get total count from first page
84-
const totalCount = (data?.pages?.[0] as EventsResponse | undefined)?.totalCount ?? 0;
98+
const totalCount =
99+
(data?.pages?.[0] as EventsResponse | undefined)?.totalCount ?? 0;
85100

86101
// Filter events by interested if the filter is active
87102
const filteredEvents = useMemo(() => {
@@ -95,9 +110,11 @@ export function useEvents() {
95110

96111
const documentTitle = useMemo(() => {
97112
let title: string;
98-
113+
99114
// Use totalCount if available and not filtering by interested (which filters client-side)
100-
const displayCount = showInterested ? filteredEvents.length : totalCount || filteredEvents.length;
115+
const displayCount = showInterested
116+
? filteredEvents.length
117+
: totalCount || filteredEvents.length;
101118

102119
if (searchTerm || categories) {
103120
title = `${displayCount} Found Events - Wat2Do`;
@@ -116,7 +133,16 @@ export function useEvents() {
116133
}
117134

118135
return previousTitleRef.current;
119-
}, [filteredEvents.length, totalCount, isLoading, searchTerm, categories, dtstart_utc, addedAt, showInterested]);
136+
}, [
137+
filteredEvents.length,
138+
totalCount,
139+
isLoading,
140+
searchTerm,
141+
categories,
142+
dtstart_utc,
143+
addedAt,
144+
showInterested,
145+
]);
120146

121147
useDocumentTitle(documentTitle);
122148

@@ -156,7 +182,6 @@ export function useEvents() {
156182
const isoString = cutoffDate.toISOString();
157183
nextParams.set("added_at", isoString);
158184
nextParams.delete("dtstart_utc");
159-
nextParams.delete("interested");
160185
}
161186
return nextParams;
162187
});
@@ -176,12 +201,22 @@ export function useEvents() {
176201
// When deselecting interested, remove all filters to show upcoming events
177202
nextParams.delete("interested");
178203
nextParams.delete("dtstart_utc");
179-
nextParams.delete("added_at");
180204
} else {
181205
nextParams.set("interested", "true");
182-
// Reveal all interested from 2025-01-01
183-
nextParams.set("dtstart_utc", "2025-01-01T00:00:00Z");
206+
}
207+
return nextParams;
208+
});
209+
};
210+
211+
const handleToggleAllEvents = () => {
212+
setSearchParams((prev) => {
213+
const nextParams = new URLSearchParams(prev);
214+
215+
if (dtstart_utc) {
216+
nextParams.delete("dtstart_utc");
217+
} else {
184218
nextParams.delete("added_at");
219+
nextParams.set("dtstart_utc", "2025-01-01T00:00:00Z");
185220
}
186221
return nextParams;
187222
});
@@ -201,6 +236,7 @@ export function useEvents() {
201236
handleToggleStartDate,
202237
handleToggleNewEvents,
203238
handleToggleInterested,
239+
handleToggleAllEvents,
204240
fetchNextPage,
205241
hasNextPage,
206242
isFetchingNextPage,

0 commit comments

Comments
 (0)