Skip to content

Commit 0712384

Browse files
committed
simplifies the API design
1 parent c6960c0 commit 0712384

10 files changed

Lines changed: 140 additions & 88 deletions

File tree

feed/urls.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,11 @@
1414
views.api.CollectionUpdateView.as_view(),
1515
name="api_collection_update",
1616
),
17-
path(
18-
"api/v1/collections/<int:pk>/feed/",
19-
views.api.CollectionFeedListView.as_view(),
20-
name="api_collection_feed_list",
21-
),
2217
path(
2318
"api/v1/subscriptions/",
2419
views.api.SubscriptionsListView.as_view(),
2520
name="api_subscriptions_list",
2621
),
27-
path(
28-
"api/v1/subscriptions/<int:pk>/feed/",
29-
views.api.SubscriptionsFeedListView.as_view(),
30-
name="api_subscription_feed_list",
31-
),
3222
path(
3323
"api/v1/subscriptions/<int:pk>/subscription/",
3424
views.api.Subscription.as_view(),

feed/views/api/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
SubscriptionsListView,
33
CollectionListView,
44
FeedListView,
5-
SubscriptionsFeedListView,
6-
CollectionFeedListView,
75
FeedItemUpdateView,
86
FeedItemActionView,
97
CollectionUpdateView,
@@ -15,8 +13,6 @@
1513
"SubscriptionsListView",
1614
"CollectionListView",
1715
"FeedListView",
18-
"SubscriptionsFeedListView",
19-
"CollectionFeedListView",
2016
"FeedItemUpdateView",
2117
"FeedItemActionView",
2218
"CollectionUpdateView",

feed/views/api/feed.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ def get_queryset(self):
116116
queryset = self.apply_type_filters(queryset)
117117
queryset = self.apply_exclude_filters(queryset)
118118
queryset = self.apply_search_filter(queryset)
119+
queryset = self.apply_collection_filter(queryset)
120+
queryset = self.apply_subscription_filter(queryset)
119121
return queryset
120122

121123
def apply_exclude_filters(self, queryset):
@@ -163,15 +165,17 @@ def apply_search_filter(self, queryset):
163165
| Q(description__icontains=self.request.GET.get("search"))
164166
)
165167

168+
def apply_collection_filter(self, queryset):
169+
collection_id = self.request.GET.get("collection_id")
170+
if collection_id is None:
171+
return queryset
172+
return queryset.filter(feed__collections=collection_id)
166173

167-
class CollectionFeedListView(FeedListView):
168-
def get_queryset(self):
169-
return super().get_queryset().filter(feed__collections=self.kwargs.get("pk"))
170-
171-
172-
class SubscriptionsFeedListView(FeedListView):
173-
def get_queryset(self):
174-
return super().get_queryset().filter(feed=self.kwargs.get("pk"))
174+
def apply_subscription_filter(self, queryset):
175+
subscription_id = self.request.GET.get("subscription_id")
176+
if subscription_id is None:
177+
return queryset
178+
return queryset.filter(feed=subscription_id)
175179

176180

177181
class FeedItemUpdateView(UpdateAPIView):

frontend/src/components/feed/Filters.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, defineEmits } from 'vue'
2+
import { computed } from 'vue'
33
import { useFeedStore } from '@/stores/feed.ts'
44
import IconThumbDown from '@/components/icons/IconThumbDown.vue'
55
import IconTaskAlt from '@/components/icons/IconTaskAlt.vue'

