SpringでRedditへの投稿をスケジュールする
1. 概要
2. Userと投稿
まず、UserとPostの2つの主要なエンティティを作成しましょう。 Userは、ユーザー名といくつかの追加のOauth情報を追跡します。
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String username;
private String accessToken;
private String refreshToken;
private Date tokenExpiration;
private boolean needCaptcha;
// standard setters and getters
}
次へ–Postエンティティ– Redditへのリンクを送信するために必要な情報を保持します:title、URL、subreddit、…など。
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false) private String title;
@Column(nullable = false) private String subreddit;
@Column(nullable = false) private String url;
private boolean sendReplies;
@Column(nullable = false) private Date submissionDate;
private boolean isSent;
private String submissionResponse;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
// standard setters and getters
}
3. 永続層
Spring Data JPA for persistenceを使用するので、リポジトリのよく知られたインターフェイス定義を除いて、ここで確認することはそれほど多くありません。
-
UserRepository:
public interface UserRepository extends JpaRepository {
User findByUsername(String username);
User findByAccessToken(String token);
}
-
ポストリポジトリ:
public interface PostRepository extends JpaRepository {
List findBySubmissionDateBeforeAndIsSent(Date date, boolean isSent);
List findByUser(User user);
}
4. スケジューラー
アプリのスケジュール設定についても、すぐに使用できるSpringサポートを活用します。
1分ごとに実行するタスクを定義しています。これは単にRedditへのPosts that are due to be submittedを探します:
public class ScheduledTasks {
private final Logger logger = LoggerFactory.getLogger(getClass());
private OAuth2RestTemplate redditRestTemplate;
@Autowired
private PostRepository postReopsitory;
@Scheduled(fixedRate = 1 * 60 * 1000)
public void reportCurrentTime() {
List posts =
postReopsitory.findBySubmissionDateBeforeAndIsSent(new Date(), false);
for (Post post : posts) {
submitPost(post);
}
}
private void submitPost(Post post) {
try {
User user = post.getUser();
DefaultOAuth2AccessToken token =
new DefaultOAuth2AccessToken(user.getAccessToken());
token.setRefreshToken(new DefaultOAuth2RefreshToken((user.getRefreshToken())));
token.setExpiration(user.getTokenExpiration());
redditRestTemplate.getOAuth2ClientContext().setAccessToken(token);
UsernamePasswordAuthenticationToken userAuthToken =
new UsernamePasswordAuthenticationToken(
user.getUsername(), token.getValue(),
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(userAuthToken);
MultiValueMap param = new LinkedMultiValueMap();
param.add("api_type", "json");
param.add("kind", "link");
param.add("resubmit", "true");
param.add("then", "comments");
param.add("title", post.getTitle());
param.add("sr", post.getSubreddit());
param.add("url", post.getUrl());
if (post.isSendReplies()) {
param.add(RedditApiConstants.SENDREPLIES, "true");
}
JsonNode node = redditRestTemplate.postForObject(
"https://oauth.reddit.com/api/submit", param, JsonNode.class);
JsonNode errorNode = node.get("json").get("errors").get(0);
if (errorNode == null) {
post.setSent(true);
post.setSubmissionResponse("Successfully sent");
postReopsitory.save(post);
} else {
post.setSubmissionResponse(errorNode.toString());
postReopsitory.save(post);
}
} catch (Exception e) {
logger.error("Error occurred", e);
}
}
}
何か問題が発生した場合、投稿はsentとしてマークされないことに注意してください。つまり、the next cycle will try to submit it again after one minuteです。
5. ログインプロセス
セキュリティ固有の情報を保持している新しいユーザーエンティティでは、modify our simple login process to store that informationを実行する必要があります。
@RequestMapping("/login")
public String redditLogin() {
JsonNode node = redditRestTemplate.getForObject(
"https://oauth.reddit.com/api/v1/me", JsonNode.class);
loadAuthentication(node.get("name").asText(), redditRestTemplate.getAccessToken());
return "redirect:home.html";
}
AndloadAuthentication():
private void loadAuthentication(String name, OAuth2AccessToken token) {
User user = userReopsitory.findByUsername(name);
if (user == null) {
user = new User();
user.setUsername(name);
}
if (needsCaptcha().equalsIgnoreCase("true")) {
user.setNeedCaptcha(true);
} else {
user.setNeedCaptcha(false);
}
user.setAccessToken(token.getValue());
user.setRefreshToken(token.getRefreshToken().getValue());
user.setTokenExpiration(token.getExpiration());
userReopsitory.save(user);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(user, token.getValue(),
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(auth);
}
ユーザーがまだ存在しない場合、ユーザーがどのように自動的に作成されるかに注意してください。 これにより、「Login with Reddit」プロセスにより、最初のログイン時にシステムにローカルユーザーが作成されます。
6. スケジュールページ
次へ–新しい投稿のスケジュールを設定できるページを見てみましょう。
@RequestMapping("/postSchedule")
public String showSchedulePostForm(Model model) {
boolean isCaptchaNeeded = getCurrentUser().isCaptchaNeeded();
if (isCaptchaNeeded) {
model.addAttribute("msg", "Sorry, You do not have enought karma");
return "submissionResponse";
}
return "schedulePostForm";
}
private User getCurrentUser() {
return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
schedulePostForm.html:
キャプチャを確認する方法に注意してください。 これは、–if the user has less than 10 karma –キャプチャを入力しないと投稿をスケジュールできないためです。
7. POST
スケジュールフォームが送信されると、the post info is simply validated and persistedが後でスケジューラーによって取得されます。
@RequestMapping(value = "/api/scheduledPosts", method = RequestMethod.POST)
@ResponseBody
public Post schedule(@RequestBody Post post) {
if (submissionDate.before(new Date())) {
throw new InvalidDateException("Scheduling Date already passed");
}
post.setUser(getCurrentUser());
post.setSubmissionResponse("Not sent yet");
return postReopsitory.save(post);
}
8. 予定されている投稿のリスト
次に、スケジュールされた投稿を取得するための簡単なRESTAPIを実装しましょう。
@RequestMapping(value = "/api/scheduledPosts")
@ResponseBody
public List getScheduledPosts() {
User user = getCurrentUser();
return postReopsitory.findByUser(user);
}
そして、display these scheduled posts on the front endへの簡単で迅速な方法:
Post title Submission Date
9. スケジュールされた投稿を編集する
次へ–スケジュールされた投稿を編集する方法を見てみましょう。
フロントエンドから始めましょう–まず、非常に単純なMVC操作です。
@RequestMapping(value = "/editPost/{id}", method = RequestMethod.GET)
public String showEditPostForm() {
return "editPostForm";
}
単純なAPIの後、それを使用するフロントエンドは次のとおりです。
それでは、REST APIを見てみましょう。
@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.GET)
@ResponseBody
public Post getPost(@PathVariable("id") Long id) {
return postReopsitory.findOne(id);
}
@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void updatePost(@RequestBody Post post, @PathVariable Long id) {
if (post.getSubmissionDate().before(new Date())) {
throw new InvalidDateException("Scheduling Date already passed");
}
post.setUser(getCurrentUser());
postReopsitory.save(post);
}
10. Unschedule/Delete a Post
また、スケジュールされた投稿の簡単な削除操作も提供します。
@RequestMapping(value = "/api/scheduledPosts/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deletePost(@PathVariable("id") Long id) {
postReopsitory.delete(id);
}
クライアント側からの呼び方は次のとおりです。
11. 結論
Redditのケーススタディのこの部分では、Reddit APIを使用して投稿のスケジューリングを行うことにより、最初の重要な機能を構築しました。
これは、特にhow time-sensitive Reddit submissions areを考慮すると、真面目なRedditユーザーにとって非常に便利な機能です。
次へ– Redditでコンテンツを賛成するのに役立つ、さらに役立つ機能を構築します–機械学習の提案。
このチュートリアルのfull implementationは、the github projectにあります。これはEclipseベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。