(* Forum -- forum subapp Copyright (C) 2013 Benjamin Barenblat This file is a part of 6.947. 6.947 is is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6.947 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with 6.947. If not, see . *) functor Make(Template : sig val generic : option string -> xbody -> page end) = struct open Styles style entryList style entryMetadata style entryTitle style entryBody style voting table entry : { Id : int, References : option int, Class : EntryClass.entryClass, Title : option string, Body : string, Author : Author.usernameOrAnonymous } PRIMARY KEY Id sequence entryIdS table vote : { QuestionId : int, Author : Author.username, Value : Score.score } CONSTRAINT OneVotePerEntry UNIQUE (QuestionId, Author), CONSTRAINT RefersToEntry FOREIGN KEY QuestionId REFERENCES entry(Id) (* Like query1', but automatically dereferences the field *) fun queryColumn [tab ::: Name] [field ::: Name] [state ::: Type] (q : sql_query [] [] [tab = [field = state]] []) (f : state -> state -> state) (initial : state) : transaction state = query q (fn row state => return (f row.tab.field state)) initial fun unless [m ::: Type -> Type] (_ : monad m) (cond : bool) (computation : m {}) = if cond then return () else computation (* Sum all the votes on a single question. *) fun getScore (questionId : int) : transaction Score.score = queryColumn (SELECT Vote.Value FROM vote WHERE Vote.QuestionId = {[questionId]}) Score.update Score.undecided fun recordVote (value : Score.score) (entryId : int) _formData : transaction page = authorOpt <- Author.current; (* If the user didn't exist, the user should not have been allowed to vote in the first place. *) let val author = Author.nameError authorOpt in existingVote <- oneOrNoRows1 (SELECT Vote.Value FROM vote WHERE Vote.QuestionId = {[entryId]} AND Vote.Author = {[author]}); (* This mimics Reddit's upvote/downvote behavior, which is a bizarrely complex state machine that is nonetheless totally intuitive, especially when you're using an AJAXy interface. TODO: Write an AJAXy interface. *) (case existingVote of None => dml (INSERT INTO vote (QuestionId, Author, Value) VALUES ({[entryId]}, {[author]}, {[value]})) | Some v => if v.Value = value then dml (DELETE FROM vote WHERE QuestionId = {[entryId]} AND Author = {[author]}) else dml (UPDATE vote SET Value = {[value]} WHERE QuestionId = {[entryId]} AND Author = {[author]})); detail entryId end and upvote entryId _formData = recordVote Score.insightful entryId _formData and downvote entryId _formData = recordVote Score.inane entryId _formData (***************************** Single questions ******************************) and detail (id : int) : transaction page = authorOpt <- Author.current; question <- oneRow1 (SELECT * FROM entry WHERE Entry.Class = {[EntryClass.question]} AND Entry.Id = {[id]}); score <- getScore id; answerBlock <- queryX1' (SELECT * FROM entry WHERE Entry.Class = {[EntryClass.answer]} AND Entry.References = {[Some id]}) (fn answer => score <- getScore answer.Id; return (

{[answer.Body]} —{[answer.Author]} ({[Score.withUnits score "point"]})

)); return ( Template.generic (Some "Forum")

{[question.Title]}

{[question.Body]}

Asked by {[question.Author]} ({[Score.withUnits score "point"]})

{Author.whenIdentified authorOpt
}
{answerBlock}

Your answer