Skip to content

Commit 1edfab2

Browse files
authored
visibility for home feed (#168)
- This PR implement the visibility feature for home feed. The home feed will contain all of the public status, quite public status and follower status. - [x] Implement Visibility.homefeedViewable to check if a status can be displayed on given user homefeed. - [x] replace manual LOG definition with lombok's CommonLog in StatusService and VisibilityService. - [x] integration test.
1 parent 77d7839 commit 1edfab2

File tree

3 files changed

+75
-11
lines changed

3 files changed

+75
-11
lines changed

server/src/main/java/edu/sjsu/moth/server/service/StatusService.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import edu.sjsu.moth.server.db.StatusHistoryRepository;
2020
import edu.sjsu.moth.server.db.StatusMention;
2121
import edu.sjsu.moth.server.db.StatusRepository;
22+
import lombok.extern.apachecommons.CommonsLog;
2223
import org.bson.types.ObjectId;
2324
import org.jetbrains.annotations.NotNull;
2425
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,9 +32,9 @@
3132
import java.security.Principal;
3233
import java.util.ArrayList;
3334
import java.util.List;
34-
import java.util.logging.Logger;
3535

3636
@Configuration
37+
@CommonsLog
3738
public class StatusService {
3839

3940
@Autowired
@@ -57,8 +58,6 @@ public class StatusService {
5758
@Autowired
5859
VisibilityService visibilityService;
5960

60-
Logger LOG = Logger.getLogger(StatusService.class.getName());
61-
6261
public Mono<ArrayList<StatusEdit>> findHistory(String id) {
6362
return statusHistoryRepository.findById(id).map(edits -> edits.collection);
6463
}
@@ -97,13 +96,13 @@ public Mono<Status> save(Status status) {
9796
for (String s : accountsmentioned) {
9897
String[] tokens = s.split("@");
9998
if (tokens.length > 2) {
100-
LOG.warning("Does not store remote user mention: " + s);
99+
log.warn("Does not store remote user mention: " + s);
101100
continue;
102101
}
103102
String username = tokens[1];
104103
mono = mono.then(accountService.getAccountById(username).switchIfEmpty(
105104
Mono.error(new UsernameNotFoundException("Mentioned account not found: " + username))).map(acc -> {
106-
LOG.finest("Adding mention: " + acc.username);
105+
log.debug("Adding mention: " + acc.username);
107106
status.mentions.add(new StatusMention(acc.id, acc.username, acc.url, acc.acct));
108107
return Mono.empty();
109108
}));
@@ -167,9 +166,9 @@ public Mono<List<Status>> getHomeTimeline(Principal user, String max_id, String
167166
var predicate = qStatus.content.isNotNull();
168167
predicate = addRangeQueries(predicate, max_id, since_id, max_id);
169168
var external = externalStatusRepository.findAll(predicate, Sort.by(Sort.Direction.DESC, "id"))
170-
.flatMap(statuses -> filterStatusByViewable(user, statuses, isFollowingTimeline)).take(limit);
169+
.flatMap(statuses -> visibilityService.homefeedViewable(user, statuses)).take(limit);
171170
var internal = statusRepository.findAll(predicate, Sort.by(Sort.Direction.DESC, "id"))
172-
.flatMap(statuses -> filterStatusByViewable(user, statuses, isFollowingTimeline)).take(limit);
171+
.flatMap(statuses -> visibilityService.homefeedViewable(user, statuses)).take(limit);
173172

174173
//TODO: we may want to merge sort them, unsure if merge does that
175174
return Flux.merge(external, internal).collectList();
@@ -264,4 +263,4 @@ private String convertToHex(String payload) {
264263
return String.format("%1$24s", payload).replace(' ', '0');
265264
}
266265

267-
}
266+
}

server/src/main/java/edu/sjsu/moth/server/service/VisibilityService.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,47 @@
1111
import reactor.core.publisher.Mono;
1212

1313
import java.security.Principal;
14-
import java.util.logging.Logger;
1514

1615
@Service
1716
@CommonsLog
1817
public class VisibilityService {
18+
@Autowired
19+
AccountService accountService;
20+
21+
@Autowired
22+
FollowRepository followRepository;
23+
1924
final String PUBLIC_VISIBILITY = "public";
2025
final String QUITE_PUBLIC = "unlisted";
2126
final String DIRECT_VISIBILITY = "direct";
27+
final String PRIVATE_VISIBILITY = "private";
2228

2329

2430
public Flux<Status> publicTimelinesViewable(Status status) {
2531
if (status.visibility.equals(PUBLIC_VISIBILITY)) return Flux.just(status);
2632
return Flux.empty();
2733
}
2834

35+
public Flux<Status> homefeedViewable(Principal user, Status status) {
36+
return accountService
37+
.getAccount(user.getName())
38+
.switchIfEmpty(Mono.error(new UsernameNotFoundException("User not found")))
39+
.flatMapMany(account -> followRepository.findAllByFollowerId(account.id).flatMap(
40+
follow -> {
41+
if (follow.id.followed_id.equals(status.account.id)
42+
&& validHomeFeedVisibility(status.visibility)) {
43+
return Flux.just(status);
44+
}
45+
return Flux.empty();
46+
}
47+
)
48+
);
49+
}
50+
51+
private boolean validHomeFeedVisibility(String visibility) {
52+
return switch (visibility) {
53+
case PUBLIC_VISIBILITY, PRIVATE_VISIBILITY, QUITE_PUBLIC -> true;
54+
default -> false;
55+
};
56+
}
2957
}

server/src/test/java/edu/sjsu/moth/controllers/StatusControllerTest.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import edu.sjsu.moth.server.controller.StatusController;
1515
import edu.sjsu.moth.server.db.Account;
1616
import edu.sjsu.moth.server.db.AccountRepository;
17+
import edu.sjsu.moth.server.db.Follow;
18+
import edu.sjsu.moth.server.db.FollowRepository;
1719
import edu.sjsu.moth.server.db.StatusRepository;
1820
import edu.sjsu.moth.server.db.TokenRepository;
1921
import edu.sjsu.moth.server.service.StatusService;
@@ -54,7 +56,7 @@ public class StatusControllerTest {
5456
final AccountRepository accountRepository;
5557
final StatusService statusService;
5658
final StatusRepository statusRepository;
57-
59+
final FollowRepository followRepository;
5860
static {
5961
try {
6062
var fullname = IntegrationTest.class.getResource("/test.cfg").getFile();
@@ -70,12 +72,13 @@ public class StatusControllerTest {
7072
@Autowired
7173
public StatusControllerTest(WebTestClient webTestClient, TokenRepository tokenRepository,
7274
AccountRepository accountRepository, StatusService statusService,
73-
StatusRepository statusRepository) {
75+
StatusRepository statusRepository, FollowRepository followRepository) {
7476
this.webTestClient = webTestClient;
7577
this.tokenRepository = tokenRepository;
7678
this.accountRepository = accountRepository;
7779
this.statusService = statusService;
7880
this.statusRepository = statusRepository;
81+
this.followRepository = followRepository;
7982
}
8083

8184
@AfterAll
@@ -151,4 +154,38 @@ public void testPostStatusWithRemoteMentions() {
151154
Assertions.assertEquals(1, status.mentions.size());
152155
Assertions.assertEquals("test-mention", status.mentions.get(0).acct);
153156
}
157+
158+
@Test
159+
public void testHomeFeedVisibility() {
160+
String HOME_FEED_END_POINT = "/api/v1/timelines/home";
161+
prepareStatusForHomeFeedVisibility();
162+
accountRepository.save(new Account("test-fetch")).block();
163+
followRepository.save(new Follow("test-fetch", "test-creator")).block();
164+
// Mock the authentication
165+
webTestClient
166+
.mutateWith(mockJwt().jwt(jwt -> jwt.claim("sub", "test-fetch")))
167+
.get()
168+
.uri(HOME_FEED_END_POINT)
169+
.exchange().expectStatus().isOk()
170+
.expectBody()
171+
.jsonPath("$.length()").isEqualTo(3);
172+
}
173+
174+
private void prepareStatusForHomeFeedVisibility() {
175+
String statusCreator = "test-creator";
176+
accountRepository.save(new Account(statusCreator)).block();
177+
178+
StatusController.V1PostStatus request;
179+
String[] visibilities = { "public", "unlisted", "private", "direct" };
180+
for (String visibility : visibilities) {
181+
request = new StatusController.V1PostStatus();
182+
request.status = String.format("This is a %s status", visibility);
183+
request.visibility = visibility;
184+
185+
webTestClient.mutateWith(mockJwt().jwt(jwt -> jwt.claim("sub", statusCreator))).post()
186+
.uri(POST_STATUS_ENDPOINT).contentType(MediaType.APPLICATION_JSON).bodyValue(request).exchange()
187+
.expectStatus().isOk();
188+
}
189+
}
190+
154191
}

0 commit comments

Comments
 (0)