Merge pull request #153 from Doha2012/master

add schedule post to reddit
This commit is contained in:
Eugen 2015-03-02 22:03:51 +02:00
commit cc759f1cf9
19 changed files with 2520 additions and 33 deletions

View File

@ -23,6 +23,9 @@ import org.springframework.util.MultiValueMap;
public class MyAuthorizationCodeAccessTokenProvider extends AuthorizationCodeAccessTokenProvider implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3822611002661972274L;
private StateKeyGenerator stateKeyGenerator = new DefaultStateKeyGenerator();

View File

@ -0,0 +1,75 @@
package org.baeldung.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@PropertySource({ "classpath:persistence.properties" })
@ComponentScan({ "org.baeldung.persistence" })
@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao")
public class PersistenceJPAConfig {
@Autowired
private Environment env;
public PersistenceJPAConfig() {
super();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" });
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
@Bean
public JpaTransactionManager transactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
return hibernateProperties;
}
}

View File

@ -13,7 +13,7 @@ public class ServletInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebConfig.class);
context.register(PersistenceJPAConfig.class, WebConfig.class);
return context;
}

View File

@ -2,12 +2,15 @@ package org.baeldung.config;
import java.util.Arrays;
import org.baeldung.web.schedule.ScheduledTasks;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
@ -27,7 +30,9 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "org.baeldung.web" })
@EnableScheduling
@EnableAsync
@ComponentScan({ "org.baeldung.web" })
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
@ -48,6 +53,25 @@ public class WebConfig extends WebMvcConfigurerAdapter {
configurer.enable();
}
// @Bean
// public RedditController redditController(OAuth2RestTemplate redditRestTemplate) {
// RedditController controller = new RedditController();
// controller.setRedditRestTemplate(redditRestTemplate);
// return controller;
// }
//
// @Bean
// public RestExceptionHandler restExceptionHandler() {
// return new RestExceptionHandler();
// }
//
@Bean
public ScheduledTasks scheduledTasks(OAuth2ProtectedResourceDetails reddit) {
ScheduledTasks s = new ScheduledTasks();
s.setRedditRestTemplate(new OAuth2RestTemplate(reddit));
return s;
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}

View File

@ -0,0 +1,15 @@
package org.baeldung.persistence.dao;
import java.util.Date;
import java.util.List;
import org.baeldung.persistence.model.Post;
import org.baeldung.persistence.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
public List<Post> findBySubmissionDateBefore(Date date);
public List<Post> findByUser(User user);
}

View File

@ -0,0 +1,10 @@
package org.baeldung.persistence.dao;
import org.baeldung.persistence.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
public User findByUsername(String username);
public User findByAccessToken(String token);
}

View File

@ -0,0 +1,103 @@
package org.baeldung.persistence.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String subreddit;
private String url;
private Date submissionDate;
private boolean isSent;
private String submissionResponse;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
public Post() {
super();
}
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSubreddit() {
return subreddit;
}
public void setSubreddit(String subreddit) {
this.subreddit = subreddit;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Date getSubmissionDate() {
return submissionDate;
}
public void setSubmissionDate(Date submissionDate) {
this.submissionDate = submissionDate;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public boolean isSent() {
return isSent;
}
public void setSent(boolean isSent) {
this.isSent = isSent;
}
public String getSubmissionResponse() {
return submissionResponse;
}
public void setSubmissionResponse(String submissionResponse) {
this.submissionResponse = submissionResponse;
}
}

View File

@ -0,0 +1,104 @@
package org.baeldung.persistence.model;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String accessToken;
private String refreshToken;
private Date tokenExpiration;
@OneToMany(mappedBy = "user")
private List<Post> posts;
public User() {
super();
}
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public Date getTokenExpiration() {
return tokenExpiration;
}
public void setTokenExpiration(Date tokenExpiration) {
this.tokenExpiration = tokenExpiration;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final User user = (User) obj;
if (!username.equals(user.username))
return false;
return true;
}
}

View File

