commit 2c4825c3bfa5fd1610104b0486b303396d536bab Author: phantomix Date: Wed May 15 18:42:43 2019 +0200 190515PX initial commit diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..4abf5be --- /dev/null +++ b/.hgignore @@ -0,0 +1,5 @@ +syntax: glob +obj/* +*.csproj.user +.vs/* +bin/* diff --git a/App.config b/App.config new file mode 100644 index 0000000..77ed642 --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..cede5be --- /dev/null +++ b/Program.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using dezentrale.model; + +namespace dezentrale +{ + public class Program + { + public static uint VersionNumber { get; private set; } = 0x19051500; + public static string VersionString { get; private set; } = $"{VersionNumber:x}"; + + public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + public static string DmDirectory = Path.Combine(AppData, "dezentrale-members"); + + public static string ConfigFile = Path.Combine(DmDirectory, "configuration.xml"); + public static string MemberFile = Path.Combine(DmDirectory, "members.xml"); + + public static Configuration config = new Configuration(); + public static MemberList members = new MemberList(); + public enum eMode + { + CommandLine = 0, + Gui = 1, + Cronjob = 2, + } + + public static eMode ProgramMode = eMode.CommandLine; + + static int Main(string[] args) + { + bool printHelp = false; + Console.WriteLine($"dezentrale-members, Version {VersionString}"); + + try { Directory.CreateDirectory(DmDirectory); } + catch (Exception ex) { Console.WriteLine($"Error while creating data directory:\n{ex.Message}"); return 1; } + + try + { + try { config = (Configuration)XmlData.LoadFromFile(ConfigFile); } + catch (FileNotFoundException) { XmlData.SaveToFile(ConfigFile, config); Console.WriteLine("Created new configuration file."); } + + try { members = (MemberList)XmlData.LoadFromFile(MemberFile); } + catch (FileNotFoundException) { XmlData.SaveToFile(MemberFile, members); Console.WriteLine("Created new member file."); } + } catch (Exception ex) + { + Console.WriteLine("Error while accessing program data:"); + Console.WriteLine(ex.Message); + return 1; + } + + List clArgs = new List(); + foreach(string argIt in args) + { + string argn; + string argv; + if (argIt.Contains("=")) + { + argn = argIt.Substring(0, argIt.IndexOf('=')); + argv = argIt.Substring(argIt.IndexOf('=') + 1); + } else + { + argn = argIt; + argv = ""; + } + if (argn.StartsWith("-")) argn = argn.Substring(1); + if (argn.StartsWith("/") || argn.StartsWith("-")) argn = argn.Substring(1); + switch(argn.ToLower()) + { + case "help": printHelp = true; break; + case "mode": + switch(argv.ToLower()) + { + case "cl": ProgramMode = eMode.CommandLine; Console.WriteLine($"Setting Mode to {ProgramMode}"); break; + case "gui": ProgramMode = eMode.Gui; Console.WriteLine($"Setting Mode to {ProgramMode}"); break; + case "cronjob": + case "auto": ProgramMode = eMode.Cronjob; Console.WriteLine($"Setting Mode to {ProgramMode}"); break; + default: + Console.WriteLine("Invalid mode."); + printHelp = true; + break; + } + break; + default: + Console.WriteLine($"Invalid command({argn})"); + printHelp = true; + break; + } + } + + if (printHelp) + { + Console.WriteLine("command line arguments:"); + Console.WriteLine("--help Display this help message"); + Console.WriteLine("--mode= Starts the program in given mode. Supported modes are:"); + Console.WriteLine(" cl (default) Manual/interactive use from command line."); + Console.WriteLine(" gui Start in GUI mode (not implemented yet)"); + Console.WriteLine(" auto, cronjob Check and perform pending required membership actions"); + Console.WriteLine(); + return 1; + } + + + + Member m = new Member(); + + FormMail result = FormMail.GenerateNewMemberWelcome().ReplaceReflect(m); + Console.WriteLine($" To: {result.To}"); + Console.WriteLine($" Subject: {result.Subject}"); + Console.WriteLine($" Body: {result.Body}"); + + //result.Send(); + return 0; + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..879a317 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("dezentrale-members")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("dezentrale-members")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("c5709dda-d3a7-4c2e-9ab5-98dd3ed1180e")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dezentrale-members.csproj b/dezentrale-members.csproj new file mode 100644 index 0000000..58855d2 --- /dev/null +++ b/dezentrale-members.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {C5709DDA-D3A7-4C2E-9AB5-98DD3ED1180E} + Exe + dezentrale + dezentrale-members + v3.5 + 512 + true + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 + true + + + + \ No newline at end of file diff --git a/dezentrale-members.sln b/dezentrale-members.sln new file mode 100644 index 0000000..7c98b72 --- /dev/null +++ b/dezentrale-members.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2024 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dezentrale-members", "dezentrale-members.csproj", "{C5709DDA-D3A7-4C2E-9AB5-98DD3ED1180E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C5709DDA-D3A7-4C2E-9AB5-98DD3ED1180E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5709DDA-D3A7-4C2E-9AB5-98DD3ED1180E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5709DDA-D3A7-4C2E-9AB5-98DD3ED1180E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5709DDA-D3A7-4C2E-9AB5-98DD3ED1180E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {901A729B-6A62-423D-B793-3EE7A81BF327} + EndGlobalSection +EndGlobal diff --git a/model/ConfigSmtp.cs b/model/ConfigSmtp.cs new file mode 100644 index 0000000..e1629d6 --- /dev/null +++ b/model/ConfigSmtp.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace dezentrale.model +{ + public class ConfigSmtp : XmlData + { + [XmlElement] public string Host { get; set; } = "localhost"; + [XmlElement] public int Port { get; set; } = 587; + [XmlElement] public bool SSL { get; set; } = true; + [XmlElement] public string From { get; set; } = "John Doe "; + [XmlElement] public string UserName { get; set; } = "username"; //you might need to use a complete e-mail address here. + [XmlElement] public string Password { get; set; } = "password"; + } +} diff --git a/model/ConfigVSMail.cs b/model/ConfigVSMail.cs new file mode 100644 index 0000000..899fdbf --- /dev/null +++ b/model/ConfigVSMail.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace dezentrale.model +{ + public class ConfigVSMail : XmlData + { + [XmlElement] public string VSName { get; set; } = "dezentrale Vorstand"; + [XmlElement] public string VSEmail { get; set; } = "vorstand@dezentrale.space"; + } +} diff --git a/model/Configuration.cs b/model/Configuration.cs new file mode 100644 index 0000000..5dc0c12 --- /dev/null +++ b/model/Configuration.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace dezentrale.model +{ + public class Configuration : XmlData + { + [XmlElement] public ConfigSmtp Smtp { get; set; } = new ConfigSmtp(); + [XmlElement] public ConfigVSMail VS { get; set; } = new ConfigVSMail(); + + } +} diff --git a/model/FormMail.cs b/model/FormMail.cs new file mode 100644 index 0000000..3edd1c3 --- /dev/null +++ b/model/FormMail.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using System.Reflection; +using System.Text; + +namespace dezentrale.model +{ + public class FormMail : XmlData + { + public string To { get; set; } = "{Nickname} <{EMail}>"; + public string Subject { get; set; } + public string Body { get; set; } + + + public FormMail() { } + public FormMail(FormMail copyFrom) + { + if(copyFrom != null) + { + this.To = copyFrom.To; + this.Subject = copyFrom.Subject; + this.Body = copyFrom.Body; + } + } + + public FormMail ReplaceReflect(object o) + { + FormMail ret = new FormMail(this); + + PropertyInfo[] objProperties = o.GetType().GetProperties(); + + foreach (var mailProperty in ret.GetType().GetProperties()) + { + if (mailProperty.PropertyType != typeof(string)) continue; + if (!mailProperty.CanRead) continue; + if (!mailProperty.CanWrite) continue; + + string propVal = (string)mailProperty.GetValue(ret, null); + if (propVal == null) continue; + bool changed = false; + foreach (var objProperty in objProperties) + { + if (!objProperty.CanRead) continue; + + //check if objProperty occurs in mailProperty contents, then replace + string token = $"{{{objProperty.Name}}}"; + string tokenValue = objProperty.GetValue(o, null).ToString(); + + if (propVal.Contains(token)) + { + //NOTE: This is problematic because it allows the user to generate recursive replacements by setting e.g. the Nickname to "{PgpFingerprint}" + propVal = propVal.Replace(token, tokenValue); + changed = true; + } + } + if (changed) + { + mailProperty.SetValue(ret, propVal, null); + } + } + return ret; + } + + public bool Send(object replaceReflect = null) + { + FormMail src = replaceReflect == null ? this : ReplaceReflect(replaceReflect); + + SmtpClient client = new SmtpClient(Program.config.Smtp.Host, Program.config.Smtp.Port); + client.DeliveryMethod = SmtpDeliveryMethod.Network; + client.EnableSsl = Program.config.Smtp.SSL; + client.UseDefaultCredentials = false; + client.Credentials = new System.Net.NetworkCredential(Program.config.Smtp.UserName, Program.config.Smtp.Password); + try + { + client.Send(Program.config.Smtp.From, src.To, src.Subject, src.Body); + } catch(Exception ex) + { + Console.WriteLine($"Error while sending Mail:\n{ex.Message}\n"); + return false; + } + return true; + } + + + public static FormMail GenerateNewMemberWelcome() + { + FormMail ret = new FormMail(); + ret.To = "{Nickname} <{EMail}>"; + ret.Subject = "Willkommen in der dezentrale, {Nickname}!"; + ret.Body = "Hello {Nickname}!\n" + + "\n" + + "Welcome to the dezentrale Hackspace.\n" + + "Your membership number is {Number}\n"; + return ret; + } + + public static FormMail GenerateNewMemberVorstand() + { + FormMail ret = new FormMail(); + ret.To = "{VSName} <{VSEMail}>"; + ret.Subject = "Neuer Mitgliedsantrag, Nummer = {Number}, nick = {Nickname}!"; + ret.Body = "Hallo Vorstandsliste,!\n" + + "\n" + + "Folgender Nutzer wurde in die Datenbank aufgenommen.\n" + + "Mitgliednummer: {Number}\n" + + "Nickname: {Nickname}\n" + + "Realname: {FirstName} {LastName}\n" + + "EMail: {EMail}\n" + + "\n" + + "Damit beginnt eine 7-Tage-Einspruchsfrist für den Vorstand.\n" + + "\n" + + "\n" + + "Dies ist eine automatisch generierte E-Mail.\n"; + return ret; + } + } +} diff --git a/model/LogEntry.cs b/model/LogEntry.cs new file mode 100644 index 0000000..77c2a50 --- /dev/null +++ b/model/LogEntry.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dezentrale.model +{ + public class LogEntry : XmlData + { + } +} diff --git a/model/Member.cs b/model/Member.cs new file mode 100644 index 0000000..4fadfb0 --- /dev/null +++ b/model/Member.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace dezentrale.model +{ + public class Member : XmlData + { + public enum eRole + { + Normal = 0, + Vorstandsvorsitzender, + Schatzmeister, + Schriftfuehrer, + Beisitzer, + } + + public enum eType + { + Regulaer = 0, + Foerdermitglied + } + + public enum eStatus + { + Uninitialized = 0, + Greeted, + Active, + Bannend, + Disabled, + } + + public enum ePaymentClass + { + Reduced = 0, + Normal, + NerdClass1, + NerdClass2, + NerdClass3, + NerdClass4, + } + + //metadata + [XmlElement] public uint Number { get; set; } = 0; + [XmlElement] public eRole Role { get; set; } = eRole.Normal; + [XmlElement] public eType Type { get; set; } = eType.Regulaer; + [XmlElement] public eStatus Status { get; set; } = eStatus.Uninitialized; + [XmlElement] public List OpenPayments { get; set; } = new List(); //todo: data type + [XmlElement] public List Log { get; set; } = new List(); + + //personal data + [XmlElement] public string Nickname { get; set; } = "{PgpFingerprint}";//"DoeJohnz"; + [XmlElement] public string FirstName { get; set; } = "John"; + [XmlElement] public string LastName { get; set; } = "_Doe"; + [XmlElement] public string AddressStreet { get; set; } = "_HomeStreet"; + [XmlElement] public string AddressNumber { get; set; } = "123"; + [XmlElement] public string Zipcode { get; set; } = "12345"; + [XmlElement] public string City { get; set; } = "_HomeCity"; + [XmlElement] public DateTime Birthday { get; set; } + [XmlElement] public string EMail { get; set; } = "john.doe@example.com"; + [XmlElement] public string PgpFingerprint { get; set; } = "print"; + + //membership organizational data + [XmlElement] public bool MvInvitationByPost { get; set; } = false; + [XmlElement] public DateTime SpawnDate { get; set; } + [XmlElement] public ePaymentClass PaymentClass { get; set; } = ePaymentClass.Normal; + [XmlElement] public DateTime MemberFormDate { get; set; } + } +} diff --git a/model/MemberList.cs b/model/MemberList.cs new file mode 100644 index 0000000..1908178 --- /dev/null +++ b/model/MemberList.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace dezentrale.model +{ + public class MemberList : XmlData + { + [XmlElement("Member")] public List Entries { get; set; } = new List(); + } +} diff --git a/model/MemberPayment.cs b/model/MemberPayment.cs new file mode 100644 index 0000000..c89a841 --- /dev/null +++ b/model/MemberPayment.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dezentrale.model +{ + public class MemberPayment : XmlData + { + //date + //amount + } +} diff --git a/model/XmlData.cs b/model/XmlData.cs new file mode 100644 index 0000000..49d2b1e --- /dev/null +++ b/model/XmlData.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace dezentrale.model +{ + public class XmlData + { + [XmlElement] public DateTime LastChanged { get; set; } = DateTime.Now; + + [XmlIgnore] + private static Type[] SerializeTypes = + { + typeof(bool), + typeof(uint), + typeof(int), + typeof(string), + typeof(DateTime), + typeof(Configuration), + typeof(ConfigSmtp), + typeof(ConfigVSMail), + typeof(FormMail), + typeof(LogEntry), + typeof(Member), + typeof(Member.ePaymentClass), + typeof(Member.eRole), + typeof(Member.eStatus), + typeof(Member.eType), + typeof(MemberList), + typeof(MemberPayment), + //typeof(List), + //typeof(List), + //typeof(List), + }; + + public static XmlData LoadFromFile(string fileName) + { + FileStream fs = null; + try + { + XmlSerializer ser = new XmlSerializer(typeof(XmlData), XmlData.SerializeTypes); + + fs = new FileStream(fileName, FileMode.Open); + XmlData ds = (XmlData)ser.Deserialize(fs); + fs.Close(); + return ds; + } + catch (Exception e) + { + if (fs != null) fs.Close(); + if (e.InnerException != null) throw e.InnerException; else throw e; + } + } + + public static bool SaveToFile(string fileName, XmlData ds) + { + try + { + if (File.Exists(fileName)) + { + if (File.Exists(fileName + ".bak")) File.Delete(fileName + ".bak"); + File.Move(fileName, fileName + ".bak"); + } + + XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + XmlSerializer ser = new XmlSerializer(typeof(XmlData), XmlData.SerializeTypes); + FileStream fs; + fs = new FileStream(fileName, FileMode.Create); + ser.Serialize(fs, ds, ns); + fs.Close(); + } + catch (Exception e) + { + if (e.InnerException != null) + throw e.InnerException; + else + throw e; + } + return true; + } + + } +}