From 7c1467da42d3fd313c25705d1636f140b038c56d Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 20 May 2014 10:46:18 +0200 Subject: [PATCH 1/4] Rebuild example with Spring Boot Initial prototype. --- retwisj-reboot/build.gradle | 35 ++ retwisj-reboot/pom.xml | 70 ++++ .../java/org/example/retwisj/Application.java | 16 + .../main/java/org/example/retwisj/Post.java | 118 ++++++ .../main/java/org/example/retwisj/Range.java | 45 ++ .../org/example/retwisj/RetwisSecurity.java | 64 +++ .../main/java/org/example/retwisj/User.java | 99 +++++ .../org/example/retwisj/redis/KeyUtils.java | 90 ++++ .../retwisj/redis/RetwisRepository.java | 383 ++++++++++++++++++ .../retwisj/web/CookieInterceptor.java | 64 +++ .../retwisj/web/NoSuchDataException.java | 47 +++ .../example/retwisj/web/RetwisController.java | 220 ++++++++++ .../java/org/example/retwisj/web/WebPost.java | 161 ++++++++ .../org/example/retwisj/web/WebUtils.java | 80 ++++ .../src/main/resources/application.properties | 0 .../src/main/resources/log4j.properties | 16 + .../src/main/resources/messages.properties | 55 +++ .../src/main/resources/messages_cn.properties | 55 +++ .../src/main/resources/messages_en.properties | 1 + .../src/main/resources/messages_es.properties | 55 +++ .../src/main/resources/messages_ro.properties | 55 +++ .../src/main/resources/redis.properties | 5 + .../org/example/retwisj/ApplicationTests.java | 18 + retwisj-reboot/static/images/favicon.ico | Bin 0 -> 1150 bytes .../static/images/springsource-logo.png | Bin 0 -> 4974 bytes retwisj-reboot/static/styles/custom.css | 30 ++ retwisj-reboot/static/styles/ie.css | 36 ++ retwisj-reboot/static/styles/print.css | 29 ++ retwisj-reboot/static/styles/screen.css | 280 +++++++++++++ retwisj-reboot/templates/footer.jspf | 18 + retwisj-reboot/templates/header.jspf | 51 +++ retwisj-reboot/templates/network.jspf | 34 ++ retwisj-reboot/templates/post.jspf | 19 + retwisj-reboot/templates/posts.jspf | 22 + retwisj-reboot/templates/userFollow.jspf | 14 + 35 files changed, 2285 insertions(+) create mode 100644 retwisj-reboot/build.gradle create mode 100644 retwisj-reboot/pom.xml create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/Application.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/Post.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/Range.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/RetwisSecurity.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/User.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/redis/KeyUtils.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/redis/RetwisRepository.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/web/CookieInterceptor.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/web/NoSuchDataException.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/web/WebPost.java create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/web/WebUtils.java create mode 100644 retwisj-reboot/src/main/resources/application.properties create mode 100644 retwisj-reboot/src/main/resources/log4j.properties create mode 100644 retwisj-reboot/src/main/resources/messages.properties create mode 100644 retwisj-reboot/src/main/resources/messages_cn.properties create mode 100644 retwisj-reboot/src/main/resources/messages_en.properties create mode 100644 retwisj-reboot/src/main/resources/messages_es.properties create mode 100644 retwisj-reboot/src/main/resources/messages_ro.properties create mode 100644 retwisj-reboot/src/main/resources/redis.properties create mode 100644 retwisj-reboot/src/test/java/org/example/retwisj/ApplicationTests.java create mode 100644 retwisj-reboot/static/images/favicon.ico create mode 100644 retwisj-reboot/static/images/springsource-logo.png create mode 100644 retwisj-reboot/static/styles/custom.css create mode 100644 retwisj-reboot/static/styles/ie.css create mode 100644 retwisj-reboot/static/styles/print.css create mode 100644 retwisj-reboot/static/styles/screen.css create mode 100644 retwisj-reboot/templates/footer.jspf create mode 100644 retwisj-reboot/templates/header.jspf create mode 100644 retwisj-reboot/templates/network.jspf create mode 100644 retwisj-reboot/templates/post.jspf create mode 100644 retwisj-reboot/templates/posts.jspf create mode 100644 retwisj-reboot/templates/userFollow.jspf diff --git a/retwisj-reboot/build.gradle b/retwisj-reboot/build.gradle new file mode 100644 index 0000000..d392eb2 --- /dev/null +++ b/retwisj-reboot/build.gradle @@ -0,0 +1,35 @@ +buildscript { + ext { + springBootVersion = '1.0.2.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'spring-boot' + +jar { + baseName = 'retwisj-demo' + version = '0.0.1-SNAPSHOT' +} + +repositories { + mavenCentral() +} + +dependencies { + compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}") + compile("org.springframework.boot:spring-boot-starter-redis:${springBootVersion}") + testCompile("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.12' +} diff --git a/retwisj-reboot/pom.xml b/retwisj-reboot/pom.xml new file mode 100644 index 0000000..0a4a24c --- /dev/null +++ b/retwisj-reboot/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + retwisj-demo + 0.0.1-SNAPSHOT + + retwisj-reboot + retwisj demo project + + + org.springframework.boot + spring-boot-starter-parent + 1.0.2.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-redis + + + + org.codehaus.jackson + jackson-mapper-asl + 1.9.13 + + + + org.springframework.data + spring-data-redis + 1.2.1.RELEASE + + + + org.apache.commons + commons-pool2 + 2.2 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + UTF-8 + org.example.retwisj.Application + 1.7 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/Application.java b/retwisj-reboot/src/main/java/org/example/retwisj/Application.java new file mode 100644 index 0000000..acf48ed --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/Application.java @@ -0,0 +1,16 @@ +package org.example.retwisj; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableAutoConfiguration +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/Post.java b/retwisj-reboot/src/main/java/org/example/retwisj/Post.java new file mode 100644 index 0000000..7cac567 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/Post.java @@ -0,0 +1,118 @@ +/* + * Copyright 2011 the original uid or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj; + + +/** + * Class describing a post. + * + * @author Costin Leau + */ +public class Post { + + private String content; + private String uid; + private String time = String.valueOf(System.currentTimeMillis()); + private String replyPid; + private String replyUid; + + /** + * Returns the content. + * + * @return Returns the content + */ + public String getContent() { + return content; + } + + /** + * @param content The content to set. + */ + public void setContent(String content) { + this.content = content; + } + + + /** + * Returns the uid. + * + * @return Returns the uid + */ + public String getUid() { + return uid; + } + + /** + * @param uid The uid to set. + */ + public void setUid(String author) { + this.uid = author; + } + + /** + * Returns the time. + * + * @return Returns the time + */ + public String getTime() { + return time; + } + + /** + * @param time The time to set. + */ + public void setTime(String time) { + this.time = time; + } + + /** + * Returns the replyPid. + * + * @return Returns the replyPid + */ + public String getReplyPid() { + return replyPid; + } + + /** + * @param replyPid The replyPid to set. + */ + public void setReplyPid(String replyPid) { + this.replyPid = replyPid; + } + + /** + * Returns the replyUid. + * + * @return Returns the replyUid + */ + public String getReplyUid() { + return replyUid; + } + + /** + * @param replyUid The replyUid to set. + */ + public void setReplyUid(String replyUid) { + this.replyUid = replyUid; + } + + @Override + public String toString() { + return "Post [content=" + content + ", replyPid=" + replyPid + ", replyUid=" + replyUid + + ", time=" + time + ", uid=" + uid + "]"; + } +} \ No newline at end of file diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/Range.java b/retwisj-reboot/src/main/java/org/example/retwisj/Range.java new file mode 100644 index 0000000..71c0d30 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/Range.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj; + +/** + * Basic object indicating a range of objects to retrieve. Default is 10 objects (starting at zero). + * + * @author Costin Leau + */ +public class Range { + + private static final int SIZE = 9; + public int begin = 0; + public int end = SIZE; + + public Range() { + }; + + public Range(int begin, int end) { + this.begin = begin; + this.end = end; + } + + public Range(int pageNumber) { + this.begin = 0; + this.end = pageNumber * SIZE; + } + + public int getPages() { + return (int) Math.round(Math.ceil(end / SIZE)); + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/RetwisSecurity.java b/retwisj-reboot/src/main/java/org/example/retwisj/RetwisSecurity.java new file mode 100644 index 0000000..3c50815 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/RetwisSecurity.java @@ -0,0 +1,64 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj; + +import org.springframework.core.NamedThreadLocal; +import org.springframework.util.StringUtils; + +/** + * Simple security class that saves the local user info across each request. + * + * @author Costin Leau + */ +public abstract class RetwisSecurity { + + private static class UserInfo { + String name; + String uid; + } + + private static final ThreadLocal user = new NamedThreadLocal("Retwis-id"); + + public static String getName() { + UserInfo userInfo = user.get(); + return (userInfo != null ? userInfo.name : null); + } + + public static String getUid() { + UserInfo userInfo = user.get(); + return (userInfo != null ? userInfo.uid : null); + } + + public static void setUser(String name, String uid) { + UserInfo userInfo = new UserInfo(); + userInfo.name = name; + userInfo.uid = uid; + user.set(userInfo); + } + + public static boolean isUserSignedIn(String name) { + UserInfo userInfo = user.get(); + return userInfo != null && userInfo.name.equals(name); + } + + public static boolean isSignedIn() { + return StringUtils.hasText(getName()); + } + + public static void clean() { + user.set(null); + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/User.java b/retwisj-reboot/src/main/java/org/example/retwisj/User.java new file mode 100644 index 0000000..6185aa3 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/User.java @@ -0,0 +1,99 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj; + + +/** + * Class describing a user. + * + * @author Costin Leau + */ +public class User { + + private Long id; + private String name; + private String pass; + private String authKey; + + public User(String name, String pass) { + this.name = name; + this.pass = pass; + } + + /** + * Returns the id. + * + * @return Returns the id + */ + public Long getId() { + return id; + } + + /** + * @param id The id to set. + */ + public void setId(Long id) { + this.id = id; + } + + /** + * Returns the name. + * + * @return Returns the name + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the pass. + * + * @return Returns the pass + */ + public String getPass() { + return pass; + } + + /** + * @param pass The pass to set. + */ + public void setPass(String password) { + this.pass = password; + } + + /** + * Returns the authKey. + * + * @return Returns the authKey + */ + public String getAuthKey() { + return authKey; + } + + /** + * @param authKey The authKey to set. + */ + public void setAuthKey(String loginKey) { + this.authKey = loginKey; + } +} \ No newline at end of file diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/redis/KeyUtils.java b/retwisj-reboot/src/main/java/org/example/retwisj/redis/KeyUtils.java new file mode 100644 index 0000000..72d6565 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/redis/KeyUtils.java @@ -0,0 +1,90 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.redis; + +/** + * Simple class keeping all the key patterns to avoid the proliferation of + * Strings through the code. + * + * @author Costin Leau + */ +abstract class KeyUtils { + static final String UID = "uid:"; + + static String following(String uid) { + return UID + uid + ":following"; + } + + static String followers(String uid) { + return UID + uid + ":followers"; + } + + static String timeline(String uid) { + return UID + uid + ":timeline"; + } + + static String mentions(String uid) { + return UID + uid + ":mentions"; + } + + static String posts(String uid) { + return UID + uid + ":posts"; + } + + static String auth(String uid) { + return UID + uid + ":auth"; + } + + static String uid(String uid) { + return UID + uid; + } + + static String post(String pid) { + return "pid:" + pid; + } + + static String authKey(String auth) { + return "auth:" + auth; + } + + public static String user(String name) { + return "user:" + name + ":uid"; + } + + static String users() { + return "users"; + } + + static String timeline() { + return "timeline"; + } + + static String globalUid() { + return "global:uid"; + } + + static String globalPid() { + return "global:pid"; + } + + static String alsoFollowed(String uid, String targetUid) { + return UID + uid + ":also:uid:" + targetUid; + } + + static String commonFollowers(String uid, String targetUid) { + return UID + uid + ":common:uid:" + targetUid; + } +} \ No newline at end of file diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/redis/RetwisRepository.java b/retwisj-reboot/src/main/java/org/example/retwisj/redis/RetwisRepository.java new file mode 100644 index 0000000..3b522db --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/redis/RetwisRepository.java @@ -0,0 +1,383 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.redis; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.example.retwisj.Post; +import org.example.retwisj.Range; +import org.example.retwisj.RetwisSecurity; +import org.example.retwisj.web.WebPost; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundHashOperations; +import org.springframework.data.redis.core.BulkMapper; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.core.query.SortQuery; +import org.springframework.data.redis.core.query.SortQueryBuilder; +import org.springframework.data.redis.hash.DecoratingStringHashMapper; +import org.springframework.data.redis.hash.HashMapper; +import org.springframework.data.redis.hash.JacksonHashMapper; +import org.springframework.data.redis.support.atomic.RedisAtomicLong; +import org.springframework.data.redis.support.collections.DefaultRedisList; +import org.springframework.data.redis.support.collections.DefaultRedisMap; +import org.springframework.data.redis.support.collections.DefaultRedisSet; +import org.springframework.data.redis.support.collections.RedisList; +import org.springframework.data.redis.support.collections.RedisMap; +import org.springframework.data.redis.support.collections.RedisSet; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * Twitter-clone on top of Redis. + * + * @author Costin Leau + */ +@Component +public class RetwisRepository { + + private static final Pattern MENTION_REGEX = Pattern.compile("@[\\w]+"); + + private final StringRedisTemplate template; + private final ValueOperations valueOps; + + private final RedisAtomicLong postIdCounter; + private final RedisAtomicLong userIdCounter; + + // global users + private RedisList users; + // global timeline + private final RedisList timeline; + + private final HashMapper postMapper = new DecoratingStringHashMapper( + (HashMapper) (Object) new JacksonHashMapper(Post.class)); + + @Autowired + public RetwisRepository(StringRedisTemplate template) { + this.template = template; + valueOps = template.opsForValue(); + + users = new DefaultRedisList(KeyUtils.users(), template); + timeline = new DefaultRedisList(KeyUtils.timeline(), template); + userIdCounter = new RedisAtomicLong(KeyUtils.globalUid(), template.getConnectionFactory()); + postIdCounter = new RedisAtomicLong(KeyUtils.globalPid(), template.getConnectionFactory()); + } + + public String addUser(String name, String password) { + String uid = String.valueOf(userIdCounter.incrementAndGet()); + + // save user as hash + // uid -> user + BoundHashOperations userOps = template.boundHashOps(KeyUtils.uid(uid)); + userOps.put("name", name); + userOps.put("pass", password); + valueOps.set(KeyUtils.user(name), uid); + + users.addFirst(name); + return addAuth(name); + } + + public List getPost(String pid) { + return Collections.singletonList(convertPost(pid, post(pid))); + } + + public List getPosts(String uid, Range range) { + return convertPidsToPosts(KeyUtils.posts(uid), range); + } + + public List getTimeline(String uid, Range range) { + return convertPidsToPosts(KeyUtils.timeline(uid), range); + } + + public Collection getFollowers(String uid) { + return covertUidsToNames(KeyUtils.followers(uid)); + } + + public Collection getFollowing(String uid) { + return covertUidsToNames(KeyUtils.following(uid)); + } + + public List getMentions(String uid, Range range) { + return convertPidsToPosts(KeyUtils.mentions(uid), range); + } + + public Collection timeline(Range range) { + return convertPidsToPosts(KeyUtils.timeline(), range); + } + + public Collection newUsers(Range range) { + return users.range(range.begin, range.end); + } + + public void post(String username, WebPost post) { + Post p = post.asPost(); + + String uid = findUid(username); + p.setUid(uid); + + String pid = String.valueOf(postIdCounter.incrementAndGet()); + + String replyName = post.getReplyTo(); + if (StringUtils.hasText(replyName)) { + String mentionUid = findUid(replyName); + p.setReplyUid(mentionUid); + // handle mentions below + p.setReplyPid(post.getReplyPid()); + } + + // add post + post(pid).putAll(postMapper.toHash(p)); + + // add links + posts(uid).addFirst(pid); + timeline(uid).addFirst(pid); + + // update followers + for (String follower : followers(uid)) { + timeline(follower).addFirst(pid); + } + + timeline.addFirst(pid); + handleMentions(p, pid, replyName); + } + + private void handleMentions(Post post, String pid, String name) { + // find mentions + Collection mentions = findMentions(post.getContent()); + + for (String mention : mentions) { + String uid = findUid(mention); + if (uid != null) { + mentions(uid).addFirst(pid); + } + } + } + + public String findUid(String name) { + return valueOps.get(KeyUtils.user(name)); + } + + public boolean isUserValid(String name) { + return template.hasKey(KeyUtils.user(name)); + } + + public boolean isPostValid(String pid) { + return template.hasKey(KeyUtils.post(pid)); + } + + private String findName(String uid) { + if (!StringUtils.hasText(uid)) { + return ""; + } + BoundHashOperations userOps = template.boundHashOps(KeyUtils.uid(uid)); + return userOps.get("name"); + } + + public boolean auth(String user, String pass) { + // find uid + String uid = findUid(user); + if (StringUtils.hasText(uid)) { + BoundHashOperations userOps = template.boundHashOps(KeyUtils.uid(uid)); + return userOps.get("pass").equals(pass); + } + + return false; + } + + public String findNameForAuth(String value) { + String uid = valueOps.get(KeyUtils.authKey(value)); + return findName(uid); + } + + public String addAuth(String name) { + String uid = findUid(name); + // add random auth key relation + String auth = UUID.randomUUID().toString(); + valueOps.set(KeyUtils.auth(uid), auth); + valueOps.set(KeyUtils.authKey(auth), uid); + return auth; + } + + public void deleteAuth(String user) { + String uid = findUid(user); + + String authKey = KeyUtils.auth(uid); + String auth = valueOps.get(authKey); + + template.delete(Arrays.asList(authKey, KeyUtils.authKey(auth))); + } + + public boolean hasMorePosts(String targetUid, Range range) { + return posts(targetUid).size() > range.end + 1; + } + + public boolean hasMoreTimeline(String targetUid, Range range) { + return timeline(targetUid).size() > range.end + 1; + } + + public boolean hasMoreTimeline(Range range) { + return timeline.size() > range.end + 1; + } + + public boolean isFollowing(String uid, String targetUid) { + return following(uid).contains(targetUid); + } + + public void follow(String targetUser) { + String targetUid = findUid(targetUser); + + following(RetwisSecurity.getUid()).add(targetUid); + followers(targetUid).add(RetwisSecurity.getUid()); + } + + public void stopFollowing(String targetUser) { + String targetUid = findUid(targetUser); + + following(RetwisSecurity.getUid()).remove(targetUid); + followers(targetUid).remove(RetwisSecurity.getUid()); + } + + public List alsoFollowed(String uid, String targetUid) { + RedisSet tempSet = following(uid).intersectAndStore(followers(targetUid), + KeyUtils.alsoFollowed(uid, targetUid)); + + String key = tempSet.getKey(); + template.expire(key, 5, TimeUnit.SECONDS); + + return covertUidsToNames(key); + } + + public List commonFollowers(String uid, String targetUid) { + RedisSet tempSet = following(uid).intersectAndStore(following(targetUid), + KeyUtils.commonFollowers(uid, targetUid)); + + tempSet.expire(5, TimeUnit.SECONDS); + + return covertUidsToNames(tempSet.getKey()); + } + + // collections mapping the core data structures + + private RedisList timeline(String uid) { + return new DefaultRedisList(KeyUtils.timeline(uid), template); + } + + private RedisSet following(String uid) { + return new DefaultRedisSet(KeyUtils.following(uid), template); + } + + private RedisSet followers(String uid) { + return new DefaultRedisSet(KeyUtils.followers(uid), template); + } + + private RedisList mentions(String uid) { + return new DefaultRedisList(KeyUtils.mentions(uid), template); + } + + private RedisMap post(String pid) { + return new DefaultRedisMap(KeyUtils.post(pid), template); + } + + private RedisList posts(String uid) { + return new DefaultRedisList(KeyUtils.posts(uid), template); + } + + // various util methods + + private String replaceReplies(String content) { + Matcher regexMatcher = MENTION_REGEX.matcher(content); + while (regexMatcher.find()) { + String match = regexMatcher.group(); + int start = regexMatcher.start(); + int stop = regexMatcher.end(); + + String uName = match.substring(1); + if (isUserValid(uName)) { + content = content.substring(0, start) + "" + match + "" + + content.substring(stop); + } + } + return content; + } + + private List covertUidsToNames(String key) { + return template.sort(SortQueryBuilder.sort(key).noSort().get("uid:*->name").build()); + } + + private List convertPidsToPosts(String key, Range range) { + String pid = "pid:*->"; + final String pidKey = "#"; + final String uid = "uid"; + final String content = "content"; + final String replyPid = "replyPid"; + final String replyUid = "replyUid"; + final String time = "time"; + + SortQuery query = SortQueryBuilder.sort(key).noSort().get(pidKey).get(pid + uid).get(pid + content) + .get(pid + replyPid).get(pid + replyUid).get(pid + time).limit(range.begin, range.end).build(); + BulkMapper hm = new BulkMapper() { + @Override + public WebPost mapBulk(List bulk) { + Map map = new LinkedHashMap(); + Iterator iterator = bulk.iterator(); + + String pid = iterator.next(); + map.put(uid, iterator.next()); + map.put(content, iterator.next()); + map.put(replyPid, iterator.next()); + map.put(replyUid, iterator.next()); + map.put(time, iterator.next()); + + return convertPost(pid, map); + } + }; + List sort = template.sort(query, hm); + + return sort; + } + + private WebPost convertPost(String pid, Map hash) { + Post post = postMapper.fromHash(hash); + WebPost wPost = new WebPost(post); + wPost.setPid(pid); + wPost.setName(findName(post.getUid())); + wPost.setReplyTo(findName(post.getReplyUid())); + wPost.setContent(replaceReplies(post.getContent())); + return wPost; + } + + public static Collection findMentions(String content) { + Matcher regexMatcher = MENTION_REGEX.matcher(content); + List mentions = new ArrayList(4); + + while (regexMatcher.find()) { + mentions.add(regexMatcher.group().substring(1)); + } + + return mentions; + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/CookieInterceptor.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/CookieInterceptor.java new file mode 100644 index 0000000..cd4abdd --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/CookieInterceptor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.web; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.example.retwisj.RetwisSecurity; +import org.example.retwisj.redis.RetwisRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.ObjectUtils; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +/** + * Basic interceptor that checks that each request has been authenticated against Redis. + * + * @author Costin Leau + */ +public class CookieInterceptor extends HandlerInterceptorAdapter { + + public static final String RETWIS_COOKIE = "retwisauth"; + + @Autowired private RetwisRepository twitter; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // all non-root requests get analyzed + Cookie[] cookies = request.getCookies(); + + if (!ObjectUtils.isEmpty(cookies)) { + for (Cookie cookie : cookies) { + if (RETWIS_COOKIE.equals(cookie.getName())) { + String auth = cookie.getValue(); + String name = twitter.findNameForAuth(auth); + if (name != null) { + String uid = twitter.findUid(name); + RetwisSecurity.setUser(name, uid); + } + } + } + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + RetwisSecurity.clean(); + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/NoSuchDataException.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/NoSuchDataException.java new file mode 100644 index 0000000..c986abf --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/NoSuchDataException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.web; + +/** + * Exception thrown when the controller is invoked with invalid data. + * + * @author Costin Leau + */ +public class NoSuchDataException extends RuntimeException { + + private final String data; + private final boolean userRelated; + + public NoSuchDataException(String data, boolean userRelated) { + + super("Invalid data " + data); + this.data = data; + this.userRelated = userRelated; + } + + /** + * Returns the name. + * + * @return Returns the name + */ + public String getData() { + return data; + } + + public boolean isPost() { + return !userRelated; + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java new file mode 100644 index 0000000..f9fcd0d --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java @@ -0,0 +1,220 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.web; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.example.retwisj.Post; +import org.example.retwisj.Range; +import org.example.retwisj.RetwisSecurity; +import org.example.retwisj.redis.RetwisRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Annotation-driven controller for Retwis. + * + * @author Costin Leau + */ +@Controller +public class RetwisController { + + private final RetwisRepository retwis; + + @Autowired + public RetwisController(RetwisRepository twitter) { + this.retwis = twitter; + } + + @RequestMapping("/") + public String root(@RequestParam(required = false) Integer page, Model model) { + if (RetwisSecurity.isSignedIn()) { + return "redirect:/!" + RetwisSecurity.getName(); + } + return timeline(page, model); + } + + @RequestMapping("/signUp") + public String signUp(String name, String pass, String pass2, Model model, HttpServletResponse response) { + if (retwis.isUserValid(name)) { + model.addAttribute("errorduplicateuser", Boolean.TRUE); + return "signin"; + } + + if (!StringUtils.hasText(pass) || !StringUtils.hasText(pass2) || !pass.equals(pass2)) { + model.addAttribute("errormatch", Boolean.TRUE); + return "signin"; + } + + String auth = retwis.addUser(name, pass); + addAuthCookie(auth, name, response); + + return "redirect:/!" + name; + } + + @RequestMapping("/signIn") + public String signIn(@RequestParam(required = false) String name, @RequestParam(required = false) String pass, + Model model, HttpServletResponse response) { + // add tracing cookie + if (retwis.auth(name, pass)) { + addAuthCookie(retwis.addAuth(name), name, response); + return "redirect:/!" + name; + } else if (StringUtils.hasText(name) || StringUtils.hasText(pass)) { + model.addAttribute("errorpass", Boolean.TRUE); + } + // go back to sign in screen + return "signin"; + } + + private void addAuthCookie(String auth, String name, HttpServletResponse response) { + RetwisSecurity.setUser(name, retwis.findUid(name)); + + Cookie cookie = new Cookie(CookieInterceptor.RETWIS_COOKIE, auth); + cookie.setComment("Retwis-J demo"); + // cookie valid for up to 1 week + cookie.setMaxAge(60 * 60 * 24 * 7); + response.addCookie(cookie); + } + + @RequestMapping(value = "/!{name}", method = RequestMethod.GET) + public String posts(@PathVariable String name, @RequestParam(required = false) String replyto, @RequestParam( + required = false) String replypid, @RequestParam(required = false) Integer page, Model model) { + checkUser(name); + String targetUid = retwis.findUid(name); + model.addAttribute("post", new Post()); + model.addAttribute("name", name); + model.addAttribute("followers", retwis.getFollowers(targetUid)); + model.addAttribute("following", retwis.getFollowing(targetUid)); + + if (RetwisSecurity.isSignedIn()) { + model.addAttribute("replyTo", replyto); + model.addAttribute("replyPid", replypid); + + if (!targetUid.equals(RetwisSecurity.getUid())) { + model.addAttribute("also_followed", retwis.alsoFollowed(RetwisSecurity.getUid(), targetUid)); + model.addAttribute("common_followers", retwis.commonFollowers(RetwisSecurity.getUid(), targetUid)); + model.addAttribute("follows", retwis.isFollowing(RetwisSecurity.getUid(), targetUid)); + } + } + // sanitize page attribute + page = (page != null ? Math.abs(page) : 1); + model.addAttribute("page", page + 1); + Range range = new Range(page); + model.addAttribute( + "moreposts", + (RetwisSecurity.isUserSignedIn(targetUid) ? retwis.hasMoreTimeline(targetUid, range) : retwis.hasMorePosts( + targetUid, range))); + model.addAttribute("posts", (RetwisSecurity.isUserSignedIn(targetUid) ? retwis.getTimeline(targetUid, range) + : retwis.getPosts(targetUid, range))); + + return "home"; + } + + @RequestMapping(value = "/!{name}", method = RequestMethod.POST) + public String posts(@PathVariable String name, WebPost post, Model model, HttpServletRequest request) { + checkUser(name); + retwis.post(name, post); + return "redirect:/!" + name; + } + + @RequestMapping("/!{name}/follow") + public String follow(@PathVariable String name) { + checkUser(name); + retwis.follow(name); + return "redirect:/!" + name; + } + + @RequestMapping("/!{name}/stopfollowing") + public String stopFollowing(@PathVariable String name) { + checkUser(name); + retwis.stopFollowing(name); + return "redirect:/!" + name; + } + + @RequestMapping("/!{name}/mentions") + public String mentions(@PathVariable String name, Model model) { + checkUser(name); + model.addAttribute("name", name); + String targetUid = retwis.findUid(name); + + model.addAttribute("posts", retwis.getMentions(targetUid, new Range())); + model.addAttribute("followers", retwis.getFollowers(targetUid)); + model.addAttribute("following", retwis.getFollowing(targetUid)); + + if (RetwisSecurity.isSignedIn() && !targetUid.equals(RetwisSecurity.getUid())) { + model.addAttribute("also_followed", retwis.alsoFollowed(RetwisSecurity.getUid(), targetUid)); + model.addAttribute("common_followers", retwis.commonFollowers(RetwisSecurity.getUid(), targetUid)); + model.addAttribute("follows", retwis.isFollowing(RetwisSecurity.getUid(), targetUid)); + } + + return "mentions"; + } + + @RequestMapping("/timeline") + public String timeline(@RequestParam(required = false) Integer page, Model model) { + // sanitize page attribute + page = (page != null ? Math.abs(page) : 1); + model.addAttribute("page", page + 1); + Range range = new Range(page); + model.addAttribute("moreposts", retwis.hasMoreTimeline(range)); + model.addAttribute("posts", retwis.timeline(range)); + model.addAttribute("users", retwis.newUsers(new Range())); + return "timeline"; + } + + @RequestMapping("/logout") + public String logout() { + String user = RetwisSecurity.getName(); + // invalidate auth + retwis.deleteAuth(user); + return "redirect:/"; + } + + @RequestMapping("/status") + public String status(String pid, Model model) { + checkPost(pid); + model.addAttribute("posts", retwis.getPost(pid)); + return "status"; + } + + private void checkUser(String username) { + if (!retwis.isUserValid(username)) { + throw new NoSuchDataException(username, true); + } + } + + private void checkPost(String pid) { + if (!retwis.isPostValid(pid)) { + throw new NoSuchDataException(pid, false); + } + } + + @ExceptionHandler(NoSuchDataException.class) + public String handleNoUserException(NoSuchDataException ex) { + // model.addAttribute("data", ex.getData()); + // model.addAttribute("nodatatype", ex.isPost() ? "nodata.post" : "nodata.user"); + return "nodata"; + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/WebPost.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/WebPost.java new file mode 100644 index 0000000..67dbd4c --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/WebPost.java @@ -0,0 +1,161 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.web; + +import org.example.retwisj.Post; + +/** + * DTO suitable for presenting a Post through the Web UI. + * + * @author Costin Leau + */ +public class WebPost { + + private String content; + private String name; + private String replyTo; + private String replyPid; + private String pid; + private String time; + private String timeArg; + + public WebPost() {} + + public WebPost(Post post) { + + String tempTime = WebUtils.timeInWords(Long.valueOf(post.getTime())); + int lastIndexOf = tempTime.lastIndexOf("#"); + if (lastIndexOf > 0) { + this.time = tempTime.substring(0, lastIndexOf); + this.timeArg = tempTime.substring(lastIndexOf + 1); + } else { + this.time = tempTime; + this.timeArg = ""; + } + this.replyPid = post.getReplyPid(); + this.content = post.getContent(); + } + + /** + * Returns the replyPid. + * + * @return Returns the replyPid + */ + public String getReplyPid() { + return replyPid; + } + + /** + * @param replyPid The replyPid to set. + */ + public void setReplyPid(String replyPid) { + this.replyPid = replyPid; + } + + /** + * Returns the name. + * + * @return Returns the name + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the replyTo. + * + * @return Returns the replyTo + */ + public String getReplyTo() { + return replyTo; + } + + /** + * Returns the content. + * + * @return Returns the content + */ + public String getContent() { + return content; + } + + /** + * @param content The content to set. + */ + public void setContent(String content) { + this.content = content; + } + + /** + * Returns the pid. + * + * @return Returns the pid + */ + public String getPid() { + return pid; + } + + /** + * @param pid The pid to set. + */ + public void setPid(String pid) { + this.pid = pid; + } + + /** + * Returns the time. + * + * @return Returns the time + */ + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getTimeArg() { + return timeArg; + } + + /** + * @param replyTo The replyTo to set. + */ + public void setReplyTo(String replyName) { + this.replyTo = replyName; + } + + public Post asPost() { + Post post = new Post(); + post.setReplyPid(replyPid); + post.setContent(content); + return post; + } + + @Override + public String toString() { + return "WebPost [content=" + content + ", name=" + name + ", pid=" + pid + ", replyTo=" + replyTo + ", replyPid=" + + replyPid + ", time=" + time + "]"; + } +} diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/WebUtils.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/WebUtils.java new file mode 100644 index 0000000..78fd1d7 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/WebUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.web; + + +/** + * Utility converting the date (stored as long) into human-friendly message. + * + * @author Costin Leau + */ +public class WebUtils { + + public static String timeInWords(long time) { + long elapsed = System.currentTimeMillis() - time; + + // seconds + elapsed /= 1000; + + if (elapsed < 10) { + return "time.now"; + } + if (elapsed < 60) { + return "time.minute.less"; + } + + // minutes + elapsed /= 60; + + if (elapsed < 2) { + return "time.minute"; + } + + if (elapsed < 45) { + return "time.minutes#" + elapsed; + } + + if (elapsed < 90) { + return "time.hour"; + } + + if (elapsed < 1440) { + return "time.hours#" + elapsed / 60; + } + + if (elapsed < 2880) { + return "time.day"; + } + + if (elapsed < 43200) { + return "time.days#" + (elapsed / 1440); + } + + if (elapsed < 86400) { + return "time.month"; + } + + if (elapsed < 525600) { + return "time.months#" + (elapsed / 43200); + } + + if (elapsed < 1051199) { + return "time.year"; + } + + return "time.years#" + (elapsed / 525600); + } +} \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/application.properties b/retwisj-reboot/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/retwisj-reboot/src/main/resources/log4j.properties b/retwisj-reboot/src/main/resources/log4j.properties new file mode 100644 index 0000000..1ace7fa --- /dev/null +++ b/retwisj-reboot/src/main/resources/log4j.properties @@ -0,0 +1,16 @@ +# For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml! +# For all other servers: Comment out the Log4J listener in web.xml to activate Log4J. +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.appender.logfile=org.apache.log4j.RollingFileAppender +log4j.appender.logfile.File=${retwis.root}/WEB-INF/retwis.log +log4j.appender.logfile.MaxFileSize=512KB +# Keep three backup files. +log4j.appender.logfile.MaxBackupIndex=3 +# Pattern to output: date priority [category] - message +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n +#log4j.logger.org.springframework.web=DEBUG \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/messages.properties b/retwisj-reboot/src/main/resources/messages.properties new file mode 100644 index 0000000..b0b4f05 --- /dev/null +++ b/retwisj-reboot/src/main/resources/messages.properties @@ -0,0 +1,55 @@ +home=Home +lang=Language +docs=Docs +update=Post +mentions=mentions +follower=Follower +followers=Followers +following=Following +follow.both=You both follow +follow=Follow +follow.stop=Stop following +follow.also=Also followed by +timeline=Timeline +logout=logout +signin=Sign In +signup=Sign Up +welcome=Welcome +username=Username +password=Password +password.again=Repeat password +required=is required +wazza=what's happening ? +itsyou = That's you! +notFound=User has not been found +duplicate=is already in use +reply=Reply +replyto=Reply to +inreplyto=in reply to +Mentions=Mentions of +error.pass =Incorrect username or password +error.match =Passwords do not match +error.duplicateuser =User name already in use +nodata =The requested data does not exist +nodata.post =post +nodata.user =user +all.posts =Posts from all users +newusers =New Users +nopost =No posts +nopost.note =You have no posts. Why not say hi to +oops = Oops! Something went wrong... +and.more=and more... +more=More +# time messages +time.now=just now +time.minute.less=less than a minute ago +time.minute=one minute ago +time.minutes={0} minutes ago +time.hour=about an hour ago +time.hours={0} hours ago +time.day=about a day ago +time.days={0} days ago +time.month=about a month ago +time.months={0} months ago +time.year=about a year ago +time.years=over {0} years ago \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/messages_cn.properties b/retwisj-reboot/src/main/resources/messages_cn.properties new file mode 100644 index 0000000..a9b88bf --- /dev/null +++ b/retwisj-reboot/src/main/resources/messages_cn.properties @@ -0,0 +1,55 @@ +home=首页 +lang=语言 +docs=文档 +update=邮政 +mentions=提到 +follower=信徒 +followers=信徒 +following=以下 +follow.both=你们都遵循 +follow=按照 +follow.stop=停止跟踪 +follow.also=也跟着 +timeline=时间轴 +logout=注销 +signin=登录 +signup=注册 +welcome=欢迎 +username=用户名 +password=密码 +password.again=重复密码 +required=需要 +wazza=发生了什么? +itsyou = 就是你! +notFound=用户没有被发现 +duplicate=已在使用 +reply=答复 +replyto=回复 +inreplyto =在回答 +Mentions=提到 +error.pass =用户名或密码不正确 +error.match =密码不匹配 +error.duplicateuser =用户名已在使用 +nodata =请求的数据不存在 +nodata.post =文章 +nodata.user =用户 +all.posts =员额从所有用户 +newusers =新用户 +nopost =没有帖子 +nopost.note =你有没有文章。为什么不打招呼 +oops = 哎呀!出事了... +and.more=和更多... +more=更多 +# time messages +time.now=刚才 +time.minute.less=不到一分钟前 +time.minute=一分钟前 +time.minutes={0}分钟前 +time.hour=大约一个小时前 +time.hours={0}小时前 +time.day=大约一小时前 +time.days={0}天前 +time.month=大约一个月前 +time.months={0}个月前 +time.year=大约一年前 +time.years=超过{0}年前 \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/messages_en.properties b/retwisj-reboot/src/main/resources/messages_en.properties new file mode 100644 index 0000000..05d519b --- /dev/null +++ b/retwisj-reboot/src/main/resources/messages_en.properties @@ -0,0 +1 @@ +# This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file. \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/messages_es.properties b/retwisj-reboot/src/main/resources/messages_es.properties new file mode 100644 index 0000000..fabb856 --- /dev/null +++ b/retwisj-reboot/src/main/resources/messages_es.properties @@ -0,0 +1,55 @@ +home=Casa +lang=Lenguaje +docs=Documentación +update=Mensaje +mentions=menciona +follower=Seguidor +followers=Seguidores +following=Partidarios +follow.both=Los dos siguen +follow=Seguir +follow.stop=Dejar de seguir +follow.also=También seguido por +timeline=Historia +logout=Salida +signin=Registrarse +signup=Registrarse +welcome=¡Bienvenido +username=Nombre +password=Contraseña +password.again=Repita password +required=se requiere +wazza=¿Qué pasa +itsyou = Eso eres tú! +notFound=El usuario no se ha encontrado +duplicate=ya está en uso +reply=Responder +replyto=Responder a +inreplyto =en respuesta a +Mentions=Menciones de +error.pass =Incorrecto nombre o contraseña +error.match =Las contraseñas no coinciden +error.duplicateuser =Nombre ya está en uso +nodata =Los datos solicitados no existe +nodata.post =puesto +nodata.user =usuario +all.posts =Mensajes de todos los usuarios +newusers =Nuevo usuario +nopost =No hay mensajes +nopost.note =Usted no tiene mensajes. ¿Por qué no saludar a +oops = ¡Uy! Algo salió mal ... +and.more=y mucho más ... +more=Más +# time messages +time.now=en este momento +time.minute.less=less than a minute ago +time.minute=hace menos de un minuto +time.minutes=hace {0} minutos +time.hour=hace de una hora +time.hours=hace {0} horas +time.day=hace alrededor de un día +time.days=hace {0} días +time.month=hace aproximadamente un mes +time.months=hace {0} meses +time.year=hace un año +time.years=más de {0} años \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/messages_ro.properties b/retwisj-reboot/src/main/resources/messages_ro.properties new file mode 100644 index 0000000..3c4f345 --- /dev/null +++ b/retwisj-reboot/src/main/resources/messages_ro.properties @@ -0,0 +1,55 @@ +home=Acasă +lang=Limbă +docs=Documentaţie +update=Postează +mentions=Meniţuni +follower=Urmărit de +followers=Urmărit de +following=Urmăreşte pe +follow.both=Amândoi urmăriţi pe +follow=Urmăreşte +follow.stop=Nu mai urmări +follow.also=Urmărit şi de +timeline=Scurt istoric +logout=Ieşire +signin=Înscriere +signup=Autentificare +welcome=Bine ai venit +username=Utilizator +password=Parolă +password.again=Repetă parola +required=este necesar +wazza =Ce mai faci? +itsyou =Esti chiar tu! +notFound=Utilizatorul nu a fost găsit +duplicate=este deja folosit +reply=Răspunde +replyto=Răspunde lui +inreplyto =răspuns către +Mentions=Menţiuni +error.pass = Utilizator sau parolă incorectă +error.match = Parolele nu sunt identice +error.duplicateuser = Utilizatorul deja există +nodata = Nu există informaţia cerută +nodata.post = mesajul +nodata.user = utilizatorul +all.posts = Postările tuturor utilizatorilor +newusers =Utilizatori noi +nopost = Nici o postare +nopost.note = Nu ai nici o postare. De ce nu îl saluţi pe +oops = Oops! Ceva nu merge... +and.more=şi alţii... +more=Mai mult +# time messages +time.now=chiar acum +time.minute.less=sub un minut +time.minute=un minut +time.minutes=acum {0} minute +time.hour=cam de o oră +time.hours=acum {0} ore +time.day=cam de o zi +time.days=acum {0} zile +time.month=cam de o lună +time.months=acum {0} luni +time.year=cam de un an +time.years=acum mai bine {0} ani \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/redis.properties b/retwisj-reboot/src/main/resources/redis.properties new file mode 100644 index 0000000..8943d15 --- /dev/null +++ b/retwisj-reboot/src/main/resources/redis.properties @@ -0,0 +1,5 @@ +# Redis settings + +redis.host=localhost +redis.port=6379 +redis.pass= \ No newline at end of file diff --git a/retwisj-reboot/src/test/java/org/example/retwisj/ApplicationTests.java b/retwisj-reboot/src/test/java/org/example/retwisj/ApplicationTests.java new file mode 100644 index 0000000..7ec4b2e --- /dev/null +++ b/retwisj-reboot/src/test/java/org/example/retwisj/ApplicationTests.java @@ -0,0 +1,18 @@ +package org.example.retwisj; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@WebAppConfiguration +public class ApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/retwisj-reboot/static/images/favicon.ico b/retwisj-reboot/static/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..54371328f654e8dea8b34c7dee5c96f7402a9144 GIT binary patch literal 1150 zcmd5)El9P=;L{!Z(p`uk$Dk`6dKaVB(6qQH8(dq z0sa55y8A0*g1x9sI=2TVczv!SRlh)@`z-}OEn5~J>Su^g*AZRqAbh-p*4r3sY`M^& z4f{9HvrEM1n^-ztE43D%?tato$yF`t+4;<;F8g3#?T0hFCdv7B{D)5+_G4)bo6fKH zweZph=R&O_p7S~HGFG>zMs&Sb*5-UqIcXiQn&bJ!JjpMX*6?nrQ=Mn6ZozG`s>vRy zKU5$`_TM#e?Ywe#bAD>)ruCK2Ud*)he^m#xLZEH@*3rm!fPr*eBi-0Y;20T+ksgf9 X#P4z#VDJWfx;=cITmWVRxc~SDx#oF# literal 0 HcmV?d00001 diff --git a/retwisj-reboot/static/images/springsource-logo.png b/retwisj-reboot/static/images/springsource-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e170f8abf778b24ed1de11dad6f1444e849269ba GIT binary patch literal 4974 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 p code {*white-space:normal;} +hr {margin:-8px auto 11px;} +img {-ms-interpolation-mode:bicubic;} +.clearfix, .container {display:inline-block;} +* html .clearfix, * html .container {height:1%;} +fieldset {padding-top:0;} +textarea {overflow:auto;} +input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;} +input.text:focus, input.title:focus {border-color:#666;} +input.text, input.title, textarea, select {margin:0.5em 0;} +input.checkbox, input.radio {position:relative;top:.25em;} +form.inline div, form.inline p {vertical-align:middle;} +form.inline label {position:relative;top:-0.25em;} +form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;} +button, input.button {position:relative;top:0.25em;} \ No newline at end of file diff --git a/retwisj-reboot/static/styles/print.css b/retwisj-reboot/static/styles/print.css new file mode 100644 index 0000000..fdb8220 --- /dev/null +++ b/retwisj-reboot/static/styles/print.css @@ -0,0 +1,29 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* print.css */ +body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;} +.container {background:none;} +hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;} +code {font:.9em "Courier New", Monaco, Courier, monospace;} +a img {border:none;} +p img.top {margin-top:0;} +blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;} +.small {font-size:.9em;} +.large {font-size:1.1em;} +.quiet {color:#999;} +.hide {display:none;} +a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;} +a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;} \ No newline at end of file diff --git a/retwisj-reboot/static/styles/screen.css b/retwisj-reboot/static/styles/screen.css new file mode 100644 index 0000000..c18b9a5 --- /dev/null +++ b/retwisj-reboot/static/styles/screen.css @@ -0,0 +1,280 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* reset.css */ +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} +body {line-height:1.5;} +table {border-collapse:separate;border-spacing:0;} +caption, th, td {text-align:left;font-weight:normal;} +table, td, th {vertical-align:middle;} +blockquote:before, blockquote:after, q:before, q:after {content:"";} +blockquote, q {quotes:"" "";} +a img {border:none;} + +/* typography.css */ +html {font-size:100.01%;} +body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;} +h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;} +h1 {font-size:3em;line-height:1;margin-bottom:0.5em;} +h2 {font-size:2em;margin-bottom:0.75em;} +h3 {font-size:1.5em;line-height:1;margin-bottom:1em;} +h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;} +h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;} +h6 {font-size:1em;font-weight:bold;} +h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;} +p {margin:0 0 1.5em;} +p img.left {float:left;margin:1.5em 1.5em 1.5em 0;padding:0;} +p img.right {float:right;margin:1.5em 0 1.5em 1.5em;} +a:focus, a:hover {color:#000;} +a {color:#009;text-decoration:underline;} +blockquote {margin:1.5em;color:#666;font-style:italic;} +strong {font-weight:bold;} +em, dfn {font-style:italic;} +dfn {font-weight:bold;} +sup, sub {line-height:0;} +abbr, acronym {border-bottom:1px dotted #666;} +address {margin:0 0 1.5em;font-style:italic;} +del {color:#666;} +pre {margin:1.5em 0;white-space:pre;} +pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;} +li ul, li ol {margin:0 1.5em;} +ul, ol {margin:0 1.5em 1.5em 1.5em;} +ul {list-style-type:disc;} +ol {list-style-type:decimal;} +dl {margin:0 0 1.5em 0;} +dl dt {font-weight:bold;} +dd {margin-left:1.5em;} +table {margin-bottom:1.4em;width:100%;} +th {font-weight:bold;} +thead th {background:#c3d9ff;} +th, td, caption {padding:4px 10px 4px 5px;} +tr.even td {background:#e5ecf9;} +tfoot {font-style:italic;} +caption {background:#eee;} +.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;} +.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;} +.hide {display:none;} +.quiet {color:#666;} +.loud {color:#000;} +.highlight {background:#ff0;} +.added {background:#060;color:#fff;} +.removed {background:#900;color:#fff;} +.first {margin-left:0;padding-left:0;} +.last {margin-right:0;padding-right:0;} +.top {margin-top:0;padding-top:0;} +.bottom {margin-bottom:0;padding-bottom:0;} + +/* forms.css */ +label {font-weight:bold;} +fieldset {padding:1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;} +legend {font-weight:bold;font-size:1.2em;} +input[type=text], input[type=password], input.text, input.title, textarea, select {background-color:#fff;border:1px solid #bbb;} +input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus, select:focus {border-color:#666;} +input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;} +input.text, input.title {width:300px;padding:5px;} +input.title {font-size:1.5em;} +textarea {width:390px;height:250px;padding:5px;} +input[type=checkbox], input[type=radio], input.checkbox, input.radio {position:relative;top:.25em;} +form.inline {line-height:3;} +form.inline p {margin-bottom:0;} +.error, .notice, .success {padding:.8em;margin-bottom:1em;border:2px solid #ddd;} +.error {background:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;} +.notice {background:#FFF6BF;color:#514721;border-color:#FFD324;} +.success {background:#E6EFC2;color:#264409;border-color:#C6D880;} +.error a {color:#8a1f11;} +.notice a {color:#514721;} +.success a {color:#264409;} + +/* grid.css */ +.container {width:950px;margin:0 auto;} +.showgrid {background:url(src/grid.png);} +.column, div.span-1, div.span-2, div.span-3, div.span-4, div.span-5, div.span-6, div.span-7, div.span-8, div.span-9, div.span-10, div.span-11, div.span-12, div.span-13, div.span-14, div.span-15, div.span-16, div.span-17, div.span-18, div.span-19, div.span-20, div.span-21, div.span-22, div.span-23, div.span-24 {float:left;margin-right:10px;} +.last, div.last {margin-right:0;} +.span-1 {width:30px;} +.span-2 {width:70px;} +.span-3 {width:110px;} +.span-4 {width:150px;} +.span-5 {width:190px;} +.span-6 {width:230px;} +.span-7 {width:270px;} +.span-8 {width:310px;} +.span-9 {width:350px;} +.span-10 {width:390px;} +.span-11 {width:430px;} +.span-12 {width:470px;} +.span-13 {width:510px;} +.span-14 {width:550px;} +.span-15 {width:590px;} +.span-16 {width:630px;} +.span-17 {width:670px;} +.span-18 {width:710px;} +.span-19 {width:750px;} +.span-20 {width:790px;} +.span-21 {width:830px;} +.span-22 {width:870px;} +.span-23 {width:910px;} +.span-24, div.span-24 {width:950px;margin-right:0;} +input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px!important;border-right-width:1px!important;padding-left:5px!important;padding-right:5px!important;} +input.span-1, textarea.span-1 {width:18px!important;} +input.span-2, textarea.span-2 {width:58px!important;} +input.span-3, textarea.span-3 {width:98px!important;} +input.span-4, textarea.span-4 {width:138px!important;} +input.span-5, textarea.span-5 {width:178px!important;} +input.span-6, textarea.span-6 {width:218px!important;} +input.span-7, textarea.span-7 {width:258px!important;} +input.span-8, textarea.span-8 {width:298px!important;} +input.span-9, textarea.span-9 {width:338px!important;} +input.span-10, textarea.span-10 {width:378px!important;} +input.span-11, textarea.span-11 {width:418px!important;} +input.span-12, textarea.span-12 {width:458px!important;} +input.span-13, textarea.span-13 {width:498px!important;} +input.span-14, textarea.span-14 {width:538px!important;} +input.span-15, textarea.span-15 {width:578px!important;} +input.span-16, textarea.span-16 {width:618px!important;} +input.span-17, textarea.span-17 {width:658px!important;} +input.span-18, textarea.span-18 {width:698px!important;} +input.span-19, textarea.span-19 {width:738px!important;} +input.span-20, textarea.span-20 {width:778px!important;} +input.span-21, textarea.span-21 {width:818px!important;} +input.span-22, textarea.span-22 {width:858px!important;} +input.span-23, textarea.span-23 {width:898px!important;} +input.span-24, textarea.span-24 {width:938px!important;} +.append-1 {padding-right:40px;} +.append-2 {padding-right:80px;} +.append-3 {padding-right:120px;} +.append-4 {padding-right:160px;} +.append-5 {padding-right:200px;} +.append-6 {padding-right:240px;} +.append-7 {padding-right:280px;} +.append-8 {padding-right:320px;} +.append-9 {padding-right:360px;} +.append-10 {padding-right:400px;} +.append-11 {padding-right:440px;} +.append-12 {padding-right:480px;} +.append-13 {padding-right:520px;} +.append-14 {padding-right:560px;} +.append-15 {padding-right:600px;} +.append-16 {padding-right:640px;} +.append-17 {padding-right:680px;} +.append-18 {padding-right:720px;} +.append-19 {padding-right:760px;} +.append-20 {padding-right:800px;} +.append-21 {padding-right:840px;} +.append-22 {padding-right:880px;} +.append-23 {padding-right:920px;} +.prepend-1 {padding-left:40px;} +.prepend-2 {padding-left:80px;} +.prepend-3 {padding-left:120px;} +.prepend-4 {padding-left:160px;} +.prepend-5 {padding-left:200px;} +.prepend-6 {padding-left:240px;} +.prepend-7 {padding-left:280px;} +.prepend-8 {padding-left:320px;} +.prepend-9 {padding-left:360px;} +.prepend-10 {padding-left:400px;} +.prepend-11 {padding-left:440px;} +.prepend-12 {padding-left:480px;} +.prepend-13 {padding-left:520px;} +.prepend-14 {padding-left:560px;} +.prepend-15 {padding-left:600px;} +.prepend-16 {padding-left:640px;} +.prepend-17 {padding-left:680px;} +.prepend-18 {padding-left:720px;} +.prepend-19 {padding-left:760px;} +.prepend-20 {padding-left:800px;} +.prepend-21 {padding-left:840px;} +.prepend-22 {padding-left:880px;} +.prepend-23 {padding-left:920px;} +div.border {padding-right:4px;margin-right:5px;border-right:1px solid #eee;} +div.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #eee;} +.pull-1 {margin-left:-40px;} +.pull-2 {margin-left:-80px;} +.pull-3 {margin-left:-120px;} +.pull-4 {margin-left:-160px;} +.pull-5 {margin-left:-200px;} +.pull-6 {margin-left:-240px;} +.pull-7 {margin-left:-280px;} +.pull-8 {margin-left:-320px;} +.pull-9 {margin-left:-360px;} +.pull-10 {margin-left:-400px;} +.pull-11 {margin-left:-440px;} +.pull-12 {margin-left:-480px;} +.pull-13 {margin-left:-520px;} +.pull-14 {margin-left:-560px;} +.pull-15 {margin-left:-600px;} +.pull-16 {margin-left:-640px;} +.pull-17 {margin-left:-680px;} +.pull-18 {margin-left:-720px;} +.pull-19 {margin-left:-760px;} +.pull-20 {margin-left:-800px;} +.pull-21 {margin-left:-840px;} +.pull-22 {margin-left:-880px;} +.pull-23 {margin-left:-920px;} +.pull-24 {margin-left:-960px;} +.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;} +.push-1 {margin:0 -40px 1.5em 40px;} +.push-2 {margin:0 -80px 1.5em 80px;} +.push-3 {margin:0 -120px 1.5em 120px;} +.push-4 {margin:0 -160px 1.5em 160px;} +.push-5 {margin:0 -200px 1.5em 200px;} +.push-6 {margin:0 -240px 1.5em 240px;} +.push-7 {margin:0 -280px 1.5em 280px;} +.push-8 {margin:0 -320px 1.5em 320px;} +.push-9 {margin:0 -360px 1.5em 360px;} +.push-10 {margin:0 -400px 1.5em 400px;} +.push-11 {margin:0 -440px 1.5em 440px;} +.push-12 {margin:0 -480px 1.5em 480px;} +.push-13 {margin:0 -520px 1.5em 520px;} +.push-14 {margin:0 -560px 1.5em 560px;} +.push-15 {margin:0 -600px 1.5em 600px;} +.push-16 {margin:0 -640px 1.5em 640px;} +.push-17 {margin:0 -680px 1.5em 680px;} +.push-18 {margin:0 -720px 1.5em 720px;} +.push-19 {margin:0 -760px 1.5em 760px;} +.push-20 {margin:0 -800px 1.5em 800px;} +.push-21 {margin:0 -840px 1.5em 840px;} +.push-22 {margin:0 -880px 1.5em 880px;} +.push-23 {margin:0 -920px 1.5em 920px;} +.push-24 {margin:0 -960px 1.5em 960px;} +.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:right;position:relative;} +.prepend-top {margin-top:1.5em;} +.append-bottom {margin-bottom:1.5em;} +.box {padding:1.5em;margin-bottom:1.5em;background:#E5ECF9;} +hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:.1em;margin:0 0 1.45em;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;} +.clearfix, .container {display:block;} +.clear {clear:both;} + +/* fancy-type */ +p + p {text-indent:2em;margin-top:-1.5em;} +form p + p {text-indent:0;} +.alt {color:#666;font-family:"Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua", Georgia, serif;font-style:italic;font-weight:normal;} +.dquo {margin-left:-.5em;} +p.incr, .incr p {font-size:10px;line-height:1.44em;margin-bottom:1.5em;} +.caps {font-variant:small-caps;letter-spacing:1px;text-transform:lowercase;font-size:1.2em;line-height:1%;font-weight:bold;padding:0 2px;} + +/* buttons */ +a.button, button {display:block;float:left;margin:0.7em 0.5em 0.7em 0;padding:5px 10px 5px 7px;border:1px solid #dedede;border-top:1px solid #eee;border-left:1px solid #eee;background-color:#f5f5f5;font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;font-size:100%;line-height:130%;text-decoration:none;font-weight:bold;color:#565656;cursor:pointer;} +button {width:auto;overflow:visible;padding:4px 10px 3px 7px;} +button[type] {padding:4px 10px 4px 7px;line-height:17px;} +*:first-child+html button[type] {padding:4px 10px 3px 7px;} +button img, a.button img {margin:0 3px -3px 0 !important;padding:0;border:none;width:16px;height:16px;float:none;} +button:hover, a.button:hover {background-color:#dff4ff;border:1px solid #c2e1ef;color:#336699;} +a.button:active {background-color:#6299c5;border:1px solid #6299c5;color:#fff;} +body .positive {color:#529214;} +a.positive:hover, button.positive:hover {background-color:#E6EFC2;border:1px solid #C6D880;color:#529214;} +a.positive:active {background-color:#529214;border:1px solid #529214;color:#fff;} +body .negative {color:#d12f19;} +a.negative:hover, button.negative:hover {background-color:#fbe3e4;border:1px solid #fbc2c4;color:#d12f19;} +a.negative:active {background-color:#d12f19;border:1px solid #d12f19;color:#fff;} \ No newline at end of file diff --git a/retwisj-reboot/templates/footer.jspf b/retwisj-reboot/templates/footer.jspf new file mode 100644 index 0000000..cc008a7 --- /dev/null +++ b/retwisj-reboot/templates/footer.jspf @@ -0,0 +1,18 @@ + + + + + diff --git a/retwisj-reboot/templates/header.jspf b/retwisj-reboot/templates/header.jspf new file mode 100644 index 0000000..4825f2b --- /dev/null +++ b/retwisj-reboot/templates/header.jspf @@ -0,0 +1,51 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + +<%@page import="org.springframework.data.redis.samples.retwisj.RetwisSecurity"%> + + + + + + + + " type="text/css" media="screen, projection"/> + " type="text/css" media="print"/> + + " type="text/css" media="screen, projection"/> + "> + Retwis-J :: a Spring Data Redis demonstration + + + +
+
+ +
+ <% + pageContext.setAttribute("loggedIn", RetwisSecurity.isSignedIn(), PageContext.PAGE_SCOPE); + pageContext.setAttribute("loggedUser", RetwisSecurity.getName(), PageContext.PAGE_SCOPE); + %> + + "> | + ">@${loggedUser} | + "> | + "> + + + + + + +
+
+
+
+ diff --git a/retwisj-reboot/templates/network.jspf b/retwisj-reboot/templates/network.jspf new file mode 100644 index 0000000..d093a88 --- /dev/null +++ b/retwisj-reboot/templates/network.jspf @@ -0,0 +1,34 @@ +
+ +
+

:

+ + + +
+
+
+

+ : ${fn:length(followers)} +

+ + + +
+
+

: ${fn:length(following)}

+ + + +
+
diff --git a/retwisj-reboot/templates/post.jspf b/retwisj-reboot/templates/post.jspf new file mode 100644 index 0000000..24feec2 --- /dev/null +++ b/retwisj-reboot/templates/post.jspf @@ -0,0 +1,19 @@ + +
+
+

+ ">${p.name} ${p.content} +
+ + "> + + + "> ${p.replyTo} + +     + + "> + +

+
+
diff --git a/retwisj-reboot/templates/posts.jspf b/retwisj-reboot/templates/posts.jspf new file mode 100644 index 0000000..254dd1b --- /dev/null +++ b/retwisj-reboot/templates/posts.jspf @@ -0,0 +1,22 @@ +
+ + + + + ">@costinl ? + + + + + + + + <%@ include file="/WEB-INF/templates/post.jspf" %> + + + +
+ +
+
+
diff --git a/retwisj-reboot/templates/userFollow.jspf b/retwisj-reboot/templates/userFollow.jspf new file mode 100644 index 0000000..2c0e1d3 --- /dev/null +++ b/retwisj-reboot/templates/userFollow.jspf @@ -0,0 +1,14 @@ +
+ + "> + "> + + | "> +
+ + +
+ : ">${f} +
+
+ \ No newline at end of file From ecaba53f42bb4adac1bf78bacc9ff1dace7294fe Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 23 May 2014 14:18:16 +0200 Subject: [PATCH 2/4] added thymeleaf starter started transformation to thymeleaf for * login * logout * signUp * post a tweet * show timeline this is work in progress! --- retwisj-reboot/pom.xml | 4 ++ .../java/org/example/retwisj/Application.java | 40 +++++++++++++-- .../example/retwisj/web/RetwisController.java | 12 +++-- .../main/resources}/static/images/favicon.ico | Bin .../static/images/springsource-logo.png | Bin 0 -> 2818 bytes .../main/resources}/static/styles/custom.css | 0 .../main/resources}/static/styles/ie.css | 0 .../main/resources}/static/styles/print.css | 0 .../main/resources}/static/styles/screen.css | 0 .../main/resources}/templates/footer.jspf | 0 .../resources/templates/fragments/footer.html | 38 ++++++++++++++ .../resources/templates/fragments/header.html | 26 ++++++++++ .../resources/templates/fragments/post.html | 21 ++++++++ .../resources/templates/fragments/tweet.html | 26 ++++++++++ .../main/resources}/templates/header.jspf | 0 .../main/resources/templates/home/home.html | 21 ++++++++ .../resources/templates/home/notSignedIn.html | 47 ++++++++++++++++++ .../resources/templates/home/timeline.html | 28 +++++++++++ .../src/main/resources/templates/index.html | 11 ++++ .../src/main/resources/templates/layout.html | 23 +++++++++ .../main/resources}/templates/network.jspf | 0 .../main/resources}/templates/post.jspf | 0 .../main/resources}/templates/posts.jspf | 0 .../main/resources}/templates/userFollow.jspf | 0 .../static/images/springsource-logo.png | Bin 4974 -> 0 bytes 25 files changed, 289 insertions(+), 8 deletions(-) rename retwisj-reboot/{ => src/main/resources}/static/images/favicon.ico (100%) create mode 100644 retwisj-reboot/src/main/resources/static/images/springsource-logo.png rename retwisj-reboot/{ => src/main/resources}/static/styles/custom.css (100%) rename retwisj-reboot/{ => src/main/resources}/static/styles/ie.css (100%) rename retwisj-reboot/{ => src/main/resources}/static/styles/print.css (100%) rename retwisj-reboot/{ => src/main/resources}/static/styles/screen.css (100%) rename retwisj-reboot/{ => src/main/resources}/templates/footer.jspf (100%) create mode 100644 retwisj-reboot/src/main/resources/templates/fragments/footer.html create mode 100644 retwisj-reboot/src/main/resources/templates/fragments/header.html create mode 100644 retwisj-reboot/src/main/resources/templates/fragments/post.html create mode 100644 retwisj-reboot/src/main/resources/templates/fragments/tweet.html rename retwisj-reboot/{ => src/main/resources}/templates/header.jspf (100%) create mode 100644 retwisj-reboot/src/main/resources/templates/home/home.html create mode 100644 retwisj-reboot/src/main/resources/templates/home/notSignedIn.html create mode 100644 retwisj-reboot/src/main/resources/templates/home/timeline.html create mode 100644 retwisj-reboot/src/main/resources/templates/index.html create mode 100644 retwisj-reboot/src/main/resources/templates/layout.html rename retwisj-reboot/{ => src/main/resources}/templates/network.jspf (100%) rename retwisj-reboot/{ => src/main/resources}/templates/post.jspf (100%) rename retwisj-reboot/{ => src/main/resources}/templates/posts.jspf (100%) rename retwisj-reboot/{ => src/main/resources}/templates/userFollow.jspf (100%) delete mode 100644 retwisj-reboot/static/images/springsource-logo.png diff --git a/retwisj-reboot/pom.xml b/retwisj-reboot/pom.xml index 0a4a24c..91f6fd5 100644 --- a/retwisj-reboot/pom.xml +++ b/retwisj-reboot/pom.xml @@ -26,6 +26,10 @@ org.springframework.boot spring-boot-starter-redis + + org.springframework.boot + spring-boot-starter-thymeleaf + org.codehaus.jackson diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/Application.java b/retwisj-reboot/src/main/java/org/example/retwisj/Application.java index acf48ed..7eff206 100644 --- a/retwisj-reboot/src/main/java/org/example/retwisj/Application.java +++ b/retwisj-reboot/src/main/java/org/example/retwisj/Application.java @@ -1,16 +1,50 @@ package org.example.retwisj; +import java.util.Collection; +import java.util.Collections; + +import org.example.retwisj.web.CookieInterceptor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.thymeleaf.dialect.IDialect; @Configuration @ComponentScan @EnableAutoConfiguration public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + public Collection dialects() { + // TODO: add spring security dialect + return Collections.emptyList(); + } + + @Bean + public WebMvcConfigurerAdapter webMvcConfigurerAdapter() { + WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + super.addInterceptors(registry); + registry.addInterceptor(cookieInterceptor()); + } + }; + + return adapter; + + } + + @Bean + CookieInterceptor cookieInterceptor() { + return new CookieInterceptor(); + } } diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java index f9fcd0d..08bc2aa 100644 --- a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java @@ -56,16 +56,16 @@ public String root(@RequestParam(required = false) Integer page, Model model) { return timeline(page, model); } - @RequestMapping("/signUp") + @RequestMapping(value = "/signUp") public String signUp(String name, String pass, String pass2, Model model, HttpServletResponse response) { if (retwis.isUserValid(name)) { model.addAttribute("errorduplicateuser", Boolean.TRUE); - return "signin"; + return "home/notSignedIn"; } if (!StringUtils.hasText(pass) || !StringUtils.hasText(pass2) || !pass.equals(pass2)) { model.addAttribute("errormatch", Boolean.TRUE); - return "signin"; + return "home/notSignedIn"; } String auth = retwis.addUser(name, pass); @@ -111,6 +111,7 @@ public String posts(@PathVariable String name, @RequestParam(required = false) S if (RetwisSecurity.isSignedIn()) { model.addAttribute("replyTo", replyto); model.addAttribute("replyPid", replypid); + model.addAttribute("loggedUser", RetwisSecurity.getName()); if (!targetUid.equals(RetwisSecurity.getUid())) { model.addAttribute("also_followed", retwis.alsoFollowed(RetwisSecurity.getUid(), targetUid)); @@ -129,7 +130,7 @@ public String posts(@PathVariable String name, @RequestParam(required = false) S model.addAttribute("posts", (RetwisSecurity.isUserSignedIn(targetUid) ? retwis.getTimeline(targetUid, range) : retwis.getPosts(targetUid, range))); - return "home"; + return "home/home"; } @RequestMapping(value = "/!{name}", method = RequestMethod.POST) @@ -181,7 +182,7 @@ public String timeline(@RequestParam(required = false) Integer page, Model model model.addAttribute("moreposts", retwis.hasMoreTimeline(range)); model.addAttribute("posts", retwis.timeline(range)); model.addAttribute("users", retwis.newUsers(new Range())); - return "timeline"; + return "home/timeline"; } @RequestMapping("/logout") @@ -217,4 +218,5 @@ public String handleNoUserException(NoSuchDataException ex) { // model.addAttribute("nodatatype", ex.isPost() ? "nodata.post" : "nodata.user"); return "nodata"; } + } diff --git a/retwisj-reboot/static/images/favicon.ico b/retwisj-reboot/src/main/resources/static/images/favicon.ico similarity index 100% rename from retwisj-reboot/static/images/favicon.ico rename to retwisj-reboot/src/main/resources/static/images/favicon.ico diff --git a/retwisj-reboot/src/main/resources/static/images/springsource-logo.png b/retwisj-reboot/src/main/resources/static/images/springsource-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1840af274b55e7f2d19931a5afecb576cb17f44a GIT binary patch literal 2818 zcmV+d3;pzoP)U?}ki*8K!3QU& z=?Q@la#*cHF7c9)lQnD%-fbIW6LMI;ulLnVO{uE8sz*}v(+_$y>fh?B_r3SMdR3*B zyLa!3Tyn|9EUWmlva*u-2Cu$dF49MdH%OU3BigR>=inF59fo;z=Q2+M=dlw1@dOBi zVw<)qZAFhu`?T%jlK>OtO_0k%0fc}7FVUf`a>i!^96PjiO+dkOX#XH@qT@O*uoupa z$}9qe8VL>5qOCFJGN8e?sNwtCvyNz6TXuz@y67l%j=Ff9>SUO!?(_h1&e4Khn*cZj zTgE+lMvs93X*_uTVu9(~GJ~U_)j7V`bG4nWoJ(mWG{}KxoRLN1=g{*ik6b9VUVh>Y z?AyqjCzqK82!o@?mTMYPy8pf}AG}byEWZAghsA~SU#l#0!!W(wKG^SKwn?e5}gX;8|UH4Ik@p=SIY3>?`lQEl!VaP~SIns5d;X#br|86BKV( zDETX`jK^hP6gia#p_e_FhSdgrdj8w59LF0rdFLMiwwjtJ@c@UKLr9XLreHmBx;r(b zW9ziPp(wabpfqhdziR;99qn8wQdyY0Zjl=b=(;? zrZP75_n%iel?P$VGcj<0Lcv>H`QdNHOG+@FB#+#qS)~l%=n40{4J_XtTXG>ryR<6| zwvNNSpeP-FzAC17=m>jWqo~}^w7+P0?t$Yw*A3-UW6y@;d-fQp1;eq0q*&W$QU3Ex zlhi;P)aeo(!*LspO1rN(8T{x9j*3O`LPfcXCdQ|nql<5n6iWC$S<)*hN@lh48JeNw zSw5X^^)eWvo>*{M)fB`zuzvKRr^J)rczPtb9(V6nhDO^oP^fjai&Tjvqy==%F$|bO z^_Bb7`Chuhx9E7_(KUixltB=Mw!r`ntmL_a?n}n*r!D5O9RM^L?(+efmsKMnJ-` zMjD^r$`WVUVi&#KhPX;Mh%JjTSl8=?;|}@0r9}tGIal`#Px1N>emzxiXz@{2sVnh? z&>A}e&2RumnAAJNy<9cJp{fHo*2e^nzyOZpz}aR(qdv57=}dEek*%vz-7+HA&9_LB0dIk#UHOFH#gnjx{^t{6-?ZI)nF0qOai$ z>qX>I*GmDAq0M_tI_5|N$BmB$SwI3V{*V3A%4!^B_h(d}%@e`OWn=#an;>rFgM)InhJGyWIZl z)>tb80XN20c?OuRG%v!jj1Gk19Po5g+gH=P1NY9=Csd|Q-Y|;dfE4%|?=O2&-WXI8 z01nTydXTBiIY3wLum1Xz_!FKZ%|pHWpFeq(jT>!`6&0?josi@3QC!>)-NfsbRanpf zrmSRWrR4d(A^`yZ2pVkX;=hA0Rhz>1-8&UZ~#@o%-OD z8!;$eXq-satV0P&$hk)cBB43t!h<=9Kr5N?APx8`NpBG5pppQgnN)+a=z8l=Gc zieZY!juB`E3W(&oxLxe0@6wR%FaYH0H!{wP=)4+VTT*D#A$y|=x8W9jkLY`bU>u4OVd4wiHlNz98)8gTnH@w zd*m%Zi`eqVf|0+^f9n;mL?E8|-o5-s+q045F+v<*wnX{}<#@P6%CK}fKra#}Gv|^5 z-ln0~P4eSHN^qRk12$ByRRdY4i=U4X-xw{ z67B7albMrST(>QbMt0hg*z(JkY2xgDa=YYJd zDUfmcu4a7JSqTaZN*93Clxue7U_@Mt0GbsfaxoWq-CVwTMDhh{x0nsay_+JzhygY zD9?F7(ThlJ+mWS5ZBt7@kuc;0fVfGN2{cLT4egJDngsx||qtoUdeBHvE)#s;fhX9aG`Ua46TU}M!{Y08oh!V5s@<6fwLkAx|4 zOac0YEo)*CUIV~TywFy@Q1et}TkD8`@+0>%WUDHBunGyZHEXY3pSxsda(ScGgrKxY zu}292fxsiY{)cB{&jBF7qW~1X0~tZh00ej;NzXKI)SR~fcLC(kqWDGOHVQz_Yw2q))Grv!Kiq|hpac-d_#ye3zV9${Uy4=~!f zb + + + + + + + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/fragments/header.html b/retwisj-reboot/src/main/resources/templates/fragments/header.html new file mode 100644 index 0000000..719abda --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/fragments/header.html @@ -0,0 +1,26 @@ + + + + + + +
+
+

+ CLOUT +

+
+
+ Welcome + Logout +
+ + + +
+
+
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/fragments/post.html b/retwisj-reboot/src/main/resources/templates/fragments/post.html new file mode 100644 index 0000000..c179a07 --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/fragments/post.html @@ -0,0 +1,21 @@ + + + + + +
+
+ + reply to user + username + + +
+ + + +
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/fragments/tweet.html b/retwisj-reboot/src/main/resources/templates/fragments/tweet.html new file mode 100644 index 0000000..8f4bc3a --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/fragments/tweet.html @@ -0,0 +1,26 @@ + + + + + +
+

+ User Name + Content +
+ + + add formatted date here + + + +  -  + Reply To + +  -  + +

+
+ + \ No newline at end of file diff --git a/retwisj-reboot/templates/header.jspf b/retwisj-reboot/src/main/resources/templates/header.jspf similarity index 100% rename from retwisj-reboot/templates/header.jspf rename to retwisj-reboot/src/main/resources/templates/header.jspf diff --git a/retwisj-reboot/src/main/resources/templates/home/home.html b/retwisj-reboot/src/main/resources/templates/home/home.html new file mode 100644 index 0000000..0d524a0 --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/home/home.html @@ -0,0 +1,21 @@ + + + + + +
+
+ new post widget +
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html b/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html new file mode 100644 index 0000000..2ef8cd8 --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html @@ -0,0 +1,47 @@ + + + + + +
+
+

signin

+
+ + + + + + + + + +
+ +
+
+
+

signup

+
+ + + + + + + + + + + + + +
+ +
+
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/home/timeline.html b/retwisj-reboot/src/main/resources/templates/home/timeline.html new file mode 100644 index 0000000..b35102f --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/home/timeline.html @@ -0,0 +1,28 @@ + + + + + +
+
+ new post widget +
+ +
+

Time Line

+
+
+ Post from all users +
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/index.html b/retwisj-reboot/src/main/resources/templates/index.html new file mode 100644 index 0000000..5a8461d --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/index.html @@ -0,0 +1,11 @@ + + + + Hello Spring Boot! + + + + +

Hello Spring Boot!

+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/layout.html b/retwisj-reboot/src/main/resources/templates/layout.html new file mode 100644 index 0000000..00491e1 --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/layout.html @@ -0,0 +1,23 @@ + + + + CLOUT :: Pivotal Cloud Foundry Twitter Clone + + + + + +
+
+ Login/Logout +
+
+ dynamic content pane +
+
+ © 2014 Spring by Pivotal. +
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/templates/network.jspf b/retwisj-reboot/src/main/resources/templates/network.jspf similarity index 100% rename from retwisj-reboot/templates/network.jspf rename to retwisj-reboot/src/main/resources/templates/network.jspf diff --git a/retwisj-reboot/templates/post.jspf b/retwisj-reboot/src/main/resources/templates/post.jspf similarity index 100% rename from retwisj-reboot/templates/post.jspf rename to retwisj-reboot/src/main/resources/templates/post.jspf diff --git a/retwisj-reboot/templates/posts.jspf b/retwisj-reboot/src/main/resources/templates/posts.jspf similarity index 100% rename from retwisj-reboot/templates/posts.jspf rename to retwisj-reboot/src/main/resources/templates/posts.jspf diff --git a/retwisj-reboot/templates/userFollow.jspf b/retwisj-reboot/src/main/resources/templates/userFollow.jspf similarity index 100% rename from retwisj-reboot/templates/userFollow.jspf rename to retwisj-reboot/src/main/resources/templates/userFollow.jspf diff --git a/retwisj-reboot/static/images/springsource-logo.png b/retwisj-reboot/static/images/springsource-logo.png deleted file mode 100644 index e170f8abf778b24ed1de11dad6f1444e849269ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4974 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 Date: Mon, 26 May 2014 09:57:52 +0200 Subject: [PATCH 3/4] Added (un)following and status pages. Update notSignedInPage to show only signUp info. Fixed broken links. --- .../example/retwisj/web/RetwisController.java | 2 +- .../main/resources/static/styles/screen.css | 2 +- .../resources/templates/fragments/header.html | 3 +- .../templates/fragments/network.html | 39 +++++++++++++++++++ .../resources/templates/fragments/post.html | 6 +-- .../resources/templates/fragments/tweet.html | 3 +- .../main/resources/templates/home/home.html | 11 ++++-- .../resources/templates/home/notSignedIn.html | 16 -------- .../main/resources/templates/home/status.html | 19 +++++++++ .../resources/templates/home/timeline.html | 5 +-- 10 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 retwisj-reboot/src/main/resources/templates/fragments/network.html create mode 100644 retwisj-reboot/src/main/resources/templates/home/status.html diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java index 08bc2aa..c9e510a 100644 --- a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java @@ -197,7 +197,7 @@ public String logout() { public String status(String pid, Model model) { checkPost(pid); model.addAttribute("posts", retwis.getPost(pid)); - return "status"; + return "home/status"; } private void checkUser(String username) { diff --git a/retwisj-reboot/src/main/resources/static/styles/screen.css b/retwisj-reboot/src/main/resources/static/styles/screen.css index c18b9a5..64022d3 100644 --- a/retwisj-reboot/src/main/resources/static/styles/screen.css +++ b/retwisj-reboot/src/main/resources/static/styles/screen.css @@ -83,7 +83,7 @@ input[type=text]:focus, input[type=password]:focus, input.text:focus, input.titl input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;} input.text, input.title {width:300px;padding:5px;} input.title {font-size:1.5em;} -textarea {width:390px;height:250px;padding:5px;} +/*textarea {width:390px;height:250px;padding:5px;}*/ input[type=checkbox], input[type=radio], input.checkbox, input.radio {position:relative;top:.25em;} form.inline {line-height:3;} form.inline p {margin-bottom:0;} diff --git a/retwisj-reboot/src/main/resources/templates/fragments/header.html b/retwisj-reboot/src/main/resources/templates/fragments/header.html index 719abda..70fce19 100644 --- a/retwisj-reboot/src/main/resources/templates/fragments/header.html +++ b/retwisj-reboot/src/main/resources/templates/fragments/header.html @@ -12,13 +12,14 @@

- Welcome + Welcome Logout
+ sign up

diff --git a/retwisj-reboot/src/main/resources/templates/fragments/network.html b/retwisj-reboot/src/main/resources/templates/fragments/network.html new file mode 100644 index 0000000..5497cd9 --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/fragments/network.html @@ -0,0 +1,39 @@ + + + + + +
+
+ + +
+
+

+
    +
  • +
+
+
+

+
    +
  • + +
  • +
+ +
+
+

+
    +
  • + +
  • +
+ +
+ +
+ + diff --git a/retwisj-reboot/src/main/resources/templates/fragments/post.html b/retwisj-reboot/src/main/resources/templates/fragments/post.html index c179a07..9be36fb 100644 --- a/retwisj-reboot/src/main/resources/templates/fragments/post.html +++ b/retwisj-reboot/src/main/resources/templates/fragments/post.html @@ -7,10 +7,10 @@
- reply to user - username + username + reply to user - +
diff --git a/retwisj-reboot/src/main/resources/templates/fragments/tweet.html b/retwisj-reboot/src/main/resources/templates/fragments/tweet.html index 8f4bc3a..790e358 100644 --- a/retwisj-reboot/src/main/resources/templates/fragments/tweet.html +++ b/retwisj-reboot/src/main/resources/templates/fragments/tweet.html @@ -16,11 +16,12 @@  -  - Reply To + Reply To  - 

+
\ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/home/home.html b/retwisj-reboot/src/main/resources/templates/home/home.html index 0d524a0..f9bd59f 100644 --- a/retwisj-reboot/src/main/resources/templates/home/home.html +++ b/retwisj-reboot/src/main/resources/templates/home/home.html @@ -9,13 +9,16 @@
new post widget
-
-
-
-
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html b/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html index 2ef8cd8..133dc37 100644 --- a/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html +++ b/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html @@ -6,22 +6,6 @@
-
-

signin

- - - - - - - - - - -
- - -

signup

diff --git a/retwisj-reboot/src/main/resources/templates/home/status.html b/retwisj-reboot/src/main/resources/templates/home/status.html new file mode 100644 index 0000000..4438641 --- /dev/null +++ b/retwisj-reboot/src/main/resources/templates/home/status.html @@ -0,0 +1,19 @@ + + + + + +
+
+
+ +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/retwisj-reboot/src/main/resources/templates/home/timeline.html b/retwisj-reboot/src/main/resources/templates/home/timeline.html index b35102f..7414e90 100644 --- a/retwisj-reboot/src/main/resources/templates/home/timeline.html +++ b/retwisj-reboot/src/main/resources/templates/home/timeline.html @@ -17,9 +17,8 @@

Time Line

Post from all users

-
-
-
+
+
From c9ee560ccea7ef367928d505d85d335d810728c1 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 26 May 2014 11:40:28 +0200 Subject: [PATCH 4/4] Add custom processor for date formatting. Add errors on signUp/signIn. Remove obsolete resources. --- .../java/org/example/retwisj/Application.java | 55 ++++++++++++++--- .../example/retwisj/web/RetwisController.java | 2 +- .../retwisj/web/TweetDateProcessor.java | 60 +++++++++++++++++++ .../src/main/resources/templates/footer.jspf | 18 ------ .../resources/templates/fragments/tweet.html | 2 +- .../src/main/resources/templates/header.jspf | 51 ---------------- .../resources/templates/home/notSignedIn.html | 10 ++++ .../src/main/resources/templates/network.jspf | 34 ----------- .../src/main/resources/templates/post.jspf | 19 ------ .../src/main/resources/templates/posts.jspf | 22 ------- .../main/resources/templates/userFollow.jspf | 14 ----- 11 files changed, 119 insertions(+), 168 deletions(-) create mode 100644 retwisj-reboot/src/main/java/org/example/retwisj/web/TweetDateProcessor.java delete mode 100644 retwisj-reboot/src/main/resources/templates/footer.jspf delete mode 100644 retwisj-reboot/src/main/resources/templates/header.jspf delete mode 100644 retwisj-reboot/src/main/resources/templates/network.jspf delete mode 100644 retwisj-reboot/src/main/resources/templates/post.jspf delete mode 100644 retwisj-reboot/src/main/resources/templates/posts.jspf delete mode 100644 retwisj-reboot/src/main/resources/templates/userFollow.jspf diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/Application.java b/retwisj-reboot/src/main/java/org/example/retwisj/Application.java index 7eff206..1c56fc0 100644 --- a/retwisj-reboot/src/main/java/org/example/retwisj/Application.java +++ b/retwisj-reboot/src/main/java/org/example/retwisj/Application.java @@ -1,9 +1,25 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.example.retwisj; -import java.util.Collection; import java.util.Collections; +import java.util.Set; import org.example.retwisj.web.CookieInterceptor; +import org.example.retwisj.web.TweetDateProcessor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; @@ -11,8 +27,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.thymeleaf.dialect.AbstractDialect; import org.thymeleaf.dialect.IDialect; +import org.thymeleaf.processor.IProcessor; +/** + * @author Thomas Darimont + * @author Christoph Strobl + */ @Configuration @ComponentScan @EnableAutoConfiguration @@ -22,12 +44,6 @@ public static void main(String[] args) { SpringApplication.run(Application.class, args); } - @Bean - public Collection dialects() { - // TODO: add spring security dialect - return Collections.emptyList(); - } - @Bean public WebMvcConfigurerAdapter webMvcConfigurerAdapter() { WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @@ -40,11 +56,34 @@ public void addInterceptors(InterceptorRegistry registry) { }; return adapter; - } @Bean CookieInterceptor cookieInterceptor() { return new CookieInterceptor(); } + + @Bean + public IDialect tweetDialect() { + + return new AbstractDialect() { + + @Override + public Set getProcessors() { + return Collections. singleton(tweetDateProcessor()); + } + + @Override + public String getPrefix() { + return "tweet"; + } + + }; + + } + + @Bean + public TweetDateProcessor tweetDateProcessor() { + return new TweetDateProcessor(); + } } diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java index c9e510a..1661c80 100644 --- a/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/RetwisController.java @@ -85,7 +85,7 @@ public String signIn(@RequestParam(required = false) String name, @RequestParam( model.addAttribute("errorpass", Boolean.TRUE); } // go back to sign in screen - return "signin"; + return "home/notSignedIn"; } private void addAuthCookie(String auth, String name, HttpServletResponse response) { diff --git a/retwisj-reboot/src/main/java/org/example/retwisj/web/TweetDateProcessor.java b/retwisj-reboot/src/main/java/org/example/retwisj/web/TweetDateProcessor.java new file mode 100644 index 0000000..33b1b97 --- /dev/null +++ b/retwisj-reboot/src/main/java/org/example/retwisj/web/TweetDateProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.example.retwisj.web; + +import org.thymeleaf.Arguments; +import org.thymeleaf.Configuration; +import org.thymeleaf.dom.Element; +import org.thymeleaf.processor.AttributeNameProcessorMatcher; +import org.thymeleaf.processor.IAttributeNameProcessorMatcher; +import org.thymeleaf.processor.attr.AbstractTextChildModifierAttrProcessor; +import org.thymeleaf.standard.expression.IStandardExpression; +import org.thymeleaf.standard.expression.IStandardExpressionParser; +import org.thymeleaf.standard.expression.StandardExpressions; + +/** + * @author Christoph Strobl + */ +public class TweetDateProcessor extends AbstractTextChildModifierAttrProcessor { + + public TweetDateProcessor() { + this(new AttributeNameProcessorMatcher("date")); + } + + public TweetDateProcessor(IAttributeNameProcessorMatcher matcher) { + super(matcher); + } + + @Override + protected String getText(Arguments arguments, Element element, String attributeName) { + + Configuration configuration = arguments.getConfiguration(); + + IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration); + String attributeValue = element.getAttributeValue(attributeName); + IStandardExpression expression = parser.parseExpression(configuration, arguments, attributeValue); + + WebPost post = (WebPost) expression.execute(configuration, arguments); + + return getMessage(arguments, post.getTime(), new Object[] { post.getTimeArg() }); + } + + @Override + public int getPrecedence() { + return 0; + } + +} diff --git a/retwisj-reboot/src/main/resources/templates/footer.jspf b/retwisj-reboot/src/main/resources/templates/footer.jspf deleted file mode 100644 index cc008a7..0000000 --- a/retwisj-reboot/src/main/resources/templates/footer.jspf +++ /dev/null @@ -1,18 +0,0 @@ -
- - - - diff --git a/retwisj-reboot/src/main/resources/templates/fragments/tweet.html b/retwisj-reboot/src/main/resources/templates/fragments/tweet.html index 790e358..55e1cc0 100644 --- a/retwisj-reboot/src/main/resources/templates/fragments/tweet.html +++ b/retwisj-reboot/src/main/resources/templates/fragments/tweet.html @@ -10,7 +10,7 @@ Content
- + add formatted date here diff --git a/retwisj-reboot/src/main/resources/templates/header.jspf b/retwisj-reboot/src/main/resources/templates/header.jspf deleted file mode 100644 index 4825f2b..0000000 --- a/retwisj-reboot/src/main/resources/templates/header.jspf +++ /dev/null @@ -1,51 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> - -<%@page import="org.springframework.data.redis.samples.retwisj.RetwisSecurity"%> - - - - - - - - " type="text/css" media="screen, projection"/> - " type="text/css" media="print"/> - - " type="text/css" media="screen, projection"/> - "> - Retwis-J :: a Spring Data Redis demonstration - - - -
-
- -
- <% - pageContext.setAttribute("loggedIn", RetwisSecurity.isSignedIn(), PageContext.PAGE_SCOPE); - pageContext.setAttribute("loggedUser", RetwisSecurity.getName(), PageContext.PAGE_SCOPE); - %> - - "> | - ">@${loggedUser} | - "> | - "> - - - - - - -
-
-
-
- diff --git a/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html b/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html index 133dc37..0966f45 100644 --- a/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html +++ b/retwisj-reboot/src/main/resources/templates/home/notSignedIn.html @@ -7,7 +7,17 @@
+
+ Username password wrong. +

signup

+
+ Duplicate user +
+
+ Match user +
+ diff --git a/retwisj-reboot/src/main/resources/templates/network.jspf b/retwisj-reboot/src/main/resources/templates/network.jspf deleted file mode 100644 index d093a88..0000000 --- a/retwisj-reboot/src/main/resources/templates/network.jspf +++ /dev/null @@ -1,34 +0,0 @@ -
- -
-

:

- - - -
-
-
-

- : ${fn:length(followers)} -

- - - -
-
-

: ${fn:length(following)}

- - - -
-
diff --git a/retwisj-reboot/src/main/resources/templates/post.jspf b/retwisj-reboot/src/main/resources/templates/post.jspf deleted file mode 100644 index 24feec2..0000000 --- a/retwisj-reboot/src/main/resources/templates/post.jspf +++ /dev/null @@ -1,19 +0,0 @@ - -
-
-

- ">${p.name} ${p.content} -
- - "> - - - "> ${p.replyTo} - -     - - "> - -

-
-
diff --git a/retwisj-reboot/src/main/resources/templates/posts.jspf b/retwisj-reboot/src/main/resources/templates/posts.jspf deleted file mode 100644 index 254dd1b..0000000 --- a/retwisj-reboot/src/main/resources/templates/posts.jspf +++ /dev/null @@ -1,22 +0,0 @@ -
- - - - - ">@costinl ? - - - - - - - - <%@ include file="/WEB-INF/templates/post.jspf" %> - - - -
- -
-
-
diff --git a/retwisj-reboot/src/main/resources/templates/userFollow.jspf b/retwisj-reboot/src/main/resources/templates/userFollow.jspf deleted file mode 100644 index 2c0e1d3..0000000 --- a/retwisj-reboot/src/main/resources/templates/userFollow.jspf +++ /dev/null @@ -1,14 +0,0 @@ -
- - "> - "> - - | "> -
- - -
- : ">${f} -
-
- \ No newline at end of file