Commit 5d97b618 by Riku Silvola

comment

2 parents e72f6cfb 03adad00
Showing with 325 additions and 95 deletions
......@@ -9,6 +9,7 @@ import java.util.Map.Entry;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.EJBException;
import javax.ejb.Stateless;
import org.slf4j.Logger;
......@@ -128,4 +129,15 @@ public class AccountEventBean implements AccountEventBeanLocal {
}
@Override
public AccountEvent markDelivered(AccountEvent e, Calendar c) {
accountfacade.reload(e);
if (e.getDelivered() != null)
{
throw new EJBException("AccountEvent " + e + " already marked paid!");
}
e.setDelivered(c);
return e;
}
}
......@@ -46,6 +46,7 @@ import fi.insomnia.bortal.model.User;
UserPermission.S_INVITE_USERS,
UserPermission.S_READ_ORGROLES,
UserPermission.S_WRITE_ORGROLES,
UserPermission.S_VITUTTAAKO,
MapPermission.S_VIEW,
MapPermission.S_MANAGE_MAPS,
......
......@@ -520,4 +520,10 @@ public class UserBean implements UserBeanLocal {
return eventUserFacade.searchEventUsers(searchQuery);
}
@Override
public void submitFeedback(String feedback) {
logger.warn("Received feedback {}", feedback);
// TODO
}
}
\ No newline at end of file
package fi.insomnia.bortal.facade;
import java.util.List;
import javax.ejb.EJB;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import fi.insomnia.bortal.beans.EventBeanLocal;
import fi.insomnia.bortal.model.EventUser;
import fi.insomnia.bortal.model.Feedback;
import fi.insomnia.bortal.model.Feedback_;
public class FeedbackFacade extends IntegerPkGenericFacade<Feedback> {
public FeedbackFacade() {
super(Feedback.class);
}
@EJB
EventBeanLocal eventBean;
public List<Feedback> findAll() {
CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Feedback> cq = cb.createQuery(Feedback.class);
Root<Feedback> root = cq.from(Feedback.class);
cq.where(cb.equal(root.get(Feedback_.event), eventBean.getCurrentEvent()));
cq.orderBy(cb.desc(root.get(Feedback_.id)));
return getEm().createQuery(cq).getResultList();
}
public List<Feedback> find(EventUser user) {
CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Feedback> cq = cb.createQuery(Feedback.class);
Root<Feedback> root = cq.from(Feedback.class);
cq.where(cb.equal(root.get(Feedback_.event), eventBean.getCurrentEvent()),
cb.equal(root.get(Feedback_.sender), user));
return getEm().createQuery(cq).getResultList();
}
}
package fi.insomnia.bortal.beans;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
......@@ -25,4 +26,6 @@ public interface AccountEventBeanLocal {
List<Role> getRolesFromAccountEvents(EventUser u);
AccountEvent markDelivered(AccountEvent e, Calendar c);
}
......@@ -81,4 +81,6 @@ public interface UserBeanLocal {
SearchResult<EventUser> getThisEventsUsers(UserSearchQuery searchQuery);
void submitFeedback(String feedback);
}
package fi.insomnia.bortal.model;
import java.io.Serializable;
import java.util.Calendar;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Feedback extends GenericEntity implements Serializable {
private static final long serialVersionUID = 1L;
@ManyToOne(optional = true)
@JoinColumn(nullable = true, name = "eventuser_id")
private EventUser sender;
@ManyToOne(optional = false)
@JoinColumn(nullable = false, name = "event_id")
private LanEvent event;
@Temporal(TemporalType.TIMESTAMP)
private Calendar timestamp;
@Lob
private String message;
public Feedback() {
super();
}
public EventUser getSender() {
return sender;
}
public void setSender(EventUser sender) {
this.sender = sender;
}
public Calendar getTimestamp() {
return timestamp;
}
public void setTimestamp(Calendar timestamp) {
this.timestamp = timestamp;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public LanEvent getEvent() {
return event;
}
public void setEvent(LanEvent event) {
this.event = event;
}
}
......@@ -126,6 +126,14 @@ public class FoodWave extends GenericEntity {
}
public boolean isFull() {
if(getMaximumFoods() <= 0) {
return false;
}
if(getReservedCount() >= getMaximumFoods()) {
return true;
}
return false;
}
......@@ -174,6 +182,12 @@ public class FoodWave extends GenericEntity {
}
public Integer getMaximumFoods() {
if(maximumFoods == null) {
return 0;
}
return maximumFoods;
}
......
......@@ -17,7 +17,7 @@ public enum UserPermission implements IAppPermission {
MANAGE_HTTP_SESSION, // ("Manage http sessions"),
INVITE_USERS, // ("Invite users"),
READ_ORGROLES, // ("View organization roles"),
WRITE_ORGROLES, // ("Modify organization roles"),
WRITE_ORGROLES, VITUTTAAKO, // ("Modify organization roles"),
;
public static final String S_VIEW_ALL = "USER/VIEW_ALL";
......@@ -35,6 +35,7 @@ public enum UserPermission implements IAppPermission {
public static final String S_INVITE_USERS = "USER/INVITE_USERS";
public static final String S_READ_ORGROLES = "USER/READ_ORGROLES";
public static final String S_WRITE_ORGROLES = "USER/WRITE_ORGROLES";
public static final String S_VITUTTAAKO = "USER/VITUTTAAKO";
private final String fullName;
private final String key;
......
......@@ -9,9 +9,9 @@
</session-config>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<!-- <param-value>Production</param-value> -->
<param-value>Production</param-value>
<param-value>Development</param-value>
<!-- <param-value>Development</param-value>-->
</context-param>
<context-param>
......
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
xmlns:users="http://java.sun.com/jsf/composite/tools/user" xmlns:p="http://primefaces.org/ui" xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:body>
<ui:composition template="/layout/#{sessionHandler.layout}/template.xhtml">
<ui:param name="thispage" value="page.eventorg.create" />
<f:metadata>
<f:event type="preRenderView" listener="#{feedbackView.initFeedback}" />
</f:metadata>
<ui:define name="content">
<h1>Avaudu tähän:</h1>
<h:form id="feedback">
<h:inputTextarea value="#{feedbackView.feedback}" cols="50" rows="10"/><br/>
<p:commandButton ajax="false" value="#{i18n['feedback.submit']}" action="#{feedbackView.submitFeedback}" />
</h:form>
</ui:define>
</ui:composition>
</h:body>
</html>
\ No newline at end of file
......@@ -14,8 +14,7 @@
<ui:define name="content">
<h:form>
<p:dataTable styleClass="bordertable" value="#{foodWaveView.foodWaves}" sortBy="#{foodwave.time}" sortOrder="ascending" var="foodwave" editable="true" rowStyleClass="#{not foodwave.isOrderable() ? foodwave.isDelivered() ? 'hidden' : 'closed' : null}">
<!-- grey rows if old, hidden if delivrd -->
<p:ajax event="rowEdit" listener="#{foodWaveView.onEditFoodWave}" />
<p:ajax event="rowEdit" listener="#{foodWaveView.onEditFoodWave}" />
<f:facet name="header">
<h:outputLabel value="#{i18n['foodWave.activeFoodWaves']}" />
</f:facet>
......
......@@ -45,12 +45,17 @@
<h:outputText rendered="#{sessionHandler.loggedIn}" value="#{i18n['template.loggedInAs']} #{sessionHandler.currentUser.nick}" />
<div>
<tools:loginLogout />
</div>
</div>
<ui:fragment rendered="#{menuView.getMenu(0).size() > 1}">
<ul id="topmenu">
<li jsfc="ui:repeat" var="menuitem" value="#{menuView.getMenu(0)}"><h:link outcome="#{menuitem.outcome}" value="#{i18n[menuitem.navigation.key]}"
styleClass="#{menuitem.selected?'active':''}" /></li>
<ui:fragment rendered="#{feedbackView.canFeedback}" >
<li><h:link outcome="/feedback/index" value="#{i18n['feedback.canFeedback']}" /></li>
</ui:fragment>
<li></li>
</ul>
</ui:fragment>
</div>
......
......@@ -15,18 +15,20 @@
<!-- <h:outputScript target="head" library="script" name="shopscript.js" /> -->
<h:outputScript library="primefaces" name="jquery/jquery.js" />
<p:dataTable columnClasses="nowrap,numalign,numalign,nowrap,numalign" styleClass="bordertable" value="#{foodWaveView.foodWaves}" var="foodwave">
<p:column>
<p:dataTable columnClasses="nowrap,numalign,numalign,nowrap,numalign" styleClass="bordertable" value="#{foodWaveView.foodWaves}" var="foodwave" sortBy="#{foodwave.time.time}" rowStyleClass="#{foodwave.isFull() ? 'closed' : null}">
<p:column sortBy="#{i18n['foodWave.name']}">
<f:facet name="header">
<h:outputLabel id="name" value="${i18n['foodWave.name']}" />
<h:outputLabel id="name" value="#{i18n['foodWave.name']}" />
</f:facet>
<h:link outcome="#{cc.attrs.outcome}" value="#{foodwave.name}">
<h:link rendered="#{not foodwave.isFull()}" outcome="#{cc.attrs.outcome}" value="#{foodwave.name}">
<f:param name="foodwaveid" value="#{foodwave.id}" />
<f:param name="userid" value="#{userView.user.user.id}" />
</h:link>
<h:outputText rendered="#{foodwave.isFull()}" value="#{foodwave.name}" />
</p:column>
<p:column>
<p:column sortBy="#{foodwave.template.name}">
<f:facet name="header">
<h:outputText value="Menu" />
</f:facet>
......@@ -34,13 +36,22 @@
<f:param name="foodwaveid" value="#{foodwave.id}" />
</h:outputText>
</p:column>
<p:column>
<p:column sortBy="#{foodwave.template.description}">
<f:facet name="header">
<h:outputText value="${i18n['foodWave.description']}" />
</f:facet>
<h:outputText id="description" value="#{foodwave.template.description}" />
</p:column>
<p:column>
<p:column sortBy="#{foodwave.reservedCount}">
<f:facet name="header">
<h:outputText value="${i18n['foodWave.totalReserved']}" />
</f:facet>
<h:outputText value="#{foodwave.reservedCount}" /> / <h:outputText value="#{foodwave.maximumFoods}" />
</p:column>
<p:column sortBy="#{foodwave.time.time}">
<f:facet name="header">
<h:outputText value="${i18n['foodWave.time']}" />
</f:facet>
......
......@@ -38,7 +38,7 @@ td span span div.rating-cancel {
}
#login {
width: 400px;
width: 385px;
height: 23px;
float: right;
margin-top: 10px;
......
#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net)
#Bill number
# Validationmessages
actionlog.create.header = Luo uusi ActionMessage
actionlog.create.message = Viesti
actionlog.create.role = Kohderooli
actionlog.create.submitbutton = L\u00E4het\u00E4
actionlog.create.taskradio = Teht\u00E4v\u00E4
actionlog.crew = Crew
actionlog.message = Tapahtuma
actionlog.messagelist.description = Voit seurata sek\u00E4 luoda uusia ActionMessageja t\u00E4ss\u00E4 n\u00E4kym\u00E4ss\u00E4.
actionlog.messagelist.header = Viestilista
actionlog.state = Tila
actionlog.task = Teht\u00E4v\u00E4
actionlog.tasklist.header = Teht\u00E4v\u00E4lista
actionlog.time = Aika
actionlog.user = Tekij\u00E4
bill.billMarkedPaidMail.message = Laskusi numero {0} on merkitty maksetuksi. Voit nyt siirty\u00E4 lippukauppaan varamaamaan haluamasi paikat. \nTervetuloa tapahtumaan!\n\nTerveisin,\nInsomnia lippupalvelu\nwww.insomnia.fi
bill.billMarkedPaidMail.subject = [INSOMNIA] Lasku merkitty maksetuksi
error.contact = Jos t\u00E4m\u00E4 toistuu, ota seuraava koodi talteen ja ota yhteys Infoon:
error.error = Olet kohdannut virheen.
eventorg.create = Luo
eventorg.edit = Muokkaa
global.cancel = Peruuta
global.copyright = Codecrew Ry
global.infomail = info@insomnia.fi
global.notAuthorizedExecute = Sinulla ei ole riitt\u00E4v\u00E4sti oikeuksia suorittaa t\u00E4t\u00E4 toimenpidett\u00E4!
global.notauthorized = Sinulla ei ole riitt\u00E4vi\u00E4 oikeuksia t\u00E4lle sivulle.
global.save = Tallenna
global.webpage = http://www.insomnia.fi
httpsession.creationTime = Luotu
login.login = Kirjaudu sis\u00E4\u00E4n
login.logout = Kirjaudu ulos
login.logoutmessage = Olet kirjautunut ulos j\u00E4rjestelm\u00E4st\u00E4.
login.password = Salasana
login.submit = Kirjaudu sis\u00E4\u00E4n
login.username = K\u00E4ytt\u00E4j\u00E4tunnus
loginerror.header = Kirjautuminen ep\u00E4onnistui
loginerror.message = K\u00E4ytt\u00E4j\u00E4tunnus tai salasana ei ollut oikein.
loginerror.resetpassword = Salasana unohtunut?
passwordChanged.body = Voit nyt kirjautua k\u00E4ytt\u00E4j\u00E4tunnuksella ja uudella salasanalla sis\u00E4\u00E4n j\u00E4rjestelm\u00E4\u00E4n.
passwordChanged.header = Salasana vaihdettu onnistuneesti
passwordReset.errorChanging = Odotamaton virhe. Ota yhteytt\u00E4 yll\u00E4pitoon.
passwordReset.hashNotFound = Salasanan vaihto on vanhentunut. Jos haluat vaihtaa salasanan l\u00E4het\u00E4 vaihtopyynt\u00F6 uudelleen.
permissiondenied.alreadyLoggedIn = Sinulla ei ole riitt\u00E4v\u00E4sti oikeuksia!
permissiondenied.header = P\u00E4\u00E4sy kielletty
permissiondenied.notLoggedIn = Sinulla ei ole riitt\u00E4v\u00E4sti oikeuksia t\u00E4lle sivulle.
resetMail.body = Voit vaihtaa unohtuneen salasanan sy\u00F6tt\u00E4m\u00E4ll\u00E4 k\u00E4ytt\u00E4j\u00E4tunnuksesi allaolevaan kentt\u00E4\u00E4n. Tunnukseen liitettyyn s\u00E4hk\u00F6postiosoitteeseen l\u00E4hetet\u00E4\u00E4n kertak\u00E4ytt\u00F6inen osoite jossa voit vaihtaa sy\u00F6tt\u00E4m\u00E4si k\u00E4ytt\u00E4j\u00E4tunnuksen salasanan.
resetMail.header = Salasana unohtunut?
resetMail.send = L\u00E4het\u00E4 s\u00E4hk\u00F6posti
resetmailSent.body = Antamasi k\u00E4ytt\u00E4j\u00E4tunnuksen s\u00E4hk\u00F6postiosoitteeseen on l\u00E4hetetty osoite jossa voit vaihtaa tunnuksen salasanan.
resetmailSent.header = S\u00E4hk\u00F6posti l\u00E4hetetty
#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net)
#Bill number
# Validationmessages
actionlog.create.header = Luo uusi ActionMessage
actionlog.create.message = Viesti
actionlog.create.role = Kohderooli
actionlog.create.submitbutton = L\u00E4het\u00E4
actionlog.create.taskradio = Teht\u00E4v\u00E4
actionlog.crew = Crew
actionlog.message = Tapahtuma
actionlog.messagelist.description = Voit seurata sek\u00E4 luoda uusia ActionMessageja t\u00E4ss\u00E4 n\u00E4kym\u00E4ss\u00E4.
actionlog.messagelist.header = Viestilista
actionlog.state = Tila
actionlog.task = Teht\u00E4v\u00E4
actionlog.tasklist.header = Teht\u00E4v\u00E4lista
actionlog.time = Aika
actionlog.user = Tekij\u00E4
bill.billMarkedPaidMail.message = Laskusi numero {0} on merkitty maksetuksi. Voit nyt siirty\u00E4 lippukauppaan varamaamaan haluamasi paikat. \nTervetuloa tapahtumaan!\n\nTerveisin,\nInsomnia lippupalvelu\nwww.insomnia.fi
bill.billMarkedPaidMail.subject = [INSOMNIA] Lasku merkitty maksetuksi
error.contact = Jos t\u00E4m\u00E4 toistuu, ota seuraava koodi talteen ja ota yhteys Infoon:
error.error = Olet kohdannut virheen.
eventorg.create = Luo
eventorg.edit = Muokkaa
global.cancel = Peruuta
global.copyright = Codecrew Ry
global.infomail = info@insomnia.fi
global.notAuthorizedExecute = Sinulla ei ole riitt\u00E4v\u00E4sti oikeuksia suorittaa t\u00E4t\u00E4 toimenpidett\u00E4!
global.notauthorized = Sinulla ei ole riitt\u00E4vi\u00E4 oikeuksia t\u00E4lle sivulle.
global.save = Tallenna
global.webpage = http://www.insomnia.fi
httpsession.creationTime = Luotu
login.login = Kirjaudu sis\u00E4\u00E4n
login.logout = Kirjaudu ulos
login.logoutmessage = Olet kirjautunut ulos j\u00E4rjestelm\u00E4st\u00E4.
login.password = Salasana
login.submit = Kirjaudu sis\u00E4\u00E4n
login.username = K\u00E4ytt\u00E4j\u00E4tunnus
loginerror.header = Kirjautuminen ep\u00E4onnistui
loginerror.message = K\u00E4ytt\u00E4j\u00E4tunnus tai salasana ei ollut oikein.
loginerror.resetpassword = Salasana unohtunut?
passwordChanged.body = Voit nyt kirjautua k\u00E4ytt\u00E4j\u00E4tunnuksella ja uudella salasanalla sis\u00E4\u00E4n j\u00E4rjestelm\u00E4\u00E4n.
passwordChanged.header = Salasana vaihdettu onnistuneesti
passwordReset.errorChanging = Odotamaton virhe. Ota yhteytt\u00E4 yll\u00E4pitoon.
passwordReset.hashNotFound = Salasanan vaihto on vanhentunut. Jos haluat vaihtaa salasanan l\u00E4het\u00E4 vaihtopyynt\u00F6 uudelleen.
permissiondenied.alreadyLoggedIn = Sinulla ei ole riitt\u00E4v\u00E4sti oikeuksia!
permissiondenied.header = P\u00E4\u00E4sy kielletty
permissiondenied.notLoggedIn = Sinulla ei ole riitt\u00E4v\u00E4sti oikeuksia t\u00E4lle sivulle.
resetMail.body = Voit vaihtaa unohtuneen salasanan sy\u00F6tt\u00E4m\u00E4ll\u00E4 k\u00E4ytt\u00E4j\u00E4tunnuksesi allaolevaan kentt\u00E4\u00E4n. Tunnukseen liitettyyn s\u00E4hk\u00F6postiosoitteeseen l\u00E4hetet\u00E4\u00E4n kertak\u00E4ytt\u00F6inen osoite jossa voit vaihtaa sy\u00F6tt\u00E4m\u00E4si k\u00E4ytt\u00E4j\u00E4tunnuksen salasanan.
resetMail.header = Salasana unohtunut?
resetMail.send = L\u00E4het\u00E4 s\u00E4hk\u00F6posti
resetmailSent.body = Antamasi k\u00E4ytt\u00E4j\u00E4tunnuksen s\u00E4hk\u00F6postiosoitteeseen on l\u00E4hetetty osoite jossa voit vaihtaa tunnuksen salasanan.
resetmailSent.header = S\u00E4hk\u00F6posti l\u00E4hetetty
......@@ -67,7 +67,7 @@ public class FoodWaveView extends GenericCDIView {
private Integer foodWaveId;
private ListDataModel<AccountEvent> accountEventLines;
private ListDataModel<FoodWave> foodWaves;
private List<FoodWave> foodWaves;
private static final Logger logger = LoggerFactory.getLogger(FoodWaveView.class);
public List<Product> getProducts() {
......@@ -79,11 +79,6 @@ public class FoodWaveView extends GenericCDIView {
public void onEdit() {
this.foodWaveBean.saveTemplate(template);
}
public void onEditFoodWave() {
FoodWave fw = foodWaves.getRowData();
this.foodWaveBean.merge(fw);
}
public void initTemplateList() {
super.requirePermissions(ShopPermission.SHOP_FOODWAVE);
......@@ -92,11 +87,7 @@ public class FoodWaveView extends GenericCDIView {
public void initFoodwaveManagerList() {
if (super.requirePermissions(ShopPermission.MANAGE_FOODWAVES))
{
if(foodWaves == null) {
super.beginConversation();
foodWaves = new ListDataModel<FoodWave>(foodWaveBean.getEventFoodWaves());
}
foodWaves = foodWaveBean.getEventFoodWaves();
}
}
......@@ -119,9 +110,9 @@ public class FoodWaveView extends GenericCDIView {
if (templateId != null)
{
template = foodWaveBean.findTemplate(templateId);
foodWaves = new ListDataModel<FoodWave>(template.getFoodwaves());
foodWaves = template.getFoodwaves();
} else {
foodWaves = new ListDataModel<FoodWave>(foodWaveBean.getOpenFoodWaves());
foodWaves = foodWaveBean.getOpenFoodWaves();
}
super.beginConversation();
}
......@@ -244,8 +235,7 @@ public class FoodWaveView extends GenericCDIView {
public String deliverAccountEvent() {
if (getAccountEventLines().isRowAvailable()) {
AccountEvent e = getAccountEventLines().getRowData();
e.setDelivered(Calendar.getInstance());
e = accountEventBean.merge(e);
e = accountEventBean.markDelivered(e, Calendar.getInstance());
foodWaveId = selectedFoodWave.getId();
selectedFoodWave = null;
......@@ -344,11 +334,11 @@ public class FoodWaveView extends GenericCDIView {
this.billLines = billLines;
}
public void setFoodWaves(ListDataModel<FoodWave> foodWaves) {
public void setFoodWaves(List<FoodWave> foodWaves) {
this.foodWaves = foodWaves;
}
public ListDataModel<FoodWave> getFoodWaves() {
public List<FoodWave> getFoodWaves() {
return foodWaves;
}
......
package fi.insomnia.bortal.web.helper;
import javax.ejb.EJB;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Named;
import fi.insomnia.bortal.beans.PermissionBeanLocal;
import fi.insomnia.bortal.beans.UserBeanLocal;
import fi.insomnia.bortal.enums.apps.UserPermission;
import fi.insomnia.bortal.web.cdiview.GenericCDIView;
@Named
@ConversationScoped
public class FeedbackView extends GenericCDIView {
private static final long serialVersionUID = -6231624059763086722L;
@EJB
private PermissionBeanLocal permissionBean;
private String feedback;
public void initFeedback() {
super.beginConversation();
}
@EJB
private UserBeanLocal userbean;
public boolean isCanFeedback()
{
return permissionBean.hasPermission(UserPermission.VITUTTAAKO);
}
public String submitFeedback()
{
userbean.submitFeedback(getFeedback());
feedback = "";
super.addFaceMessage("feedback.thanks");
return null;
}
public String getFeedback() {
return feedback;
}
public void setFeedback(String feedback) {
this.feedback = feedback;
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!