package in.partake.controller.api.event;
import in.partake.app.PartakeApp;
import in.partake.base.DateTime;
import in.partake.base.PartakeException;
import in.partake.base.TimeUtil;
import in.partake.base.Util;
import in.partake.controller.api.AbstractPartakeAPI;
import in.partake.controller.base.permission.EventEditPermission;
import in.partake.model.IPartakeDAOs;
import in.partake.model.UserEx;
import in.partake.model.access.Transaction;
import in.partake.model.dao.DAOException;
import in.partake.model.dao.PartakeConnection;
import in.partake.model.daofacade.EventDAOFacade;
import in.partake.model.daofacade.UserDAOFacade;
import in.partake.model.dto.Event;
import in.partake.model.dto.EventTicket;
import in.partake.model.dto.UserImage;
import in.partake.model.dto.auxiliary.EventCategory;
import in.partake.resource.UserErrorCode;
import in.partake.service.IEventSearchService;
import in.partake.view.util.Helper;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.ObjectNode;
import play.mvc.Result;
public class ModifyAPI extends AbstractPartakeAPI {
public static Result post() throws DAOException, PartakeException {
return new ModifyAPI().execute();
}
@Override
protected Result doExecute() throws DAOException, PartakeException {
UserEx user = ensureLogin();
ensureValidSessionToken();
String eventId = getValidEventIdParameter();
Map<String, String[]> params = getFormParameters();
ModifyTransaction transaction = new ModifyTransaction(user, eventId, params);
transaction.execute();
Event event = transaction.getEvent();
List<EventTicket> tickets = transaction.getEventTickets();
// If the event is already published, We update event search index.
IEventSearchService searchService = PartakeApp.getEventSearchService();
if (!event.isSearchable())
searchService.remove(eventId);
else if (searchService.hasIndexed(eventId))
searchService.update(event, tickets);
else
searchService.create(event, tickets);
ObjectNode obj = new ObjectNode(JsonNodeFactory.instance);
obj.putAll(transaction.getJSONObject());
return renderOK(obj);
}
}
class ModifyTransaction extends Transaction<Void> {
private UserEx user;
private String eventId;
private Map<String, String[]> params;
private Event event;
private List<EventTicket> tickets;
private ObjectNode json;
public ModifyTransaction(UserEx user, String eventId, Map<String, String[]> params) {
this.user = user;
this.eventId = eventId;
this.params = params;
// When event information is changed, you can add the editted value here to return to the client.
// For example, you can filter script tag for event description, and so on.
this.json = new ObjectNode(JsonNodeFactory.instance);
}
@Override
protected Void doExecute(PartakeConnection con, IPartakeDAOs daos) throws DAOException, PartakeException {
event = daos.getEventAccess().find(con, eventId);
if (event == null)
throw new PartakeException(UserErrorCode.INVALID_EVENT_ID);
if (!EventEditPermission.check(event, user))
throw new PartakeException(UserErrorCode.FORBIDDEN_EVENT_EDIT);
event = new Event(event);
updateEvent(con, daos);
event.setModifiedAt(TimeUtil.getCurrentDateTime());
EventDAOFacade.modify(con, daos, event);
tickets = daos.getEventTicketAccess().findEventTicketsByEventId(con, eventId);
return null;
}
private void updateEvent(PartakeConnection con, IPartakeDAOs daos) throws PartakeException, DAOException {
if (params.containsKey("title")) {
String title = getString("title");
if (StringUtils.isBlank(title) || title.length() > 100)
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "title", "タイトルは 100 文字以下で必ず入力してください。");
else
event.setTitle(title);
}
if (params.containsKey("summary")) {
String summary = getString("summary");
if (summary != null && summary.length() > 100)
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "summary", "概要は 100 文字以下で入力してください。");
else
event.setSummary(summary);
}
if (params.containsKey("category")) {
String category = getString("category");
if (category == null || !EventCategory.isValidCategoryName(category))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "category", "カテゴリーは正しいものを必ず入力してください。");
else
event.setCategory(category);
}
if (params.containsKey("beginDate")) {
DateTime beginDate = getDateTime("beginDate");
if (beginDate == null)
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "beginDate", "開始日時は必ず入力してください。");
Calendar beginCalendar = TimeUtil.calendar(beginDate.toDate());
if (beginCalendar.get(Calendar.YEAR) < 2000 || 2100 < beginCalendar.get(Calendar.YEAR))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "beginDate", "開始日時の範囲が不正です。");
else
event.setBeginDate(beginDate);
json.put("eventDuration", Helper.readableDuration(event.getBeginDate(), event.getEndDate()));
}
// TODO: What happend if beginDate is set after endDate is set and endDate <= beginDate. It should be an error.
if (params.containsKey("endDate")) {
String endDateStr = getString("endDate");
if (StringUtils.isBlank(endDateStr))
event.setEndDate(null);
else {
DateTime endDate = TimeUtil.parseForEvent(endDateStr);
if (endDate == null)
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "endDate", "終了日時が不正です。");
else {
Calendar endCalendar = TimeUtil.calendar(endDate.toDate());
if (endCalendar.get(Calendar.YEAR) < 2000 || 2100 < endCalendar.get(Calendar.YEAR))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "endDate", "終了日時の範囲が不正です。");
else if (event.getBeginDate() != null && endDate.isBefore(event.getBeginDate()))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "endDate", "終了日時が開始日時より前になっています。");
else
event.setEndDate(endDate);
}
}
json.put("eventDuration", Helper.readableDuration(event.getBeginDate(), event.getEndDate()));
}
if (params.containsKey("url")) {
String urlStr = getString("url");
if (StringUtils.isBlank(urlStr))
event.setUrl(urlStr);
else if (3000 < urlStr.length())
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "url", "URL が長すぎます。");
else if (!urlStr.startsWith("http://") && !urlStr.startsWith("https://"))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "url", "URL が不正です。");
else {
try {
new URL(urlStr); // Confirms URL is not malformed.
event.setUrl(urlStr);
} catch (MalformedURLException e) {
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "url", "URL が不正です。");
}
}
}
if (params.containsKey("place")) {
String place = getString("place");
if (place != null && 300 < place.length())
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "place", "場所が長すぎます");
else
event.setPlace(place);
}
if (params.containsKey("address")) {
String address = getString("address");
if (address != null && 300 < address.length())
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "address", "住所が長すぎます。");
else
event.setAddress(address);
}
if (params.containsKey("description")) {
String description = getString("description");
if (description != null && 1000000 < description.length())
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "description", "説明は 1000000 文字以下で入力してください。");
else
event.setDescription(description);
}
if (params.containsKey("hashTag")) {
String hashTag = getString("hashTag");
if (StringUtils.isBlank(hashTag))
event.setHashTag(null);
else if (100 < hashTag.length())
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "hashTag", "ハッシュタグは100文字以内で記述してください。");
else if (!Util.isValidHashtag(hashTag))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "hashTag", "ハッシュタグは # から始まる英数字や日本語が指定できます。記号は使えません。");
else
event.setHashTag(hashTag);
}
if (params.containsKey("passcode")) {
String passcode = getString("passcode");
if (StringUtils.isBlank(passcode))
event.setPasscode(null);
else if (20 < passcode.length())
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "passcode", "パスコードは20文字以下で記入してください。");
else
event.setPasscode(passcode);
}
if (params.containsKey("foreImageId")) {
String foreImageId = getString("foreImageId");
if (StringUtils.isBlank(foreImageId) || "null".equals(foreImageId))
event.setForeImageId(null);
else if (!Util.isUUID(foreImageId))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "foreImageId", "画像IDが不正です。");
else {
// Check foreImageId is owned by the owner.
UserImage image = daos.getImageAccess().find(con, foreImageId);
if (image == null)
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "foreImageId", "画像IDが不正です。");
if (!user.getId().equals(image.getUserId()))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "foreImageId", "あなたが所持していない画像の ID が指定されています。");
// OK.
event.setForeImageId(foreImageId);
}
}
if (params.containsKey("backImageId")) {
String backImageId = getString("backImageId");
if (StringUtils.isBlank(backImageId) || "null".equals(backImageId))
event.setBackImageId(null);
else if (!Util.isUUID(backImageId))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "backImageId", "画像IDが不正です。");
else {
// Check foreImageId is owned by the owner.
UserImage image = daos.getImageAccess().find(con, backImageId);
if (image == null)
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "backImageId", "画像IDが不正です。");
if (!user.getId().equals(image.getUserId()))
throw new PartakeException(UserErrorCode.INVALID_PARAMETERS, "backImageId", "あなたが所持していない画像の ID が指定されています。");
// OK.
event.setBackImageId(backImageId);
}
}
if (params.containsKey("relatedEventIds[]")) {
Set<String> visitedIds = new HashSet<String>();
String[] relatedEventIds = getStrings("relatedEventIds[]");
List<String> eventIds = new ArrayList<String>();
ArrayNode array = new ArrayNode(JsonNodeFactory.instance);
for (String relatedEventId : relatedEventIds) {
if (!Util.isUUID(relatedEventId))
continue;
if (eventId.equals(relatedEventId))
continue;
if (visitedIds.contains(relatedEventId))
continue;
visitedIds.add(relatedEventId);
Event relatedEvent = daos.getEventAccess().find(con, relatedEventId);
if (relatedEvent == null)
continue;
eventIds.add(relatedEventId);
ObjectNode obj = new ObjectNode(JsonNodeFactory.instance);
obj.put("id", relatedEvent.getId());
obj.put("title", relatedEvent.getTitle());
array.add(obj);
}
event.setRelatedEventIds(eventIds);
// OK. We want to return event.id and event.title.
json.put("relatedEvents", array);
}
if (params.containsKey("editorIds[]")) {
ArrayNode array = new ArrayNode(JsonNodeFactory.instance);
List<String> editorIds = new ArrayList<String>();
Set<String> visitedIds = new HashSet<String>();
for (String editorId : getStrings("editorIds[]")) {
// Skips invalid users here.
if (!Util.isUUID(editorId))
continue;
if (visitedIds.contains(editorId))
continue;
visitedIds.add(editorId);
UserEx editor = UserDAOFacade.getUserEx(con, daos, editorId);
if (editor == null)
continue;
// OK.
editorIds.add(editor.getId());
array.add(editor.toSafeJSON());
}
event.setEditorIds(editorIds);
json.put("editors", array);
}
}
private String getString(String key) {
Object obj = params.get(key);
if (obj instanceof String)
return (String) obj;
else if (obj instanceof String[] && ((String[]) obj).length > 0)
return ((String[]) obj)[0];
else
return null;
}
private String[] getStrings(String key) {
Object obj = params.get(key);
if (obj instanceof String)
return new String[] { (String) obj };
else if (obj instanceof String[])
return (String[]) obj;
else
return null;
}
private DateTime getDateTime(String key) {
String value = getString(key);
if (value == null)
return null;
DateTime date = TimeUtil.parseForEvent(value);
if (date != null)
return date;
return null;
}
public Event getEvent() {
return event;
}
public List<EventTicket> getEventTickets() {
return tickets;
}
public ObjectNode getJSONObject() {
return json;
}
}