frontend/src/components/feed/List.vue

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { computed, ref, watchEffect } from 'vue'
55
import { useRoute } from 'vue-router'
66
import IconInboxEmpty from '@/components/icons/IconInboxEmpty.vue'
77
import Loader from '@/components/feed/Loader.vue'
8+
import { buildUrlWithParams } from '@/utils/helpers.ts'
89
910
const { fetchUrl } = defineProps({ fetchUrl: String })
1011
const nextLink = ref(null)
@@ -15,47 +16,27 @@ const requestUrl = computed(() => {
1516
if (!fetchUrl) {
1617
return
1718
}
18-
const url = new URL(fetchUrl, window.location.origin)
19-
const filtersKey = route.matched[0]?.name || route.name
20-
for (const [filter, values] of Object.entries(feedStore.getFilters(filtersKey as string))) {
21-
if (Array.isArray(values)) {
22-
values.forEach((value) => {
23-
url.searchParams.append(filter, value)
24-
})
25-
} else {
26-
url.searchParams.append(filter, values)
27-
}
28-
}
2919
30-
return url.href
20+
const filtersKey = route.matched[0]?.name || route.name
21+
return buildUrlWithParams(fetchUrl, feedStore.getFilters(filtersKey as string))
3122
})
3223
3324
function handleScroll(idx: number) {
3425
if (idx === feedStore.items.length - 1) {
3526
if (nextLink.value) {
36-
fetchFeed(nextLink.value)
27+
feedStore.fetchByUrl(nextLink.value, false).then((response) => {
28+
nextLink.value = response.next
29+
})
3730
}
3831
}
3932
}
4033
41-
async function fetchFeed(url: string | URL) {
42-
return fetch(url)
43-
.then((res) => res.json())
44-
.then((res) => {
45-
feedStore.appendItems(res.results)
46-
nextLink.value = res.next
47-
feedStore.isLoading = false
48-
})
49-
}
50-
5134
watchEffect(async () => {
5235
if (!requestUrl.value) {
5336
return
5437
}
55-
56-
feedStore.isLoading = true
57-
feedStore.setItems([]);
58-
await fetchFeed(requestUrl.value)
38+
const response = await feedStore.fetchByUrl(requestUrl.value)
39+
nextLink.value = response.next
5940
})
6041
6142
</script>