@ -1,15 +1,21 @@
package org.baeldung.web;
import java.io.IOException;
import java.util.ArrayList;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.baeldung.persistence.dao.PostRepository;
import org.baeldung.persistence.dao.UserRepository;
import org.baeldung.persistence.model.Post;
import org.baeldung.persistence.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
@ -17,37 +23,43 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@Controller
public class RedditController {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Autowired
private OAuth2RestTemplate redditRestTemplate;
// API
@Autowired
private UserRepository userReopsitory;
@Autowired
private PostRepository postReopsitory;
@RequestMapping("/info")
public final String getInfo(Model model) {
final JsonNode node = redditRestTemplate.getForObject("https://oauth.reddit.com/api/v1/me", JsonNode.class);
final String name = node.get("name").asText();
JsonNode node = redditRestTemplate.getForObject("https://oauth.reddit.com/api/v1/me", JsonNode.class);
String name = node.get("name").asText();
addUser(name, redditRestTemplate.getAccessToken());
model.addAttribute("info", name);
return "reddit";
}
@RequestMapping("/submit")
public final String submit(Model model, @RequestParam Map<String, String> formParams) {
final MultiValueMap<String, String> param = new LinkedMultiValueMap<String, String>();
MultiValueMap<String, String> param = new LinkedMultiValueMap<String, String>();
param.add("api_type", "json");
param.add("kind", "link");
param.add("resubmit", "true");
param.add("sendreplies", "false");
param.add("then", "comments");
for (final Map.Entry<String, String> entry : formParams.entrySet()) {
for (Map.Entry<String, String> entry : formParams.entrySet()) {
param.add(entry.getKey(), entry.getValue());
}
@ -60,44 +72,73 @@ public class RedditController {
}
@RequestMapping("/post")
public final String showSubmissionForm(final Model model) {
final String needsCaptchaResult = needsCaptcha();
public final String showSubmissionForm(Model model) {
String needsCaptchaResult = needsCaptcha();
if (needsCaptchaResult.equalsIgnoreCase("true")) {
final String iden = getNewCaptcha();
String iden = getNewCaptcha();
model.addAttribute("iden", iden);
}
return "submissionForm";
}
// === private
final List<String> getSubreddit() throws JsonProcessingException, IOException {
final String result = redditRestTemplate.getForObject("https://oauth.reddit.com/subreddits/popular?limit=50", String.class);
final JsonNode node = new ObjectMapper().readTree(result).get("data").get("children");
final List<String> subreddits = new ArrayList<String>();
for (JsonNode child : node) {
subreddits.add(child.get("data").get("display_name").asText());
@RequestMapping("/postSchedule")
public final String showSchedulePostForm(Model model) {
String needsCaptchaResult = needsCaptcha();
if (needsCaptchaResult.equalsIgnoreCase("true")) {
model.addAttribute("msg", "Sorry, You do not have enought karma");
return "submissionResponse";
}
return subreddits;
return "schedulePostForm";
}
@RequestMapping("/schedule")
public final String schedule(Model model, @RequestParam Map<String, String> formParams) throws ParseException {
logger.info("User scheduling Post with these parameters: " + formParams.entrySet());
User user = userReopsitory.findByAccessToken(redditRestTemplate.getAccessToken().getValue());
Post post = new Post();
post.setUser(user);
post.setSent(false);
post.setTitle(formParams.get("title"));
post.setSubreddit(formParams.get("sr"));
post.setUrl(formParams.get("url"));
post.setSubmissionDate(dateFormat.parse(formParams.get("date")));
if (post.getSubmissionDate().before(new Date())) {
model.addAttribute("msg", "Invalid date");
return "submissionResponse";
}
postReopsitory.save(post);
List<Post> posts = postReopsitory.findByUser(user);
model.addAttribute("posts", posts);
return "postListView";
}
@RequestMapping("/posts")
public final String getScheduledPosts(Model model) {
User user = userReopsitory.findByAccessToken(redditRestTemplate.getAccessToken().getValue());
List<Post> posts = postReopsitory.findByUser(user);
model.addAttribute("posts", posts);
return "postListView";
}
// === private
private final String needsCaptcha() {
String result = redditRestTemplate.getForObject("https://oauth.reddit.com/api/needs_captcha.json", String.class);
return result;
}
private final String getNewCaptcha() {
final Map<String, String> param = new HashMap<String, String>();
Map<String, String> param = new HashMap<String, String>();
param.put("api_type", "json");
final String result = redditRestTemplate.postForObject("https://oauth.reddit.com/api/new_captcha", param, String.class, param);
final String[] split = result.split("\"");
String result = redditRestTemplate.postForObject("https://oauth.reddit.com/api/new_captcha", param, String.class, param);
String[] split = result.split("\"");
return split[split.length - 2];
}
private final String parseResponse(final JsonNode node) {
private final String parseResponse(JsonNode node) {
String result = "";
final JsonNode errorNode = node.get("json").get("errors").get(0);
JsonNode errorNode = node.get("json").get("errors").get(0);
if (errorNode != null) {
for (JsonNode child : errorNode) {
result = result + child.toString().replaceAll("\"|null", "") + "<br>";
@ -111,4 +152,16 @@ public class RedditController {
}
}
private final void addUser(String name, OAuth2AccessToken token) {
User user = userReopsitory.findByUsername(name);
if (user == null) {
user = new User();
user.setUsername(name);
user.setAccessToken(token.getValue());
user.setRefreshToken(token.getRefreshToken().getValue());
user.setTokenExpiration(token.getExpiration());
userReopsitory.save(user);
}
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler implements Serializable {
private static final long serialVersionUID = -3861125729653781371L;
public RestExceptionHandler() {
@ -36,5 +37,4 @@ public class RestExceptionHandler extends ResponseEntityExceptionHandler impleme
String response = "Error Occurred : " + ex.getMessage();
return handleExceptionInternal(ex, response, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}

View File

@ -0,0 +1,77 @@
package org.baeldung.web.schedule;
import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.baeldung.persistence.dao.PostRepository;
import org.baeldung.persistence.model.Post;
import org.baeldung.persistence.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@EnableScheduling
public class ScheduledTasks {
private OAuth2RestTemplate redditRestTemplate;
@Autowired
private PostRepository postReopsitory;
@Scheduled(fixedRate = 5 * 60 * 1000)
public void reportCurrentTime() throws JsonProcessingException, IOException, ParseException {
List<Post> posts = postReopsitory.findBySubmissionDateBefore(new Date());
System.out.println(posts.size());
for (Post post : posts) {
if (post.isSent())
continue;
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<String, String> param = new LinkedMultiValueMap<String, String>();
param.add("api_type", "json");
param.add("kind", "link");
param.add("resubmit", "true");
param.add("sendreplies", "false");
param.add("then", "comments");
param.add("title", post.getTitle());
param.add("sr", post.getSubreddit());
param.add("url", post.getUrl());
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);
postReopsitory.save(post);
} else {
post.setSubmissionResponse(errorNode.toString());
postReopsitory.save(post);
}
}
}
public void setRedditRestTemplate(OAuth2RestTemplate redditRestTemplate) {
this.redditRestTemplate = redditRestTemplate;
}
}

View File

@ -0,0 +1,10 @@
################### DataSource Configuration ##########################
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/oauth_reddit?createDatabaseIfNotExist=true
jdbc.user=tutorialuser
jdbc.pass=tutorialmy5ql
init-db=false
################### Hibernate Configuration ##########################
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=false
hibernate.hbm2ddl.auto=create-drop

View File

@ -0,0 +1,54 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Spring Security OAuth</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="info">Spring Security OAuth</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">My Scheduled Posts</a></li>
<li><a href="post">Submit Post</a></li>
<li><a href="postSchedule">Schedule Post</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<h1>My Scheduled Posts</h1>
<table class="table table-bordered">
<c:forEach var="post" items="${posts}" >
<thead>
<tr>
<th>Post title</th>
<th>Submission Date</th>
<th>Notes</th>
</tr>
</thead>
<tr <c:if test="${post.isSent()}"> class="success"</c:if>>
<td><c:out value="${post.getTitle()}"/></td>
<td><c:out value="${post.getSubmissionDate()}"/></td>
<td><c:out value="${post.getSubmissionResponse()}"/></td>
</tr>
</c:forEach>
</table>
</div>
</body>
</html>

View File

@ -7,12 +7,34 @@
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Spring Security OAuth</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="posts">My Scheduled Posts</a></li>
<li><a href="post">Submit Post</a></li>
<li><a href="postSchedule">Schedule Post</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<c:choose>
<c:when test="${info != null}">
<h1>Your Reddit Info</h1>
<b>Your reddit username is </b>${info}
<br><br><br>
<h1>Welcome, <small>${info}</small></h1>
<a href="post" class="btn btn-primary">Submit to Reddit</a>
</c:when>
<c:otherwise>

View File

@ -0,0 +1,71 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Spring Security OAuth</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="<c:url value="/resources/datetime-picker.css" />">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="<c:url value="/resources/datetime-picker.js" />"></script>
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Spring Security OAuth</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="posts">My Scheduled Posts</a></li>
<li><a href="post">Submit Post</a></li>
<li class="active"><a href="#">Schedule Post</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<h1>Schedule Post</h1>
<form action="schedule" method="post">
<div class="row">
<div class="form-group">
<label class="col-sm-3">Title</label>
<span class="col-sm-9"><input name="title" placeholder="title" class="form-control" /></span>
</div>
<br><br>
<div class="form-group">
<label class="col-sm-3">Url</label>
<span class="col-sm-9"><input name="url" placeholder="url" class="form-control" /></span>
</div>
<br><br>
<div class="form-group">
<label class="col-sm-3">Subreddit</label>
<span class="col-sm-9"><input name="sr" placeholder="Subreddit" class="form-control" /></span>
</div>
<br><br>
<label class="col-sm-3">Submission Date</label>
<span class="col-sm-9"><input type="text" name="date" class="form-control"></span>
<script type="text/javascript">
$(function(){
$('*[name=date]').appendDtpicker({"inline": true});
});
</script>
<br><br>
<button type="submit" class="btn btn-primary">Post</button>
</div>
</form>
</div>
</body>
</html>

View File

@ -7,6 +7,30 @@
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Spring Security OAuth</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="posts">My Scheduled Posts</a></li>
<li class="active"><a href="#">Submit Post</a></li>
<li><a href="postSchedule">Schedule Post</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<h1>Submit to Reddit</h1>
<form action="submit" method="post">

View File

@ -7,9 +7,32 @@
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Spring Security OAuth</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="posts">My Scheduled Posts</a></li>
<li><a href="post">Submit Post</a></li>
<li><a href="postSchedule">Schedule Post</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<h1>${msg}</h1>
<a href="post" class="btn btn-primary">Submit another link to Reddit</a>
</div>
</body>
</html>

View File

@ -0,0 +1,332 @@
/**
* Style-sheet for dtpicker
* https://github.com/mugifly/jquery-simple-datetimepicker
*/
.datepicker {
position: relative;
display: inline-block;
font: 15px/1.5 "Helvetica Neue", mplus-2c, Helvetica, Arial, "Hiragino Kaku Gothic Pro", Meiryo, sans-serif;
font-weight: 300;
border: 1px solid #dfdfdf;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
box-shadow: 0.5px 0.5px 0px #c8c8c8;
-webkit-box-shadow: 0.5px 0.5px 3px #eeeeee;
-moz-box-shadow: 0.5px 0.5px 3px #eeeeee;
}
/*
* datepicker_header
*/
.datepicker > .datepicker_header{
padding-top: 2px;
padding-bottom: 2px;
padding-left: 5px;
padding-right: 5px;
background-color: #eeeeee;
color: #3f3f3f;
text-align: center;
font-size: 9pt;
font-weight: bold;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
.datepicker > .datepicker_header > a {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
cursor: pointer;
color: #3b7796;
padding: 3px 16px;
font-size: 20px;
}
.datepicker > .datepicker_header > a:hover {
color: #303030;
background-color: #c8c8c8;
}
.datepicker > .datepicker_header > a:active {
color: #ffffff;
background-color: #808080;
}
.datepicker > .datepicker_header > span {
margin-left: 20px;
margin-right: 20px;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
.datepicker > .datepicker_header > .icon-home {
position: absolute;
display: block;
width: 16px;
height: 16px;
vertical-align: middle;
padding: 8px;
top: 0;
left: 0;
}
.datepicker > .datepicker_header > .icon-close {
position: absolute;
display: block;
width: 16px;
height: 16px;
vertical-align: middle;
padding: 8px;
top: 0;
right: 0;
}
.datepicker > .datepicker_header > .icon-home > div {
width: 16px;
height: 16px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAepJREFUSEudk71Kw2AUhnVxVnCyN1EcCjXUujmKF1AoRRCcNU4OIg5dtXF36tAb6B107qjg0iC4STt0EdTnTU8+v5rGNh54aM7Pe/J+abLmR3AZ7QeXD0+WZkI9zViaiXq9vg+5evU0Y+ksgjCqsrgPA2jBOzQs91FNPc0o70vLQtGHAbTgHRqW+6imnmaUS1PVqWL4SgijJr8juHK1H1Qb2Uxai1kivowmjODKq6Wopp5m0losA95Noh43ONXi+XpCPOsx49W9ZaIHp+CbSlFNPc24+i8DxfGX/YeZgTDqQrsgXc9AF9oFkcY9gQN7J3MjuOhs2WUS0ngGluqZmdNLs7IBTltmZsrvsZUKGaBfhik4vTSw3EBwfrdBfwhvwcX9tpVXNkBvA4bwBk4vDSw2UD27WbdLnf42mQmjIysl8ZeBWq3m9PRubWZOL43qGQOccpf8lc9tj5tWuP7g+tHaLqSR1pY7Pde78Ap7UIEPyOip5RgIOzvkzzCGF4gxtWltF9LAIgM78AxjeAF9/xk9tfy/gBuWOLVMfPIUDq08F3kGFOQlkIlPWKiXBvJfQl6+EjdvWJqJvwwoqMlErl4aabVowmmvbWEBEs2EJeLaFhZBmone8hMtAj2JIkykZYmQifRprAqa+sk3UgBXF8VWaxMAAAAASUVORK5CYII=);
}
.datepicker > .datepicker_header > .icon-close> div {
width: 16px;
height: 16px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAfhJREFUSEvNVEtKA0EUHHBhsvZ7D0HF4PRMnERxI+hCQ4xuBE/g5yLRA+il/CCIXkDElRGt6qkZO91j1JUW1KJf1XvVn2Gif4XGUX986aRf0zIANXq0DBBv7Iwvb+192U+NHi2H0Tg+O2ycnN2DV+CcyiVQW5B2T6/KJUxr7dAkySN4bVbaiyqXMM1s3mr0wKtyDgytgQ/gu/gKbkumvgsOpJH0lieNN3s1O9iYd/EN657kyJikZ2uFDi97JDOgX8fAWyeg2MQO6IeT8Pbrao+Wtw/qJklvywBnE5ZuOAkve9SeA0PnQT+Ia7/2BoZXnK0uBkH5Oqxl4RNZYHAH9ANdMnxP9gA42X5FoMuhp6kEAnjlDPpVeIERm0B4+m0/N9AFq26Bta5sX0JvPvDCycFPTs+PbtQTUOvIHgAhu+DoJ4BH9mFgcAzyy3cDee3+c9ATq60ErjfDcP/kDPQ3hJtIM7XlwMAJ8FoBBe2bi/4m6J1QO34yrWlc740XxDffr/wm6EWP2u0GJsEnDS/DJVPvge7T0DspmT+aGQx+cUKGPriKTcCbzEhGwOn5GAZegM/gHdiSVAK1VBo9F+yRFJn2+hgGXtrBSXpn0mbQb5orbavl4ZfskZRDm5gGp1QKQM16nPACdhNpczZur3+ezAM1eoLwv0MUfQBkquZISGUUwgAAAABJRU5ErkJggg==);
}
.datepicker > .datepicker_header > a:hover > div, .datepicker > .datepicker_header > a:hover > div {
background-position: -16px 0px;
}
/*
* datepicker_inner_container
*/
.datepicker > .datepicker_inner_container {
margin: -2px 0px -2px 0px;
background-color: #d2d2d2;
border: 1px solid #c8c8c8;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
box-shadow: 0.5px 0px 3px #c8c8c8;
-webkit-box-shadow: 0.5px 0px 3px #c8c8c8;
-moz-box-shadow: 0.5px 0px 3px #c8c8c8;
}
.datepicker > .datepicker_inner_container:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
/*
* datepicker_inner_container > datepicker_calendar
*/
.datepicker > .datepicker_inner_container > .datepicker_calendar {
float: left;
width: 18.3em;
margin-top: -0.5px;
margin-left: -1px;
margin-bottom: -2px;
background-color: #ffffff;
border: 1px solid #c8c8c8;
border-top:none;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
-webkit-border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-bottomleft: 3px;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > table {
padding: 10px;
}
/*
* datepicker_inner_container > datepicker_calendar > datepicker_table > tbody > tr > th (WDay-cell)
*/
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > th {
color: #646464;
width: 18px;
font-size: small;
font-weight: normal;
text-align:center;
}
/*
* datepicker_inner_container > datepicker_calendar > datepicker_table > tbody > tr > td (Day-cell)
*/
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td {
color: #000000;
font-size: small;
text-align:center;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
cursor: pointer;
padding: 10px;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.today {
border-bottom: #bfbfbf solid 2px;
margin-bottom: -2px;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.wday_sat {
color: #0044aa;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.wday_sun {
color: #e13b00;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_another_month {
color: #cccccc;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_in_past {
cursor: default;
color: #cccccc;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_in_unallowed {
cursor: default;
color: #cccccc;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.out_of_range {
cursor: default;
color: #cccccc;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.active {
color: #ffffff;
background-color: #808080;
}
.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.hover {
color: #000000;
background-color: #c8c8c8;
}
/*
* datepicker_inner_container > datepicker_timelist
*/
.datepicker > .datepicker_inner_container > .datepicker_timelist {
float: left;
margin-top: -0.5px;
padding: 5px 0px;
overflow: auto;
overflow-x: hidden;
background-color: #ffffff;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
-webkit-border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
-moz-border-radius-topright: 3px;
-moz-border-radius-bottomright: 3px;
text-align: right;
width: 4.9em;
}
/*
.datepicker > .datepicker_inner_container > .datepicker_timelist::after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
*/
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar {
overflow: hidden;
width: 6px;
background: #fafafa;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
-webkit-border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
-moz-border-radius-topright: 3px;
-moz-border-radius-bottomright: 3px;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar:horizontal {
height: 1px;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-button {
display: none;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-piece {
background: #eee;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-piece:start {
background: #eee;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-thumb {
background: #aaaaaa;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-corner {
background: #333;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item {
padding-top: 5px;
padding-bottom:5px;
padding-left: 7px;
padding-right: 7px;
margin-top: 5px;
margin-bottom: 2px;
font-size: small;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
cursor: pointer;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.time_in_past {
cursor: default;
color: #cccccc;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.out_of_range {
cursor: default;
color: #cccccc;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.active {
color: #ffffff;
background-color: #808080;
}
.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.hover {
color: #000000;
background-color: #c8c8c8;
}

File diff suppressed because it is too large Load Diff