diff options
-rw-r--r-- | src/com/p4square/grow/backend/db/CassandraProviderImpl.java | 3 | ||||
-rw-r--r-- | src/com/p4square/grow/backend/feed/TopicResource.java | 8 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/ErrorPage.java | 3 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/FeedData.java | 31 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/FeedResource.java | 107 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/GrowFrontend.java | 2 | ||||
-rw-r--r-- | src/com/p4square/grow/frontend/JsonRequestProvider.java | 24 | ||||
-rw-r--r-- | src/com/p4square/grow/model/Message.java | 6 | ||||
-rw-r--r-- | src/com/p4square/grow/model/UserRecord.java | 93 | ||||
-rw-r--r-- | src/com/p4square/grow/provider/JsonEncodedProvider.java | 2 | ||||
-rw-r--r-- | src/com/p4square/grow/tools/AssessmentStats.java | 3 | ||||
-rw-r--r-- | src/templates/templates/communityfeed.ftl | 10 | ||||
-rw-r--r-- | src/templates/templates/training.ftl | 8 | ||||
-rw-r--r-- | web/scripts/growth.js | 14 | ||||
-rw-r--r-- | web/style.css | 5 |
15 files changed, 293 insertions, 26 deletions
diff --git a/src/com/p4square/grow/backend/db/CassandraProviderImpl.java b/src/com/p4square/grow/backend/db/CassandraProviderImpl.java index c1f6e6d..da5a9f2 100644 --- a/src/com/p4square/grow/backend/db/CassandraProviderImpl.java +++ b/src/com/p4square/grow/backend/db/CassandraProviderImpl.java @@ -6,6 +6,7 @@ package com.p4square.grow.backend.db; import java.io.IOException; +import com.p4square.grow.provider.Provider; import com.p4square.grow.provider.JsonEncodedProvider; /** @@ -13,7 +14,7 @@ import com.p4square.grow.provider.JsonEncodedProvider; * * @author Jesse Morgan <jesse@jesterpm.net> */ -public class CassandraProviderImpl<V> extends JsonEncodedProvider<CassandraKey, V> { +public class CassandraProviderImpl<V> extends JsonEncodedProvider<V> implements Provider<CassandraKey, V> { private final CassandraDatabase mDb; public CassandraProviderImpl(CassandraDatabase db, Class<V> clazz) { diff --git a/src/com/p4square/grow/backend/feed/TopicResource.java b/src/com/p4square/grow/backend/feed/TopicResource.java index 3519ca9..5826355 100644 --- a/src/com/p4square/grow/backend/feed/TopicResource.java +++ b/src/com/p4square/grow/backend/feed/TopicResource.java @@ -76,8 +76,12 @@ public class TopicResource extends ServerResource { try { // Deserialize the incoming message. - JacksonRepresentation<Message> jsonRep = new JacksonRepresentation<Message>(entity, Message.class); - Message message = jsonRep.getObject(); + JacksonRepresentation<MessageThread> jsonRep = + new JacksonRepresentation<MessageThread>(entity, MessageThread.class); + + // Get the message from the request. + // Throw away the wrapping MessageThread because we'll create our own later. + Message message = jsonRep.getObject().getMessage(); if (message.getCreated() == null) { message.setCreated(new Date()); } diff --git a/src/com/p4square/grow/frontend/ErrorPage.java b/src/com/p4square/grow/frontend/ErrorPage.java index 39aae93..61f6a35 100644 --- a/src/com/p4square/grow/frontend/ErrorPage.java +++ b/src/com/p4square/grow/frontend/ErrorPage.java @@ -36,6 +36,9 @@ public class ErrorPage extends WriterRepresentation { public static final ErrorPage BACKEND_ERROR = new ErrorPage("Error communicating with backend."); + public static final ErrorPage NOT_FOUND = + new ErrorPage("The requested URL could not be found."); + private static Template cTemplate = null; private static Map<String, Object> cRoot = null; diff --git a/src/com/p4square/grow/frontend/FeedData.java b/src/com/p4square/grow/frontend/FeedData.java index acff8d9..eddc6a4 100644 --- a/src/com/p4square/grow/frontend/FeedData.java +++ b/src/com/p4square/grow/frontend/FeedData.java @@ -28,8 +28,12 @@ public class FeedData { private final Config mConfig; private final String mBackendURI; - private final Provider<String, List<MessageThread>> mThreadProvider; - private final Provider<String, List<Message>> mMessageProvider; + // TODO: Elegantly merge the List and individual providers. + private final JsonRequestProvider<List<MessageThread>> mThreadsProvider; + private final JsonRequestProvider<MessageThread> mThreadProvider; + + private final JsonRequestProvider<List<Message>> mMessagesProvider; + private final JsonRequestProvider<Message> mMessageProvider; public FeedData(final Context context, final Config config) { mConfig = config; @@ -40,18 +44,33 @@ public class FeedData { TypeFactory factory = JsonEncodedProvider.MAPPER.getTypeFactory(); JavaType threadType = factory.constructCollectionType(List.class, MessageThread.class); - mThreadProvider = new JsonRequestProvider<List<MessageThread>>(clientDispatcher, threadType); + mThreadsProvider = new JsonRequestProvider<List<MessageThread>>(clientDispatcher, threadType); + mThreadProvider = new JsonRequestProvider<MessageThread>(clientDispatcher, MessageThread.class); JavaType messageType = factory.constructCollectionType(List.class, Message.class); - mMessageProvider = new JsonRequestProvider<List<Message>>(clientDispatcher, messageType); + mMessagesProvider = new JsonRequestProvider<List<Message>>(clientDispatcher, messageType); + mMessageProvider = new JsonRequestProvider<Message>(clientDispatcher, Message.class); } public List<MessageThread> getThreads(final String topic) throws IOException { - return mThreadProvider.get(makeUrl(topic)); + return mThreadsProvider.get(makeUrl(topic)); } public List<Message> getMessages(final String topic, final String threadId) throws IOException { - return mMessageProvider.get(makeUrl(topic, threadId)); + return mMessagesProvider.get(makeUrl(topic, threadId)); + } + + public void createThread(final String topic, final Message message) throws IOException { + MessageThread thread = new MessageThread(); + thread.setMessage(message); + + mThreadProvider.post(makeUrl(topic), thread); + } + + public void createResponse(final String topic, final String thread, final Message message) + throws IOException { + + mMessageProvider.post(makeUrl(topic, thread), message); } private String makeUrl(String... parts) { diff --git a/src/com/p4square/grow/frontend/FeedResource.java b/src/com/p4square/grow/frontend/FeedResource.java new file mode 100644 index 0000000..eea89b1 --- /dev/null +++ b/src/com/p4square/grow/frontend/FeedResource.java @@ -0,0 +1,107 @@ +/* + * Copyright 2014 Jesse Morgan + */ + +package com.p4square.grow.frontend; + +import java.io.IOException; + +import java.util.Arrays; +import java.util.HashSet; + +import org.restlet.data.Form; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.resource.ServerResource; + +import org.apache.log4j.Logger; + +import com.p4square.grow.config.Config; +import com.p4square.grow.model.Message; +import com.p4square.grow.model.UserRecord; + +/** + * This resource handles user interactions with the feed. + */ +public class FeedResource extends ServerResource { + private static final Logger LOG = Logger.getLogger(FeedResource.class); + + private static final HashSet<String> TOPICS = new HashSet(Arrays.asList("introduction", "seeker", "believer", + "disciple", "teacher")); + + private Config mConfig; + + private FeedData mFeedData; + + // Fields pertaining to this request. + protected String mTopic; + protected String mThread; + + @Override + public void doInit() { + super.doInit(); + + GrowFrontend growFrontend = (GrowFrontend) getApplication(); + mConfig = growFrontend.getConfig(); + + mFeedData = new FeedData(getContext(), mConfig); + + mTopic = getAttribute("topic"); + if (mTopic != null) { + mTopic = mTopic.trim(); + } + + mThread = getAttribute("thread"); + if (mThread != null) { + mThread = mThread.trim(); + } + } + + /** + * Create a new MessageThread. + */ + @Override + protected Representation post(Representation entity) { + try { + if (mTopic == null || mTopic.length() == 0 || !TOPICS.contains(mTopic)) { + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return ErrorPage.NOT_FOUND; + } + + Form form = new Form(entity); + + String question = form.getFirstValue("question"); + + Message message = new Message(); + message.setMessage(question); + + UserRecord user = new UserRecord(getRequest().getClientInfo().getUser()); + message.setAuthor(user); + + if (mThread != null && mThread.length() != 0) { + // Post a response + mFeedData.createResponse(mTopic, mThread, message); + + } else { + // Post a new thread + mFeedData.createThread(mTopic, message); + } + + /* + * Can't trust the referrer, so we'll send them to the + * appropriate part of the training page + * TODO: This could be better done. + */ + String nextPage = mConfig.getString("dynamicRoot", ""); + nextPage += "/account/training/" + mTopic; + getResponse().redirectSeeOther(nextPage); + return null; + + } catch (IOException e) { + LOG.fatal("Could not save message: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); + return ErrorPage.BACKEND_ERROR; + + } + } +} diff --git a/src/com/p4square/grow/frontend/GrowFrontend.java b/src/com/p4square/grow/frontend/GrowFrontend.java index 4f67afb..ac323ec 100644 --- a/src/com/p4square/grow/frontend/GrowFrontend.java +++ b/src/com/p4square/grow/frontend/GrowFrontend.java @@ -111,6 +111,8 @@ public class GrowFrontend extends FMFacade { accountRouter.attach("/training/leader", GroupLeaderTrainingPageResource.class); accountRouter.attach("/training/{chapter}", TrainingPageResource.class); accountRouter.attach("/training", TrainingPageResource.class); + accountRouter.attach("/feed/{topic}", FeedResource.class); + accountRouter.attach("/feed/{topic}/{thread}", FeedResource.class); final Authenticator accountGuard = createAuthenticatorChain(accountRouter); router.attach("/account", accountGuard); diff --git a/src/com/p4square/grow/frontend/JsonRequestProvider.java b/src/com/p4square/grow/frontend/JsonRequestProvider.java index 8eee6d3..a04294d 100644 --- a/src/com/p4square/grow/frontend/JsonRequestProvider.java +++ b/src/com/p4square/grow/frontend/JsonRequestProvider.java @@ -16,6 +16,7 @@ import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; +import com.p4square.grow.provider.Provider; import com.p4square.grow.provider.JsonEncodedProvider; /** @@ -23,7 +24,7 @@ import com.p4square.grow.provider.JsonEncodedProvider; * * @author Jesse Morgan <jesse@jesterpm.net> */ -public class JsonRequestProvider<V> extends JsonEncodedProvider<String, V> { +public class JsonRequestProvider<V> extends JsonEncodedProvider<V> implements Provider<String, V> { private final Restlet mDispatcher; @@ -66,7 +67,26 @@ public class JsonRequestProvider<V> extends JsonEncodedProvider<String, V> { if (!response.getStatus().isSuccess()) { throw new IOException("Could not put object. " + response.getStatus()); } - } + /** + * Variant of put() which makes a POST request to the url. + * + * This method may eventually be incorporated into Provider for + * creating new objects with auto-generated IDs. + * + * @param url The url to make the request to. + * @param obj The post to post. + * @throws IOException on failure. + */ + public void post(String url, V obj) throws IOException { + final Request request = new Request(Method.POST, url); + request.setEntity(new StringRepresentation(encode(obj))); + + final Response response = mDispatcher.handle(request); + + if (!response.getStatus().isSuccess()) { + throw new IOException("Could not put object. " + response.getStatus()); + } + } } diff --git a/src/com/p4square/grow/model/Message.java b/src/com/p4square/grow/model/Message.java index 6e07150..ad02af9 100644 --- a/src/com/p4square/grow/model/Message.java +++ b/src/com/p4square/grow/model/Message.java @@ -14,7 +14,7 @@ import java.util.Date; public class Message { private String mThreadId; private String mId; - private String mAuthor; + private UserRecord mAuthor; private Date mCreated; private String mMessage; @@ -51,7 +51,7 @@ public class Message { /** * @return The author of the message. */ - public String getAuthor() { + public UserRecord getAuthor() { return mAuthor; } @@ -59,7 +59,7 @@ public class Message { * Set the author of the message. * @param author The new author. */ - public void setAuthor(String author) { + public void setAuthor(UserRecord author) { mAuthor = author; } diff --git a/src/com/p4square/grow/model/UserRecord.java b/src/com/p4square/grow/model/UserRecord.java new file mode 100644 index 0000000..0702eb1 --- /dev/null +++ b/src/com/p4square/grow/model/UserRecord.java @@ -0,0 +1,93 @@ +/* + * Copyright 2014 Jesse Morgan + */ + +package com.p4square.grow.model; + +import org.restlet.security.User; + +/** + * A simple user representation without any secrets. + */ +public class UserRecord { + private String mId; + private String mFirstName; + private String mLastName; + private String mEmail; + + /** + * Create an empty UserRecord. + */ + public UserRecord() { + } + + /** + * Create a new UserRecord with the information from a User. + */ + public UserRecord(final User user) { + mId = user.getIdentifier(); + mFirstName = user.getFirstName(); + mLastName = user.getLastName(); + mEmail = user.getEmail(); + } + + /** + * @return The user's identifier. + */ + public String getId() { + return mId; + } + + /** + * Set the user's identifier. + * @param value The new id. + */ + public void setId (final String value) { + mId = value; + } + + /** + * @return The user's email. + */ + public String getEmail() { + return mEmail; + } + + /** + * Set the user's email. + * @param value The new email. + */ + public void setEmail (final String value) { + mEmail = value; + } + + /** + * @return The user's first name. + */ + public String getFirstName() { + return mFirstName; + } + + /** + * Set the user's first name. + * @param value The new first name. + */ + public void setFirstName (final String value) { + mFirstName = value; + } + + /** + * @return The user's last name. + */ + public String getLastName() { + return mLastName; + } + + /** + * Set the user's last name. + * @param value The new last name. + */ + public void setLastName (final String value) { + mLastName = value; + } +} diff --git a/src/com/p4square/grow/provider/JsonEncodedProvider.java b/src/com/p4square/grow/provider/JsonEncodedProvider.java index 03e4056..7ae3f71 100644 --- a/src/com/p4square/grow/provider/JsonEncodedProvider.java +++ b/src/com/p4square/grow/provider/JsonEncodedProvider.java @@ -17,7 +17,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; * * @author Jesse Morgan <jesse@jesterpm.net> */ -public abstract class JsonEncodedProvider<K, V> implements Provider<K, V> { +public abstract class JsonEncodedProvider<V> { public static final ObjectMapper MAPPER = new ObjectMapper(); static { MAPPER.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true); diff --git a/src/com/p4square/grow/tools/AssessmentStats.java b/src/com/p4square/grow/tools/AssessmentStats.java index c06e853..ca83411 100644 --- a/src/com/p4square/grow/tools/AssessmentStats.java +++ b/src/com/p4square/grow/tools/AssessmentStats.java @@ -20,6 +20,7 @@ import com.p4square.grow.model.Answer; import com.p4square.grow.model.Question; import com.p4square.grow.model.RecordedAnswer; import com.p4square.grow.model.Score; +import com.p4square.grow.provider.Provider; import com.p4square.grow.provider.JsonEncodedProvider; /** @@ -149,7 +150,7 @@ public class AssessmentStats { doFindHighestFromId(questions, q.getNextQuestion(), scores, path); } - private static class FileQuestionProvider extends JsonEncodedProvider<String, Question> { + private static class FileQuestionProvider extends JsonEncodedProvider<Question> implements Provider<String, Question> { private String mBaseDir; public FileQuestionProvider(String directory) { diff --git a/src/templates/templates/communityfeed.ftl b/src/templates/templates/communityfeed.ftl index b80ef9c..632a201 100644 --- a/src/templates/templates/communityfeed.ftl +++ b/src/templates/templates/communityfeed.ftl @@ -10,22 +10,24 @@ <div><a class="reply" href="#" onclick="answerQuestion('${thread.id}'); return false;">Answer</a></div> </div> <div class="answer hidden" id="answer-${thread.id}"> - <form action="${dynamicRoot}/feed/${chapter}/${thread.id}" method="post"> + <form action="${dynamicRoot}/account/feed/${chapter}/${thread.id}" method="post"> <textarea name="question" rows="5" defaultValue="Write your reply."></textarea> <div><a class="send" href="#" onclick="$(this).closest('form').submit(); return false;">Send</a></div> </form> </div> <#list messages as msg> - <div class="answer" id="${msg.id}"> + <div class="answer slider" id="${msg.id}"> A: ${msg.message!""} - <#--<a class="readmore" href="#">(continue)</a>--> + <#if msg_has_next && msg_index == 0> + <div><a class="readmore" href="#" onclick="showAnswers(this); return false;">(show more)</a></div> + </#if> </div> </#list> </article> </#list> <article> <div class="question"> - <form action="${dynamicRoot}/feed/${chapter}" name="newquestion" method="post"> + <form action="${dynamicRoot}/account/feed/${chapter}" name="newquestion" method="post"> <textarea name="question" rows="5" defaultValue="Ask your own question."></textarea> <div><a class="send" href="#" onclick="$(this).closest('form').submit(); return false;">Send</a></div> </form> diff --git a/src/templates/templates/training.ftl b/src/templates/templates/training.ftl index 65ca5d9..ca1ebbd 100644 --- a/src/templates/templates/training.ftl +++ b/src/templates/templates/training.ftl @@ -66,10 +66,6 @@ </#list> </div> - <#if showfeed!false> - <#include "/templates/communityfeed.ftl"> - </#if> - <#if deeperinclude?has_content> <div id="deeper"> <h2>Going Deeper</h2> @@ -81,6 +77,10 @@ <#include deeperinclude> </div> </#if> + + <#if showfeed!false> + <#include "/templates/communityfeed.ftl"> + </#if> </div> <div id="videoplayer"> diff --git a/web/scripts/growth.js b/web/scripts/growth.js index 86ac21f..9ae6786 100644 --- a/web/scripts/growth.js +++ b/web/scripts/growth.js @@ -105,6 +105,8 @@ $(document).ready(function() } }).trigger('blur'); + $("#thefeed article .answer:nth-child(3)").delay(300).slideDown(); + $("#banner").slideDown(); }); @@ -300,6 +302,14 @@ function submitClassForm() return false; } -function answerQuestion(id) { - $("#answer-" + id).slideDown(); +function answerQuestion(id) +{ + $("#answer-" + id).slideToggle(); +} + +function showAnswers(obj) +{ + $(obj).hide(); + $(obj).parents(".answer").siblings(".slider").slideDown(); } + diff --git a/web/style.css b/web/style.css index 4253a66..23f6a39 100644 --- a/web/style.css +++ b/web/style.css @@ -375,6 +375,7 @@ footer a:hover { #thefeed article .answer { background: #f6f6f6; + color: #999; } #thefeed article div div { @@ -402,6 +403,10 @@ footer a:hover { font-weight: bold; } +#thefeed .answer.slider { + display: none; +} + #thefeed textarea { background: #fff; width: 100%; |