frontend/src/router/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const router = createRouter({
99
path: '/',
1010
name: 'feed',
1111
component: FeedView,
12-
props: { feed_type: 'default' },
12+
props: { feedType: 'generic' },
1313
children: [
1414
{
1515
path: 'items/:id',
@@ -22,7 +22,7 @@ const router = createRouter({
2222
path: '/favorites',
2323
name: 'favorites',
2424
component: FeedView,
25-
props: { feed_type: 'favorites' },
25+
props: { feedType: 'favorites' },
2626
children: [
2727
{
2828
path: 'items/:id',
@@ -35,7 +35,7 @@ const router = createRouter({
3535
path: '/articles',
3636
name: 'articles',
3737
component: FeedView,
38-
props: { feed_type: 'articles' },
38+
props: { feedType: 'articles' },
3939
children: [
4040
{
4141
path: 'items/:id',
@@ -48,7 +48,7 @@ const router = createRouter({
4848
path: '/podcasts',
4949
name: 'podcasts',
5050
component: FeedView,
51-
props: { feed_type: 'podcasts' },
51+
props: { feedType: 'podcasts' },
5252
children: [
5353
{
5454
path: 'items/:id',
@@ -61,7 +61,7 @@ const router = createRouter({
6161
path: '/videos',
6262
name: 'videos',
6363
component: FeedView,
64-
props: { feed_type: 'videos' },
64+
props: { feedType: 'videos' },
6565
children: [
6666
{
6767
path: 'items/:id',
@@ -74,7 +74,7 @@ const router = createRouter({
7474
path: '/collection/:slug',
7575
name: 'collection_feed_list',
7676
component: FeedView,
77-
props: { feed_type: 'collection' },
77+
props: { feedType: 'collection' },
7878
children: [
7979
{
8080
path: 'items/:id',
@@ -87,7 +87,7 @@ const router = createRouter({
8787
path: '/:slug',
8888
name: 'feed_list',
8989
component: FeedView,
90-
props: { feed_type: 'feed' },
90+
props: { feedType: 'subscription' },
9191
children: [
9292
{
9393
path: 'items/:id',

frontend/src/stores/feed.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { defineStore } from 'pinia'
2+
import type { FeedType } from '@/utils/types.ts'
3+
import { getFeedBaseUrl } from '@/utils/helpers.ts'
24

35
interface Attachment {
46
url: string;
@@ -26,7 +28,9 @@ interface FeedState {
2628
items: FeedItem[]
2729
total: number
2830
filters: Record<'default' | string, FilterRecord>
29-
isLoading: boolean
31+
isLoading: boolean,
32+
type: FeedType,
33+
url: string,
3034
}
3135

3236
export const useFeedStore = defineStore('feed', {
@@ -35,6 +39,8 @@ export const useFeedStore = defineStore('feed', {
3539
items: [],
3640
total: 0,
3741
isLoading: false,
42+
type: 'generic',
43+
url: getFeedBaseUrl('generic'),
3844
filters: {
3945
default: { exclude: ['viewed', 'not_interesting'] },
4046
favorites: { exclude: ['not_interesting'] },
@@ -60,19 +66,18 @@ export const useFeedStore = defineStore('feed', {
6066

6167
if (!this.filters[category]) {
6268
this.filters[category] = {
63-
...this.filters.default,
64-
...filters as FilterRecord,
69+
...this.filters.default
6570
}
66-
} else {
67-
for (const key of Object.keys(filters)) {
68-
if (filters[key] !== null) {
69-
this.filters[category] = {
70-
...this.filters[category],
71-
[key]: filters[key],
72-
}
73-
} else {
74-
delete this.filters[category][key]
71+
}
72+
73+
for (const key of Object.keys(filters)) {
74+
if (filters[key] !== null) {
75+
this.filters[category] = {
76+
...this.filters[category],
77+
[key]: filters[key]
7578
}
79+
} else {
80+
delete this.filters[category][key]
7681
}
7782
}
7883
},
@@ -90,6 +95,27 @@ export const useFeedStore = defineStore('feed', {
9095
},
9196
appendItems(items: FeedItem[]) {
9297
this.items = this.items.concat(items)
98+
},
99+
async fetchByUrl(url: string, initial: boolean = true) {
100+
if (initial) {
101+
this.isLoading = true
102+
this.items = []
103+
}
104+
105+
const response = await fetch(url)
106+
.then(response => response.json())
107+
108+
if (initial) {
109+
this.items = response.results
110+
} else {
111+
this.items = this.items.concat(response.results)
112+
}
113+
114+
if (initial) {
115+
this.isLoading = false
116+
}
117+
118+
return response
93119
}
94120
}
95121
})

frontend/src/utils/helpers.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import type { FeedType, GenericFeedType, IdentifiableFeedType } from '@/utils/types.ts'
2+
13
export function debounce<F extends (...args: Parameters<F>) => unknown>(fn: F, delay: number) {
24
let timeoutId: number
3-
return function (this: unknown, ...args: Parameters<F>) {
5+
return function(this: unknown, ...args: Parameters<F>) {
46
clearTimeout(timeoutId)
57
timeoutId = setTimeout(() => {
68
fn.apply(this, args)
@@ -23,3 +25,43 @@ export function getCookie(name: string): string {
2325

2426
return lastPart.split(';').shift() as string
2527
}
28+
29+
type ParamsKey = keyof unknown;
30+
type ParamsValue = string | Array<string>
31+
32+
export function buildUrlWithParams<K extends ParamsKey, V extends ParamsValue>(baseURL: string, params: Record<K, V> = {}): string {
33+
const url = new URL(baseURL, window.location.origin)
34+
35+
for (const [key, value] of Object.entries<V>(params)) {
36+
if (Array.isArray(value)) {
37+
value.forEach((value) => {
38+
url.searchParams.append(key, value)
39+
})
40+
} else {
41+
url.searchParams.append(key, value)
42+
}
43+
}
44+
45+
return url.href
46+
}
47+
48+
export function getFeedBaseUrl(type: GenericFeedType): string;
49+
export function getFeedBaseUrl(type: FeedType, id?: string): string;
50+
export function getFeedBaseUrl(type: FeedType, id?: string): string {
51+
switch (type) {
52+
// case 'subscription':
53+
// return `/api/v1/feed/?subscription_id=${id}`
54+
// case 'collection':
55+
// return `/api/v1/feed/?collection_id=${id}`
56+
case 'favorites':
57+
return '/api/v1/feed/?type=favorite'
58+
case 'articles':
59+
return '/api/v1/feed/?type=article'
60+
case 'podcasts':
61+
return '/api/v1/feed/?type=podcast'
62+
case 'videos':
63+
return '/api/v1/feed/?type=video'
64+
default:
65+
return '/api/v1/feed/'
66+
}
67+
}

frontend/src/utils/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type GenericFeedType = 'generic' | 'favorites' | 'articles' | 'videos' | 'podcasts'
2+
export type IdentifiableFeedType = 'subscription' | 'collection'
3+
export type FeedType = GenericFeedType | IdentifiableFeedType

0 commit comments

Comments
 (0)