diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index 0acdeb5..565f1b0 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -216 \ No newline at end of file +231 \ No newline at end of file diff --git a/Common/Common.csproj b/Common/Common.csproj index 9935d40..af70483 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -54,6 +54,7 @@ + @@ -61,6 +62,7 @@ + diff --git a/Common/Config.cs b/Common/Config.cs index 914a379..db4cd92 100644 --- a/Common/Config.cs +++ b/Common/Config.cs @@ -42,6 +42,13 @@ namespace FLocal.Common { base.Dispose(); } + public static void Transactional(Action action) { + using(Core.DB.Transaction transaction = Core.DB.IDBConnectionExtensions.beginTransaction(instance.mainConnection)) { + action(transaction); + transaction.Commit(); + } + } + } } diff --git a/Common/SqlObject.cs b/Common/SqlObject.cs index 67c3823..d2e129b 100644 --- a/Common/SqlObject.cs +++ b/Common/SqlObject.cs @@ -6,7 +6,9 @@ using FLocal.Core; using FLocal.Core.DB; namespace FLocal.Common { - abstract public class SqlObject : Core.DataObject where T : SqlObject, new() { + abstract public class SqlObject : Core.DataObject + where T : SqlObject, new() + where TKey : struct { protected SqlObject() : base() { } @@ -15,7 +17,15 @@ namespace FLocal.Common { get; } - private bool isLoaded = false; + private bool _isLoaded = false; + protected bool isLoaded { + get { + return this._isLoaded; + } + private set { + this._isLoaded = value; + } + } private object lockFiller = new object(); private object lockInitializer = new object(); @@ -82,6 +92,15 @@ namespace FLocal.Common { if(!forLoadingFromHash) this.Load(); } + protected static void Refresh(TKey id) { + Dictionary objects = LoadByIdsForLoadingFromHash(new List() { id }); + objects[id].ReLoad(); + } + + } + + abstract public class SqlObject : SqlObject where T : SqlObject, new() { + public static List LoadByIds(IEnumerable ids) { Dictionary rawRes = LoadByIdsForLoadingFromHash(ids); @@ -116,10 +135,6 @@ namespace FLocal.Common { return res; } - protected static void Refresh(int id) { - Dictionary objects = LoadByIdsForLoadingFromHash(new List() { id }); - objects[id].ReLoad(); - } - } + } diff --git a/Common/actions/ChangeSet.cs b/Common/actions/ChangeSet.cs index 4955483..d75cf8a 100644 --- a/Common/actions/ChangeSet.cs +++ b/Common/actions/ChangeSet.cs @@ -11,7 +11,7 @@ namespace FLocal.Common.actions { /// Note that you should create ChangeSet instances outside of transactions they're using! /// Otherwise, there will be a 100% DB deadlock in Dispose (or data in registry won't update) /// - class ChangeSet : IDisposable { + internal class ChangeSet : IDisposable { private static readonly object tablesLockOrder_locker = new object(); private static IEnumerable tablesLockOrder { @@ -23,6 +23,7 @@ namespace FLocal.Common.actions { "Boards", "Threads", "Posts", + "Sessions", } ); } diff --git a/Common/actions/ChangeSetUtil.cs b/Common/actions/ChangeSetUtil.cs new file mode 100644 index 0000000..ce0b71d --- /dev/null +++ b/Common/actions/ChangeSetUtil.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FLocal.Core.DB; + +namespace FLocal.Common.actions { + class ChangeSetUtil { + + public static void WithChangeSet(Action action) { + using(ChangeSet changeSet = new ChangeSet()) { + Config.Transactional(transaction => { + action(changeSet, transaction); + changeSet.Apply(transaction); + }); + } + } + + public static void WithChangeSet(Action action) { + WithChangeSet((changeset, transaction) => action(changeset)); + } + + public static void ApplyChanges(IEnumerable changes) { + using(ChangeSet changeSet = new ChangeSet()) { + foreach(AbstractChange change in changes) { + changeSet.Add(change); + } + Config.Transactional(transaction => { + changeSet.Apply(transaction); + }); + } + } + + } +} diff --git a/Common/actions/InsertChange.cs b/Common/actions/InsertChange.cs index c8bf5fb..1d3a032 100644 --- a/Common/actions/InsertChange.cs +++ b/Common/actions/InsertChange.cs @@ -9,7 +9,7 @@ namespace FLocal.Common.actions { private int? id; - public InsertChange(ISqlObjectTableSpec tableSpec, Dictionary data, int id) + public InsertChange(ISqlObjectTableSpec tableSpec, Dictionary data) : base(tableSpec, data) { this.id = null; } diff --git a/Common/dataobjects/Account.cs b/Common/dataobjects/Account.cs index d414e9e..8d3a9f2 100644 --- a/Common/dataobjects/Account.cs +++ b/Common/dataobjects/Account.cs @@ -15,8 +15,9 @@ namespace FLocal.Common.dataobjects { public const string TABLE = "Accounts"; public const string FIELD_ID = "Id"; public const string FIELD_USERID = "UserId"; - public const string FIELD_PASSWORD = "Password"; + public const string FIELD_PASSWORDHASH = "Password"; public const string FIELD_NEEDSMIGRATION = "NeedsMigration"; + public const string FIELD_NAME = "Name"; public static readonly TableSpec instance = new TableSpec(); public string name { get { return TABLE; } } public string idName { get { return FIELD_ID; } } @@ -38,11 +39,11 @@ namespace FLocal.Common.dataobjects { } } - private string _password; - private string password { + private string _passwordHash; + private string passwordHash { get { this.LoadIfNotLoaded(); - return this._password; + return this._passwordHash; } } @@ -56,21 +57,22 @@ namespace FLocal.Common.dataobjects { protected override void doFromHash(Dictionary data) { this._userId = int.Parse(data[TableSpec.FIELD_USERID]); - this._password = data[TableSpec.FIELD_PASSWORD]; + this._passwordHash = data[TableSpec.FIELD_PASSWORDHASH]; this._needsMigration = Util.string2bool(data[TableSpec.FIELD_NEEDSMIGRATION]); } - private static Dictionary userid2id = new Dictionary(); - public static Account LoadByUser(User user) { - if(!userid2id.ContainsKey(user.id)) { - lock(userid2id) { - if(!userid2id.ContainsKey(user.id)) { + private static Dictionary name2id = new Dictionary(); + public static Account LoadByName(string _name) { + string name = _name.ToLower(); + if(!name2id.ContainsKey(name)) { + lock(name2id) { + if(!name2id.ContainsKey(name)) { List ids = Config.instance.mainConnection.LoadIdsByConditions( TableSpec.instance, new ComparisonCondition( - TableSpec.instance.getColumnSpec(TableSpec.FIELD_USERID), + TableSpec.instance.getColumnSpec(TableSpec.FIELD_NAME), ComparisonType.EQUAL, - user.id.ToString() + name ), Diapasone.unlimited, new JoinSpec[0] @@ -78,39 +80,43 @@ namespace FLocal.Common.dataobjects { if(ids.Count > 1) { throw new CriticalException("not unique"); } else if(ids.Count == 1) { - userid2id[user.id] = int.Parse(ids[0]); + name2id[name] = int.Parse(ids[0]); } else { throw new NotFoundInDBException(); } } } } - return Account.LoadById(userid2id[user.id]); + return Account.LoadById(name2id[name]); + } + + private string hashPassword(string password) { + return Util.md5(password + " " + Config.instance.SaltMigration + " " + this.id); } public void updatePassword(string newPassword) { - using(ChangeSet changeSet = new ChangeSet()) { - changeSet.Add( - new UpdateChange( - TableSpec.instance, - new Dictionary() { - { - TableSpec.FIELD_PASSWORD, - new ScalarFieldValue(Util.md5(newPassword + " " + Config.instance.SaltMigration + " " + this.id)) - }, - { - TableSpec.FIELD_NEEDSMIGRATION, - new ScalarFieldValue("0") - }, + ChangeSetUtil.ApplyChanges(new AbstractChange[] { + new UpdateChange( + TableSpec.instance, + new Dictionary() { + { + TableSpec.FIELD_PASSWORDHASH, + new ScalarFieldValue(this.hashPassword(newPassword)) }, - this.id - ) - ); - using(Transaction transaction = Config.instance.mainConnection.beginTransaction()) { - changeSet.Apply(transaction); - transaction.Commit(); - } - } + { + TableSpec.FIELD_NEEDSMIGRATION, + new ScalarFieldValue("0") + }, + }, + this.id + ) + }); + } + + public static Account tryAuthorize(string name, string password) { + Account account = LoadByName(name); + if(account.passwordHash != account.hashPassword(password)) throw new NotFoundInDBException(); + return account; } } diff --git a/Common/dataobjects/Session.cs b/Common/dataobjects/Session.cs new file mode 100644 index 0000000..8f122de --- /dev/null +++ b/Common/dataobjects/Session.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Core.DB; +using FLocal.Core.DB.conditions; +using FLocal.Common.actions; + +namespace FLocal.Common.dataobjects { + public class Session : SqlObject { + + public struct SessionKey : IEquatable { + + private string key; + + public static SessionKey Parse(string _key) { + return new SessionKey { key = _key }; + } + + bool IEquatable.Equals(SessionKey _key) { + return this.key.Equals(_key.key); + } + + public override bool Equals(object obj) { + if(obj == null) return base.Equals(obj); + if(!(obj is SessionKey)) throw new InvalidCastException(); + return ((IEquatable)this).Equals((SessionKey)obj); + } + + public override int GetHashCode() { + return this.key.GetHashCode(); + } + + public override string ToString() { + return this.key; + } + + } + + public class TableSpec : ISqlObjectTableSpec { + public const string TABLE = "Sessions"; + public const string FIELD_ID = FIELD_SESSIONKEY; + public const string FIELD_SESSIONKEY = "SessionKey"; + public const string FIELD_ACCOUNTID = "AccountId"; + public const string FIELD_LASTACTIVITY = "LastActivity"; + public static readonly TableSpec instance = new TableSpec(); + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } + public void refreshSqlObject(int id) { throw new NotImplementedException(); } + } + + protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } } + + private string _sessionKey; + public string sessionKey { + get { + this.LoadIfNotLoaded(); + return this._sessionKey; + } + } + + private int _accountId; + public int accountId { + get { + this.LoadIfNotLoaded(); + return this._accountId; + } + } + public Account account { + get { + return Account.LoadById(this.accountId); + } + } + + private DateTime _lastActivity; + public DateTime lastActivity { + get { + this.LoadIfNotLoaded(); + return this._lastActivity; + } + } + + protected override void doFromHash(Dictionary data) { + this._sessionKey = data[TableSpec.FIELD_SESSIONKEY]; + this._lastActivity = new DateTime(long.Parse(data[TableSpec.FIELD_LASTACTIVITY])); + this._accountId = int.Parse(data[TableSpec.FIELD_ACCOUNTID]); + } + + public void updateLastActivity() { + try { + Config.Transactional(transaction => { + Config.instance.mainConnection.update( + transaction, + TableSpec.instance, + this.id.ToString(), + new Dictionary() { + { TableSpec.FIELD_LASTACTIVITY, DateTime.Now.ToUTCString() } + } + ); + }); + } finally { + this.ReLoad(); + } + } + + public static Session create(Account account) { + + string key=null; + + Config.Transactional(transaction => { + + Config.instance.mainConnection.lockTable(transaction, TableSpec.instance); + + do { + key = Util.RandomString(32, Util.RandomSource.LETTERS_DIGITS); + } while(Config.instance.mainConnection.LoadByIds(transaction, TableSpec.instance, new List() { key }).Count > 0); + + Config.instance.mainConnection.insert( + transaction, + TableSpec.instance, + new Dictionary() { + { TableSpec.FIELD_SESSIONKEY, key }, + { TableSpec.FIELD_ACCOUNTID, account.id.ToString() }, + { TableSpec.FIELD_LASTACTIVITY, DateTime.Now.ToUTCString() }, + } + ); + }); + return Session.LoadById(SessionKey.Parse(key)); + } + + public void delete() { + Config.Transactional(transaction => Config.instance.mainConnection.delete(transaction, TableSpec.instance, this.id.ToString())); + this.deleteFromRegistry(); + } + + public XElement exportToXml(UserContext context) { + return new XElement("session", + new XElement("lastActivity", this.lastActivity.ToXml()), + new XElement("sessionKey", this.sessionKey), + this.account.user.exportToXmlForViewing(context) + ); + } + + public static Session LoadByKey(string sessionKey) { + return LoadById(SessionKey.Parse(sessionKey)); + } + + } +} diff --git a/Common/dataobjects/Thread.cs b/Common/dataobjects/Thread.cs index 873c1b2..f6e7a2b 100644 --- a/Common/dataobjects/Thread.cs +++ b/Common/dataobjects/Thread.cs @@ -196,8 +196,8 @@ namespace FLocal.Common.dataobjects { } public void incrementViewsCounter() { - using(ChangeSet changeSet = new ChangeSet()) { - changeSet.Add(new UpdateChange( + ChangeSetUtil.ApplyChanges(new AbstractChange[] { + new UpdateChange( TableSpec.instance, new Dictionary() { { @@ -206,12 +206,8 @@ namespace FLocal.Common.dataobjects { } }, this.id - )); - using(Transaction transaction = Config.instance.mainConnection.beginTransaction()) { - changeSet.Apply(transaction); - transaction.Commit(); - } - } + ) + }); } } diff --git a/Core/DataObject.cs b/Core/DataObject.cs index 69a5ec0..e182c42 100644 --- a/Core/DataObject.cs +++ b/Core/DataObject.cs @@ -65,6 +65,10 @@ namespace FLocal.Core { } } + protected void deleteFromRegistry() { + registry.Delete(new TKey[] { this.id }); + } + } } diff --git a/Core/Util.cs b/Core/Util.cs index f767e71..cf69d3c 100644 --- a/Core/Util.cs +++ b/Core/Util.cs @@ -165,6 +165,15 @@ namespace FLocal.Core { return sBuilder.ToString(); } + public static bool throws(Action action) where TException : Exception { + try { + action(); + return false; + } catch(TException) { + return true; + } + } + } } diff --git a/Core/extensions/Extensions.cs b/Core/extensions/Extensions.cs index 241f345..1887b6c 100644 --- a/Core/extensions/Extensions.cs +++ b/Core/extensions/Extensions.cs @@ -226,5 +226,9 @@ namespace FLocal.Core { return decimal.ToInt64(number); } + public static string ToUTCString(this DateTime date) { + return date.ToUniversalTime().ToString("u"); + } + } } diff --git a/IISMainHandler/HandlersFactory.cs b/IISMainHandler/HandlersFactory.cs index 1df0f0e..1d2dca2 100644 --- a/IISMainHandler/HandlersFactory.cs +++ b/IISMainHandler/HandlersFactory.cs @@ -36,6 +36,10 @@ namespace FLocal.IISHandler { return new handlers.WrongUrlHandler(); } else { switch(context.requestParts[1].ToLower()) { + case "login": + return new handlers.request.LoginHandler(); + case "logout": + return new handlers.request.LogoutHandler(); case "migrateaccount": return new handlers.request.MigrateAccountHandler(); default: diff --git a/IISMainHandler/IISMainHandler.csproj b/IISMainHandler/IISMainHandler.csproj index 0f0dd43..f42e8e2 100644 --- a/IISMainHandler/IISMainHandler.csproj +++ b/IISMainHandler/IISMainHandler.csproj @@ -58,6 +58,8 @@ + + diff --git a/IISMainHandler/WebContext.cs b/IISMainHandler/WebContext.cs index 09e5e97..b83d78f 100644 --- a/IISMainHandler/WebContext.cs +++ b/IISMainHandler/WebContext.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Web; using FLocal.Core; +using FLocal.Common.dataobjects; +using System.Xml.Linq; namespace FLocal.IISHandler { class WebContext : Common.UserContext { @@ -57,9 +59,24 @@ namespace FLocal.IISHandler { public DateTime requestTime; + public Session session; + public WebContext(HttpContext httpcontext) { this.httpcontext = httpcontext; this.requestTime = DateTime.Now; + HttpCookie sessionCookie = this.httprequest.Cookies["session"]; + if(sessionCookie != null && sessionCookie.Value != null && sessionCookie.Value != "") { + try { + this.session = Session.LoadByKey(sessionCookie.Value); + sessionCookie.Expires = DateTime.Now.AddDays(3); + this.httpresponse.AppendCookie(sessionCookie); + } catch(NotFoundInDBException) { + sessionCookie.Value = ""; + sessionCookie.Expires = DateTime.Now.AddDays(-1); + this.httpresponse.AppendCookie(sessionCookie); + //throw; //TODO: remove me! + } + } } public string Transform(string templateName, System.Xml.Linq.XDocument data) { @@ -67,5 +84,24 @@ namespace FLocal.IISHandler { return TemplateEngine.Compile(this.design.fsname + this.config.DirSeparator + templateName, data); } + public XElement exportSession() { + if(this.session != null) { + return session.exportToXml(this); + } else { + return new XElement("session", + new XElement("notLoggedIn", true) + ); + } + } + + public HttpCookie createCookie(string name) { + HttpCookie result = new HttpCookie(name); + result.HttpOnly = true; + result.Secure = true; + result.Domain = "." + String.Join(".", this.httprequest.Url.Host.Split(".", StringSplitOptions.RemoveEmptyEntries).Slice(1).ToArray()); + result.Path = "/"; + return result; + } + } } diff --git a/IISMainHandler/handlers/AbstractGetHandler.cs b/IISMainHandler/handlers/AbstractGetHandler.cs index d6841e1..73a8fe4 100644 --- a/IISMainHandler/handlers/AbstractGetHandler.cs +++ b/IISMainHandler/handlers/AbstractGetHandler.cs @@ -18,6 +18,7 @@ namespace FLocal.IISHandler.handlers { return new XDocument( new XElement("root", new XElement("title", Config.instance.AppInfo), + context.exportSession(), this.getSpecificData(context) ) ); diff --git a/IISMainHandler/handlers/request/AbstractPostHandler.cs b/IISMainHandler/handlers/request/AbstractPostHandler.cs index 6ed60b7..6071dd3 100644 --- a/IISMainHandler/handlers/request/AbstractPostHandler.cs +++ b/IISMainHandler/handlers/request/AbstractPostHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; +using FLocal.Core; using FLocal.Common; namespace FLocal.IISHandler.handlers.request { @@ -12,18 +13,33 @@ namespace FLocal.IISHandler.handlers.request { get; } + virtual protected bool shouldBeLoggedIn { + get { + return true; + } + } + + virtual protected bool shouldBeGuest { + get { + return false; + } + } + abstract protected XElement[] Do(WebContext context); private XDocument getData(WebContext context) { return new XDocument( new XElement("root", new XElement("title", Config.instance.AppInfo), - this.Do(context) + this.Do(context), + context.exportSession() ) ); } public void Handle(WebContext context) { + if(this.shouldBeGuest && context.session != null) throw new FLocalException("Should be guest"); + if(this.shouldBeLoggedIn && context.session == null) throw new FLocalException("Should be anonymous"); context.httpresponse.Write(context.Transform(this.templateName, this.getData(context))); } diff --git a/IISMainHandler/handlers/request/LoginHandler.cs b/IISMainHandler/handlers/request/LoginHandler.cs new file mode 100644 index 0000000..a849da6 --- /dev/null +++ b/IISMainHandler/handlers/request/LoginHandler.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Common.dataobjects; +using FLocal.Importer; +using System.Text.RegularExpressions; +using FLocal.Core; +using FLocal.Common; +using FLocal.Common.actions; +using System.Web; + +namespace FLocal.IISHandler.handlers.request { + class LoginHandler : AbstractPostHandler { + + protected override string templateName { + get { + return "result/Login.xslt"; + } + } + + protected override bool shouldBeLoggedIn { + get { return false; } + } + + protected override bool shouldBeGuest { + get { return true; } + } + + protected override XElement[] Do(WebContext context) { + Account account = Account.tryAuthorize(context.httprequest.Form["name"], context.httprequest.Form["password"]); + Session session = Session.create(account); + + HttpCookie sessionCookie = context.createCookie("session"); + sessionCookie.Value = session.sessionKey; + sessionCookie.Expires = DateTime.Now.AddDays(3); + context.httpresponse.AppendCookie(sessionCookie); + context.session = session; + + return new XElement[0]; + } + + } +} diff --git a/IISMainHandler/handlers/request/LogoutHandler.cs b/IISMainHandler/handlers/request/LogoutHandler.cs new file mode 100644 index 0000000..cc27636 --- /dev/null +++ b/IISMainHandler/handlers/request/LogoutHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Common.dataobjects; +using FLocal.Importer; +using System.Text.RegularExpressions; +using FLocal.Core; +using FLocal.Common; +using FLocal.Common.actions; +using System.Web; + +namespace FLocal.IISHandler.handlers.request { + class LogoutHandler : AbstractPostHandler { + + protected override string templateName { + get { + return "result/Logout.xslt"; + } + } + + protected override XElement[] Do(WebContext context) { + if(context.session.id.ToString().ToLower() != context.httprequest.QueryString["sessionKey"].ToLower()) { + throw new FLocalException("Wrong session id"); + } + context.session.delete(); + + HttpCookie sessionCookie = context.createCookie("session"); + sessionCookie.Value = ""; + sessionCookie.Expires = DateTime.Now.AddDays(-1); + context.httpresponse.AppendCookie(sessionCookie); + context.session = null; + + return new XElement[0]; + } + + } +} diff --git a/IISMainHandler/handlers/request/MigrateAccountHandler.cs b/IISMainHandler/handlers/request/MigrateAccountHandler.cs index 3f80107..f882bcb 100644 --- a/IISMainHandler/handlers/request/MigrateAccountHandler.cs +++ b/IISMainHandler/handlers/request/MigrateAccountHandler.cs @@ -19,6 +19,14 @@ namespace FLocal.IISHandler.handlers.request { } } + protected override bool shouldBeLoggedIn { + get { return false; } + } + + protected override bool shouldBeGuest { + get { return true; } + } + protected override XElement[] Do(WebContext context) { Account account = Account.LoadById(int.Parse(context.httprequest.Form["accountId"])); if(!account.needsMigration) throw new FLocalException("Already migrated"); diff --git a/IISMainHandler/handlers/response/MigrateAccountHandler.cs b/IISMainHandler/handlers/response/MigrateAccountHandler.cs index 40302b8..4759319 100644 --- a/IISMainHandler/handlers/response/MigrateAccountHandler.cs +++ b/IISMainHandler/handlers/response/MigrateAccountHandler.cs @@ -26,8 +26,7 @@ namespace FLocal.IISHandler.handlers.response { } username = context.requestParts[1]; } - User user = User.LoadByName(username); - Account account = Account.LoadByUser(user); + Account account = Account.LoadByName(username); if(!account.needsMigration) throw new FLocalException("Already migrated"); string key = Util.RandomString(8, Util.RandomSource.LETTERS_DIGITS); return new XElement[] { diff --git a/MySQLConnector/Connection.cs b/MySQLConnector/Connection.cs index 2b856dd..fbc5306 100644 --- a/MySQLConnector/Connection.cs +++ b/MySQLConnector/Connection.cs @@ -251,25 +251,37 @@ namespace FLocal.MySQLConnector { lock(transaction) { using(DbCommand command = transaction.sqlconnection.CreateCommand()) { List updates = new List(); + List updatesPlaceholders = new List(); ParamsHolder paramsholder = new ParamsHolder(); foreach(KeyValuePair kvp in data) { - updates.Add(this.traits.escapeIdentifier(kvp.Key) + " = " + this.traits.markParam(paramsholder.Add(kvp.Value))); + updates.Add(this.traits.escapeIdentifier(kvp.Key)); + updatesPlaceholders.Add(this.traits.markParam(paramsholder.Add(kvp.Value))); } command.Transaction = transaction.sqltransaction; command.CommandType = System.Data.CommandType.Text; - command.CommandText = "INSERT INTO " + table.compile(this.traits) + " SET " + String.Join(", ", updates.ToArray()); + command.CommandText = "INSERT INTO " + table.compile(this.traits) + " (" + String.Join(", ", updates.ToArray()) + ") VALUES (" + String.Join(", ", updatesPlaceholders.ToArray()) + ")"; foreach(KeyValuePair kvp in paramsholder.data) { command.AddParameter(kvp.Key, kvp.Value); } command.ExecuteNonQuery(); + if(data.ContainsKey(table.idName)) return data[table.idName]; return this.traits.LastInsertId(command, table).ToString(); } } } - public void delete(FLocal.Core.DB.Transaction transaction, ITableSpec table, string id) { - throw new NotImplementedException(); + public void delete(FLocal.Core.DB.Transaction _transaction, ITableSpec table, string id) { + Transaction transaction = (Transaction)_transaction; + lock(transaction) { + using(DbCommand command = transaction.sqlconnection.CreateCommand()) { + command.Transaction = transaction.sqltransaction; + command.CommandType = System.Data.CommandType.Text; + command.CommandText = "DELETE FROM " + table.compile(traits) + " where " + table.getIdSpec().compile(this.traits) + " = " + this.traits.markParam("id"); + command.AddParameter("id", id); + command.ExecuteNonQuery(); + } + } } public void Dispose() { diff --git a/templates/Full/Boards.xslt b/templates/Full/Boards.xslt index f03a7d5..3e600d7 100644 --- a/templates/Full/Boards.xslt +++ b/templates/Full/Boards.xslt @@ -22,7 +22,14 @@ - Âû âîøëè â ôîðóì êàê summersun + + + Âû âîøëè â ôîðóì êàê + + + Âû íå âîøëè â ôîðóì + +
18983 Çàðåãèñòðèðîâàííûõ ïîëüçîâàòåëåé.
diff --git a/templates/Full/Login.xslt b/templates/Full/Login.xslt index b677379..dc7da04 100644 --- a/templates/Full/Login.xslt +++ b/templates/Full/Login.xslt @@ -21,7 +21,7 @@
Ëîãèí
-
+
Ïàðîëü

diff --git a/templates/Full/Root.xslt b/templates/Full/Root.xslt index 09bc028..b894111 100644 --- a/templates/Full/Root.xslt +++ b/templates/Full/Root.xslt @@ -37,7 +37,14 @@
- Hello, summersun (External IP). + + + Hello, + + + Hello, Guest + + diff --git a/templates/Full/result/Login.xslt b/templates/Full/result/Login.xslt new file mode 100644 index 0000000..9c429e3 --- /dev/null +++ b/templates/Full/result/Login.xslt @@ -0,0 +1,25 @@ + + + + +
+ + + +
+ + + + + + + +
+ Âõîä â ïàíåëü +
+ Àâòîðèçàöèÿ ïðîøëà óñïåøíî +
+
+ + + \ No newline at end of file diff --git a/templates/Full/result/Logout.xslt b/templates/Full/result/Logout.xslt new file mode 100644 index 0000000..6da221d --- /dev/null +++ b/templates/Full/result/Logout.xslt @@ -0,0 +1,25 @@ + + + + + + + + +
+ + + + + + + +
+ Âûõîä èç ïàíåëè +
+ Ñåññèÿ óäàëåíà +
+
+
+ +
\ No newline at end of file