* BackOfficeController.cs: Impléménte un accès à l'API back office pour l'envoi d'une notification aux mobiles des cercles séléctionné de l'utilisateur * NotifyEvent.aspx: Implemente un formulaire d'envoi d'une notification mobile * NotifyEventResponse.aspx: initie une page de resultat à la notification mobile * NpgsqlContentProvider.cs: reccupère la photo activité en base de données * BlogsController.cs: En attendant mieux, les billets de blog sont noté par leur auteur, ou un admin. * CalendarController.cs: Les fontions de notification GCM et de declaration d'enregistrement GCM partent dans le controlleur back office. * activity.sql: ajoute la photo activité dans la definition du modèle de données Npgsql * style.css: La fonte de caractère "Open Sans" (Google fonts) pour le thème "clear" * style.css: La fonte de caractère "Open Sans" (Google fonts) pour le thème "dark" aussi * style.css: pas de fonte de caractère dans la feuille de style basique. * BackOfficeController.cs: Impléménte un accès web back office pour l'envoi d'une notification aux mobiles des cercles séléctionné de l'utilisateur * BlogsController.cs: Fixe la page de garde du blogspot en cas d'absence de billet à publier publiquement. * FrontOfficeController.cs: la fontion booking est maintenant censée conerner uniquement une activité, dont le code APE est donné en dernière partie de la route de l'Url, ou en argument `id`. * HomeController.cs: La page d'accueil présente maintenant les activités mises en oeuvre par au moins un utilisateur enregistré, validé et non vérouillé. Le titre de l'activité est un lien vers la methode de reservation simple. * GoogleHelpers.cs: recoit l'implementation de la methode de notification mobile * SimpleJsonPostMethod.cs: Fixe l'envoi en UTF-8 * App.master: fonte de caractère pour le lien vers "Vos billets" * parallax.js: fixe la différence de traitement entre l'horizontale et la verticale * Profile.aspx: ajoute le sommaire de la validation * Index.aspx: initie un index pour le back office * Activities.aspx: nettoye la console de log javascript * Booking.aspx: ajoute le champ caché au formulaire de resa * Index.aspx: affiche les services fournis sur le site, par des utilisateur enregistrés validés non bloqué. * Web.config: rends l'id Google d'enregistrement GCM accessible au utilisateurs anonymes, qui voudraient commander ou interagir avec l'application, sans avoir à créer de compte sur le site. * Web.csproj: ajoute le contrôleur du back office, et le formulaire de notification mobile * EventPub.cs: * BaseEvent.cs: internationalise l'évennement (la base d'une notification) * YaEvent.cs: les champs fournisseur d'une notification ne sont pas requis. On peut en avoir de la part du site lui même, qui ne soient pas à l'origine d'un evennement tiers du workflow. Dans tous les autres cas, ils devraient être renseignés ... à suivre. * Circle.cs: * CircleBase.cs: ajoute une methode de construition des destinataires à partir d'un tableau d'identifiants de cercles. * Activity.cs: implémente la photo activité dans le modèle * SimpleBookingQuery.cs: refabriqué * MessageWithPayLoad.cs: accuse aussi le champ `to` en cas d'absence de destinataire. * LocalizedText.resx: * LocalizedText.fr.resx: * LocalizedText.Designer.cs: * LocalizedText.fr.Designer.cs: internationalisation
286 lines
8.3 KiB
C#
286 lines
8.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Web;
|
|
using System.Web.Security;
|
|
using System.Web.Http;
|
|
using Npgsql.Web.Blog;
|
|
using Yavsc.Model.Blogs;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
using System.Diagnostics;
|
|
using Yavsc.Formatters;
|
|
using Yavsc.Model;
|
|
|
|
namespace Yavsc.ApiControllers
|
|
{
|
|
/// <summary>
|
|
/// Blogs API controller.
|
|
/// </summary>
|
|
public class BlogsController : YavscController
|
|
{
|
|
/// <summary>
|
|
/// Tag the specified model.
|
|
/// </summary>
|
|
/// <param name="model">Model.</param>
|
|
[Authorize,
|
|
AcceptVerbs ("POST")]
|
|
public void Tag (PostTag model) {
|
|
if (ModelState.IsValid) {
|
|
BlogManager.GetForEditing (model.PostId);
|
|
BlogManager.Tag (model.PostId, model.Tag);
|
|
}
|
|
}
|
|
static string [] officalTags = new string[] { "Artistes", "Accueil", "Événements", "Mentions légales", "Admin", "Web" } ;
|
|
/// <summary>
|
|
/// Tags the specified pattern.
|
|
/// </summary>
|
|
/// <param name="pattern">Pattern.</param>
|
|
[ValidateAjaxAttribute]
|
|
public IEnumerable<string> Tags(string pattern)
|
|
{
|
|
return officalTags;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Untag the specified model.
|
|
/// </summary>
|
|
/// <param name="model">Model.</param>
|
|
[Authorize,
|
|
AcceptVerbs ("POST")]
|
|
public void Untag (PostTag model) {
|
|
if (ModelState.IsValid) {
|
|
BlogManager.GetForEditing (model.PostId);
|
|
BlogManager.Untag (model.PostId, model.Tag);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the post.
|
|
/// </summary>
|
|
/// <param name="user">User.</param>
|
|
/// <param name="title">Title.</param>
|
|
[Authorize, ValidateAjaxAttribute, HttpPost]
|
|
public void RemoveTitle(string user, string title) {
|
|
if (Membership.GetUser ().UserName != user)
|
|
if (!Roles.IsUserInRole("Admin"))
|
|
throw new AuthorizationDenied (user);
|
|
BlogManager.RemoveTitle (user, title);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the tag.
|
|
/// </summary>
|
|
/// <param name="tagid">Tagid.</param>
|
|
[Authorize, ValidateAjaxAttribute, HttpPost]
|
|
public void RemoveTag([FromBody] long tagid) {
|
|
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The allowed media types.
|
|
/// </summary>
|
|
protected string[] allowedMediaTypes = {
|
|
"text/plain",
|
|
"text/x-tex",
|
|
"text/html",
|
|
"image/png",
|
|
"image/gif",
|
|
"image/jpeg",
|
|
"image/x-xcf",
|
|
"application/pdf",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
};
|
|
|
|
/// <summary>
|
|
/// Posts the file.
|
|
/// </summary>
|
|
/// <returns>The file.</returns>
|
|
[Authorize, HttpPost]
|
|
public async Task<HttpResponseMessage> PostFile(long id) {
|
|
if (!(Request.Content.Headers.ContentType.MediaType=="multipart/form-data"))
|
|
throw new HttpRequestException ("not a multipart/form-data request");
|
|
BlogEntry be = BlogManager.GetPost (id);
|
|
if (be.Author != Membership.GetUser ().UserName)
|
|
throw new AuthorizationDenied ("b"+id);
|
|
string root = HttpContext.Current.Server.MapPath("~/bfiles/"+id);
|
|
DirectoryInfo di = new DirectoryInfo (root);
|
|
if (!di.Exists) di.Create ();
|
|
|
|
var provider = new MultipartFormDataStreamProvider(root);
|
|
try
|
|
{
|
|
// Read the form data.
|
|
await Request.Content.ReadAsMultipartAsync(provider) ;
|
|
var invalidChars = Path.GetInvalidFileNameChars();
|
|
foreach (string fkey in provider.BodyPartFileNames.Keys)
|
|
{
|
|
string filename = provider.BodyPartFileNames[fkey];
|
|
Trace.WriteLine(filename);
|
|
|
|
string nicename = HttpUtility.UrlDecode(fkey) ;
|
|
if (fkey.StartsWith("\"") && fkey.EndsWith("\"") && fkey.Length > 2)
|
|
nicename = fkey.Substring(1,fkey.Length-2);
|
|
nicename = new string (nicename.Where( x=> !invalidChars.Contains(x)).ToArray());
|
|
nicename = nicename.Replace(' ','_');
|
|
var dest = Path.Combine(root,nicename);
|
|
var fi = new FileInfo(dest);
|
|
if (fi.Exists) fi.Delete();
|
|
File.Move(filename, fi.FullName);
|
|
}
|
|
|
|
return Request.CreateResponse(HttpStatusCode.OK);
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the specified blog entry.
|
|
/// </summary>
|
|
/// <param name="be">Bp.</param>
|
|
[Authorize, HttpPost]
|
|
public long Post (BlogEntry be)
|
|
{
|
|
if (be.Id == 0)
|
|
return BlogManager.Post (User.Identity.Name, be.Title,
|
|
be.Content, be.Visible,be.AllowedCircles );
|
|
else
|
|
BlogManager.UpdatePost (be);
|
|
return 0;
|
|
}
|
|
/// <summary>
|
|
/// Blog entry rating.
|
|
/// </summary>
|
|
public class BlogEntryRating {
|
|
/// <summary>
|
|
/// Gets or sets the post identifier.
|
|
/// </summary>
|
|
/// <value>The post identifier.</value>
|
|
public long Id { get; set; }
|
|
/// <summary>
|
|
/// Gets or sets the rate.
|
|
/// </summary>
|
|
/// <value>The rate.</value>
|
|
public int Rate { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rate the specified model.
|
|
/// </summary>
|
|
/// <param name="model">Model.</param>
|
|
[Authorize(Roles="Moderator"), HttpPost, ValidateAjax]
|
|
public void Rate (BlogEntryRating model)
|
|
{
|
|
if (model.Rate < 0 || model.Rate > 100)
|
|
ModelState.AddModelError ("Rate", "0<=Rate<=100");
|
|
else {
|
|
BlogManager.GetForEditing (model.Id);
|
|
BlogManager.Rate (model.Id, model.Rate);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Searchs the file.
|
|
/// </summary>
|
|
/// <returns>The file.</returns>
|
|
/// <param name="id">Postid.</param>
|
|
/// <param name="terms">Terms.</param>
|
|
[HttpGet]
|
|
public async Task<HttpResponseMessage> SearchFile(long id, string terms) {
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the photo.
|
|
/// </summary>
|
|
/// <param name="id">Identifier.</param>
|
|
/// <param name="photo">Photo.</param>
|
|
[Authorize, HttpPost, ValidateAjaxAttribute]
|
|
public void SetPhoto(long id, [FromBody] string photo)
|
|
{
|
|
BlogManager.GetForEditing (id);
|
|
BlogManager.UpdatePostPhoto (id, photo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import the specified id.
|
|
/// </summary>
|
|
/// <param name="id">Identifier.</param>
|
|
[Authorize, HttpPost, ValidateAjaxAttribute]
|
|
public async Task<HttpResponseMessage> Import(long id) {
|
|
if (!(Request.Content.Headers.ContentType.MediaType=="multipart/form-data"))
|
|
throw new HttpRequestException ("not a multipart/form-data request");
|
|
BlogEntry be = BlogManager.GetPost (id);
|
|
if (be.Author != Membership.GetUser ().UserName)
|
|
throw new AuthorizationDenied ("post: "+id);
|
|
string root = HttpContext.Current.Server.MapPath("~/bfiles/"+id);
|
|
DirectoryInfo di = new DirectoryInfo (root);
|
|
if (!di.Exists) di.Create ();
|
|
var provider = new MultipartFormDataStreamProvider(root);
|
|
try
|
|
{
|
|
// Read the form data.
|
|
//IEnumerable<HttpContent> data =
|
|
await Request.Content.ReadAsMultipartAsync(provider) ;
|
|
|
|
var invalidChars = Path.GetInvalidFileNameChars();
|
|
List<string> bodies = new List<string>();
|
|
|
|
foreach (string fkey in provider.BodyPartFileNames.Keys)
|
|
{
|
|
string filename = provider.BodyPartFileNames[fkey];
|
|
|
|
string nicename=fkey;
|
|
var filtered = new string (nicename.Where( x=> !invalidChars.Contains(x)).ToArray());
|
|
|
|
FileInfo fi = new FileInfo(filtered);
|
|
FileInfo fo = new FileInfo(filtered+".md");
|
|
FileInfo fp = new FileInfo (Path.Combine(root,filename));
|
|
if (fi.Exists) fi.Delete();
|
|
fp.MoveTo(fi.FullName);
|
|
// TODO Get the mime type
|
|
using (Process p = new Process ()) {
|
|
p.StartInfo.WorkingDirectory = root;
|
|
p.StartInfo = new ProcessStartInfo ();
|
|
p.StartInfo.UseShellExecute = false;
|
|
p.StartInfo.FileName = "/usr/bin/pandoc";
|
|
p.StartInfo.Arguments =
|
|
string.Format (" -o '{0}' -t markdown '{1}'",
|
|
fo.FullName,
|
|
fi.FullName);
|
|
p.StartInfo.RedirectStandardError = true;
|
|
p.StartInfo.RedirectStandardOutput = true;
|
|
p.Start ();
|
|
p.WaitForExit ();
|
|
if (p.ExitCode != 0) {
|
|
return Request.CreateResponse (HttpStatusCode.InternalServerError,
|
|
"# Import failed with exit code: " + p.ExitCode + "---\n"
|
|
+ LocalizedText.ImportException + "---\n"
|
|
+ p.StandardError.ReadToEnd() + "---\n"
|
|
+ p.StandardOutput.ReadToEnd()
|
|
);
|
|
}
|
|
}
|
|
bodies.Add(fo.OpenText().ReadToEnd());
|
|
|
|
|
|
fi.Delete();
|
|
fo.Delete();
|
|
}
|
|
|
|
return Request.CreateResponse(HttpStatusCode.OK,string.Join("\n---\n",bodies),new SimpleFormatter("text/plain"));
|
|
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|