/* * This work is licensed under the Creative Commons Attribution 2.5 License. * To view a copy of this license, visit http://creativecommons.org/licenses/by/2.5/ * or send a letter to Creative Commons, 543 Howard Street, 5th Floor, * San Francisco, California, 94105, USA. * * Original developer: David Betz * */ using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Data.Common; using System.Data.SqlClient; using System.Configuration; using System.Data; using MinimaDAL.DaoClasses; using MinimaDAL.HelperClasses; using MinimaDAL.EntityClasses; using MinimaDAL.CollectionClasses; using SD.LLBLGen.Pro.ORMSupportClasses; using Rss; using Minima.Service; using MinimaDAL.StoredProcedureCallerClasses; using General; using Minima.IO; namespace Minima { /// /// Class to simplify and centralize interaction with the database. /// This class is built to serve not only as a local fascade, but /// also as a service. /// This class was originally it's own facade as a static /// class, but the service class (MinimaService) always had to /// reflect the changes. So, the two were merged, thus forbidding /// the facade from being static. /// public class MinimaFacade : IMinimaService { #region Add-ons /// /// Pings Technorati - see www.technorati.com /// /// ID of the blog to ping public void PingTechnorati(Int32 blogId) { BlogEntity blog = new BlogEntity(); blog.FetchUsingPK(blogId); TechnoratiNotifier.Ping(blog.BlogTitle, new Uri(blog.BlogPrimaryUrl)); } #endregion #region Blogs /// /// Returns only the details of a blog /// /// ID of the blog /// A filled blog object; it does not contain entries public Blog GetBlogDetails(Int32 blogId) { BlogEntity blogEntity = new BlogEntity(); blogEntity.FetchUsingPK(blogId); return new Blog(blogId, blogEntity.BlogTitle, blogEntity.BlogDescription, blogEntity.BlogPrimaryUrl, blogEntity.BlogFeedUrl, blogEntity.BlogFeedTitle); } /// /// Returns a blog. /// /// ID of the blog /// Number of most recent entries to retrieve /// If true, the content of the blog entries are returned. Otherwise, it is not. You should only return the content if you absolutely need it. /// A filled blog object public Blog GetBlog(Int32 blogId, Int32 count, Boolean returnBlogEntryContent) { BlogEntryCollection be = new BlogEntryCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(new PredicateExpression(BlogEntryFields.BlogId == blogId)); SortExpression sort = new SortExpression(); sort.Add(BlogEntryFields.BlogEntryPostDateTime | SortOperator.Descending); be.GetMulti(filter, count, sort); // LLBLGen was just NOT sorting it the way I wanted. I'll fix this later. BlogEntryEntity[] bea = new BlogEntryEntity[be.Count]; Int16 i = 0; foreach (BlogEntryEntity bee in be) { bea[i] = be[i++]; } Array.Reverse(bea); BlogEntryCollection actual = new BlogEntryCollection(); foreach (BlogEntryEntity bee in bea) { actual.Add(bee); } Blog blog = this.GetBlogDetails(blogId); AssignBlogEntries(actual, blog, returnBlogEntryContent); return blog; } /// /// Returns a blog entry wrapped in a Blog object. Mainly only used by the website. /// /// Url mapping as would be seen in the URL address. /// A blog object filled with a single blog entry public Blog GetWrappedBlogEntryByUrlMapping(String urlMapping) { BlogEntryCollection be = new BlogEntryCollection(); RelationCollection r = new RelationCollection(); r.Add(BlogEntryEntity.Relations.BlogEntryUrlMappingEntityUsingBlogEntryId); PredicateExpression filter = new PredicateExpression(); filter.Add(BlogEntryUrlMappingFields.BlogEntryUrlMappingName == urlMapping.ToLower()); be.GetMulti(filter, 0, null, r); if (be.Count > 0) { Blog b = new Blog(); b.BlogEntries.Add(new BlogEntry(be[0].BlogEntryId, be[0].BlogEntryTitle, be[0].BlogEntryText, be[0].BlogEntryPostDateTime)); return b; } return null; } #endregion #region Blog Entry /// /// Creates a blog entry /// /// ID of the blog /// Set of authors /// Title of the blog entry /// Date and time of the blog entry, for "now" use a date before 1950 /// Content of the blog entry /// Set of labels, these will be created if they do not exist /// public Int32 CreateNewBlogEntry(Int32 blogId, Author[] authorSet, String title, String content, DateTime dateTime, Label[] labelSet) { if (authorSet == null || authorSet.Length < 1) { throw new ArgumentNullException("Author may not be null"); } else { foreach (Author author in authorSet) { if (String.IsNullOrEmpty(author.Email)) { throw new ArgumentNullException("Author e-mail may not be null"); } } } BlogEntryEntity be = new BlogEntryEntity(); be.BlogId = blogId; be.BlogEntryTitle = title; be.BlogEntryText = content; if (dateTime.Year >= 1950) { be.BlogEntryPostDateTime = dateTime; } be.BlogEntryModifyDateTime = DateTime.Now; be.Save(); BlogEntryUrlMappingEntity beum = new BlogEntryUrlMappingEntity(); beum.BlogEntryId = be.BlogEntryId; beum.BlogEntryUrlMappingName = CreateBlogEntryPostUrlMapping(title); beum.Save(); if (labelSet != null && labelSet.Length > 0) { foreach (Label label in labelSet) { LabelCollection c = new LabelCollection(); c.GetMulti(null); Int32 labelId = 0; String lower = label.Title.ToLower(); foreach (LabelEntity l in c) { if (l.LabelTitle.ToLower() == lower) { labelId = l.LabelId; break; } } if (labelId == 0) { LabelEntity l = new LabelEntity(); l.BlogId = blogId; l.LabelTitle = label.Title; l.Save(); labelId = l.LabelId; } LabelBlogEntryEntity lbe = new LabelBlogEntryEntity(); lbe.BlogEntryId = be.BlogEntryId; lbe.LabelId = labelId; lbe.Save(); } } if (authorSet != null && authorSet.Length > 0) { foreach (Author author in authorSet) { AuthorCollection c = new AuthorCollection(); c.GetMulti(null); Int32 authorId = 0; String lower = author.Email.ToLower(); foreach (AuthorEntity l in c) { if (l.AuthorEmail.ToLower() == lower) { authorId = l.AuthorId; break; } } if (authorId == 0) { if (String.IsNullOrEmpty(author.Name)) { throw new ArgumentNullException("Author does not exist. Therefore, the author name is required."); } AuthorEntity l = new AuthorEntity(); l.AuthorName = author.Name; l.AuthorEmail = author.Email; l.Save(); authorId = l.AuthorId; } BlogEntryAuthorEntity lbe = new BlogEntryAuthorEntity(); lbe.BlogEntryId = be.BlogEntryId; lbe.AuthorId = authorId; lbe.Save(); } } else { throw new ArgumentNullException("There must be at least one author for a blog entry"); } return be.BlogEntryId; } /// /// Deletes a blog entry. For debug purposes only. /// /// ID of the blog entry to delete public void DeleteBlogEntry(Int32 blogEntryId) { BlogEntryAuthorCollection authorLinks = new BlogEntryAuthorCollection(); BlogEntryUrlMappingCollection urlLinks = new BlogEntryUrlMappingCollection(); LabelBlogEntryCollection labelLinks = new LabelBlogEntryCollection(); PredicateExpression filter = new PredicateExpression(BlogEntryAuthorFields.BlogEntryId == blogEntryId); authorLinks.GetMulti(filter); if (authorLinks.Count > 0) { foreach (BlogEntryAuthorEntity t1 in authorLinks) { t1.Delete(); } } filter = new PredicateExpression(BlogEntryUrlMappingFields.BlogEntryId == blogEntryId); urlLinks.GetMulti(filter); if (urlLinks.Count > 0) { foreach (BlogEntryUrlMappingEntity t2 in urlLinks) { t2.Delete(); } } filter = new PredicateExpression(LabelBlogEntryFields.BlogEntryId == blogEntryId); labelLinks.GetMulti(filter); if (labelLinks.Count > 0) { foreach (LabelBlogEntryEntity t3 in labelLinks) { t3.Delete(); } } BlogEntryEntity b = new BlogEntryEntity(blogEntryId); b.Delete(); } /// /// Updates various properties of a blog entry /// /// ID of the blog entry to update /// New blog entry title, leave blank if unchanged /// New blog entry content, leave blank if unchanged public void UpdateBlogEntry(Int32 blogEntryId, String title, String content) { BlogEntryEntity be = new BlogEntryEntity(); be.FetchUsingPK(blogEntryId); if (!String.IsNullOrEmpty(content)) { be.BlogEntryText = content; } if (!String.IsNullOrEmpty(title)) { // Has this title's mapping been used by a different blog entry? String mapping = CreateBlogEntryPostUrlMapping(title); BlogEntryUrlMappingCollection beumc = new BlogEntryUrlMappingCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(BlogEntryUrlMappingFields.BlogEntryUrlMappingName == mapping); filter.Add(BlogEntryUrlMappingFields.BlogEntryId != blogEntryId); beumc.GetMulti(filter); // Probably something I'll be fixing in the future... if (beumc.Count > 1) { throw new ArgumentException("This title's mapping has already been used, please change the title"); } // Has this title's mapping been used by a different blog entry? beumc = new BlogEntryUrlMappingCollection(); filter = new PredicateExpression(); filter.Add(BlogEntryUrlMappingFields.BlogEntryUrlMappingName == mapping); filter.Add(BlogEntryUrlMappingFields.BlogEntryId == blogEntryId); beumc.GetMulti(filter); // If this is a new title completely, create a new mapping to allow access by // the old and the new links if (beumc.Count < 0) { BlogEntryUrlMappingEntity beum = new BlogEntryUrlMappingEntity(); beum.BlogEntryId = blogEntryId; beum.BlogEntryUrlMappingName = CreateBlogEntryPostUrlMapping(title); beum.Save(); } } be.BlogEntryTitle = title; be.Save(); } /// /// Returns a complete blog entry /// /// ID of the blog entry /// The blog entry object public BlogEntry GetBlogEntry(Int32 blogEntryId, Boolean returnContents) { BlogEntryEntity blogEntryEntity = new BlogEntryEntity(); blogEntryEntity.FetchUsingPK(blogEntryId); BlogEntry blogEntry = new BlogEntry(blogEntryId, blogEntryEntity.BlogEntryTitle, blogEntryEntity.BlogEntryText, blogEntryEntity.BlogEntryPostDateTime); AssignLabels(blogEntryEntity.LabelCollectionViaLabelBlogEntry, blogEntry.Labels); AssignAuthors(blogEntryEntity.AuthorCollectionViaBlogEntryAuthor, blogEntry.Authors); if (returnContents) { AssignComments(blogEntryEntity.Comment, blogEntry.Comments, true); } return blogEntry; } #endregion #region Feed /// /// Returns the feed URL of the blog /// /// /// A feed object public Feed GetFeedUrl(Int32 blogId) { BlogEntity blog = new BlogEntity(blogId); return new Feed(blog.BlogFeedTitle, blog.BlogFeedUrl); } /// /// Publish RSS feed to specified location /// /// ID of the blog /// Maximum number of blog entries to return /// XML of RSS Feed public String GetRssFeed(Int32 blogId, Int32 maxNumberOfEntries) { BlogEntryCollection collection = new BlogEntryCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(BlogEntryFields.BlogId == blogId); SortExpression sort = new SortExpression(); sort.Add(BlogEntryFields.BlogEntryPostDateTime | SortOperator.Descending); collection.GetMulti(filter, maxNumberOfEntries, sort); RssFeed feed = new RssFeed(); RssChannel channel = new RssChannel(); RssCategory category = new RssCategory(); if (collection.Count < 1) { throw new ArgumentException("There are no blog entries"); } channel.Title = new BlogEntity(blogId).BlogTitle; channel.Description = new BlogEntity(blogId).BlogDescription; channel.Link = new Uri(MinimaConfiguration.Domain); foreach (BlogEntryEntity entity in collection) { RssItem item = new RssItem(); StringBuilder authors = new StringBuilder(); foreach (BlogEntryAuthorEntity a in entity.BlogEntryAuthor) { if (authors.Length > 0) { authors.Append(", "); }; authors.Append(String.Format("{0} <{1}>", a.Author.AuthorName, a.Author.AuthorEmail)); } item.Author = authors.ToString(); item.Title = entity.BlogEntryTitle; if (entity.BlogEntryUrlMapping == null) { throw new ArgumentNullException("Missing url mapping. Every blog entry must have at least one associated url mapping."); } item.Link = new Uri(String.Format("{0}/{1}/{2}/{3}.aspx", MinimaConfiguration.Domain, entity.BlogEntryPostYear, (entity.BlogEntryPostMonth > 9 ? entity.BlogEntryPostMonth.ToString() : "0" + entity.BlogEntryPostMonth.ToString()), entity.BlogEntryUrlMapping[0].BlogEntryUrlMappingName)); if (entity.BlogEntryText.Length > 500) { item.Description = entity.BlogEntryText.Substring(0, 500); } else { item.Description = entity.BlogEntryText; } item.PubDate = entity.BlogEntryPostDateTime; channel.Items.Add(item); } feed.Channels.Add(channel); using (NonClosingMemoryStream stream = new NonClosingMemoryStream()) { feed.Write(stream); Byte[] buffer = new Byte[stream.Length]; unchecked { stream.Read(buffer, 0, (Int32)stream.Length); } return ASCIIEncoding.UTF8.GetString(buffer); } } #endregion #region Special /// /// Returns the blog entries associated with a particular label, wrapped in a blog object. This method is primarily for use by the website. /// /// ID of the blog /// Friendly title of the label /// A blog object public Blog GetBlogEntriesByLabel(Int32 blogId, String label) { if (label.EndsWith("/")) { label = label.Substring(0, label.Length - 1); } BlogEntryCollection collection = new BlogEntryCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(BlogEntryFields.BlogId == blogId); filter.AddWithAnd(LabelFields.LabelNetTitle == label); RelationCollection r = new RelationCollection(); r.Add(BlogEntryEntity.Relations.LabelBlogEntryEntityUsingBlogEntryId); r.Add(LabelBlogEntryEntity.Relations.LabelEntityUsingLabelId); SortExpression sort = new SortExpression(); sort.Add(BlogEntryFields.BlogEntryPostDateTime | SortOperator.Ascending); collection.GetMulti(filter, 0, sort, r); if (collection.Count > 0) { Blog blog = new Blog(blogId, collection[0].LabelBlogEntry[0].Label.LabelTitle); AssignBlogEntries(collection, blog, true); return blog; } else { LabelCollection labels = new LabelCollection(); filter = new PredicateExpression(); filter.AddWithAnd(LabelFields.LabelNetTitle == label); labels.GetMulti(filter); if (labels.Count > 0) { return new Blog(0, "No entries"); } else { return null; } } } /// /// Returns the blog entries from a particular month, wrapped in a blog object. This method is primarily for use by the website. /// /// ID of the blog /// Year the blog entries were posted /// Month the blog entries were posted /// public Blog GetBlogEntriesByMonth(Int32 blogId, Int32 year, Int32 month) { BlogEntryCollection collection = new BlogEntryCollection(); Int32 daysInMonth = DateTime.DaysInMonth(year, month); PredicateExpression filter = new PredicateExpression(); filter.Add(new FieldBetweenPredicate(BlogEntryFields.BlogEntryPostDateTime, new DateTime(year, month, 1), new DateTime(year, month, daysInMonth))); filter.AddWithAnd(BlogEntryFields.BlogId == blogId); SortExpression sort = new SortExpression(); sort.Add(BlogEntryFields.BlogEntryPostDateTime | SortOperator.Ascending); collection.GetMulti(filter, 0, sort); if (collection.Count > 0) { String blogTitle = String.Format("{0} {1}", collection[0].BlogEntryMonthString, ((DateTime)collection[0].BlogEntryPostDateTime).Year); Blog blog = new Blog(0, blogTitle); AssignBlogEntries(collection, blog, true); return blog; } else { return null; } } /// /// Returns information regarding archived entries. /// /// ID of the blog /// DataTable with a summary of archived information public DataTable GetArchivedEntries(Int32 blogId) { return RetrievalProcedures.GetArchivedEntryList(blogId); } #endregion #region Author /// /// Creates an author /// /// E-mail address of the author /// Name of the author /// ID of the created author. The ID is for internal use, use the e-mail address as an identifier public Int32 CreateAuthor(String authorEmail, String authorName) { AuthorCollection collection = new AuthorCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(AuthorFields.AuthorEmail == authorEmail); collection.GetMulti(filter); if (collection.Count > 0) { return collection[0].AuthorId; } else { AuthorEntity author = new AuthorEntity(); author.AuthorName = authorName; author.AuthorEmail = authorEmail; author.Save(); return author.AuthorId; } } /// /// Updates an author /// /// E-mail address of the author, this is the identifier /// Name of the author public void UpdateAuthor(String authorEmail, String authorName) { AuthorCollection collection = new AuthorCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(AuthorFields.AuthorEmail == authorEmail); collection.GetMulti(filter); if (collection.Count > 0) { collection[0].AuthorName = authorName; collection[0].AuthorEmail = authorEmail; collection[0].Save(); } else { throw new ArgumentOutOfRangeException("That author does not exist."); } } /// /// Connects a author with a blog entry /// /// E-mail address of the author /// ID of the blog entry public void ApplyAuthor(String authorEmail, Int32 blogEntryId) { AuthorCollection authors = new AuthorCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(AuthorFields.AuthorEmail == authorEmail); authors.GetMulti(filter); BlogEntryEntity blogEntry = new BlogEntryEntity(blogEntryId); if (authors.Count > 0 && !blogEntry.IsNew) { BlogEntryAuthorCollection beac = new BlogEntryAuthorCollection(); filter = new PredicateExpression(); filter.Add(BlogEntryAuthorFields.AuthorId == authors[0].AuthorId); filter.Add(BlogEntryAuthorFields.BlogEntryId == blogEntryId); beac.GetMulti(filter); if (beac.Count < 1) { BlogEntryAuthorEntity bea = new BlogEntryAuthorEntity(); bea.AuthorId = authors[0].AuthorId; bea.BlogEntryId = blogEntryId; bea.Save(); } } else { if (blogEntry.IsNew) { throw new ArgumentOutOfRangeException("That blog entry does not exist."); } else { throw new ArgumentOutOfRangeException("That author does not exist."); } } } /// /// Disassociates an author from a blog entry, this does NOT delete the author /// /// E-mail address of the author /// ID of the blog entry public void RemoveAuthor(String authorEmail, Int32 blogEntryId) { AuthorCollection authors = new AuthorCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(AuthorFields.AuthorEmail == authorEmail); authors.GetMulti(filter); BlogEntryEntity blogEntry = new BlogEntryEntity(blogEntryId); if (authors.Count > 0 && !blogEntry.IsNew) { BlogEntryAuthorEntity bea = new BlogEntryAuthorEntity(authors[0].AuthorId, blogEntryId); if (!bea.IsNew) { bea.Delete(); } } else { if (blogEntry.IsNew) { throw new ArgumentOutOfRangeException("That blog entry does not exist."); } else { throw new ArgumentOutOfRangeException("That author does not exist."); } } } #endregion #region Label /// /// Creates a label. It does not create labels that already exist in a particular blog. /// /// ID of the blog /// Title of the label /// ID of the created label public Int32 CreateLabel(Int32 blogId, String title) { LabelCollection labels = new LabelCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(LabelFields.BlogId == blogId); filter.Add(LabelFields.LabelTitle == title); labels.GetMulti(filter); if (labels.Count > 0) { return labels[0].LabelId; } labels = new LabelCollection(); filter = new PredicateExpression(); filter.Add(LabelFields.BlogId == blogId); filter.Add(LabelFields.LabelFriendlyTitle == CreateFriendlyLabelTitle(title)); labels.GetMulti(filter); if (labels.Count > 0) { throw new ArgumentException("The friendly name of that label is already taken, it is recommended that you completely change the label title"); } LabelEntity label = new LabelEntity(); label.BlogId = blogId; label.LabelTitle = title; label.LabelFriendlyTitle = CreateFriendlyLabelTitle(title); label.Save(); return label.LabelId; } /// /// Updates a label /// /// ID of the label to update /// New title of the label public void UpdateLabel(Int32 labelId, String title) { LabelEntity label = new LabelEntity(); label.FetchUsingPK(labelId); if (label.IsNew) { throw new ArgumentException("A label with the specified ID does not exist."); } // Does the new friendly name already exist? LabelCollection labels = new LabelCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(LabelFields.BlogId == label.BlogId); filter.Add(LabelFields.LabelFriendlyTitle == CreateFriendlyLabelTitle(title)); labels.GetMulti(filter); if (labels.Count > 0) { throw new ArgumentException("The friendly name of that label is already taken, it is recommended that you completely change the label title"); } label.LabelTitle = title; label.LabelFriendlyTitle = CreateFriendlyLabelTitle(title); label.Save(); } /// /// Associates a label with a blog entry /// /// ID of the label /// ID of the blog entry public void ApplyLabel(Int32 labelId, Int32 blogEntryId) { BlogEntryEntity blogEntry = new BlogEntryEntity(blogEntryId); LabelCollection labels = new LabelCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(LabelFields.BlogId == blogEntry.BlogId); filter.Add(LabelFields.LabelId == labelId); labels.GetMulti(filter); if (labels.Count < 1) { throw new ArgumentException("The given label ID and blog entry ID are not in the same blog."); } LabelBlogEntryCollection lbec = new LabelBlogEntryCollection(); filter = new PredicateExpression(); filter.Add(LabelBlogEntryFields.BlogEntryId == blogEntryId); filter.Add(LabelBlogEntryFields.LabelId == labelId); lbec.GetMulti(filter); if (lbec.Count < 1) { LabelBlogEntryEntity lbe = new LabelBlogEntryEntity(); lbe.BlogEntryId = blogEntryId; lbe.LabelId = labelId; lbe.Save(); } } /// /// Disassociates a label from a blog entry, it does NOT delete the label /// /// ID of the label /// ID of the blog entry public void RemoveLabel(Int32 labelId, Int32 blogEntryId) { LabelBlogEntryEntity lbe = new LabelBlogEntryEntity(labelId, blogEntryId); if (!lbe.IsNew) { lbe.Delete(); } } /// /// Deletes a label /// /// ID of the label /// ID of the blog entry public void DeleteLabel(Int32 labelId) { LabelEntity label = new LabelEntity(labelId); foreach (LabelBlogEntryEntity lbe in label.LabelBlogEntry) { lbe.Delete(); } label.Delete(); } /// /// This is to be used when Label[] doesn't have enough /// information. NOT for services. /// public LabelCollection GetBlogLabelCollection(Int32 blogId) { LabelCollection labels = new LabelCollection(); PredicateExpression filter = new PredicateExpression(); filter.Add(LabelFields.BlogId == blogId); SortExpression sort = new SortExpression(); sort.Add(LabelFields.LabelTitle | SortOperator.Ascending); labels.GetMulti(filter, 0, sort); return labels; } /// /// This is to be used in service scenarios as /// GetBlogLabelCollection uses the self-servicing model, /// not adapter and as such, it gives too much information /// to the client. It's a weird thing to do, but it can /// stand in this version. /// public Label[] GetBlogLabels(Int32 blogId) { List