Skip to content

Commit 8821716

Browse files
authored
Merge branch 'staging' into brandon3
2 parents 6b1972f + 471be5a commit 8821716

File tree

24 files changed

+1041
-362
lines changed

24 files changed

+1041
-362
lines changed

backend/conduit/articles/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ def bookmark(self, profile):
136136
return True
137137
return False
138138

139+
#Function to remove bookmark on an article
140+
def unbookmark(self, profile):
141+
if self.is_bookmarked(profile):
142+
self.bookmarkers.remove(profile)
143+
return True
144+
return False
145+
139146
#Function to check if a current bookmark already exists for a particular article and user
140147
def is_bookmarked(self, profile):
141148
return bool(self.query.filter(db.and_(bookmarker_assoc.c.bookmarker == profile.id,

backend/conduit/articles/views.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,20 @@ def bookmark_an_article(slug):
168168
return article
169169

170170

171+
#Route to remove a bookmark on a particular article
172+
@blueprint.route('/api/articles/<slug>/bookmark', methods=('DELETE',))
173+
@jwt_required
174+
@marshal_with(article_schema)
175+
def unbookmark_an_article(slug):
176+
profile = current_user.profile
177+
article = Article.query.filter_by(slug=slug).first()
178+
if not article:
179+
raise InvalidUsage.article_not_found()
180+
article.unbookmark(profile)
181+
article.save()
182+
return article
183+
184+
171185
##########
172186
# Comments
173187
##########
@@ -192,6 +206,7 @@ def make_comment_on_article(slug, body, comment_id=None, **kwargs):
192206
raise InvalidUsage.article_not_found()
193207
if comment_id:
194208
comment = Comment(None, current_user.profile, body, comment_id, **kwargs)
209+
comment.comment_id = comment_id
195210
else:
196211
comment = Comment(article, current_user.profile, body, comment_id, **kwargs)
197212
comment.save()

backend/conduit/extensions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ def update(self, commit=True, **kwargs):
2828
tags = []
2929
for tag in value:
3030
tag = Tags.query.filter_by(slug=tag).first()
31-
tags.append(tag)
31+
if tag:
32+
tags.append(tag)
33+
else:
34+
tag = Tags(tag)
35+
tag.save()
3236
self.tagList = tags
3337
else:
3438
setattr(self, attr, value)

backend/conduit/organizations/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,9 @@ def is_following(self):
111111
if current_user:
112112
return current_user.profile in self.members or current_user.profile in self.moderators
113113
return False
114+
115+
@property
116+
def is_moderator(self):
117+
if current_user:
118+
return current_user.profile in self.moderators
119+
return False

backend/conduit/organizations/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class OrganizationSchema(Schema):
1616
username = fields.Str()
1717
image = fields.Str()
1818
is_following = fields.Bool()
19+
is_moderator = fields.Bool()
1920
moderators = fields.Nested(ProfileSchema, many=True)
2021
members = fields.Nested(ProfileSchema, many=True, data_key='followers')
2122

backend/conduit/profile/views.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from conduit.tags.serializers import tags_schemas
1515
from conduit.user.serializers import followers_schema
1616
from conduit.articles.serializers import articles_schema
17+
from conduit.organizations.serializers import organizations_schema
1718

1819
blueprint = Blueprint('profiles', __name__)
1920

@@ -97,4 +98,11 @@ def get_user_articles(type=None):
9798
else:
9899
raise InvalidUsage.article_not_found()
99100
res = res.join(Article.author).join(User).filter_by(username=current_user.username)
100-
return res.all()
101+
return res.all()
102+
103+
104+
@blueprint.route('/api/profile/organizations', methods=('GET',))
105+
@jwt_required
106+
@marshal_with(organizations_schema)
107+
def get_user_organizations():
108+
return current_user.profile.mem_organization + current_user.profile.mod_organization

backend/conduit/tags/serializers.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ def dump_Tag(self, data, **kwargs):
2626
return {'tag': data}
2727

2828

29+
class TagFormSchema(Schema):
30+
tagname = fields.Str()
31+
description = fields.Str()
32+
slug = fields.Str()
33+
old_slug = fields.Str()
34+
icon = fields.Str()
35+
modSetting = fields.Int()
36+
tag = fields.Nested('self', exclude=('tag',), default=True, load_only=True)
37+
38+
@pre_load
39+
def make_Tag(self, data, **kwargs):
40+
return data['tag']
41+
42+
@post_dump
43+
def dump_Tag(self, data, **kwargs):
44+
return {'tag': data}
45+
46+
2947
class TagsSchema(TagSchema):
3048
class Meta:
3149
exclude = ('tagFollowers', 'moderators',)
@@ -49,5 +67,6 @@ def dump_Tag(self, data, **kwargs):
4967

5068

5169
tag_schema = TagSchema()
70+
tag_form_schema = TagFormSchema()
5271
tags_schemas = TagsSchema(many=True)
5372
tag_mebership_schema = TagMembershipSchema()

backend/conduit/tags/views.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .models import Tags
2-
from .serializers import tag_schema, tag_mebership_schema
2+
from .serializers import tag_schema, tag_form_schema, tag_mebership_schema
33

44
from conduit.decorators import isAdmin
55
from conduit.exceptions import InvalidUsage
@@ -46,10 +46,10 @@ def get_tag(slug, **kwargs):
4646

4747
@blueprint.route('/api/tags/<slug>', methods=('PUT',))
4848
@jwt_required
49-
@use_kwargs(tag_schema)
50-
@marshal_with(tag_schema)
51-
def update_tag(slug, **kwargs):
52-
tag = Tags.query.filter_by(slug=slug).first()
49+
@use_kwargs(tag_form_schema)
50+
@marshal_with(tag_form_schema)
51+
def update_tag(old_slug, **kwargs):
52+
tag = Tags.query.filter_by(slug=old_slug).first()
5353
if not tag:
5454
raise InvalidUsage.tag_not_found()
5555
tag.update(**kwargs)

backend/tests/test_models.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from conduit.profile.models import UserProfile
99
from conduit.articles.models import Article, Comment
1010
from conduit.tags.models import Tags
11+
from conduit.organizations.models import Organization
1112

1213
from .factories import UserFactory
1314

@@ -179,3 +180,85 @@ def test_make_comments(self, user):
179180
assert comment1.article == article
180181
assert comment1.author == user.profile
181182
assert len(article.comments.all()) == 2
183+
184+
185+
@pytest.mark.usefixtures('db')
186+
class TestOrganization:
187+
188+
def test_add_moderator(self, user):
189+
user = user.get()
190+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
191+
organization.save()
192+
193+
assert organization.add_moderator(user.profile)
194+
195+
def test_add_member(self, user):
196+
user = user.get()
197+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
198+
organization.save()
199+
200+
assert organization.add_member(user.profile)
201+
202+
def test_remove_moderator(self, user):
203+
user = user.get()
204+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
205+
organization.save()
206+
organization.add_member(user.profile)
207+
208+
assert organization.remove_member(user.profile)
209+
210+
211+
def test_update_slug_true(self):
212+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
213+
organization.save()
214+
215+
return organization.update_slug("New Slug")
216+
217+
def test_update_slug_false(self):
218+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
219+
organization.save()
220+
221+
return organization.update_slug("New Slug_of_organization")
222+
223+
def test_is_member(self, user):
224+
user = user.get()
225+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
226+
organization.save()
227+
organization.add_member(user.profile)
228+
229+
assert organization.is_member(user.profile)
230+
231+
def test_moderator(self, user):
232+
user = user.get()
233+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
234+
organization.save()
235+
organization.add_moderator(user.profile)
236+
237+
assert organization.moderator(user.profile)
238+
239+
def test_promote(self, user):
240+
user = user.get()
241+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
242+
organization.save()
243+
organization.add_member(user.profile)
244+
245+
assert organization.promote(user.profile)
246+
247+
def test_request_review(self, user):
248+
user = user.get()
249+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
250+
organization.save()
251+
article = Article(user.profile, 'title', 'some body', description='some', isPublished='True', coverImage= "Image")
252+
article.save()
253+
254+
return organization.request_review(article)
255+
256+
def test_remove_review_status(self, user):
257+
user = user.get()
258+
organization = Organization(name="Name_of_organization", description="Description_of_organization", slug="Slug_of_organization")
259+
organization.save()
260+
article = Article(user.profile, 'title', 'some body', description='some', isPublished='True', coverImage= "Image")
261+
article.save()
262+
organization.pending_articles.append(article)
263+
264+
return organization.remove_review_status(article)

components/article/ArticleList.tsx

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { useRouter } from "next/router";
22
import React from "react";
33
import useSWR from "swr";
4+
import storage from "../../lib/utils/storage";
5+
import checkLogin from "../../lib/utils/checkLogin";
6+
import Router from "next/router";
7+
import axios from "axios";
8+
49

510
import ErrorMessage from "../common/ErrorMessage";
611
import LoadingSpinner from "../common/LoadingSpinner";
@@ -16,8 +21,13 @@ import { SERVER_BASE_URL, DEFAULT_LIMIT } from "../../lib/utils/constant";
1621
import fetcher from "../../lib/utils/fetcher";
1722
import ArticleCard from "../../components/global/ArticleCard";
1823
import CustomLink from "../common/CustomLink";
24+
import ArticleAPI from "../../lib/api/article";
25+
import {message} from 'antd';
26+
1927

2028
const ArticleList = (props) => {
29+
const [refresh,setRefresh] = React.useState(false)
30+
2131
const page = usePageState();
2232
const pageCount = usePageCountState();
2333
const setPageCount = usePageCountDispatch();
@@ -32,6 +42,8 @@ const ArticleList = (props) => {
3242
const isProfilePage = pathname.startsWith(`/profile`);
3343

3444
let fetchURL = `${SERVER_BASE_URL}/articles?offset=${page * DEFAULT_LIMIT}`;
45+
const { data: currentUser } = useSWR("user", storage);
46+
const isLoggedIn = checkLogin(currentUser);
3547

3648
switch (true) {
3749
case !!tag:
@@ -80,7 +92,67 @@ const ArticleList = (props) => {
8092
if (articles && articles.length === 0) {
8193
return <div className="article-preview">No articles are here... yet.</div>;
8294
}
95+
96+
const rightButtonClicked=async(e,slug,bookmarked)=>{
97+
e.preventDefault()
98+
if(currentUser == null){
99+
message.info('Please Sign in');
100+
}else{
83101

102+
if(!bookmarked){
103+
104+
await ArticleAPI.bookmark(slug,currentUser.token);
105+
}else{
106+
await ArticleAPI.removeBookmark(slug,currentUser.token);
107+
}
108+
for (let index in props.articles){
109+
if(props.articles[index].slug== slug){
110+
props.articles[index].bookmarked = !bookmarked
111+
break;
112+
}
113+
}
114+
setRefresh(!refresh)
115+
116+
}
117+
118+
}
119+
const handleClickFavorite = async (e,slug,favorited) => {
120+
e.preventDefault()
121+
if (!isLoggedIn) {
122+
Router.push(`/user/login`);
123+
return;
124+
}
125+
try {
126+
if (favorited) {
127+
await axios.delete(`${SERVER_BASE_URL}/articles/${slug}/favorite`, {
128+
headers: {
129+
Authorization: `Token ${currentUser?.token}`,
130+
},
131+
});
132+
133+
} else {
134+
await axios.post(
135+
`${SERVER_BASE_URL}/articles/${slug}/favorite`,
136+
{},
137+
{
138+
headers: {
139+
Authorization: `Token ${currentUser?.token}`,
140+
},
141+
}
142+
);
143+
144+
}
145+
for (let index in props.articles){
146+
if(props.articles[index].slug== slug){
147+
props.articles[index].favorited = !favorited
148+
break;
149+
}
150+
}
151+
setRefresh(!refresh)
152+
} catch (error) {
153+
154+
}
155+
};
84156
return (
85157
<>
86158
{articles?.map((article) => (
@@ -89,7 +161,7 @@ const ArticleList = (props) => {
89161
as={`/article/${article.slug}`}
90162
className="preview-link"
91163
>
92-
<ArticleCard key={article.slug} article={article} />
164+
<ArticleCard key={article.slug} article={article} onRightButtonClick ={(e)=>rightButtonClicked(e,article.slug,article.bookmarked)} favoriteClick = {(e)=>handleClickFavorite(e,article.slug,article.favorited)} />
93165
</CustomLink>
94166
))}
95167

0 commit comments

Comments
 (0)