mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-07-04 13:47:04 -04:00
Removed NioTSO client and server
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
This commit is contained in:
parent
f12ba1502b
commit
22191ce648
591 changed files with 53264 additions and 3362 deletions
281
server/FSO.Server.Api.Core/Api.cs
Executable file
281
server/FSO.Server.Api.Core/Api.cs
Executable file
|
@ -0,0 +1,281 @@
|
|||
using FSO.Server.Api.Core.Services;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Common.Config;
|
||||
using FSO.Server.Database.DA;
|
||||
using FSO.Server.Domain;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
|
||||
namespace FSO.Server.Api.Core
|
||||
{
|
||||
public class Api : ApiAbstract
|
||||
{
|
||||
public static Api INSTANCE;
|
||||
|
||||
public IDAFactory DAFactory;
|
||||
public ApiConfig Config;
|
||||
public JWTFactory JWT;
|
||||
public Shards Shards;
|
||||
public IGluonHostPool HostPool;
|
||||
public IUpdateUploader UpdateUploader;
|
||||
public IUpdateUploader UpdateUploaderClient;
|
||||
public GithubConfig Github;
|
||||
|
||||
public Api()
|
||||
{
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
public void Init(NameValueCollection appSettings)
|
||||
{
|
||||
Config = new ApiConfig();
|
||||
Config.Maintainance = bool.Parse(appSettings["maintainance"]);
|
||||
Config.AuthTicketDuration = int.Parse(appSettings["authTicketDuration"]);
|
||||
Config.Regkey = appSettings["regkey"];
|
||||
Config.Secret = appSettings["secret"];
|
||||
Config.UpdateUrl = appSettings["updateUrl"];
|
||||
Config.CDNUrl = appSettings["cdnUrl"];
|
||||
Config.NFSdir = appSettings["nfsdir"];
|
||||
Config.UseProxy = bool.Parse(appSettings["useProxy"]);
|
||||
Config.UpdateID = (appSettings["updateID"] == "") ? (int?)null : int.Parse(appSettings["updateID"]);
|
||||
Config.BranchName = appSettings["branchName"] ?? "beta";
|
||||
|
||||
// new smtp config vars
|
||||
if (appSettings["smtpHost"]!=null&&
|
||||
appSettings["smtpUser"]!=null&&
|
||||
appSettings["smtpPassword"]!=null&&
|
||||
appSettings["smtpPort"]!=null)
|
||||
{
|
||||
Config.SmtpEnabled = true;
|
||||
Config.SmtpHost = appSettings["smtpHost"];
|
||||
Config.SmtpUser = appSettings["smtpUser"];
|
||||
Config.SmtpPassword = appSettings["smtpPassword"];
|
||||
Config.SmtpPort = int.Parse(appSettings["smtpPort"]);
|
||||
}
|
||||
|
||||
JWT = new JWTFactory(new JWTConfiguration()
|
||||
{
|
||||
Key = System.Text.UTF8Encoding.UTF8.GetBytes(Config.Secret)
|
||||
});
|
||||
|
||||
DAFactory = new MySqlDAFactory(new Database.DatabaseConfiguration()
|
||||
{
|
||||
ConnectionString = appSettings["connectionString"]
|
||||
});
|
||||
|
||||
Shards = new Shards(DAFactory);
|
||||
Shards.AutoUpdate();
|
||||
}
|
||||
|
||||
public JWTUser RequireAuthentication(HttpRequest request)
|
||||
{
|
||||
JWTUser result;
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Headers["Authorization"]))
|
||||
{
|
||||
result = JWT.DecodeToken(GetAuthParam(request.Headers["Authorization"]));
|
||||
}
|
||||
else
|
||||
{
|
||||
var cookies = request.Cookies;
|
||||
if (cookies == null)
|
||||
throw new SecurityException("Unable to find cookie");
|
||||
|
||||
|
||||
var cookie = cookies["fso"];
|
||||
if (string.IsNullOrEmpty(cookie))
|
||||
{
|
||||
throw new SecurityException("Unable to find cookie");
|
||||
}
|
||||
result = JWT.DecodeToken(cookie);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
throw new SecurityException("Invalid token");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetAuthParam(string auth)
|
||||
{
|
||||
var ind = auth.IndexOf(' ');
|
||||
if (ind == -1) return auth;
|
||||
return auth.Substring(ind + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email to a user to tell them that they're banned. ;(
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="end_date"></param>
|
||||
public void SendBanMail(string username, string email, uint end_date)
|
||||
{
|
||||
ApiMail banMail = new ApiMail("MailBan");
|
||||
|
||||
var date = end_date == 0 ? "Permanent ban" : Epoch.ToDate(end_date).ToString();
|
||||
|
||||
banMail.AddString("username", username);
|
||||
banMail.AddString("end", date);
|
||||
|
||||
banMail.Send(email, "Banned from ingame");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email to a user saying that the registration went OK.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="email"></param>
|
||||
public void SendEmailConfirmationOKMail(string username, string email)
|
||||
{
|
||||
ApiMail confirmOKMail = new ApiMail("MailRegistrationOK");
|
||||
|
||||
confirmOKMail.AddString("username", username);
|
||||
|
||||
confirmOKMail.Send(email, "Welcome to FreeSO, " + username + "!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email to a a new user with a token to create their user.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="confirmation_url"></param>
|
||||
/// <param name="expires"></param>
|
||||
/// <returns></returns>
|
||||
public bool SendEmailConfirmationMail(string email, string token, string confirmation_url, uint expires)
|
||||
{
|
||||
ApiMail confirmMail = new ApiMail("MailRegistrationToken");
|
||||
|
||||
confirmation_url = confirmation_url.Replace("%token%", token);
|
||||
confirmMail.AddString("token", token);
|
||||
confirmMail.AddString("expires", Epoch.HMSRemaining(expires));
|
||||
confirmMail.AddString("confirmation_url", confirmation_url);
|
||||
|
||||
return confirmMail.Send(email, "Verify your FreeSO account");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email to a user with a token to reset their password.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="confirmation_url"></param>
|
||||
/// <param name="expires"></param>
|
||||
/// <returns></returns>
|
||||
public bool SendPasswordResetMail(string email, string username, string token, string confirmation_url, uint expires)
|
||||
{
|
||||
ApiMail confirmMail = new ApiMail("MailPasswordReset");
|
||||
|
||||
confirmation_url = confirmation_url.Replace("%token%", token);
|
||||
confirmMail.AddString("token", token);
|
||||
confirmMail.AddString("expires", Epoch.HMSRemaining(expires));
|
||||
confirmMail.AddString("confirmation_url", confirmation_url);
|
||||
|
||||
return confirmMail.Send(email, "Password Reset for " + username);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a password change success email to a user.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public bool SendPasswordResetOKMail(string email, string username)
|
||||
{
|
||||
ApiMail confirmMail = new ApiMail("MailPasswordResetOK");
|
||||
|
||||
confirmMail.AddString("username", username);
|
||||
|
||||
return confirmMail.Send(email, "Your account password was reset");
|
||||
}
|
||||
|
||||
public void DemandModerator(JWTUser user)
|
||||
{
|
||||
if (!user.Claims.Contains("moderator")) throw new Exception("Requires Moderator level status");
|
||||
}
|
||||
|
||||
public void DemandAdmin(JWTUser user)
|
||||
{
|
||||
if (!user.Claims.Contains("admin")) throw new Exception("Requires Admin level status");
|
||||
}
|
||||
|
||||
public void DemandModerator(HttpRequest request)
|
||||
{
|
||||
DemandModerator(RequireAuthentication(request));
|
||||
}
|
||||
|
||||
public void DemandAdmin(HttpRequest request)
|
||||
{
|
||||
DemandAdmin(RequireAuthentication(request));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes a user's password.
|
||||
/// </summary>
|
||||
/// <param name="user_id"></param>
|
||||
/// <param name="new_password"></param>
|
||||
public void ChangePassword(uint user_id, string new_password)
|
||||
{
|
||||
using (var da = DAFactory.Get())
|
||||
{
|
||||
var passhash = PasswordHasher.Hash(new_password);
|
||||
var authSettings = new Database.DA.Users.UserAuthenticate();
|
||||
authSettings.scheme_class = passhash.scheme;
|
||||
authSettings.data = passhash.data;
|
||||
authSettings.user_id = user_id;
|
||||
|
||||
da.Users.UpdateAuth(authSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a brand new user in db.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="ip"></param>
|
||||
/// <returns></returns>
|
||||
public Database.DA.Users.User CreateUser(string username, string email, string password, string ip)
|
||||
{
|
||||
using (var da = DAFactory.Get())
|
||||
{
|
||||
var userModel = new Database.DA.Users.User();
|
||||
userModel.username = username;
|
||||
userModel.email = email;
|
||||
userModel.is_admin = false;
|
||||
userModel.is_moderator = false;
|
||||
userModel.user_state = Database.DA.Users.UserState.valid;
|
||||
userModel.register_date = Epoch.Now;
|
||||
userModel.is_banned = false;
|
||||
userModel.register_ip = ip;
|
||||
userModel.last_ip = ip;
|
||||
|
||||
var passhash = PasswordHasher.Hash(password);
|
||||
var authSettings = new Database.DA.Users.UserAuthenticate();
|
||||
authSettings.scheme_class = passhash.scheme;
|
||||
authSettings.data = passhash.data;
|
||||
|
||||
try
|
||||
{
|
||||
var userId = da.Users.Create(userModel);
|
||||
authSettings.user_id = userId;
|
||||
da.Users.CreateAuth(authSettings);
|
||||
return da.Users.GetById(userId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
server/FSO.Server.Api.Core/ApiConfig.cs
Executable file
42
server/FSO.Server.Api.Core/ApiConfig.cs
Executable file
|
@ -0,0 +1,42 @@
|
|||
using FSO.Server.Api.Core.Services;
|
||||
|
||||
namespace FSO.Server.Api.Core
|
||||
{
|
||||
public class ApiConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// How long an auth ticket is valid for
|
||||
/// </summary>
|
||||
public int AuthTicketDuration = 300;
|
||||
|
||||
/// <summary>
|
||||
/// If non-null, the user must provide this key to register an account.
|
||||
/// </summary>
|
||||
public string Regkey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, only authentication from moderators and admins will be accepted
|
||||
/// </summary>
|
||||
public bool Maintainance { get; set; }
|
||||
|
||||
public string Secret { get; set; }
|
||||
|
||||
public string UpdateUrl { get; set; }
|
||||
public string CDNUrl { get; set; }
|
||||
|
||||
public string NFSdir { get; set; }
|
||||
|
||||
public string SmtpHost { get; set; }
|
||||
public int SmtpPort { get; set; }
|
||||
public string SmtpPassword { get; set; }
|
||||
public string SmtpUser { get; set; }
|
||||
|
||||
public bool SmtpEnabled { get; set; }
|
||||
public bool UseProxy { get; set; }
|
||||
|
||||
public int? UpdateID { get; set; }
|
||||
|
||||
public string BranchName { get; set; } = "dev";
|
||||
public IUpdateUploader UpdateUploader { get; set; }
|
||||
}
|
||||
}
|
157
server/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs
Executable file
157
server/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs
Executable file
|
@ -0,0 +1,157 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Api.Core.Models;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Database.DA.DbEvents;
|
||||
using FSO.Server.Database.DA.Tuning;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/events")]
|
||||
[ApiController]
|
||||
public class AdminEventsController : ControllerBase
|
||||
{
|
||||
//List events
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset, string order)
|
||||
{
|
||||
if (limit == 0) limit = 20;
|
||||
if (order == null) order = "start_day";
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Events.All((int)offset, (int)limit, order);
|
||||
return ApiResponse.PagedList<DbEvent>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("presets")]
|
||||
public IActionResult GetPresets()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Tuning.GetAllPresets().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("presets")]
|
||||
public IActionResult CreatePreset([FromBody]PresetCreateModel request)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
//make the preset first
|
||||
var preset_id = da.Tuning.CreatePreset(
|
||||
new DbTuningPreset()
|
||||
{
|
||||
name = request.name,
|
||||
description = request.description,
|
||||
flags = request.flags
|
||||
});
|
||||
|
||||
foreach (var item in request.items)
|
||||
{
|
||||
da.Tuning.CreatePresetItem(new DbTuningPresetItem()
|
||||
{
|
||||
preset_id = preset_id,
|
||||
tuning_type = item.tuning_type,
|
||||
tuning_table = item.tuning_table,
|
||||
tuning_index = item.tuning_index,
|
||||
value = item.value
|
||||
});
|
||||
}
|
||||
return new JsonResult(da.Tuning.GetAllPresets().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("presets/{preset_id}")]
|
||||
public IActionResult GetPresetEntries(int preset_id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Tuning.GetPresetItems(preset_id).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("presets/{preset_id}")]
|
||||
public IActionResult DeletePreset(int preset_id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return da.Tuning.DeletePreset(preset_id) ? (IActionResult)Ok() : NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// POST admin/updates (start update generation)
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromBody]EventCreateModel request)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
DbEventType type;
|
||||
try
|
||||
{
|
||||
type = Enum.Parse<DbEventType>(request.type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BadRequest("Event type must be one of:" + string.Join(", ", Enum.GetNames(typeof(DbEventType))));
|
||||
}
|
||||
var model = new DbEvent()
|
||||
{
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
start_day = request.start_day,
|
||||
end_day = request.end_day,
|
||||
type = type,
|
||||
value = request.value,
|
||||
value2 = request.value2,
|
||||
mail_subject = request.mail_subject,
|
||||
mail_message = request.mail_message,
|
||||
mail_sender = request.mail_sender,
|
||||
mail_sender_name = request.mail_sender_name
|
||||
};
|
||||
return new JsonResult(new { id = da.Events.Add(model) });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[Route("{id}")]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (!da.Events.Delete(id)) return NotFound();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
30
server/FSO.Server.Api.Core/Controllers/Admin/AdminHostsController.cs
Executable file
30
server/FSO.Server.Api.Core/Controllers/Admin/AdminHostsController.cs
Executable file
|
@ -0,0 +1,30 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/hosts")]
|
||||
[ApiController]
|
||||
public class AdminHostsController : ControllerBase
|
||||
{
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var hosts = api.HostPool.GetAll();
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, hosts.Select(x => new {
|
||||
role = x.Role,
|
||||
call_sign = x.CallSign,
|
||||
internal_host = x.InternalHost,
|
||||
public_host = x.PublicHost,
|
||||
connected = x.Connected,
|
||||
time_boot = x.BootTime
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
129
server/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs
Executable file
129
server/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs
Executable file
|
@ -0,0 +1,129 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/oauth/token")]
|
||||
[ApiController]
|
||||
public class AdminOAuthController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromForm] AuthRequest auth)
|
||||
{
|
||||
if (auth == null) Ok();
|
||||
if (auth.grant_type == "password")
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var user = da.Users.GetByUsername(auth.username);
|
||||
if (user == null || user.is_banned || !(user.is_admin || user.is_moderator))
|
||||
{
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "unauthorized_client",
|
||||
error_description = "user_credentials_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
var accLock = da.Users.GetRemainingAuth(user.user_id, ip);
|
||||
if (accLock != null && (accLock.active || accLock.count >= AuthLoginController.LockAttempts) && accLock.expire_time > Epoch.Now)
|
||||
{
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "unauthorized_client",
|
||||
error_description = "account_locked"
|
||||
});
|
||||
}
|
||||
|
||||
var authSettings = da.Users.GetAuthenticationSettings(user.user_id);
|
||||
var isPasswordCorrect = PasswordHasher.Verify(auth.password, new PasswordHash
|
||||
{
|
||||
data = authSettings.data,
|
||||
scheme = authSettings.scheme_class
|
||||
});
|
||||
|
||||
if (!isPasswordCorrect)
|
||||
{
|
||||
var durations = AuthLoginController.LockDuration;
|
||||
var failDelay = 60 * durations[Math.Min(durations.Length - 1, da.Users.FailedConsecutive(user.user_id, ip))];
|
||||
if (accLock == null)
|
||||
{
|
||||
da.Users.NewFailedAuth(user.user_id, ip, (uint)failDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = da.Users.FailedAuth(accLock.attempt_id, (uint)failDelay, AuthLoginController.LockAttempts);
|
||||
}
|
||||
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "unauthorized_client",
|
||||
error_description = "user_credentials_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
da.Users.SuccessfulAuth(user.user_id, ip);
|
||||
|
||||
JWTUser identity = new JWTUser();
|
||||
identity.UserName = user.username;
|
||||
var claims = new List<string>();
|
||||
if (user.is_admin || user.is_moderator)
|
||||
{
|
||||
claims.Add("moderator");
|
||||
}
|
||||
if (user.is_admin)
|
||||
{
|
||||
claims.Add("admin");
|
||||
}
|
||||
|
||||
identity.Claims = claims;
|
||||
identity.UserID = user.user_id;
|
||||
|
||||
var token = api.JWT.CreateToken(identity);
|
||||
|
||||
var response = ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthSuccess
|
||||
{
|
||||
access_token = token.Token,
|
||||
expires_in = token.ExpiresIn
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "invalid_request",
|
||||
error_description = "unknown grant_type"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class OAuthError
|
||||
{
|
||||
public string error_description { get; set; }
|
||||
public string error { get; set; }
|
||||
}
|
||||
|
||||
public class OAuthSuccess
|
||||
{
|
||||
public string access_token { get; set; }
|
||||
public int expires_in { get; set; }
|
||||
}
|
||||
|
||||
public class AuthRequest
|
||||
{
|
||||
public string grant_type { get; set; }
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
}
|
69
server/FSO.Server.Api.Core/Controllers/Admin/AdminShardsController.cs
Executable file
69
server/FSO.Server.Api.Core/Controllers/Admin/AdminShardsController.cs
Executable file
|
@ -0,0 +1,69 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/shards")]
|
||||
[ApiController]
|
||||
public class AdminShardsController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var shards = db.Shards.All();
|
||||
return ApiResponse.Json(HttpStatusCode.OK, shards);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("shutdown")]
|
||||
public IActionResult shutdown(ShutdownModel sd)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
ShutdownType type = ShutdownType.SHUTDOWN;
|
||||
if (sd.update) type = ShutdownType.UPDATE;
|
||||
else if (sd.restart) type = ShutdownType.RESTART;
|
||||
|
||||
api.RequestShutdown((uint)sd.timeout, type);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, true);
|
||||
}
|
||||
|
||||
[HttpPost("announce")]
|
||||
public IActionResult announce(AnnouncementModel an)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
api.BroadcastMessage(an.sender, an.subject, an.message);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class AnnouncementModel
|
||||
{
|
||||
public string sender;
|
||||
public string subject;
|
||||
public string message;
|
||||
public int[] shard_ids;
|
||||
}
|
||||
|
||||
public class ShutdownModel
|
||||
{
|
||||
public int timeout;
|
||||
public bool restart;
|
||||
public bool update;
|
||||
public int[] shard_ids;
|
||||
}
|
||||
}
|
73
server/FSO.Server.Api.Core/Controllers/Admin/AdminTasksController.cs
Executable file
73
server/FSO.Server.Api.Core/Controllers/Admin/AdminTasksController.cs
Executable file
|
@ -0,0 +1,73 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Database.DA.Tasks;
|
||||
using FSO.Server.Protocol.Gluon.Packets;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/tasks")]
|
||||
[ApiController]
|
||||
public class AdminTasksController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Tasks.All((int)offset, (int)limit);
|
||||
return ApiResponse.PagedList<DbTask>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("request")]
|
||||
public IActionResult request([FromBody] TaskRequest task)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
var taskServer = api.HostPool.GetByRole(Database.DA.Hosts.DbHostRole.task).FirstOrDefault();
|
||||
if (taskServer == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = taskServer.Call(new RequestTask()
|
||||
{
|
||||
TaskType = task.task_type.ToString(),
|
||||
ParameterJson = JsonConvert.SerializeObject(task.parameter),
|
||||
ShardId = (task.shard_id == null || !task.shard_id.HasValue) ? -1 : task.shard_id.Value
|
||||
}).Result;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public class TaskRequest
|
||||
{
|
||||
public DbTaskType task_type;
|
||||
public int? shard_id;
|
||||
public dynamic parameter;
|
||||
}
|
||||
}
|
172
server/FSO.Server.Api.Core/Controllers/Admin/AdminUpdatesController.cs
Executable file
172
server/FSO.Server.Api.Core/Controllers/Admin/AdminUpdatesController.cs
Executable file
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FSO.Server.Api.Core.Models;
|
||||
using FSO.Server.Api.Core.Services;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Database.DA.Updates;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/updates")]
|
||||
public class AdminUpdatesController : ControllerBase
|
||||
{
|
||||
//List updates
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset, string order)
|
||||
{
|
||||
if (limit == 0) limit = 20;
|
||||
if (order == null) order = "date";
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Updates.All((int)offset, (int)limit);
|
||||
return ApiResponse.PagedList<DbUpdate>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GET all branches
|
||||
[HttpGet("branches")]
|
||||
public IActionResult GetBranches()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Updates.GetBranches().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// GET all addons
|
||||
[HttpGet("addons")]
|
||||
public IActionResult GetAddons()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Updates.GetAddons(20).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// POST create a branch.
|
||||
[HttpPost("branches")]
|
||||
public IActionResult AddBranch(DbUpdateBranch branch)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (da.Updates.AddBranch(branch)) return Ok();
|
||||
else return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// POST update a branch.
|
||||
[HttpPost("branches/{id}")]
|
||||
public IActionResult UpdateBranch(DbUpdateBranch branch)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (da.Updates.UpdateBranchInfo(branch)) return Ok();
|
||||
else return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
public class AddonUploadModel
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public IFormFile clientAddon { get; set; }
|
||||
public IFormFile serverAddon { get; set; }
|
||||
}
|
||||
|
||||
static int AddonRequestID = 0;
|
||||
[HttpPost("uploadaddon")]
|
||||
[DisableRequestSizeLimit]
|
||||
[RequestFormLimits(MultipartBodyLengthLimit = 500000000)]
|
||||
public async Task<IActionResult> UploadAddon(AddonUploadModel upload)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var reqID = ++AddonRequestID;
|
||||
|
||||
var info = new DbUpdateAddon();
|
||||
if (upload.name == null || upload.name.Length > 128) return BadRequest("Invalid name.");
|
||||
if (upload.description == null || upload.description.Length > 1024) return BadRequest("Invalid description.");
|
||||
info.name = upload.name;
|
||||
info.description = upload.description;
|
||||
info.date = DateTime.UtcNow;
|
||||
|
||||
if (upload.clientAddon == null && upload.serverAddon == null)
|
||||
return BadRequest("client or server addon binary must be uploaded.");
|
||||
|
||||
var addonID = DateTime.UtcNow.Ticks;
|
||||
Directory.CreateDirectory("updateTemp/addons/");
|
||||
if (upload.clientAddon != null)
|
||||
{
|
||||
using (var file = System.IO.File.Open($"updateTemp/addons/client{reqID}.zip", FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await upload.clientAddon.CopyToAsync(file);
|
||||
}
|
||||
info.addon_zip_url = await api.UpdateUploader.UploadFile($"addons/client{addonID}.zip", $"updateTemp/addons/client{reqID}.zip", $"addon-{addonID}");
|
||||
System.IO.File.Delete($"updateTemp/addons/client{reqID}.zip");
|
||||
}
|
||||
|
||||
if (upload.serverAddon != null)
|
||||
{
|
||||
using (var file = System.IO.File.Open($"updateTemp/addons/server{reqID}.zip", FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await upload.serverAddon.CopyToAsync(file);
|
||||
}
|
||||
info.server_zip_url = await api.UpdateUploader.UploadFile($"addons/server{addonID}.zip", $"updateTemp/addons/server{reqID}.zip", $"addon-{addonID}");
|
||||
System.IO.File.Delete($"updateTemp/addons/server{reqID}.zip");
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
da.Updates.AddAddon(info);
|
||||
return new JsonResult(info);
|
||||
}
|
||||
}
|
||||
|
||||
// GET status for ongoing update generation
|
||||
[HttpGet("updateTask/{id}")]
|
||||
public IActionResult GetTask(int id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var task = GenerateUpdateService.INSTANCE.GetTask(id);
|
||||
if (task == null) return NotFound();
|
||||
else return new JsonResult(task);
|
||||
}
|
||||
|
||||
// POST admin/updates (start update generation)
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromBody]UpdateCreateModel request)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var task = GenerateUpdateService.INSTANCE.CreateTask(request);
|
||||
return new JsonResult(task);
|
||||
}
|
||||
}
|
||||
}
|
334
server/FSO.Server.Api.Core/Controllers/Admin/AdminUsersController.cs
Executable file
334
server/FSO.Server.Api.Core/Controllers/Admin/AdminUsersController.cs
Executable file
|
@ -0,0 +1,334 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.Inbox;
|
||||
using FSO.Server.Database.DA.Users;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/users")]
|
||||
[ApiController]
|
||||
public class AdminUsersController : ControllerBase
|
||||
{
|
||||
//Get information about me, useful for the admin user interface to disable UI based on who you login as
|
||||
public IActionResult current()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var user = api.RequireAuthentication(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var userModel = da.Users.GetById(user.UserID);
|
||||
if (userModel == null)
|
||||
{
|
||||
throw new Exception("Unable to find user");
|
||||
}
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Get the attributes of a specific user
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult Get(string id)
|
||||
{
|
||||
if (id == "current") return current();
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var userModel = da.Users.GetById(uint.Parse(id));
|
||||
if (userModel == null) { throw new Exception("Unable to find user"); }
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unbans a user by IP and user.
|
||||
/// </summary>
|
||||
/// <param name="user_id">ID of user to unban.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/unban")]
|
||||
public IActionResult UnbanUser([FromBody] string user_id)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
User userModel = da.Users.GetById(uint.Parse(user_id));
|
||||
|
||||
if(userModel.is_banned)
|
||||
{
|
||||
da.Users.UpdateBanned(uint.Parse(user_id), false);
|
||||
}
|
||||
|
||||
var ban = da.Bans.GetByIP(userModel.last_ip);
|
||||
|
||||
if (ban!=null)
|
||||
{
|
||||
da.Bans.Remove(userModel.user_id);
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an in-game email message to a player.
|
||||
/// </summary>
|
||||
/// <param name="mail"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/mail")]
|
||||
public IActionResult SendMail(MailCreateModel mail)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
User recipient = da.Users.GetById(uint.Parse(mail.target_id));
|
||||
|
||||
if (recipient == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_target_id"
|
||||
});
|
||||
}
|
||||
|
||||
if (mail.subject.Trim() == "")
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "subject_empty"
|
||||
});
|
||||
}
|
||||
|
||||
if (mail.body.Trim() == "")
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "body_empty"
|
||||
});
|
||||
}
|
||||
|
||||
// Save mail in db
|
||||
int message_id = da.Inbox.CreateMessage(new DbInboxMsg
|
||||
{
|
||||
sender_id = 2147483648,
|
||||
target_id = uint.Parse(mail.target_id),
|
||||
subject = mail.subject,
|
||||
body = mail.body,
|
||||
sender_name = "FreeSO Staff",
|
||||
time = DateTime.UtcNow,
|
||||
msg_type = 4,
|
||||
msg_subtype = 0,
|
||||
read_state = 0,
|
||||
});
|
||||
|
||||
// Try and notify the user ingame
|
||||
api.RequestMailNotify(message_id, mail.subject, mail.body, uint.Parse(mail.target_id));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a user out the current session.
|
||||
/// </summary>
|
||||
/// <param name="kick"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/kick")]
|
||||
public IActionResult KickUser([FromBody] string user_id)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandModerator(Request);
|
||||
|
||||
api.RequestUserDisconnect(uint.Parse(user_id));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new {
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bans a user and kicks them.
|
||||
/// </summary>
|
||||
/// <param name="ban"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/ban")]
|
||||
public IActionResult BanUser(BanCreateModel ban)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
User userModel = da.Users.GetById(uint.Parse(ban.user_id));
|
||||
|
||||
if (userModel == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_id"
|
||||
});
|
||||
}
|
||||
|
||||
if (ban.ban_type == "ip")
|
||||
{
|
||||
if (da.Bans.GetByIP(userModel.last_ip) != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "already_banned"
|
||||
});
|
||||
}
|
||||
|
||||
if (userModel.last_ip == "127.0.0.1")
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_ip"
|
||||
});
|
||||
}
|
||||
|
||||
da.Bans.Add(userModel.last_ip, userModel.user_id, ban.reason, int.Parse(ban.end_date), userModel.client_id);
|
||||
|
||||
api.RequestUserDisconnect(userModel.user_id);
|
||||
|
||||
api.SendBanMail(userModel.username, userModel.email, uint.Parse(ban.end_date));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
else if (ban.ban_type == "user")
|
||||
{
|
||||
if (userModel.is_banned)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.NotFound, new
|
||||
{
|
||||
status = "already_banned"
|
||||
});
|
||||
}
|
||||
|
||||
da.Users.UpdateBanned(userModel.user_id, true);
|
||||
|
||||
api.RequestUserDisconnect(userModel.user_id);
|
||||
|
||||
api.SendBanMail(userModel.username, userModel.email, uint.Parse(ban.end_date));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_ban_type"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//List users
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset, string order)
|
||||
{
|
||||
if (limit == 0) limit = 20;
|
||||
if (order == null) order = "register_date";
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Users.All((int)offset, (int)limit);
|
||||
return ApiResponse.PagedList<User>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new user
|
||||
[HttpPost]
|
||||
public IActionResult Post(UserCreateModel user)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var nuser = api.RequireAuthentication(Request);
|
||||
api.DemandModerator(nuser);
|
||||
|
||||
if (user.is_admin)
|
||||
{
|
||||
//I need admin claim to do this
|
||||
api.DemandAdmin(nuser);
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var userModel = new User();
|
||||
userModel.username = user.username;
|
||||
userModel.email = user.email;
|
||||
userModel.is_admin = user.is_admin;
|
||||
userModel.is_moderator = user.is_moderator;
|
||||
userModel.user_state = UserState.valid;
|
||||
userModel.register_date = Epoch.Now;
|
||||
userModel.is_banned = false;
|
||||
|
||||
var userId = da.Users.Create(userModel);
|
||||
|
||||
userModel = da.Users.GetById(userId);
|
||||
if (userModel == null) { throw new Exception("Unable to find user"); }
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UserCreateModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string email { get; set; }
|
||||
public string password { get; set; }
|
||||
public bool is_admin { get; set; }
|
||||
public bool is_moderator { get; set; }
|
||||
}
|
||||
|
||||
public class BanCreateModel
|
||||
{
|
||||
public string ban_type { get; set; }
|
||||
public string user_id { get; set; }
|
||||
public string reason { get; set; }
|
||||
public string end_date { get; set; }
|
||||
}
|
||||
|
||||
public class MailCreateModel
|
||||
{
|
||||
public string target_id { get; set; }
|
||||
public string subject { get; set; }
|
||||
public string body { get; set; }
|
||||
public string sender_name { get; set; }
|
||||
}
|
||||
}
|
134
server/FSO.Server.Api.Core/Controllers/AuthLoginController.cs
Executable file
134
server/FSO.Server.Api.Core/Controllers/AuthLoginController.cs
Executable file
|
@ -0,0 +1,134 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.AuthTickets;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("AuthLogin")]
|
||||
[ApiController]
|
||||
public class AuthLoginController : ControllerBase
|
||||
{
|
||||
private static Func<IActionResult> ERROR_020 = printError("INV-020", "Please enter your member name and password.");
|
||||
private static Func<IActionResult> ERROR_110 = printError("INV-110", "The member name or password you have entered is incorrect. Please try again.");
|
||||
private static Func<IActionResult> ERROR_302 = printError("INV-302", "The game has experienced an internal error. Please try again.");
|
||||
private static Func<IActionResult> ERROR_160 = printError("INV-160", "The server is currently down for maintainance. Please try again later.");
|
||||
private static Func<IActionResult> ERROR_150 = printError("INV-150", "We're sorry, but your account has been suspended or cancelled.");
|
||||
private static string LOCK_MESSAGE = "Your account has been locked due to too many incorrect login attempts. " +
|
||||
"If you cannot remember your password, it can be reset at https://beta.freeso.org/forgot. Locked for: ";
|
||||
|
||||
public static int LockAttempts = 5;
|
||||
|
||||
public static int[] LockDuration = new int[] {
|
||||
5,
|
||||
15,
|
||||
30,
|
||||
60,
|
||||
120,
|
||||
720,
|
||||
1440
|
||||
};
|
||||
|
||||
// GET api/<controller>
|
||||
[HttpGet]
|
||||
public IActionResult Get(string username, string password, string version, string clientid)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
return ERROR_020();
|
||||
}
|
||||
|
||||
AuthTicket ticket = null;
|
||||
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var user = db.Users.GetByUsername(username);
|
||||
if (user == null)
|
||||
{
|
||||
return ERROR_110();
|
||||
}
|
||||
|
||||
if (user.is_banned)
|
||||
{
|
||||
return ERROR_150();
|
||||
}
|
||||
|
||||
if (api.Config.Maintainance && !(user.is_admin || user.is_moderator))
|
||||
{
|
||||
return ERROR_160();
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
|
||||
var accLock = db.Users.GetRemainingAuth(user.user_id, ip);
|
||||
if (accLock != null && (accLock.active || accLock.count >= LockAttempts) && accLock.expire_time > Epoch.Now)
|
||||
{
|
||||
return printError("INV-170", LOCK_MESSAGE + Epoch.HMSRemaining(accLock.expire_time))();
|
||||
}
|
||||
|
||||
var authSettings = db.Users.GetAuthenticationSettings(user.user_id);
|
||||
var isPasswordCorrect = PasswordHasher.Verify(password, new PasswordHash
|
||||
{
|
||||
data = authSettings.data,
|
||||
scheme = authSettings.scheme_class
|
||||
});
|
||||
|
||||
if (!isPasswordCorrect)
|
||||
{
|
||||
var failDelay = 60 * LockDuration[Math.Min(LockDuration.Length - 1, db.Users.FailedConsecutive(user.user_id, ip))];
|
||||
if (accLock == null)
|
||||
{
|
||||
db.Users.NewFailedAuth(user.user_id, ip, (uint)failDelay);
|
||||
} else
|
||||
{
|
||||
var remaining = db.Users.FailedAuth(accLock.attempt_id, (uint)failDelay, LockAttempts);
|
||||
if (remaining == 0)
|
||||
return printError("INV-170", LOCK_MESSAGE + Epoch.HMSRemaining(Epoch.Now + (uint)failDelay))();
|
||||
}
|
||||
return ERROR_110();
|
||||
}
|
||||
|
||||
var ban = db.Bans.GetByIP(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
return ERROR_110();
|
||||
}
|
||||
|
||||
db.Users.SuccessfulAuth(user.user_id, ip);
|
||||
db.Users.UpdateClientID(user.user_id, clientid ?? "0");
|
||||
|
||||
/** Make a ticket **/
|
||||
ticket = new AuthTicket();
|
||||
ticket.ticket_id = Guid.NewGuid().ToString().Replace("-", "");
|
||||
ticket.user_id = user.user_id;
|
||||
ticket.date = Epoch.Now;
|
||||
ticket.ip = ip;
|
||||
|
||||
db.AuthTickets.Create(ticket);
|
||||
db.Users.UpdateLastLogin(user.user_id, Epoch.Now);
|
||||
}
|
||||
var content = "Valid=TRUE\r\nTicket=" + ticket.ticket_id.ToString() + "\r\n";
|
||||
return ApiResponse.Plain(HttpStatusCode.OK, content);
|
||||
}
|
||||
|
||||
|
||||
public static Func<IActionResult> printError(String code, String message)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.AppendLine("Valid=FALSE");
|
||||
result.AppendLine("Ticket=0");
|
||||
result.AppendLine("reasontext=" + code + ";" + message);
|
||||
result.AppendLine("reasonurl=");
|
||||
|
||||
return () =>
|
||||
{
|
||||
return ApiResponse.Plain(HttpStatusCode.OK, result.ToString());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
48
server/FSO.Server.Api.Core/Controllers/AvatarDataController.cs
Executable file
48
server/FSO.Server.Api.Core/Controllers/AvatarDataController.cs
Executable file
|
@ -0,0 +1,48 @@
|
|||
using FSO.Common.Utils;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[Route("cityselector/app/AvatarDataServlet")]
|
||||
[ApiController]
|
||||
public class AvatarDataController : ControllerBase
|
||||
{
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var user = api.RequireAuthentication(Request);
|
||||
|
||||
var result = new XMLList<AvatarData>("The-Sims-Online");
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var avatars = db.Avatars.GetSummaryByUserId(user.UserID);
|
||||
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
result.Add(new AvatarData
|
||||
{
|
||||
ID = avatar.avatar_id,
|
||||
Name = avatar.name,
|
||||
ShardName = api.Shards.GetById(avatar.shard_id).Name,
|
||||
HeadOutfitID = avatar.head,
|
||||
BodyOutfitID = avatar.body,
|
||||
AppearanceType = (AvatarAppearanceType)Enum.Parse(typeof(AvatarAppearanceType), avatar.skin_tone.ToString()),
|
||||
Description = avatar.description,
|
||||
LotId = avatar.lot_id,
|
||||
LotName = avatar.lot_name,
|
||||
LotLocation = avatar.lot_location
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.Xml(HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
}
|
321
server/FSO.Server.Api.Core/Controllers/AvatarInfoController.cs
Executable file
321
server/FSO.Server.Api.Core/Controllers/AvatarInfoController.cs
Executable file
|
@ -0,0 +1,321 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Database.DA.Avatars;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class AvatarInfoController : ControllerBase
|
||||
{
|
||||
//get the avatars by user_id
|
||||
[Route("userapi/user/avatars")]
|
||||
public IActionResult GetByUser()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var user = api.RequireAuthentication(Request);
|
||||
if (!user.Claims.Contains("userReadPermissions")) return ApiResponse.Json(HttpStatusCode.OK, new JSONAvatarError("No read premissions found."));
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatars = da.Avatars.GetByUserId(user.UserID);
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
}
|
||||
var avatarsJson = new JSONAvatars();
|
||||
avatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarsJson);
|
||||
}
|
||||
}
|
||||
//get the avatar by id
|
||||
[HttpGet]
|
||||
[Route("userapi/avatars/{avartarId}")]
|
||||
public IActionResult GetByID(uint avartarId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatar = da.Avatars.Get(avartarId);
|
||||
if (avatar == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
|
||||
var avatarJson = new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarJson);
|
||||
}
|
||||
}
|
||||
//get the avatars by ids
|
||||
[Route("userapi/avatars")]
|
||||
public IActionResult GetByIDs([FromQuery(Name = "ids")]string idsString)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
try
|
||||
{
|
||||
uint[] ids = Array.ConvertAll(idsString.Split(","), uint.Parse);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatars = da.Avatars.GetMultiple(ids);
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
}
|
||||
var avatarsJson = new JSONAvatars();
|
||||
avatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarsJson);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Error during cast. (invalid_value)"));
|
||||
}
|
||||
}
|
||||
//gets all the avatars from one city
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/avatars/page/{pageNum}")]
|
||||
public IActionResult GetAll(int shardId,int pageNum, [FromQuery(Name = "avatars_on_page")]int perPage)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
if(perPage == 0)
|
||||
{
|
||||
perPage = 100;
|
||||
}
|
||||
if (perPage > 500) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("The max amount of avatars per page is 500"));
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
pageNum = pageNum - 1;
|
||||
|
||||
var avatars = da.Avatars.AllByPage(shardId, pageNum * perPage, perPage,"avatar_id");
|
||||
var avatarCount = avatars.Total;
|
||||
var totalPages = (avatars.Total - 1)/perPage + 1;
|
||||
|
||||
var pageAvatarsJson = new JSONAvatarsPage();
|
||||
pageAvatarsJson.total_avatars = avatarCount;
|
||||
pageAvatarsJson.page = pageNum + 1;
|
||||
pageAvatarsJson.total_pages = (int)totalPages;
|
||||
pageAvatarsJson.avatars_on_page = avatars.Count();
|
||||
|
||||
if (pageNum < 0 || pageNum >= (int)totalPages) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Page not found"));
|
||||
if (avatars == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
pageAvatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, pageAvatarsJson);
|
||||
}
|
||||
}
|
||||
//gets avatar by name
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/avatars/name/{name}")]
|
||||
public IActionResult GetByName(int shardId, string name)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatar = da.Avatars.SearchExact(shardId, name, 1).FirstOrDefault();
|
||||
if (avatar == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
|
||||
var avatarJson = new JSONAvatar();
|
||||
var avatarById = da.Avatars.Get(avatar.avatar_id);
|
||||
if (avatarById == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
avatarJson = (new JSONAvatar
|
||||
{
|
||||
avatar_id = avatarById.avatar_id,
|
||||
shard_id = avatarById.shard_id,
|
||||
name = avatarById.name,
|
||||
gender = avatarById.gender,
|
||||
date = avatarById.date,
|
||||
description = avatarById.description,
|
||||
current_job = avatarById.current_job,
|
||||
mayor_nhood = avatarById.mayor_nhood
|
||||
});
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarJson);
|
||||
}
|
||||
}
|
||||
//gets all the avatars that live in a specific neighbourhood
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/avatars/neighborhood/{nhoodId}")]
|
||||
public IActionResult GetByNhood(int shardId, uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.All(shardId).Where(x => x.neighborhood_id == nhoodId);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Lots not found"));
|
||||
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
if(lot.category != FSO.Common.Enum.LotCategory.community)
|
||||
{
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id);
|
||||
var avatars = da.Avatars.GetMultiple(roomies.ToArray());
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
var avatarsJson = new JSONAvatars();
|
||||
avatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarsJson);
|
||||
}
|
||||
}
|
||||
//get all online Avatars
|
||||
[HttpGet]
|
||||
[Route("userapi/avatars/online")]
|
||||
public IActionResult GetOnline([FromQuery(Name = "compact")]bool compact)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
List<JSONAvatarSmall> avatarSmallJson = new List<JSONAvatarSmall>();
|
||||
var avatarJson = new JSONAvatarOnline();
|
||||
if (compact)
|
||||
{
|
||||
var avatarStatus = da.AvatarClaims.GetAllActiveAvatarsCount();
|
||||
if (avatarStatus == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("ammount not found"));
|
||||
avatarJson.avatars_online_count = avatarStatus;
|
||||
}
|
||||
|
||||
|
||||
if (!compact)
|
||||
{
|
||||
var avatarStatus = da.AvatarClaims.GetAllActiveAvatars();
|
||||
if (avatarStatus == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatars not found"));
|
||||
|
||||
foreach (var avatar in avatarStatus)
|
||||
{
|
||||
uint location = 0;
|
||||
if (avatar.privacy_mode == 0)
|
||||
{
|
||||
location = avatar.location;
|
||||
}
|
||||
avatarSmallJson.Add(new JSONAvatarSmall
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
name = avatar.name,
|
||||
privacy_mode = avatar.privacy_mode,
|
||||
location = location
|
||||
});
|
||||
|
||||
}
|
||||
avatarJson.avatars_online_count = avatarStatus.Count();
|
||||
}
|
||||
|
||||
avatarJson.avatars = avatarSmallJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONAvatarError
|
||||
{
|
||||
public string error;
|
||||
public JSONAvatarError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONAvatarsPage
|
||||
{
|
||||
public int page { get; set; }
|
||||
public int total_pages { get; set; }
|
||||
public int total_avatars { get; set; }
|
||||
public int avatars_on_page { get; set; }
|
||||
public List<JSONAvatar> avatars { get; set; }
|
||||
}
|
||||
public class JSONAvatarOnline
|
||||
{
|
||||
public int? avatars_online_count { get; set; }
|
||||
public List<JSONAvatarSmall> avatars { get; set; }
|
||||
}
|
||||
public class JSONAvatarSmall
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public byte privacy_mode { get; set; }
|
||||
public uint location { get; set; }
|
||||
}
|
||||
public class JSONAvatars
|
||||
{
|
||||
public List<JSONAvatar> avatars { get; set; }
|
||||
}
|
||||
public class JSONAvatar
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public DbAvatarGender gender { get; set; }
|
||||
public uint date { get; set; }
|
||||
public string description { get; set; }
|
||||
public ushort current_job { get; set; }
|
||||
public int? mayor_nhood { get; set; }
|
||||
}
|
||||
}
|
134
server/FSO.Server.Api.Core/Controllers/BulletinInfoController.cs
Executable file
134
server/FSO.Server.Api.Core/Controllers/BulletinInfoController.cs
Executable file
|
@ -0,0 +1,134 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Database.DA.Bulletin;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class BulletinInfoController : ControllerBase
|
||||
{
|
||||
[HttpGet("nhoodId")]
|
||||
[Route("userapi/neighborhood/{nhoodId}/bulletins")]
|
||||
public IActionResult GetByNhoodAndAfter(uint nhoodId, [FromQuery(Name = "after")]uint after)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (after == null) after = 0;
|
||||
var bulletins = da.BulletinPosts.GetByNhoodId(nhoodId, after);
|
||||
if (bulletins == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONBulletinError("Bulletins not found"));
|
||||
|
||||
List<JSONBulletin> bulletinJson = new List<JSONBulletin>();
|
||||
foreach (var bulletin in bulletins)
|
||||
{
|
||||
bulletinJson.Add(new JSONBulletin
|
||||
{
|
||||
bulletin_id = bulletin.bulletin_id,
|
||||
neighborhood_id = bulletin.neighborhood_id,
|
||||
avatar_id = bulletin.avatar_id,
|
||||
title = bulletin.title,
|
||||
body = bulletin.body,
|
||||
date = bulletin.date,
|
||||
flags = bulletin.flags,
|
||||
lot_id = bulletin.lot_id,
|
||||
type = bulletin.type
|
||||
});
|
||||
|
||||
}
|
||||
var bulletinsJson = new JSONBulletins();
|
||||
bulletinsJson.bulletins = bulletinJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, bulletinsJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhood/{nhoodid}/bulletins/{bulletinId}")]
|
||||
public IActionResult GetByID(uint bulletinId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var bulletin = da.BulletinPosts.Get(bulletinId);
|
||||
if (bulletin == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONBulletinError("Bulletin not found"));
|
||||
|
||||
var bulletinJson = new JSONBulletin();
|
||||
bulletinJson = new JSONBulletin
|
||||
{
|
||||
bulletin_id = bulletin.bulletin_id,
|
||||
neighborhood_id = bulletin.neighborhood_id,
|
||||
avatar_id = bulletin.avatar_id,
|
||||
title = bulletin.title,
|
||||
body = bulletin.body,
|
||||
date = bulletin.date,
|
||||
flags = bulletin.flags,
|
||||
lot_id = bulletin.lot_id,
|
||||
type = bulletin.type
|
||||
};
|
||||
return ApiResponse.Json(HttpStatusCode.OK, bulletinJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhood/{nhoodId}/bulletins/type/{bulletinType}")]
|
||||
public IActionResult GetByNhoodAndType(uint nhoodId,DbBulletinType bulletinType)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var bulletins = da.BulletinPosts.GetByNhoodId(nhoodId, 0).Where(x => x.type == bulletinType);
|
||||
if (bulletins == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONBulletinError("Bulletins not found"));
|
||||
|
||||
List<JSONBulletin> bulletinJson = new List<JSONBulletin>();
|
||||
foreach (var bulletin in bulletins)
|
||||
{
|
||||
bulletinJson.Add(new JSONBulletin
|
||||
{
|
||||
bulletin_id = bulletin.bulletin_id,
|
||||
neighborhood_id = bulletin.neighborhood_id,
|
||||
avatar_id = bulletin.avatar_id,
|
||||
title = bulletin.title,
|
||||
body = bulletin.body,
|
||||
date = bulletin.date,
|
||||
flags = bulletin.flags,
|
||||
lot_id = bulletin.lot_id,
|
||||
type = bulletin.type
|
||||
});
|
||||
|
||||
}
|
||||
var bulletinsJson = new JSONBulletins();
|
||||
bulletinsJson.bulletins = bulletinJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, bulletinsJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONBulletinError
|
||||
{
|
||||
public string error;
|
||||
public JSONBulletinError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONBulletins
|
||||
{
|
||||
public List<JSONBulletin> bulletins { get; set; }
|
||||
}
|
||||
public class JSONBulletin
|
||||
{
|
||||
public uint bulletin_id { get; set; }
|
||||
public int neighborhood_id { get; set; }
|
||||
public uint? avatar_id { get; set; }
|
||||
public string title { get; set; }
|
||||
public string body { get; set; }
|
||||
public uint date { get; set; }
|
||||
public uint flags { get; set; }
|
||||
public int? lot_id { get; set; }
|
||||
public DbBulletinType type { get; set; }
|
||||
}
|
||||
}
|
73
server/FSO.Server.Api.Core/Controllers/CityJSONController.cs
Executable file
73
server/FSO.Server.Api.Core/Controllers/CityJSONController.cs
Executable file
|
@ -0,0 +1,73 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class CityJSONController : ControllerBase
|
||||
{
|
||||
private static object ModelLock = new object { };
|
||||
private static CityInfoModel LastModel = new CityInfoModel();
|
||||
private static uint LastModelUpdate;
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/city.json")]
|
||||
public IActionResult Get(int shardid)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var now = Epoch.Now;
|
||||
if (LastModelUpdate < now - 15)
|
||||
{
|
||||
LastModelUpdate = now;
|
||||
lock (ModelLock)
|
||||
{
|
||||
LastModel = new CityInfoModel();
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.AllLocations(shardid);
|
||||
var lotstatus = da.LotClaims.AllLocations(shardid);
|
||||
LastModel.reservedLots = lots.ConvertAll(x => x.location).ToArray();
|
||||
LastModel.names = lots.ConvertAll(x => x.name).ToArray();
|
||||
LastModel.activeLots = lotstatus.ConvertAll(x => x.location).ToArray();
|
||||
LastModel.onlineCount = lotstatus.ConvertAll(x => x.active).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
lock (ModelLock)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, LastModel);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/thumbwork.json")]
|
||||
public IActionResult ThumbWork()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var work = da.Lots.Get3DWork();
|
||||
if (work == null) return ApiResponse.Plain(HttpStatusCode.OK, "");
|
||||
else
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, work);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CityInfoModel
|
||||
{
|
||||
public string[] names;
|
||||
public uint[] reservedLots;
|
||||
public uint[] activeLots;
|
||||
public int[] onlineCount;
|
||||
}
|
||||
}
|
79
server/FSO.Server.Api.Core/Controllers/ElectionInfoController.cs
Executable file
79
server/FSO.Server.Api.Core/Controllers/ElectionInfoController.cs
Executable file
|
@ -0,0 +1,79 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using FSO.Server.Database.DA.Elections;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class ElectionController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhood/{nhoodId}/elections")]
|
||||
public IActionResult GetByNhood(uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var nhood = da.Neighborhoods.Get(nhoodId);
|
||||
if (nhood.election_cycle_id == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONElectionError("Election cycle not found"));
|
||||
|
||||
var electionCycle = da.Elections.GetCycle((uint)nhood.election_cycle_id);
|
||||
|
||||
var electionCandidates = new List<DbElectionCandidate>();
|
||||
if (electionCycle.current_state == Database.DA.Elections.DbElectionCycleState.election)
|
||||
electionCandidates = da.Elections.GetCandidates(electionCycle.cycle_id, Database.DA.Elections.DbCandidateState.running);
|
||||
|
||||
if (electionCycle.current_state == Database.DA.Elections.DbElectionCycleState.ended)
|
||||
electionCandidates = da.Elections.GetCandidates(electionCycle.cycle_id, Database.DA.Elections.DbCandidateState.won);
|
||||
|
||||
if (electionCycle == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONElectionError("Election cycle not found"));
|
||||
|
||||
List<JSONCandidates> candidatesJson = new List<JSONCandidates>();
|
||||
foreach (var candidate in electionCandidates)
|
||||
{
|
||||
candidatesJson.Add(new JSONCandidates
|
||||
{
|
||||
candidate_avatar_id = candidate.candidate_avatar_id,
|
||||
comment = candidate.comment,
|
||||
state = candidate.state
|
||||
});
|
||||
|
||||
}
|
||||
var electionJson = new JSONElections();
|
||||
electionJson.candidates = candidatesJson;
|
||||
electionJson.current_state = electionCycle.current_state;
|
||||
electionJson.neighborhood_id = nhood.neighborhood_id;
|
||||
electionJson.start_date = electionCycle.start_date;
|
||||
electionJson.end_date = electionCycle.end_date;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, electionJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONElectionError
|
||||
{
|
||||
public string error;
|
||||
public JSONElectionError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONElections
|
||||
{
|
||||
public DbElectionCycleState current_state { get; set; }
|
||||
public int neighborhood_id { get; set; }
|
||||
public uint start_date { get; set; }
|
||||
public uint end_date { get; set; }
|
||||
public List<JSONCandidates> candidates { get; set; }
|
||||
}
|
||||
public class JSONCandidates
|
||||
{
|
||||
public uint candidate_avatar_id { get; set; }
|
||||
public string comment { get; set; }
|
||||
public DbCandidateState state { get; set; }
|
||||
}
|
||||
}
|
38
server/FSO.Server.Api.Core/Controllers/GameAPI/UpdateController.cs
Executable file
38
server/FSO.Server.Api.Core/Controllers/GameAPI/UpdateController.cs
Executable file
|
@ -0,0 +1,38 @@
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.GameAPI
|
||||
{
|
||||
[EnableCors]
|
||||
[Route("userapi/update")]
|
||||
public class UpdateController : ControllerBase
|
||||
{
|
||||
|
||||
// GET userapi/update
|
||||
// get recent PUBLISHED updates for the active branch, ordered by publish date
|
||||
[HttpGet()]
|
||||
public IActionResult Get(int id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var recents = da.Updates.GetRecentUpdatesForBranchByName(api.Config.BranchName, 20);
|
||||
return new JsonResult(recents.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// GET: userapi/update/<branch>
|
||||
// get recent PUBLISHED updates for a specific branch, ordered by publish date
|
||||
[HttpGet("{branch}")]
|
||||
public IActionResult Get(string branch)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var recents = da.Updates.GetRecentUpdatesForBranchByName(branch, 20);
|
||||
return new JsonResult(recents.ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
server/FSO.Server.Api.Core/Controllers/GithubController.cs
Executable file
77
server/FSO.Server.Api.Core/Controllers/GithubController.cs
Executable file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Octokit;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class GithubController : ControllerBase
|
||||
{
|
||||
readonly GitHubClient client =
|
||||
new GitHubClient(new ProductHeaderValue(Api.INSTANCE.Github.AppName), new Uri("https://github.com/"));
|
||||
|
||||
private string StoredToken;
|
||||
private static string CSRF;
|
||||
|
||||
// GET: /<controller>/
|
||||
[HttpGet]
|
||||
[Route("github/")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (Api.INSTANCE.Github == null) return NotFound();
|
||||
if (Api.INSTANCE.Github.AccessToken != null) return NotFound();
|
||||
|
||||
return Redirect(GetOauthLoginUrl());
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("github/callback")]
|
||||
public async Task<IActionResult> Callback(string code, string state)
|
||||
{
|
||||
if (Api.INSTANCE.Github == null) return NotFound();
|
||||
if (Api.INSTANCE.Github.AccessToken != null) return NotFound();
|
||||
|
||||
if (!String.IsNullOrEmpty(code))
|
||||
{
|
||||
var expectedState = CSRF;
|
||||
if (state != expectedState) throw new InvalidOperationException("SECURITY FAIL!");
|
||||
//CSRF = null;
|
||||
|
||||
var token = await client.Oauth.CreateAccessToken(
|
||||
new OauthTokenRequest(Api.INSTANCE.Github.ClientID, Api.INSTANCE.Github.ClientSecret, code)
|
||||
{
|
||||
RedirectUri = new Uri("http://localhost:80/github/callback")
|
||||
});
|
||||
StoredToken = token.AccessToken;
|
||||
}
|
||||
|
||||
return Ok(StoredToken);
|
||||
}
|
||||
|
||||
private string GetOauthLoginUrl()
|
||||
{
|
||||
var rngCsp = new RNGCryptoServiceProvider();
|
||||
string csrf = "";
|
||||
var random = new byte[24];
|
||||
rngCsp.GetBytes(random);
|
||||
for (int i=0; i<24; i++)
|
||||
{
|
||||
csrf += (char)('?' + random[i]/4);
|
||||
}
|
||||
CSRF = csrf;
|
||||
|
||||
// 1. Redirect users to request GitHub access
|
||||
var request = new OauthLoginRequest(Api.INSTANCE.Github.ClientID)
|
||||
{
|
||||
Scopes = { "admin:org", "repo" },
|
||||
State = csrf
|
||||
};
|
||||
var oauthLoginUrl = client.Oauth.GetGitHubLoginUrl(request);
|
||||
return oauthLoginUrl.ToString();
|
||||
}
|
||||
}
|
||||
}
|
93
server/FSO.Server.Api.Core/Controllers/InitialConnectController.cs
Executable file
93
server/FSO.Server.Api.Core/Controllers/InitialConnectController.cs
Executable file
|
@ -0,0 +1,93 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("cityselector/app/InitialConnectServlet")]
|
||||
[ApiController]
|
||||
public class InitialConnectController : ControllerBase
|
||||
{
|
||||
private static Func<IActionResult> ERROR_MISSING_TOKEN = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("501", "Token not found"));
|
||||
private static Func<IActionResult> ERROR_EXPIRED_TOKEN = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("502", "Token has expired"));
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Get(string ticket, string version)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
if (ticket == null || ticket == "" || version == null){
|
||||
return ERROR_MISSING_TOKEN();
|
||||
}
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var dbTicket = db.AuthTickets.Get(ticket);
|
||||
if (dbTicket == null){
|
||||
return ERROR_MISSING_TOKEN();
|
||||
}
|
||||
|
||||
db.AuthTickets.Delete((string)ticket);
|
||||
if (dbTicket.date + api.Config.AuthTicketDuration < Epoch.Now){
|
||||
return ERROR_EXPIRED_TOKEN();
|
||||
}
|
||||
|
||||
/** Is it a valid account? **/
|
||||
var user = db.Users.GetById(dbTicket.user_id);
|
||||
if (user == null){
|
||||
return ERROR_MISSING_TOKEN();
|
||||
}
|
||||
|
||||
//Use JWT to create and sign an auth cookies
|
||||
var session = new JWTUser()
|
||||
{
|
||||
UserID = user.user_id,
|
||||
UserName = user.username
|
||||
};
|
||||
|
||||
//TODO: This assumes 1 shard, when using multiple need to either have version download occour after
|
||||
//avatar select, or rework the tables
|
||||
var shardOne = api.Shards.GetById(1);
|
||||
|
||||
var token = api.JWT.CreateToken(session);
|
||||
|
||||
IActionResult response;
|
||||
if (shardOne.UpdateID != null)
|
||||
{
|
||||
var update = db.Updates.GetUpdate(shardOne.UpdateID.Value);
|
||||
response = ApiResponse.Xml(HttpStatusCode.OK, new UserAuthorized()
|
||||
{
|
||||
FSOBranch = shardOne.VersionName,
|
||||
FSOVersion = shardOne.VersionNumber,
|
||||
FSOUpdateUrl = update.full_zip,
|
||||
FSOCDNUrl = api.Config.CDNUrl
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
response = ApiResponse.Xml(HttpStatusCode.OK, new UserAuthorized()
|
||||
{
|
||||
FSOBranch = shardOne.VersionName,
|
||||
FSOVersion = shardOne.VersionNumber,
|
||||
FSOUpdateUrl = api.Config.UpdateUrl,
|
||||
FSOCDNUrl = api.Config.CDNUrl
|
||||
});
|
||||
}
|
||||
Response.Cookies.Append("fso", token.Token, new Microsoft.AspNetCore.Http.CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(1),
|
||||
Domain = Request.Host.Host,
|
||||
Path = "/"
|
||||
});
|
||||
//HttpContext.Current.Response.SetCookie(new HttpCookie("fso", token.Token));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
705
server/FSO.Server.Api.Core/Controllers/LotInfoController.cs
Executable file
705
server/FSO.Server.Api.Core/Controllers/LotInfoController.cs
Executable file
|
@ -0,0 +1,705 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Common.Enum;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
|
||||
public static class MemoryCacher
|
||||
{
|
||||
public static MemoryCache Default = new MemoryCache(new MemoryCacheOptions());
|
||||
public static object GetValue(string key)
|
||||
{
|
||||
MemoryCache memoryCache = Default;
|
||||
return memoryCache.Get(key);
|
||||
}
|
||||
|
||||
public static bool Add(string key, object value, DateTimeOffset absExpiration)
|
||||
{
|
||||
MemoryCache memoryCache = Default;
|
||||
return memoryCache.Set(key, value, absExpiration) == value;
|
||||
}
|
||||
|
||||
public static void Delete(string key)
|
||||
{
|
||||
MemoryCache memoryCache = Default;
|
||||
memoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class LotInfoController : ControllerBase
|
||||
{
|
||||
public static ConcurrentDictionary<int, ShardLocationCache> LotLocationCache = new ConcurrentDictionary<int, ShardLocationCache>();
|
||||
|
||||
public static int? IDForLocation(int shardid, uint loc)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var locToID = LotLocationCache.GetOrAdd(shardid, (ikey) =>
|
||||
{
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new ShardLocationCache(
|
||||
new ConcurrentDictionary<uint, int>(da.Lots.All(ikey).Select(x => new KeyValuePair<uint, int>(x.location, x.lot_id)))
|
||||
);
|
||||
}
|
||||
});
|
||||
if (DateTime.UtcNow - locToID.CreateTime > TimeSpan.FromMinutes(15))
|
||||
{
|
||||
ShardLocationCache removed;
|
||||
LotLocationCache.TryRemove(shardid, out removed);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return locToID.Dict.GetOrAdd(loc, (ikey) =>
|
||||
{
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return da.Lots.GetByLocation(shardid, ikey).lot_id;
|
||||
}
|
||||
});
|
||||
} catch (NullReferenceException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/{id}.png")]
|
||||
[ResponseCache(Duration = 60 * 60, Location = ResponseCacheLocation.Any)]
|
||||
public IActionResult Get(int shardid, uint id)
|
||||
{
|
||||
var dat = (byte[])MemoryCacher.GetValue("lt" + shardid + ":" + id);
|
||||
if (dat != null)
|
||||
{
|
||||
return File(dat, "image/png");
|
||||
}
|
||||
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = IDForLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var ndat = System.IO.File.ReadAllBytes(Path.Combine(api.Config.NFSdir, "Lots/" + lot.Value.ToString("x8") + "/thumb.png"));
|
||||
MemoryCacher.Add("lt" + shardid + ":" + id, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0)));
|
||||
|
||||
return File(ndat, "image/png");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/i{id}.json")]
|
||||
public IActionResult GetJSON(int shardid, uint id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var jlot = new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, jlot);
|
||||
}
|
||||
}
|
||||
|
||||
//New user API calls might replace old once later
|
||||
//get lot information by location
|
||||
[HttpGet]
|
||||
[Route("userapi/lots/{lotId}")]
|
||||
public IActionResult GetByID(int lotId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.Get(lotId);
|
||||
if (lot == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var lotJson = new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies,
|
||||
lot_id = lot.lot_id
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotJson);
|
||||
}
|
||||
}
|
||||
//get the lots by ids
|
||||
[Route("userapi/lots")]
|
||||
public IActionResult GetByIDs([FromQuery(Name = "ids")]string idsString)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
try
|
||||
{
|
||||
int[] ids = Array.ConvertAll(idsString.Split(","), int.Parse);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.GetMultiple(ids);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
|
||||
List<JSONLot> lotJson = new List<JSONLot>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
lotJson.Add(new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
}
|
||||
var lotsJson = new JSONLots();
|
||||
lotsJson.lots = lotJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotsJson);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Error during cast. (invalid_value)"));
|
||||
}
|
||||
}
|
||||
//gets all the lots from one city
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/page/{pageNum}")]
|
||||
public IActionResult GetAll(int shardId, int pageNum, [FromQuery(Name = "lots_on_page")]int perPage)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
if (perPage == 0)
|
||||
{
|
||||
perPage = 100;
|
||||
}
|
||||
if (perPage > 500) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("The max amount of lots per page is 500"));
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
pageNum = pageNum - 1;
|
||||
|
||||
var lots = da.Lots.AllByPage(shardId, pageNum * perPage, perPage,"lot_id");
|
||||
var lotCount = lots.Total;
|
||||
var totalPages = (lots.Total - 1) / perPage + 1;
|
||||
|
||||
var pageLotsJson = new JSONLotsPage();
|
||||
pageLotsJson.total_lots = lotCount;
|
||||
pageLotsJson.page = pageNum + 1;
|
||||
pageLotsJson.total_pages = (int)totalPages;
|
||||
pageLotsJson.lots_on_page = lots.Count();
|
||||
|
||||
if (pageNum < 0 || pageNum >= (int)totalPages) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Page not found"));
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
|
||||
List<JSONLotSmall> lotJson = new List<JSONLotSmall>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
lotJson.Add(new JSONLotSmall
|
||||
{
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
description = lot.description,
|
||||
category = lot.category,
|
||||
admit_mode = lot.admit_mode,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
}
|
||||
|
||||
pageLotsJson.lots = lotJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, pageLotsJson);
|
||||
}
|
||||
}
|
||||
//get lot information by location
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/location/{locationId}")]
|
||||
public IActionResult GetByLocation(int shardId, uint locationId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardId, locationId);
|
||||
if (lot == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var LotJSON = new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies,
|
||||
lot_id = lot.lot_id
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, LotJSON);
|
||||
}
|
||||
}
|
||||
//get lot information By neighbourhood
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/neighborhood/{nhoodId}")]
|
||||
public IActionResult GetByNhood(int shardId, uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.All(shardId).Where(x => x.neighborhood_id == nhoodId);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("lots not found"));
|
||||
List<JSONLotSmall> lotJson = new List<JSONLotSmall>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
lotJson.Add(new JSONLotSmall
|
||||
{
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
description = lot.description,
|
||||
category = lot.category,
|
||||
admit_mode = lot.admit_mode,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
}
|
||||
var lotsJson = new JSONLotsSmall();
|
||||
lotsJson.lots = lotJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotsJson);
|
||||
}
|
||||
}
|
||||
//get lot information by name
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/name/{lotName}")]
|
||||
public IActionResult GetByName(int shardId, string lotName)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByName(shardId, lotName);
|
||||
if (lot == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var lotJson = new JSONLot
|
||||
{
|
||||
lot_id = lot.lot_id,
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotJson);
|
||||
}
|
||||
}
|
||||
//get online lots
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/online")]
|
||||
public IActionResult GetOnline(int shardId, [FromQuery(Name = "compact")]bool compact)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
List<JSONLotSmall> lotSmallJson = new List<JSONLotSmall>();
|
||||
var lotsOnlineJson = new JSONLotsOnline();
|
||||
|
||||
if (!compact)
|
||||
{
|
||||
var activeLots = da.LotClaims.AllActiveLots(shardId);
|
||||
if (activeLots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
var totalAvatars = 0;
|
||||
foreach (var lot in activeLots)
|
||||
{
|
||||
lotSmallJson.Add(new JSONLotSmall
|
||||
{
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
description = lot.description,
|
||||
category = lot.category,
|
||||
admit_mode = lot.admit_mode,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
avatars_in_lot = lot.active,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
totalAvatars += lot.active;
|
||||
}
|
||||
lotsOnlineJson.total_lots_online = activeLots.Count();
|
||||
lotsOnlineJson.total_avatars_in_lots_online = totalAvatars;
|
||||
}
|
||||
else
|
||||
{
|
||||
var activeLots = da.LotClaims.AllLocations(shardId);
|
||||
if (activeLots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
var totalAvatars = 0;
|
||||
foreach (var lot in activeLots)
|
||||
{
|
||||
totalAvatars += lot.active;
|
||||
}
|
||||
|
||||
lotsOnlineJson.total_lots_online = activeLots.Count();
|
||||
lotsOnlineJson.total_avatars_in_lots_online = totalAvatars;
|
||||
}
|
||||
|
||||
lotsOnlineJson.lots = lotSmallJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotsOnlineJson);
|
||||
}
|
||||
}
|
||||
//get Top-100 lots by category
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/top100/category/{lotCategory}")]
|
||||
public IActionResult GetTop100ByCategory(int shardId, LotCategory lotCategory)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.LotTop100.GetByCategory(shardId, lotCategory).Take(100);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Top100 lots not found"));
|
||||
|
||||
List<JSONTop100Lot> top100Lots = new List<JSONTop100Lot>();
|
||||
foreach (var top100Lot in lots)
|
||||
{
|
||||
top100Lots.Add(new JSONTop100Lot
|
||||
{
|
||||
category = top100Lot.category,
|
||||
rank = top100Lot.rank,
|
||||
shard_id = top100Lot.shard_id,
|
||||
lot_location = top100Lot.lot_location,
|
||||
lot_name = top100Lot.lot_name,
|
||||
lot_id = top100Lot.lot_id
|
||||
});
|
||||
}
|
||||
var top100Json = new JSONTop100Lots();
|
||||
top100Json.lots = top100Lots;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, top100Json);
|
||||
}
|
||||
}
|
||||
//get Top-100 lots by shard
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/top100/all")]
|
||||
public IActionResult GetTop100ByShard(int shardId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.LotTop100.GetAllByShard(shardId);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
|
||||
List<JSONTop100Lot> top100Lots = new List<JSONTop100Lot>();
|
||||
foreach (var top100Lot in lots)
|
||||
{
|
||||
top100Lots.Add(new JSONTop100Lot
|
||||
{
|
||||
category = top100Lot.category,
|
||||
rank = top100Lot.rank,
|
||||
shard_id = top100Lot.shard_id,
|
||||
lot_location = top100Lot.lot_location,
|
||||
lot_name = top100Lot.lot_name,
|
||||
lot_id = top100Lot.lot_id
|
||||
});
|
||||
}
|
||||
var top100Json = new JSONTop100Lots();
|
||||
top100Json.lots = top100Lots;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, top100Json);
|
||||
}
|
||||
}
|
||||
|
||||
//moderation only functions, such as downloading lot state
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/{id}.fsov")]
|
||||
public IActionResult GetFSOV(int shardid, uint id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/state_" + lot.ring_backup_num.ToString() + ".fsov");
|
||||
|
||||
|
||||
stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return File(stream, "application/octet-stream");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/{id}.fsof")]
|
||||
[ResponseCache(Duration = 60 * 60, Location = ResponseCacheLocation.Any)]
|
||||
public IActionResult GetFSOF(int shardid, uint id)
|
||||
{
|
||||
var dat = (byte[])MemoryCacher.GetValue("lf" + shardid + ":" + id);
|
||||
if (dat != null)
|
||||
{
|
||||
return File(dat, "application/octet-stream");
|
||||
}
|
||||
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = IDForLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.Value.ToString("x8") + "/thumb.fsof");
|
||||
|
||||
var ndat = System.IO.File.ReadAllBytes(path);
|
||||
MemoryCacher.Add("lf" + shardid + ":" + id, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0)));
|
||||
return File(ndat, "application/octet-stream");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("userapi/city/{shardid}/uploadfacade/{id}")]
|
||||
public IActionResult UploadFacade(int shardid, uint id, List<IFormFile> files)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
if (files == null)
|
||||
return NotFound();
|
||||
|
||||
byte[] data = null;
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filename = file.FileName.Trim('\"');
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
file.CopyTo(memoryStream);
|
||||
data = memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (data == null) return NotFound();
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/thumb.fsof");
|
||||
stream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||
stream.Write(data, 0, data.Length);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
if (!Request.Content.IsMimeMultipartContent())
|
||||
return new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType);
|
||||
|
||||
var provider = new MultipartMemoryStreamProvider();
|
||||
var files = Request.Content.ReadAsMultipartAsync(provider).Result;
|
||||
|
||||
byte[] data = null;
|
||||
foreach (var file in provider.Contents)
|
||||
{
|
||||
var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
|
||||
data = file.ReadAsByteArrayAsync().Result;
|
||||
}
|
||||
|
||||
if (data == null) return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/thumb.fsof");
|
||||
stream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
response.Content = new StringContent("", Encoding.UTF8, "text/plain");
|
||||
return response;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ShardLocationCache
|
||||
{
|
||||
public ConcurrentDictionary<uint, int> Dict = new ConcurrentDictionary<uint, int>();
|
||||
public DateTime CreateTime = DateTime.UtcNow;
|
||||
|
||||
public ShardLocationCache(ConcurrentDictionary<uint, int> dict)
|
||||
{
|
||||
Dict = dict;
|
||||
}
|
||||
}
|
||||
public class JSONLotError
|
||||
{
|
||||
public string error;
|
||||
public JSONLotError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONLotsSmall
|
||||
{
|
||||
public List<JSONLotSmall> lots { get; set; }
|
||||
}
|
||||
public class JSONLots
|
||||
{
|
||||
public List<JSONLot> lots { get; set; }
|
||||
}
|
||||
public class JSONLotsPage
|
||||
{
|
||||
public int page { get; set; }
|
||||
public int total_pages { get; set; }
|
||||
public int total_lots { get; set; }
|
||||
public int lots_on_page { get; set; }
|
||||
public List<JSONLotSmall> lots { get; set; }
|
||||
}
|
||||
public class JSONLotsOnline
|
||||
{
|
||||
public int total_lots_online { get; set; }
|
||||
public int total_avatars_in_lots_online { get; set; }
|
||||
public List<JSONLotSmall> lots { get; set; }
|
||||
}
|
||||
public class JSONLotSmall
|
||||
{
|
||||
public int lot_id { get; set; }
|
||||
public uint location { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public LotCategory category { get; set; }
|
||||
public uint admit_mode { get; set; }
|
||||
public uint neighborhood_id { get; set; }
|
||||
public int avatars_in_lot { get; set; }
|
||||
}
|
||||
public class JSONLot
|
||||
{
|
||||
public int lot_id { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public uint? owner_id { get; set; }
|
||||
public uint[] roommates { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public uint location { get; set; }
|
||||
public uint neighborhood_id { get; set; }
|
||||
public uint created_date { get; set; }
|
||||
public LotCategory category { get; set; }
|
||||
public byte skill_mode { get; set; }
|
||||
public byte admit_mode { get; set; }
|
||||
}
|
||||
public class JSONTop100Lots
|
||||
{
|
||||
public List<JSONTop100Lot> lots { get; set; }
|
||||
}
|
||||
public class JSONTop100Lot
|
||||
{
|
||||
public LotCategory category { get; set; }
|
||||
public byte rank { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public string lot_name { get; set; }
|
||||
public uint? lot_location { get; set; }
|
||||
public int? lot_id { get; set; }
|
||||
}
|
||||
}
|
128
server/FSO.Server.Api.Core/Controllers/NhoodInfoController.cs
Executable file
128
server/FSO.Server.Api.Core/Controllers/NhoodInfoController.cs
Executable file
|
@ -0,0 +1,128 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class NhoodInfoController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/neighborhoods/all")]
|
||||
public IActionResult GetAll(int shardId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var nhoods = da.Neighborhoods.All(shardId);
|
||||
if (nhoods == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhoods not found"));
|
||||
|
||||
List<JSONNhood> nhoodJson = new List<JSONNhood>();
|
||||
foreach (var nhood in nhoods)
|
||||
{
|
||||
nhoodJson.Add(new JSONNhood
|
||||
{
|
||||
neighborhood_id = nhood.neighborhood_id,
|
||||
name = nhood.name,
|
||||
description = nhood.description,
|
||||
color = nhood.color,
|
||||
town_hall_id = nhood.town_hall_id,
|
||||
icon_url = nhood.icon_url,
|
||||
mayor_id = nhood.mayor_id,
|
||||
mayor_elected_date = nhood.mayor_elected_date,
|
||||
election_cycle_id = nhood.election_cycle_id
|
||||
});
|
||||
|
||||
}
|
||||
var nhoodsJson = new JSONNhoods();
|
||||
nhoodsJson.neighborhoods = nhoodJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, nhoodsJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhoods/{nhoodId}")]
|
||||
public IActionResult GetByID(uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var nhood = da.Neighborhoods.Get(nhoodId);
|
||||
if (nhood == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhood not found"));
|
||||
|
||||
var nhoodJson = new JSONNhood
|
||||
{
|
||||
neighborhood_id = nhood.neighborhood_id,
|
||||
name = nhood.name,
|
||||
description = nhood.description,
|
||||
color = nhood.color,
|
||||
town_hall_id = nhood.town_hall_id,
|
||||
icon_url = nhood.icon_url,
|
||||
mayor_id = nhood.mayor_id,
|
||||
mayor_elected_date = nhood.mayor_elected_date,
|
||||
election_cycle_id = nhood.election_cycle_id
|
||||
};
|
||||
return ApiResponse.Json(HttpStatusCode.OK, nhoodJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/neighborhoods/name/{nhoodName}")]
|
||||
public IActionResult GetByName(int shardId, string nhoodName)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var searchNhood = da.Neighborhoods.SearchExact(shardId, nhoodName, 1).FirstOrDefault();
|
||||
if (searchNhood == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhood not found"));
|
||||
|
||||
var nhood = da.Neighborhoods.Get((uint)searchNhood.neighborhood_id);
|
||||
if (nhood == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhood not found"));
|
||||
|
||||
var nhoodJson = new JSONNhood
|
||||
{
|
||||
neighborhood_id = nhood.neighborhood_id,
|
||||
name = nhood.name,
|
||||
description = nhood.description,
|
||||
color = nhood.color,
|
||||
town_hall_id = nhood.town_hall_id,
|
||||
icon_url = nhood.icon_url,
|
||||
mayor_id = nhood.mayor_id,
|
||||
mayor_elected_date = nhood.mayor_elected_date,
|
||||
election_cycle_id = nhood.election_cycle_id
|
||||
};
|
||||
return ApiResponse.Json(HttpStatusCode.OK, nhoodJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONNhoodError
|
||||
{
|
||||
public string error;
|
||||
public JSONNhoodError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONNhoods
|
||||
{
|
||||
public List<JSONNhood> neighborhoods { get; set; }
|
||||
}
|
||||
public class JSONNhood
|
||||
{
|
||||
public int neighborhood_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public uint color { get; set; }
|
||||
public int? town_hall_id { get; set; }
|
||||
public string icon_url { get; set; }
|
||||
public uint? mayor_id { get; set; }
|
||||
public uint mayor_elected_date { get; set; }
|
||||
public uint? election_cycle_id { get; set; }
|
||||
|
||||
}
|
||||
}
|
616
server/FSO.Server.Api.Core/Controllers/RegistrationController.cs
Executable file
616
server/FSO.Server.Api.Core/Controllers/RegistrationController.cs
Executable file
|
@ -0,0 +1,616 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.EmailConfirmation;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Controller for user registrations.
|
||||
/// Supports email confirmation if enabled in config.json.
|
||||
/// </summary>
|
||||
|
||||
[EnableCors]
|
||||
[Route("userapi/registration")]
|
||||
[ApiController]
|
||||
public class RegistrationController : ControllerBase
|
||||
{
|
||||
private const int REGISTER_THROTTLE_SECS = 60;
|
||||
private const int EMAIL_CONFIRMATION_EXPIRE = 2 * 60 * 60; // 2 hrs
|
||||
|
||||
/// <summary>
|
||||
/// Alphanumeric (lowercase), no whitespace or special chars, cannot start with an underscore.
|
||||
/// </summary>
|
||||
private static Regex USERNAME_VALIDATION = new Regex("^([a-z0-9]){1}([a-z0-9_]){2,23}$");
|
||||
|
||||
#region Registration
|
||||
[HttpPost]
|
||||
public IActionResult CreateUser([FromForm] RegistrationModel user)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
if(api.Config.SmtpEnabled)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "missing_confirmation_token"
|
||||
});
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
|
||||
user.username = user.username ?? "";
|
||||
user.username = user.username.ToLowerInvariant();
|
||||
user.email = user.email ?? "";
|
||||
//user.key = user.key ?? "";
|
||||
|
||||
string failReason = null;
|
||||
if (user.username.Length < 3) failReason = "user_short";
|
||||
else if (user.username.Length > 24) failReason = "user_long";
|
||||
else if (!USERNAME_VALIDATION.IsMatch(user.username ?? "")) failReason = "user_invalid";
|
||||
else if ((user.password?.Length ?? 0) == 0) failReason = "pass_required";
|
||||
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(user.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
failReason = "email_invalid";
|
||||
}
|
||||
|
||||
if (failReason != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "bad_request",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
if (!string.IsNullOrEmpty(api.Config.Regkey) && api.Config.Regkey != user.key)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "key_wrong",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
//has this ip been banned?
|
||||
var ban = da.Bans.GetByIP(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "ip_banned"
|
||||
});
|
||||
}
|
||||
|
||||
//has this user registered a new account too soon after their last?
|
||||
var now = Epoch.Now;
|
||||
var prev = da.Users.GetByRegisterIP(ip);
|
||||
if (now - (prev.FirstOrDefault()?.register_date ?? 0) < REGISTER_THROTTLE_SECS)
|
||||
{
|
||||
//cannot create a new account this soon.
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "registrations_too_frequent"
|
||||
});
|
||||
}
|
||||
|
||||
var userModel = api.CreateUser(user.username, user.email, user.password, ip);
|
||||
|
||||
if(userModel==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "user_exists"
|
||||
});
|
||||
} else {
|
||||
api.SendEmailConfirmationOKMail(user.username, user.email);
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a confirmation token and send email.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/registration/request")]
|
||||
public IActionResult CreateToken(ConfirmationCreateTokenModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
// smtp needs to be configured for this
|
||||
if(!api.Config.SmtpEnabled)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "smtp_disabled"
|
||||
});
|
||||
}
|
||||
|
||||
if(model.confirmation_url==null||model.email==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
// verify email syntax
|
||||
// To do: check if email address is disposable.
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(model.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "email_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
// email is taken
|
||||
if(da.Users.GetByEmail(model.email)!=null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "email_taken"
|
||||
});
|
||||
}
|
||||
|
||||
EmailConfirmation confirm = da.EmailConfirmations.GetByEmail(model.email, ConfirmationType.email);
|
||||
|
||||
// already waiting for confirmation
|
||||
if(confirm!=null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "confirmation_pending"
|
||||
});
|
||||
}
|
||||
|
||||
uint expires = Epoch.Now + EMAIL_CONFIRMATION_EXPIRE;
|
||||
|
||||
// create new email confirmation
|
||||
string token = da.EmailConfirmations.Create(new EmailConfirmation
|
||||
{
|
||||
type = ConfirmationType.email,
|
||||
email = model.email,
|
||||
expires = expires
|
||||
});
|
||||
|
||||
// send email with recently generated token
|
||||
bool sent = api.SendEmailConfirmationMail(model.email, token, model.confirmation_url, expires);
|
||||
|
||||
if(sent)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "email_failed"
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a user with a valid email confirmation token.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/registration/confirm")]
|
||||
public IActionResult CreateUserWithToken(RegistrationUseTokenModel user)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "invalid_token"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
EmailConfirmation confirmation = da.EmailConfirmations.GetByToken(user.token);
|
||||
|
||||
if(confirmation == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "invalid_token"
|
||||
});
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
|
||||
user.username = user.username ?? "";
|
||||
user.username = user.username.ToLowerInvariant();
|
||||
user.email = user.email ?? "";
|
||||
user.key = user.key ?? "";
|
||||
|
||||
string failReason = null;
|
||||
if (user.username.Length < 3) failReason = "user_short";
|
||||
else if (user.username.Length > 24) failReason = "user_long";
|
||||
else if (!USERNAME_VALIDATION.IsMatch(user.username ?? "")) failReason = "user_invalid";
|
||||
else if ((user.password?.Length ?? 0) == 0) failReason = "pass_required";
|
||||
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(user.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
failReason = "email_invalid";
|
||||
}
|
||||
|
||||
if (failReason != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "bad_request",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(api.Config.Regkey) && api.Config.Regkey != user.key)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "key_wrong",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
|
||||
//has this ip been banned?
|
||||
var ban = da.Bans.GetByIP(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "ip_banned"
|
||||
});
|
||||
}
|
||||
|
||||
//has this user registered a new account too soon after their last?
|
||||
var prev = da.Users.GetByRegisterIP(ip);
|
||||
if (Epoch.Now - (prev.FirstOrDefault()?.register_date ?? 0) < REGISTER_THROTTLE_SECS)
|
||||
{
|
||||
//cannot create a new account this soon.
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "registrations_too_frequent"
|
||||
});
|
||||
}
|
||||
|
||||
//create user in db
|
||||
var userModel = api.CreateUser(user.username, user.email, user.password, ip);
|
||||
|
||||
if (userModel == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "user_exists"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//send OK email
|
||||
api.SendEmailConfirmationOKMail(user.username, user.email);
|
||||
da.EmailConfirmations.Remove(user.token);
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Password reset
|
||||
[HttpPost]
|
||||
[Route("userapi/password")]
|
||||
public IActionResult ChangePassword(PasswordResetModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if (api.Config.SmtpEnabled)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_confirmation_token"
|
||||
});
|
||||
}
|
||||
|
||||
// No empty fields
|
||||
if (model.username==null||model.new_password==null||model.old_password==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var user = da.Users.GetByUsername(model.username);
|
||||
|
||||
if(user==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "user_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
var authSettings = da.Users.GetAuthenticationSettings(user.user_id);
|
||||
var correct = PasswordHasher.Verify(model.old_password, new PasswordHash
|
||||
{
|
||||
data = authSettings.data,
|
||||
scheme = authSettings.scheme_class
|
||||
});
|
||||
|
||||
if (!correct)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "incorrect_password"
|
||||
});
|
||||
}
|
||||
|
||||
api.ChangePassword(user.user_id, model.new_password);
|
||||
api.SendPasswordResetOKMail(user.email, user.username);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets a user's password using a confirmation token.
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/password/confirm")]
|
||||
public IActionResult ConfirmPwd(PasswordResetUseTokenModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if(model.token==null||model.new_password==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
EmailConfirmation confirmation = da.EmailConfirmations.GetByToken(model.token);
|
||||
|
||||
if(confirmation==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "invalid_token"
|
||||
});
|
||||
}
|
||||
|
||||
var user = da.Users.GetByEmail(confirmation.email);
|
||||
|
||||
api.ChangePassword(user.user_id, model.new_password);
|
||||
api.SendPasswordResetOKMail(user.email, user.username);
|
||||
da.EmailConfirmations.Remove(model.token);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a password reset token and mails it to the user.
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/password/request")]
|
||||
public IActionResult CreatePwdToken(ConfirmationCreateTokenModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if (model.confirmation_url == null || model.email == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
// verify email syntax
|
||||
// To do: check if email address is disposable.
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(model.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "email_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
var user = da.Users.GetByEmail(model.email);
|
||||
|
||||
if(user==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "email_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
EmailConfirmation confirm = da.EmailConfirmations.GetByEmail(model.email, ConfirmationType.password);
|
||||
|
||||
// already awaiting a confirmation
|
||||
// to-do: resend?
|
||||
if (confirm != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "confirmation_pending"
|
||||
});
|
||||
}
|
||||
|
||||
uint expires = Epoch.Now + EMAIL_CONFIRMATION_EXPIRE;
|
||||
|
||||
// create new email confirmation
|
||||
string token = da.EmailConfirmations.Create(new EmailConfirmation
|
||||
{
|
||||
type = ConfirmationType.password,
|
||||
email = model.email,
|
||||
expires = expires
|
||||
});
|
||||
|
||||
// send confirmation email with generated token
|
||||
bool sent = api.SendPasswordResetMail(model.email, user.username, token, model.confirmation_url, expires);
|
||||
|
||||
if (sent)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
// success but email shitfaced
|
||||
status = "email_failed"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Models
|
||||
public class RegistrationError
|
||||
{
|
||||
public string error_description { get; set; }
|
||||
public string error { get; set; }
|
||||
}
|
||||
|
||||
public class RegistrationModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string email { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expected request data when trying to create a token to register.
|
||||
/// </summary>
|
||||
public class ConfirmationCreateTokenModel
|
||||
{
|
||||
public string email { get; set; }
|
||||
/// <summary>
|
||||
/// The link the user will have to go to in order to confirm their token.
|
||||
/// If %token% is present in the url, it will be replaced with the user's token.
|
||||
/// </summary>
|
||||
public string confirmation_url { get; set; }
|
||||
}
|
||||
|
||||
public class PasswordResetModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string old_password { get; set; }
|
||||
public string new_password { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expected request data when trying to register with a token.
|
||||
/// </summary>
|
||||
public class RegistrationUseTokenModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
/// <summary>
|
||||
/// User email.
|
||||
/// </summary>
|
||||
public string email { get; set; }
|
||||
/// <summary>
|
||||
/// User password.
|
||||
/// </summary>
|
||||
public string password { get; set; }
|
||||
/// <summary>
|
||||
/// Registration key.
|
||||
/// </summary>
|
||||
public string key { get; set; }
|
||||
/// <summary>
|
||||
/// The unique GUID.
|
||||
/// </summary>
|
||||
public string token { get; set; }
|
||||
}
|
||||
|
||||
public class PasswordResetUseTokenModel
|
||||
{
|
||||
public string token { get; set; }
|
||||
public string new_password { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
98
server/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs
Executable file
98
server/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs
Executable file
|
@ -0,0 +1,98 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.Shards;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("cityselector/app/ShardSelectorServlet")]
|
||||
[ApiController]
|
||||
public class ShardSelectorController : ControllerBase
|
||||
{
|
||||
private static Func<IActionResult> ERROR_SHARD_NOT_FOUND = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("503", "Shard not found"));
|
||||
private static Func<IActionResult> ERROR_AVATAR_NOT_FOUND = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("504", "Avatar not found"));
|
||||
private static Func<IActionResult> ERROR_AVATAR_NOT_YOURS = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("505", "You do not own this avatar!"));
|
||||
private static Func<IActionResult> ERROR_BANNED = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("506", "Your account has been banned."));
|
||||
private static Func<IActionResult> ERROR_MAINTAINANCE = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("507", "The server is currently undergoing maintainance. Please try again later."));
|
||||
|
||||
public IActionResult Get(string shardName, string avatarId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var user = api.RequireAuthentication(Request);
|
||||
if (avatarId == null){
|
||||
//Using 0 to mean no avatar for CAS
|
||||
avatarId = "0";
|
||||
}
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
ShardStatusItem shard = api.Shards.GetByName(shardName);
|
||||
if (shard != null)
|
||||
{
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
uint avatarDBID = uint.Parse(avatarId);
|
||||
|
||||
if (avatarDBID != 0)
|
||||
{
|
||||
var avatar = db.Avatars.Get(avatarDBID);
|
||||
if (avatar == null)
|
||||
{
|
||||
//can't join server with an avatar that doesn't exist
|
||||
return ERROR_AVATAR_NOT_FOUND();
|
||||
}
|
||||
if (avatar.user_id != user.UserID || avatar.shard_id != shard.Id)
|
||||
{
|
||||
//make sure we own the avatar we're trying to connect with
|
||||
return ERROR_AVATAR_NOT_YOURS();
|
||||
}
|
||||
}
|
||||
|
||||
var ban = db.Bans.GetByIP(ip);
|
||||
var dbuser = db.Users.GetById(user.UserID);
|
||||
if (dbuser == null || ban != null || dbuser.is_banned != false)
|
||||
{
|
||||
return ERROR_BANNED();
|
||||
}
|
||||
|
||||
if (api.Config.Maintainance && !(dbuser.is_admin || dbuser.is_moderator))
|
||||
{
|
||||
return ERROR_MAINTAINANCE();
|
||||
}
|
||||
|
||||
/** Make an auth ticket **/
|
||||
var ticket = new ShardTicket
|
||||
{
|
||||
ticket_id = Guid.NewGuid().ToString().Replace("-", ""),
|
||||
user_id = user.UserID,
|
||||
avatar_id = avatarDBID,
|
||||
date = Epoch.Now,
|
||||
ip = ip
|
||||
};
|
||||
|
||||
db.Users.UpdateConnectIP(ticket.user_id, ip);
|
||||
db.Shards.CreateTicket(ticket);
|
||||
|
||||
var result = new ShardSelectorServletResponse();
|
||||
result.PreAlpha = false;
|
||||
|
||||
result.Address = shard.PublicHost;
|
||||
result.PlayerID = user.UserID;
|
||||
result.AvatarID = avatarId;
|
||||
result.Ticket = ticket.ticket_id;
|
||||
result.ConnectionID = ticket.ticket_id;
|
||||
result.AvatarID = avatarId;
|
||||
|
||||
return ApiResponse.Xml(HttpStatusCode.OK, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ERROR_SHARD_NOT_FOUND();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
server/FSO.Server.Api.Core/Controllers/ShardStatusController.cs
Executable file
29
server/FSO.Server.Api.Core/Controllers/ShardStatusController.cs
Executable file
|
@ -0,0 +1,29 @@
|
|||
using FSO.Common.Utils;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[Route("cityselector/shard-status.jsp")]
|
||||
[ApiController]
|
||||
public class ShardStatusController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var result = new XMLList<ShardStatusItem>("Shard-Status-List");
|
||||
var shards = api.Shards.All;
|
||||
foreach (var shard in shards)
|
||||
{
|
||||
result.Add(shard);
|
||||
}
|
||||
return ApiResponse.Xml(HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
}
|
122
server/FSO.Server.Api.Core/Controllers/UserOAuthController.cs
Executable file
122
server/FSO.Server.Api.Core/Controllers/UserOAuthController.cs
Executable file
|
@ -0,0 +1,122 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("userapi/oauth/token")]
|
||||
[ApiController]
|
||||
public class UserOAuthController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult CreateToken([FromForm] UserOAuthRequest userAuthRequest)
|
||||
{
|
||||
if (userAuthRequest == null) BadRequest();
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var user = da.Users.GetByUsername(userAuthRequest.username);
|
||||
if (user == null || user.is_banned) return ApiResponse.Json(System.Net.HttpStatusCode.Unauthorized, new UserOAuthError("unauthorized_client", "user_credentials_invalid"));
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
var hashSettings = da.Users.GetAuthenticationSettings(user.user_id);
|
||||
var isPasswordCorrect = PasswordHasher.Verify(userAuthRequest.password, new PasswordHash
|
||||
{
|
||||
data = hashSettings.data,
|
||||
scheme = hashSettings.scheme_class
|
||||
});
|
||||
//check if account is locked due to failed attempts
|
||||
var accLock = da.Users.GetRemainingAuth(user.user_id, ip);
|
||||
if (accLock != null && (accLock.active || accLock.count >= AuthLoginController.LockAttempts) && accLock.expire_time > Epoch.Now)
|
||||
{
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new UserOAuthError("unauthorized_client", "account_locked"));
|
||||
}
|
||||
//if the password is incorrect and check if user failed muli times and set a time out till next try.
|
||||
if (!isPasswordCorrect)
|
||||
{
|
||||
var durations = AuthLoginController.LockDuration;
|
||||
var failDelay = 60 * durations[Math.Min(durations.Length - 1, da.Users.FailedConsecutive(user.user_id, ip))];
|
||||
if (accLock == null)
|
||||
{
|
||||
da.Users.NewFailedAuth(user.user_id, ip, (uint)failDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = da.Users.FailedAuth(accLock.attempt_id, (uint)failDelay, AuthLoginController.LockAttempts);
|
||||
}
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new UserOAuthError("unauthorized_client", "user_credentials_invalid"));
|
||||
}
|
||||
|
||||
//user passed the password check, and now creates the claim/token
|
||||
da.Users.SuccessfulAuth(user.user_id, ip);
|
||||
var claims = new List<string>();
|
||||
|
||||
//set the permission level in the claim
|
||||
switch (userAuthRequest.permission_level)
|
||||
{
|
||||
case 1:
|
||||
claims.Add("userReadPermissions");
|
||||
break;
|
||||
case 2:
|
||||
claims.Add("userReadPermissions");
|
||||
claims.Add("userWritePermissions");
|
||||
break;
|
||||
case 3:
|
||||
claims.Add("userReadPermissions");
|
||||
claims.Add("userWritePermissions");
|
||||
claims.Add("userUpdatePermissions");
|
||||
break;
|
||||
case 4:
|
||||
claims.Add("userReadPermissions");
|
||||
claims.Add("userWritePermissions");
|
||||
claims.Add("userUpdatePermissions");
|
||||
claims.Add("userDeletePermissions");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//set the user identity
|
||||
JWTUser identity = new JWTUser
|
||||
{
|
||||
UserID = user.user_id,
|
||||
UserName = user.username,
|
||||
Claims = claims
|
||||
};
|
||||
|
||||
//generate the the tokenen and send it in a JSON format as response
|
||||
var generatedToken = api.JWT.CreateToken(identity);
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new UserOAuthSuccess
|
||||
{
|
||||
access_token = generatedToken.Token,
|
||||
expires_in = generatedToken.ExpiresIn
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public class UserOAuthRequest
|
||||
{
|
||||
public int permission_level { get; set; }
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
public class UserOAuthError
|
||||
{
|
||||
public string error;
|
||||
public string error_description;
|
||||
public UserOAuthError(string errorString,string errorDescriptionString)
|
||||
{
|
||||
error = errorString;
|
||||
error_description = errorDescriptionString;
|
||||
}
|
||||
}
|
||||
public class UserOAuthSuccess
|
||||
{
|
||||
public string access_token { get; set; }
|
||||
public int expires_in { get; set; }
|
||||
}
|
||||
|
||||
}
|
66
server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj
Executable file
66
server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj
Executable file
|
@ -0,0 +1,66 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="MailTemplates\MailBan.html" />
|
||||
<None Remove="MailTemplates\MailBase.html" />
|
||||
<None Remove="MailTemplates\MailPasswordReset.html" />
|
||||
<None Remove="MailTemplates\MailPasswordResetOK.html" />
|
||||
<None Remove="MailTemplates\MailRegistrationOK.html" />
|
||||
<None Remove="MailTemplates\MailRegistrationToken.html" />
|
||||
<None Remove="MailTemplates\MailUnban.html" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="MailTemplates\MailBan.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="MailTemplates\MailBase.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="MailTemplates\MailPasswordReset.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="MailTemplates\MailPasswordResetOK.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="MailTemplates\MailRegistrationOK.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="MailTemplates\MailRegistrationToken.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="MailTemplates\MailUnban.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.Core" Version="3.3.103.48" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.3.104.36" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
|
||||
<PackageReference Include="Octokit" Version="0.36.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FSO.Server.Common\FSO.Server.Common.csproj" />
|
||||
<ProjectReference Include="..\FSO.Server.Database\FSO.Server.Database.csproj" />
|
||||
<ProjectReference Include="..\FSO.Server.Domain\FSO.Server.Domain.csproj" />
|
||||
<ProjectReference Include="..\FSO.Server.Protocol\FSO.Server.Protocol.csproj" />
|
||||
<ProjectReference Include="..\tso.common\FSO.Common.csproj" />
|
||||
<ProjectReference Include="..\tso.files\FSO.Files.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
13
server/FSO.Server.Api.Core/MailTemplates/MailBan.html
Executable file
13
server/FSO.Server.Api.Core/MailTemplates/MailBan.html
Executable file
|
@ -0,0 +1,13 @@
|
|||
<strong>Account Suspended.</strong>
|
||||
<br>One of your FreeSO accounts has been suspended for not following <a href="http://freeso.org/rules">the rules</a>.
|
||||
<br>Banned account username:
|
||||
<strong>%username%</strong>.
|
||||
<br>
|
||||
<br />
|
||||
Expiration date:
|
||||
<strong>%end%</strong>
|
||||
<br />
|
||||
<br>Official Discord:
|
||||
<a href='https://discord.gg/xveESFj'>https://discord.gg/xveESFj</a>
|
||||
<br>Forums:
|
||||
<a href='http://forum.freeso.org'>http://forum.freeso.org</a>
|
9
server/FSO.Server.Api.Core/MailTemplates/MailBase.html
Executable file
9
server/FSO.Server.Api.Core/MailTemplates/MailBase.html
Executable file
|
@ -0,0 +1,9 @@
|
|||
<div style='font-family:Arial;border-radius:6px;border:1px solid #3875B9;padding:20px;max-width:500px;text-align:justify;line-height:22px'>
|
||||
<div style='display:block;padding-bottom:20px;overflow:hidden;border-bottom:1px solid #3875B9'>
|
||||
<img style='float:left' src='https://beta.freeso.org/cdn/img/slogo.png'>
|
||||
<span style='float:left;margin-left:15px;margin-top:15px'>Official Game Server</span>
|
||||
</div>
|
||||
<br />
|
||||
%content%
|
||||
<br />
|
||||
</div>
|
11
server/FSO.Server.Api.Core/MailTemplates/MailPasswordReset.html
Executable file
11
server/FSO.Server.Api.Core/MailTemplates/MailPasswordReset.html
Executable file
|
@ -0,0 +1,11 @@
|
|||
<strong>Password Reset Request</strong>
|
||||
<br>You requested a password reset. Good news, it's here!
|
||||
<br><a href='%confirmation_url%'>Click here to change your password</a>. The link will expire in %expires%.
|
||||
<br>Just in case, your token is: %token%.
|
||||
<br />
|
||||
<br>Official Discord: <a href='https://discord.gg/xveESFj'>https://discord.gg/xveESFj</a>
|
||||
<br>Forums: <a href='http://forum.freeso.org'>http://forum.freeso.org</a>
|
||||
<br>Twitter: <a href='http://twitter.com/FreeSOGame'>http://twitter.com/FreeSOGame</a>
|
||||
<br>
|
||||
<br><strong>Download the FreeSO Installer</strong>
|
||||
<br>Get the installer from <a href='http://beta.freeso.org'>FreeSO.org</a>. After confirming your account by clicking the link above, you will be able to create an account and login.
|
10
server/FSO.Server.Api.Core/MailTemplates/MailPasswordResetOK.html
Executable file
10
server/FSO.Server.Api.Core/MailTemplates/MailPasswordResetOK.html
Executable file
|
@ -0,0 +1,10 @@
|
|||
<strong>Your account password was just changed.</strong>
|
||||
<br>The password for your account with username <strong>%username%</strong> was just changed.
|
||||
<br />
|
||||
<br>Official Discord: <a href='https://discord.gg/xveESFj'>https://discord.gg/xveESFj</a>
|
||||
<br>Forums: <a href='http://forum.freeso.org'>http://forum.freeso.org</a>
|
||||
<br>Twitter: <a href='http://twitter.com/FreeSOGame'>http://twitter.com/FreeSOGame</a>
|
||||
<br>
|
||||
<br>
|
||||
<strong>Learn how to get started:</strong>
|
||||
<br>Check out TSOMania's guide at <a href="http://www.tsomania.net/gameguides/getting_started.php">TSOMania.net</a>.
|
12
server/FSO.Server.Api.Core/MailTemplates/MailRegistrationOK.html
Executable file
12
server/FSO.Server.Api.Core/MailTemplates/MailRegistrationOK.html
Executable file
|
@ -0,0 +1,12 @@
|
|||
<strong>Welcome to Sunrise Crater!</strong>
|
||||
<br>You can now login in-game. Here are your details:
|
||||
<br>Your username is: <strong>%username%</strong>. If you need any further support message us on one of our platforms.
|
||||
<br>Please check out <a href="http://freeso.org/rules">the rules</a> if you haven't already!
|
||||
<br />
|
||||
<br>Official Discord: <a href='https://discord.gg/xveESFj'>https://discord.gg/xveESFj</a>
|
||||
<br>Forums: <a href='http://forum.freeso.org'>http://forum.freeso.org</a>
|
||||
<br>Twitter: <a href='http://twitter.com/FreeSOGame'>http://twitter.com/FreeSOGame</a>
|
||||
<br>
|
||||
<br>
|
||||
<strong>Learn how to get started:</strong>
|
||||
<br>Check out TSOMania's guide at <a href="http://www.tsomania.net/gameguides/getting_started.php">TSOMania.net</a>.
|
11
server/FSO.Server.Api.Core/MailTemplates/MailRegistrationToken.html
Executable file
11
server/FSO.Server.Api.Core/MailTemplates/MailRegistrationToken.html
Executable file
|
@ -0,0 +1,11 @@
|
|||
<strong>You are almost here...</strong>
|
||||
<br>One last step! Verify your email address:
|
||||
<br><a href='%confirmation_url%'>Click here finish your registration</a>. The link will expire in %expires%.
|
||||
<br>Just in case, your token is: %token%.
|
||||
<br />
|
||||
<br>Official Discord: <a href='https://discord.gg/xveESFj'>https://discord.gg/xveESFj</a>
|
||||
<br>Forums: <a href='http://forum.freeso.org'>http://forum.freeso.org</a>
|
||||
<br>Twitter: <a href='http://twitter.com/FreeSOGame'>http://twitter.com/FreeSOGame</a>
|
||||
<br>
|
||||
<br><strong>Download the FreeSO Installer</strong>
|
||||
<br>Get the installer from <a href='http://beta.freeso.org'>FreeSO.org</a>. After confirming your account by clicking the link above, you will be able to create an account and login.
|
1
server/FSO.Server.Api.Core/MailTemplates/MailUnban.html
Executable file
1
server/FSO.Server.Api.Core/MailTemplates/MailUnban.html
Executable file
|
@ -0,0 +1 @@
|
|||
<!-- To-do -->
|
36
server/FSO.Server.Api.Core/Models/EventCreateModel.cs
Executable file
36
server/FSO.Server.Api.Core/Models/EventCreateModel.cs
Executable file
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Api.Core.Models
|
||||
{
|
||||
public class EventCreateModel
|
||||
{
|
||||
public string title;
|
||||
public string description;
|
||||
public DateTime start_day;
|
||||
public DateTime end_day;
|
||||
public string type;
|
||||
public int value;
|
||||
public int value2;
|
||||
public string mail_subject;
|
||||
public string mail_message;
|
||||
public int mail_sender;
|
||||
public string mail_sender_name;
|
||||
}
|
||||
|
||||
public class PresetCreateModel
|
||||
{
|
||||
public string name;
|
||||
public string description;
|
||||
public int flags;
|
||||
public List<PresetItemModel> items;
|
||||
}
|
||||
|
||||
public class PresetItemModel
|
||||
{
|
||||
public string tuning_type;
|
||||
public int tuning_table;
|
||||
public int tuning_index;
|
||||
public float value;
|
||||
}
|
||||
}
|
10
server/FSO.Server.Api.Core/Models/FSOUpdateManifest.cs
Executable file
10
server/FSO.Server.Api.Core/Models/FSOUpdateManifest.cs
Executable file
|
@ -0,0 +1,10 @@
|
|||
using FSO.Files.Utils;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Api.Core.Models
|
||||
{
|
||||
public class FSOUpdateManifest {
|
||||
public string Version;
|
||||
public List<FileDiff> Diffs;
|
||||
}
|
||||
}
|
14
server/FSO.Server.Api.Core/Models/UpdateCreateModel.cs
Executable file
14
server/FSO.Server.Api.Core/Models/UpdateCreateModel.cs
Executable file
|
@ -0,0 +1,14 @@
|
|||
namespace FSO.Server.Api.Core.Models
|
||||
{
|
||||
public class UpdateCreateModel
|
||||
{
|
||||
public int branchID;
|
||||
public uint scheduledEpoch;
|
||||
public string catalog;
|
||||
|
||||
public bool contentOnly;
|
||||
public bool includeMonogameDelta;
|
||||
public bool disableIncremental;
|
||||
public bool minorVersion;
|
||||
}
|
||||
}
|
69
server/FSO.Server.Api.Core/Models/UpdateGenerationStatus.cs
Executable file
69
server/FSO.Server.Api.Core/Models/UpdateGenerationStatus.cs
Executable file
|
@ -0,0 +1,69 @@
|
|||
using FSO.Server.Database.DA.Updates;
|
||||
|
||||
namespace FSO.Server.Api.Core.Models
|
||||
{
|
||||
public class UpdateGenerationStatus
|
||||
{
|
||||
public int TaskID;
|
||||
public UpdateCreateModel Request;
|
||||
public UpdateGenerationStatusCode Code;
|
||||
public float EstimatedProgress;
|
||||
|
||||
public DbUpdate Result;
|
||||
public string Failure;
|
||||
|
||||
public UpdateGenerationStatus(int taskID, UpdateCreateModel request)
|
||||
{
|
||||
TaskID = taskID;
|
||||
Request = request;
|
||||
}
|
||||
|
||||
public void UpdateStatus(UpdateGenerationStatusCode code, float progress)
|
||||
{
|
||||
Code = code;
|
||||
EstimatedProgress = progress;
|
||||
}
|
||||
|
||||
public void UpdateStatus(UpdateGenerationStatusCode code)
|
||||
{
|
||||
UpdateStatus(code, ((float)code - 1) / ((float)UpdateGenerationStatusCode.SUCCESS - 1));
|
||||
}
|
||||
|
||||
public void SetResult(DbUpdate result)
|
||||
{
|
||||
UpdateStatus(UpdateGenerationStatusCode.SUCCESS);
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public void SetFailure(string failure)
|
||||
{
|
||||
UpdateStatus(UpdateGenerationStatusCode.FAILURE, 0);
|
||||
Failure = failure;
|
||||
}
|
||||
}
|
||||
|
||||
public enum UpdateGenerationStatusCode
|
||||
{
|
||||
FAILURE = 0,
|
||||
|
||||
PREPARING = 1,
|
||||
DOWNLOADING_CLIENT,
|
||||
DOWNLOADING_SERVER,
|
||||
DOWNLOADING_CLIENT_ADDON,
|
||||
DOWNLOADING_SERVER_ADDON,
|
||||
EXTRACTING_CLIENT,
|
||||
EXTRACTING_CLIENT_ADDON,
|
||||
BUILDING_DIFF,
|
||||
BUILDING_INCREMENTAL_UPDATE,
|
||||
BUILDING_CLIENT,
|
||||
PUBLISHING_CLIENT,
|
||||
|
||||
EXTRACTING_SERVER,
|
||||
EXTRACTING_SERVER_ADDON,
|
||||
BUILDING_SERVER,
|
||||
PUBLISHING_SERVER,
|
||||
|
||||
SCHEDULING_UPDATE,
|
||||
SUCCESS
|
||||
}
|
||||
}
|
53
server/FSO.Server.Api.Core/Program.cs
Executable file
53
server/FSO.Server.Api.Core/Program.cs
Executable file
|
@ -0,0 +1,53 @@
|
|||
using FSO.Server.Common;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FSO.Server.Api.Core
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = CreateWebHostBuilder(args).Build();
|
||||
host.Run();
|
||||
}
|
||||
|
||||
public static IAPILifetime RunAsync(string[] args)
|
||||
{
|
||||
var host = CreateWebHostBuilder(args).Build();
|
||||
var lifetime = new APIControl((IApplicationLifetime)host.Services.GetService(typeof(IApplicationLifetime)));
|
||||
host.Start();
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseUrls(args[0])
|
||||
.ConfigureLogging(x =>
|
||||
{
|
||||
x.SetMinimumLevel(LogLevel.None);
|
||||
})
|
||||
.UseKestrel(options =>
|
||||
{
|
||||
options.Limits.MaxRequestBodySize = 500000000;
|
||||
})
|
||||
.SuppressStatusMessages(true)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
|
||||
public class APIControl : IAPILifetime
|
||||
{
|
||||
private IApplicationLifetime Lifetime;
|
||||
|
||||
public APIControl(IApplicationLifetime lifetime)
|
||||
{
|
||||
Lifetime = lifetime;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Lifetime.StopApplication();
|
||||
}
|
||||
}
|
||||
}
|
30
server/FSO.Server.Api.Core/Properties/launchSettings.json
Executable file
30
server/FSO.Server.Api.Core/Properties/launchSettings.json
Executable file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:61113",
|
||||
"sslPort": 44333
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"FSO.Server.Api.Core": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
server/FSO.Server.Api.Core/Services/AWSUpdateUploader.cs
Executable file
54
server/FSO.Server.Api.Core/Services/AWSUpdateUploader.cs
Executable file
|
@ -0,0 +1,54 @@
|
|||
using Amazon;
|
||||
using Amazon.Runtime;
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using FSO.Server.Common.Config;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSO.Server.Api.Core.Services
|
||||
{
|
||||
public class AWSUpdateUploader : IUpdateUploader
|
||||
{
|
||||
private AWSConfig Config;
|
||||
public AWSUpdateUploader(AWSConfig config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public async Task<string> UploadFile(string destPath, string fileName, string groupName)
|
||||
{
|
||||
var region = Config.Region;
|
||||
var bucket = Config.Bucket;
|
||||
var s3config = new AmazonS3Config()
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(region),
|
||||
Timeout = new TimeSpan(1, 0, 0),
|
||||
ReadWriteTimeout = new TimeSpan(1, 0, 0),
|
||||
MaxErrorRetry = 512
|
||||
};
|
||||
|
||||
using (var aws = new AmazonS3Client(new BasicAWSCredentials(Config.AccessKeyID, Config.SecretAccessKey), s3config))
|
||||
{
|
||||
PutObjectRequest request = new PutObjectRequest()
|
||||
{
|
||||
InputStream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
|
||||
BucketName = bucket,
|
||||
CannedACL = S3CannedACL.PublicRead,
|
||||
Key = destPath
|
||||
};
|
||||
PutObjectResponse response = await aws.PutObjectAsync(request);
|
||||
|
||||
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
return $"https://s3.{region}.amazonaws.com/{bucket}/" + destPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Uploading file " + destPath + " failed with code " + response.HttpStatusCode + "!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
324
server/FSO.Server.Api.Core/Services/GenerateUpdateService.cs
Executable file
324
server/FSO.Server.Api.Core/Services/GenerateUpdateService.cs
Executable file
|
@ -0,0 +1,324 @@
|
|||
using FSO.Server.Api.Core.Models;
|
||||
using FSO.Server.Database.DA.Updates;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FSO.Files.Utils;
|
||||
using FSO.Server.Common;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FSO.Server.Api.Core.Services
|
||||
{
|
||||
public class GenerateUpdateService
|
||||
{
|
||||
private static GenerateUpdateService _INSTANCE;
|
||||
public static GenerateUpdateService INSTANCE
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_INSTANCE == null) _INSTANCE = new GenerateUpdateService();
|
||||
return _INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
private int LastTaskID = 0;
|
||||
public Dictionary<int, UpdateGenerationStatus> Tasks = new Dictionary<int, UpdateGenerationStatus>();
|
||||
|
||||
public UpdateGenerationStatus GetTask(int id)
|
||||
{
|
||||
UpdateGenerationStatus result;
|
||||
lock (Tasks)
|
||||
{
|
||||
if (!Tasks.TryGetValue(id, out result)) return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public UpdateGenerationStatus CreateTask(UpdateCreateModel request)
|
||||
{
|
||||
UpdateGenerationStatus task;
|
||||
lock (Tasks)
|
||||
{
|
||||
task = new UpdateGenerationStatus(++LastTaskID, request);
|
||||
Tasks[LastTaskID] = task;
|
||||
}
|
||||
Task.Run(() => BuildUpdate(task));
|
||||
return task;
|
||||
}
|
||||
|
||||
private void Exec(string cmd)
|
||||
{
|
||||
var escapedArgs = cmd.Replace("\"", "\\\"");
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
FileName = "/bin/sh",
|
||||
Arguments = $"-c \"{escapedArgs}\""
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
private void ClearFolderPermissions(string folder)
|
||||
{
|
||||
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
Exec($"chmod -R 777 {folder}");
|
||||
}
|
||||
|
||||
private string GetZipFolder(string path)
|
||||
{
|
||||
var directories = Directory.GetDirectories(path);
|
||||
if (directories.Length != 1) return path;
|
||||
var files = Directory.GetFiles(path);
|
||||
if (files.Length != 0) return path;
|
||||
return directories[0];
|
||||
}
|
||||
|
||||
public async Task BuildUpdate(UpdateGenerationStatus status)
|
||||
{
|
||||
var request = status.Request;
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
try
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.PREPARING);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var baseUpdateKey = "updates/";
|
||||
var branch = da.Updates.GetBranch(status.Request.branchID);
|
||||
|
||||
//reserve update id. may cause race condition, but only one person can update anyways.
|
||||
if (request.minorVersion) ++branch.minor_version_number;
|
||||
else
|
||||
{
|
||||
++branch.last_version_number;
|
||||
branch.minor_version_number = 0;
|
||||
}
|
||||
var updateID = branch.last_version_number;
|
||||
var minorChar = (branch.minor_version_number == 0) ? "" : ((char)('a' + (branch.minor_version_number - 1))).ToString();
|
||||
var versionName = branch.version_format.Replace("#", updateID.ToString()).Replace("@", minorChar);
|
||||
var versionText = versionName;
|
||||
|
||||
var result = new DbUpdate()
|
||||
{
|
||||
addon_id = branch.addon_id,
|
||||
branch_id = branch.branch_id,
|
||||
date = DateTime.UtcNow,
|
||||
version_name = versionName,
|
||||
deploy_after = Epoch.ToDate(status.Request.scheduledEpoch)
|
||||
};
|
||||
|
||||
versionName = versionName.Replace('/', '-');
|
||||
|
||||
var client = new WebClient();
|
||||
//fetch artifacts
|
||||
//http://servo.freeso.org/guestAuth/repository/download/FreeSO_TsoClient/.lastSuccessful/client-<>.zip
|
||||
//http://servo.freeso.org/guestAuth/repository/download/FreeSO_TsoClient/.lastSuccessful/server-<>.zip
|
||||
|
||||
int updateWorkID = status.TaskID;
|
||||
|
||||
var updateDir = "updateTemp/" + updateWorkID + "/";
|
||||
try
|
||||
{
|
||||
Directory.Delete(updateDir, true);
|
||||
}
|
||||
catch (Exception) { }
|
||||
Directory.CreateDirectory(updateDir);
|
||||
Directory.CreateDirectory(updateDir + "client/");
|
||||
Directory.CreateDirectory(updateDir + "server/");
|
||||
|
||||
string clientArti = null;
|
||||
string serverArti = null;
|
||||
if (branch.base_build_url != null)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_CLIENT);
|
||||
await client.DownloadFileTaskAsync(new Uri(branch.base_build_url), updateDir + "client.zip");
|
||||
clientArti = updateDir + "client.zip";
|
||||
}
|
||||
if (branch.base_server_build_url != null)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_SERVER);
|
||||
await client.DownloadFileTaskAsync(new Uri(branch.base_server_build_url), updateDir + "server.zip");
|
||||
serverArti = updateDir + "server.zip";
|
||||
}
|
||||
|
||||
string clientAddon = null;
|
||||
string serverAddon = null;
|
||||
|
||||
if (branch.addon_id != null)
|
||||
{
|
||||
var addon = da.Updates.GetAddon(branch.addon_id.Value);
|
||||
if (addon.addon_zip_url != null)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_CLIENT_ADDON);
|
||||
await client.DownloadFileTaskAsync(new Uri(addon.addon_zip_url), updateDir + "clientAddon.zip");
|
||||
clientAddon = updateDir + "clientAddon.zip";
|
||||
}
|
||||
if (addon.server_zip_url != null)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_SERVER_ADDON);
|
||||
await client.DownloadFileTaskAsync(new Uri(addon.addon_zip_url), updateDir + "serverAddon.zip");
|
||||
serverAddon = updateDir + "serverAddon.zip";
|
||||
}
|
||||
else
|
||||
{
|
||||
serverAddon = clientAddon;
|
||||
}
|
||||
}
|
||||
|
||||
//last client update.
|
||||
var previousUpdate = (branch.current_dist_id == null) ? null : da.Updates.GetUpdate(branch.current_dist_id.Value);
|
||||
|
||||
//all files downloaded. build the folders.
|
||||
//extract the artifact and then our artifact over it.
|
||||
if (clientArti != null)
|
||||
{
|
||||
var clientPath = updateDir + "client/";
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_CLIENT);
|
||||
var clientZip = ZipFile.Open(clientArti, ZipArchiveMode.Read);
|
||||
clientZip.ExtractToDirectory(clientPath, true);
|
||||
clientZip.Dispose();
|
||||
File.Delete(clientArti);
|
||||
clientPath = GetZipFolder(clientPath);
|
||||
|
||||
if (clientAddon != null)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_CLIENT_ADDON);
|
||||
var addonZip = ZipFile.Open(clientAddon, ZipArchiveMode.Read);
|
||||
addonZip.ExtractToDirectory(clientPath, true);
|
||||
addonZip.Dispose();
|
||||
if (clientAddon != serverAddon) File.Delete(clientAddon);
|
||||
}
|
||||
//emit version number
|
||||
await System.IO.File.WriteAllTextAsync(Path.Combine(clientPath, "version.txt"), versionText);
|
||||
if (request.catalog != null)
|
||||
{
|
||||
await System.IO.File.WriteAllTextAsync(Path.Combine(clientPath, "Content/Objects/catalog_downloads.xml"), request.catalog);
|
||||
}
|
||||
|
||||
string diffZip = null;
|
||||
FSOUpdateManifest manifest = null;
|
||||
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_DIFF);
|
||||
if (previousUpdate != null || request.disableIncremental)
|
||||
{
|
||||
result.last_update_id = previousUpdate.update_id;
|
||||
//calculate difference, generate an incremental update manifest + zip
|
||||
var prevFile = updateDir + "prev.zip";
|
||||
await client.DownloadFileTaskAsync(new Uri(previousUpdate.full_zip), updateDir + "prev.zip");
|
||||
var prevZip = ZipFile.Open(prevFile, ZipArchiveMode.Read);
|
||||
prevZip.ExtractToDirectory(updateDir + "prev/", true);
|
||||
prevZip.Dispose();
|
||||
File.Delete(updateDir + "prev.zip");
|
||||
|
||||
var diffs = DiffGenerator.GetDiffs(Path.GetFullPath(updateDir + "prev/"), Path.GetFullPath(clientPath));
|
||||
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_INCREMENTAL_UPDATE);
|
||||
var toZip = diffs.Where(x => x.DiffType == FileDiffType.Add || x.DiffType == FileDiffType.Modify);
|
||||
if (request.contentOnly) toZip = toZip.Where(x => x.Path.Replace('\\', '/').TrimStart('/').StartsWith("Content"));
|
||||
if (!request.includeMonogameDelta) toZip = toZip.Where(x => !x.Path.Replace('\\', '/').TrimStart('/').StartsWith("Monogame"));
|
||||
//build diff folder
|
||||
Directory.CreateDirectory(updateDir + "diff/");
|
||||
foreach (var diff in toZip)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(updateDir + "diff/", diff.Path)));
|
||||
System.IO.File.Copy(Path.Combine(clientPath, diff.Path), Path.Combine(updateDir + "diff/", diff.Path));
|
||||
}
|
||||
diffZip = updateDir + "diffResult.zip";
|
||||
ClearFolderPermissions(updateDir + "diff/");
|
||||
ZipFile.CreateFromDirectory(updateDir + "diff/", diffZip, CompressionLevel.Optimal, false);
|
||||
Directory.Delete(updateDir + "diff/", true);
|
||||
manifest = new FSOUpdateManifest() { Diffs = diffs };
|
||||
|
||||
Directory.Delete(updateDir + "prev/", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (request.contentOnly) throw new Exception("Invalid request - you cannot make a content only update with no delta.");
|
||||
//full update only. generate simple manifest that contains all files (added)
|
||||
manifest = new FSOUpdateManifest() { Diffs = new List<FileDiff>() };
|
||||
}
|
||||
|
||||
//pack full client
|
||||
if (!request.contentOnly)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_CLIENT);
|
||||
var finalClientZip = updateDir + "clientResult.zip";
|
||||
ClearFolderPermissions(clientPath);
|
||||
ZipFile.CreateFromDirectory(clientPath, finalClientZip, CompressionLevel.Optimal, false);
|
||||
Directory.Delete(updateDir + "client/", true);
|
||||
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.PUBLISHING_CLIENT);
|
||||
result.full_zip = await Api.INSTANCE.UpdateUploaderClient.UploadFile($"{baseUpdateKey}client-{versionName}.zip", finalClientZip, versionName);
|
||||
}
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.PUBLISHING_CLIENT);
|
||||
if (diffZip != null)
|
||||
{
|
||||
result.incremental_zip = await Api.INSTANCE.UpdateUploaderClient.UploadFile($"{baseUpdateKey}incremental-{versionName}.zip", diffZip, versionName);
|
||||
}
|
||||
await System.IO.File.WriteAllTextAsync(updateDir + "manifest.json", Newtonsoft.Json.JsonConvert.SerializeObject(manifest));
|
||||
result.manifest_url = await Api.INSTANCE.UpdateUploaderClient.UploadFile($"{baseUpdateKey}{versionName}.json", updateDir + "manifest.json", versionName);
|
||||
}
|
||||
|
||||
if (serverArti != null && !request.contentOnly)
|
||||
{
|
||||
var serverPath = updateDir + "server/";
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_SERVER);
|
||||
var serverZip = ZipFile.Open(serverArti, ZipArchiveMode.Read);
|
||||
serverZip.ExtractToDirectory(serverPath, true);
|
||||
serverZip.Dispose();
|
||||
File.Delete(serverArti);
|
||||
serverPath = GetZipFolder(serverPath);
|
||||
|
||||
if (serverAddon != null)
|
||||
{
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_SERVER_ADDON);
|
||||
var addonZip = ZipFile.Open(serverAddon, ZipArchiveMode.Read);
|
||||
addonZip.ExtractToDirectory(serverPath, true);
|
||||
addonZip.Dispose();
|
||||
File.Delete(serverAddon);
|
||||
}
|
||||
//emit version number
|
||||
await System.IO.File.WriteAllTextAsync(Path.Combine(serverPath, "version.txt"), versionText);
|
||||
if (request.catalog != null)
|
||||
{
|
||||
await System.IO.File.WriteAllTextAsync(Path.Combine(serverPath, "Content/Objects/catalog_downloads.xml"), request.catalog);
|
||||
}
|
||||
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_SERVER);
|
||||
var finalServerZip = updateDir + "serverResult.zip";
|
||||
ClearFolderPermissions(serverPath);
|
||||
ZipFile.CreateFromDirectory(serverPath, finalServerZip, CompressionLevel.Optimal, false);
|
||||
Directory.Delete(updateDir + "server/", true);
|
||||
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.PUBLISHING_SERVER);
|
||||
result.server_zip = await Api.INSTANCE.UpdateUploader.UploadFile($"{baseUpdateKey}server-{versionName}.zip", finalServerZip, versionName);
|
||||
} else
|
||||
{
|
||||
result.server_zip = result.incremental_zip; //same as client, as server uses same content.
|
||||
}
|
||||
|
||||
status.UpdateStatus(UpdateGenerationStatusCode.SCHEDULING_UPDATE);
|
||||
var finalID = da.Updates.AddUpdate(result);
|
||||
da.Updates.UpdateBranchLatest(branch.branch_id, branch.last_version_number, branch.minor_version_number);
|
||||
status.SetResult(result);
|
||||
}
|
||||
} catch (Exception e)
|
||||
{
|
||||
status.SetFailure("Update could not be completed." + e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
server/FSO.Server.Api.Core/Services/GithubUpdateUploader.cs
Executable file
61
server/FSO.Server.Api.Core/Services/GithubUpdateUploader.cs
Executable file
|
@ -0,0 +1,61 @@
|
|||
using FSO.Server.Common.Config;
|
||||
using Octokit;
|
||||
using Octokit.Internal;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSO.Server.Api.Core.Services
|
||||
{
|
||||
public class GithubUpdateUploader : IUpdateUploader
|
||||
{
|
||||
private GithubConfig Config;
|
||||
|
||||
private static string Description = "This is an automated client release produced by the master FreeSO server. " +
|
||||
"These releases match up with a branch on GitHub, but with some addon content such as a custom catalog and splash. " +
|
||||
"It can be downloaded and installed directly, but it is better to do so through the game's launcher/updater. \n\n" +
|
||||
"The incremental update is applied by simply extracting the zip over the previous version of the game " +
|
||||
"(though the updater still affords extra validation here)";
|
||||
|
||||
public GithubUpdateUploader(GithubConfig config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public async Task<string> UploadFile(string destPath, string fileName, string groupName)
|
||||
{
|
||||
destPath = Path.GetFileName(destPath);
|
||||
var credentials = new InMemoryCredentialStore(new Credentials(Config.AccessToken));
|
||||
|
||||
var client = new GitHubClient(new ProductHeaderValue(Config.AppName), credentials);
|
||||
|
||||
Release release;
|
||||
try
|
||||
{
|
||||
release = await client.Repository.Release.Get(Config.User, Config.Repository, groupName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
release = null;
|
||||
}
|
||||
if (release == null) {
|
||||
var newRel = new NewRelease(groupName);
|
||||
newRel.Body = Description;
|
||||
release = await client.Repository.Release.Create(Config.User, Config.Repository, newRel);
|
||||
}
|
||||
|
||||
using (var file = File.Open(fileName, System.IO.FileMode.Open, FileAccess.Read, FileShare.Read)) {
|
||||
try
|
||||
{
|
||||
var asset = await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload(destPath, "application/zip", file, null));
|
||||
return asset.BrowserDownloadUrl;
|
||||
} catch (Exception e)
|
||||
{
|
||||
//last time i tried, it mysteriously failed here but the file was uploaded :thinking:
|
||||
Console.WriteLine($"!! Upload request for {destPath} failed, check it actually succeeded !! \n" + e.ToString());
|
||||
return $"https://github.com/{Config.User}/{Config.Repository}/releases/download/{groupName}/{destPath}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
server/FSO.Server.Api.Core/Services/IUpdateUploader.cs
Executable file
9
server/FSO.Server.Api.Core/Services/IUpdateUploader.cs
Executable file
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSO.Server.Api.Core.Services
|
||||
{
|
||||
public interface IUpdateUploader
|
||||
{
|
||||
Task<string> UploadFile(string destPath, string fileName, string groupName);
|
||||
}
|
||||
}
|
63
server/FSO.Server.Api.Core/Startup.cs
Executable file
63
server/FSO.Server.Api.Core/Startup.cs
Executable file
|
@ -0,0 +1,63 @@
|
|||
using FSO.Server.Common;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FSO.Server.Api.Core
|
||||
{
|
||||
public class Startup : IAPILifetime
|
||||
{
|
||||
public IApplicationLifetime AppLifetime;
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(
|
||||
builder =>
|
||||
{
|
||||
|
||||
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("content-disposition");
|
||||
});
|
||||
|
||||
options.AddPolicy("AdminAppPolicy",
|
||||
builder =>
|
||||
{
|
||||
builder.WithOrigins("https://freeso.org", "http://localhost:8080").AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithExposedHeaders("content-disposition");
|
||||
});
|
||||
}).AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseHsts();
|
||||
}
|
||||
app.UseCors();
|
||||
//app.UseHttpsRedirection();
|
||||
app.UseMvc();
|
||||
AppLifetime = appLifetime;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
AppLifetime.StopApplication();
|
||||
}
|
||||
}
|
||||
}
|
77
server/FSO.Server.Api.Core/Utils/ApiMail.cs
Executable file
77
server/FSO.Server.Api.Core/Utils/ApiMail.cs
Executable file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace FSO.Server.Api.Core.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to mail our users.
|
||||
/// Could be more useful in the future when out of Beta.
|
||||
/// </summary>
|
||||
public class ApiMail
|
||||
{
|
||||
string subject;
|
||||
string template;
|
||||
Dictionary<string, string> strings;
|
||||
|
||||
public ApiMail(string template)
|
||||
{
|
||||
this.template = template;
|
||||
this.strings = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public void AddString(string key, string value)
|
||||
{
|
||||
strings.Add(key, value);
|
||||
}
|
||||
|
||||
private string ComposeBody(Dictionary<string, string> strings)
|
||||
{
|
||||
string baseTemplate = File.ReadAllText("./MailTemplates/MailBase.html");
|
||||
string content = File.ReadAllText("./MailTemplates/" + template + ".html");
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in strings)
|
||||
{
|
||||
content = content.Replace("%" + entry.Key + "%", entry.Value);
|
||||
}
|
||||
|
||||
strings = new Dictionary<string, string>();
|
||||
return baseTemplate.Replace("%content%", content);
|
||||
}
|
||||
|
||||
public bool Send(string to, string subject)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if(api.Config.SmtpEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
MailMessage message = new MailMessage();
|
||||
message.From = new MailAddress(api.Config.SmtpUser, "FreeSO Staff");
|
||||
message.To.Add(to);
|
||||
message.Subject = subject;
|
||||
message.IsBodyHtml = true;
|
||||
message.Body = ComposeBody(strings);
|
||||
|
||||
SmtpClient client = new SmtpClient();
|
||||
client.UseDefaultCredentials = true;
|
||||
|
||||
client.Host = api.Config.SmtpHost;
|
||||
client.Port = api.Config.SmtpPort;
|
||||
client.EnableSsl = true;
|
||||
client.Credentials = new System.Net.NetworkCredential(api.Config.SmtpUser, api.Config.SmtpPassword);
|
||||
|
||||
// Send async
|
||||
client.SendMailAsync(message);
|
||||
|
||||
return true;
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
78
server/FSO.Server.Api.Core/Utils/ApiResponse.cs
Executable file
78
server/FSO.Server.Api.Core/Utils/ApiResponse.cs
Executable file
|
@ -0,0 +1,78 @@
|
|||
using FSO.Common.Utils;
|
||||
using FSO.Server.Database.DA.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Xml;
|
||||
|
||||
namespace FSO.Server.Api.Core.Utils
|
||||
{
|
||||
public class ApiResponse
|
||||
{
|
||||
public static IActionResult Plain(HttpStatusCode code, string text)
|
||||
{
|
||||
return new ContentResult
|
||||
{
|
||||
StatusCode = (int)code,
|
||||
Content = text,
|
||||
ContentType = "text/plain"
|
||||
};
|
||||
}
|
||||
|
||||
public static IActionResult Json(HttpStatusCode code, object obj)
|
||||
{
|
||||
return new ContentResult
|
||||
{
|
||||
StatusCode = (int)code,
|
||||
Content = Newtonsoft.Json.JsonConvert.SerializeObject(obj),
|
||||
ContentType = "application/json"
|
||||
};
|
||||
}
|
||||
|
||||
public static IActionResult PagedList<T>(HttpRequest request, HttpStatusCode code, PagedList<T> list)
|
||||
{
|
||||
request.HttpContext.Response.Headers.Add("X-Total-Count", list.Total.ToString());
|
||||
request.HttpContext.Response.Headers.Add("X-Offset", list.Offset.ToString());
|
||||
|
||||
return new ContentResult
|
||||
{
|
||||
StatusCode = (int)code,
|
||||
Content = Newtonsoft.Json.JsonConvert.SerializeObject(list),
|
||||
ContentType = "application/json"
|
||||
};
|
||||
}
|
||||
|
||||
public static IActionResult Xml(HttpStatusCode code, IXMLEntity xml)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
var firstChild = xml.Serialize(doc);
|
||||
doc.AppendChild(firstChild);
|
||||
|
||||
return new ContentResult
|
||||
{
|
||||
StatusCode = (int)code,
|
||||
Content = doc.OuterXml,
|
||||
ContentType = "text/xml"
|
||||
};
|
||||
}
|
||||
|
||||
public static Func<IActionResult> XmlFuture(HttpStatusCode code, IXMLEntity xml)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
var firstChild = xml.Serialize(doc);
|
||||
doc.AppendChild(firstChild);
|
||||
var serialized = doc.OuterXml;
|
||||
|
||||
return () =>
|
||||
{
|
||||
return new ContentResult
|
||||
{
|
||||
StatusCode = (int)code,
|
||||
Content = serialized,
|
||||
ContentType = "text/xml"
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
35
server/FSO.Server.Api.Core/Utils/ApiUtils.cs
Executable file
35
server/FSO.Server.Api.Core/Utils/ApiUtils.cs
Executable file
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Server.Api.Core.Utils
|
||||
{
|
||||
public class ApiUtils
|
||||
{
|
||||
private const string HttpContext = "MS_HttpContext";
|
||||
private const string RemoteEndpointMessage =
|
||||
"System.ServiceModel.Channels.RemoteEndpointMessageProperty";
|
||||
private const string OwinContext = "MS_OwinContext";
|
||||
|
||||
public static string GetIP(HttpRequest request)
|
||||
{
|
||||
var api = FSO.Server.Api.Core.Api.INSTANCE;
|
||||
if (!api.Config.UseProxy)
|
||||
{
|
||||
return request.HttpContext.Connection.RemoteIpAddress.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var ip = "127.0.0.1";
|
||||
var xff = request.Headers["X-Forwarded-For"];
|
||||
if (xff.Count != 0)
|
||||
{
|
||||
ip = xff.First();
|
||||
ip = ip.Substring(ip.IndexOf(",") + 1);
|
||||
var last = ip.LastIndexOf(":");
|
||||
if (last != -1 && last < ip.Length - 5) ip = ip.Substring(0, last);
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
server/FSO.Server.Api.Core/appsettings.Development.json
Executable file
9
server/FSO.Server.Api.Core/appsettings.Development.json
Executable file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
8
server/FSO.Server.Api.Core/appsettings.json
Executable file
8
server/FSO.Server.Api.Core/appsettings.json
Executable file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v2.2", FrameworkDisplayName = ".NET Core 2.2")]
|
|
@ -0,0 +1,22 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("FSO.Server.Api.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f12ba1502b1a207b2b7e131c8ed38d8d4ef33d1e")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("FSO.Server.Api.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("FSO.Server.Api.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
|
@ -0,0 +1 @@
|
|||
56295aa1e4d64bef744bb30e9641d816d4a2f240e548daea26ca3acc11acdbfc
|
|
@ -0,0 +1,5 @@
|
|||
is_global = true
|
||||
build_property.RootNamespace = FSO.Server.Api.Core
|
||||
build_property.ProjectDir = /Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
|
@ -0,0 +1 @@
|
|||
96ad7024ec164a21bac08e2efe1079153c67c90031478db2e40a71ebb48e40d6
|
|
@ -0,0 +1,19 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.RelatedAssemblyAttribute("FSO.Server.Api.Core.Views")]
|
||||
[assembly: Microsoft.AspNetCore.Razor.Hosting.RazorLanguageVersionAttribute("2.1")]
|
||||
[assembly: Microsoft.AspNetCore.Razor.Hosting.RazorConfigurationNameAttribute("MVC-2.1")]
|
||||
[assembly: Microsoft.AspNetCore.Razor.Hosting.RazorExtensionAssemblyNameAttribute("MVC-2.1", "Microsoft.AspNetCore.Mvc.Razor.Extensions")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,267 @@
|
|||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj",
|
||||
"projectName": "FSO.Server.Api.Core",
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj",
|
||||
"packagesPath": "/Volumes/TheD/.nuget",
|
||||
"outputPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/Users/tonytins/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"netcoreapp2.2"
|
||||
],
|
||||
"sources": {
|
||||
"/Volumes/TheD/.dotnet/library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp2.2": {
|
||||
"targetAlias": "netcoreapp2.2",
|
||||
"projectReferences": {
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Domain/FSO.Server.Domain.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Domain/FSO.Server.Domain.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp2.2": {
|
||||
"targetAlias": "netcoreapp2.2",
|
||||
"dependencies": {
|
||||
"AWSSDK.Core": {
|
||||
"target": "Package",
|
||||
"version": "[3.3.103.48, )"
|
||||
},
|
||||
"AWSSDK.S3": {
|
||||
"target": "Package",
|
||||
"version": "[3.3.104.36, )"
|
||||
},
|
||||
"Microsoft.AspNetCore.All": {
|
||||
"target": "Package",
|
||||
"version": "[2.2.7, )"
|
||||
},
|
||||
"Microsoft.AspNetCore.App": {
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[2.2.8, )",
|
||||
"autoReferenced": true
|
||||
},
|
||||
"Microsoft.AspNetCore.Cors": {
|
||||
"target": "Package",
|
||||
"version": "[2.2.0, )"
|
||||
},
|
||||
"Microsoft.NETCore.App": {
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[2.2.8, )",
|
||||
"autoReferenced": true
|
||||
},
|
||||
"Octokit": {
|
||||
"target": "Package",
|
||||
"version": "[0.36.0, )"
|
||||
},
|
||||
"System.Security.Cryptography.Algorithms": {
|
||||
"target": "Package",
|
||||
"version": "[4.3.1, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"runtimeIdentifierGraphPath": "/Volumes/TheD/.dotnet/sdk/8.0.204/RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj": {
|
||||
"restore": {
|
||||
"projectUniqueName": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj",
|
||||
"projectName": "FSO.Server.Common",
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj",
|
||||
"packagesPath": "/Volumes/TheD/.nuget",
|
||||
"projectStyle": "PackagesConfig",
|
||||
"configFilePaths": [
|
||||
"/Users/tonytins/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
"projectReferences": {
|
||||
"/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"packagesConfigPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/packages.config"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {}
|
||||
}
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj": {
|
||||
"restore": {
|
||||
"projectUniqueName": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj",
|
||||
"projectName": "FSO.Server.Database",
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj",
|
||||
"packagesPath": "/Volumes/TheD/.nuget",
|
||||
"projectStyle": "PackagesConfig",
|
||||
"configFilePaths": [
|
||||
"/Users/tonytins/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
"projectReferences": {
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"packagesConfigPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/packages.config"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {}
|
||||
}
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Domain/FSO.Server.Domain.csproj": {
|
||||
"restore": {
|
||||
"projectUniqueName": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Domain/FSO.Server.Domain.csproj",
|
||||
"projectName": "FSO.Server.Domain",
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Domain/FSO.Server.Domain.csproj",
|
||||
"packagesPath": "/Volumes/TheD/.nuget",
|
||||
"projectStyle": "PackagesConfig",
|
||||
"configFilePaths": [
|
||||
"/Users/tonytins/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
"projectReferences": {
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Database/FSO.Server.Database.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"packagesConfigPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Domain/packages.config"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {}
|
||||
}
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj": {
|
||||
"restore": {
|
||||
"projectUniqueName": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj",
|
||||
"projectName": "FSO.Server.Protocol",
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/FSO.Server.Protocol.csproj",
|
||||
"packagesPath": "/Volumes/TheD/.nuget",
|
||||
"projectStyle": "PackagesConfig",
|
||||
"configFilePaths": [
|
||||
"/Users/tonytins/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
"projectReferences": {
|
||||
"/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Common/FSO.Server.Common.csproj"
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj": {
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"packagesConfigPath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Protocol/packages.config"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {}
|
||||
}
|
||||
},
|
||||
"/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj": {
|
||||
"restore": {
|
||||
"projectUniqueName": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj",
|
||||
"projectName": "FSO.Common",
|
||||
"projectPath": "/Volumes/TheD/Projects/ztso/server/tso.common/FSO.Common.csproj",
|
||||
"packagesPath": "/Volumes/TheD/.nuget",
|
||||
"projectStyle": "PackagesConfig",
|
||||
"configFilePaths": [
|
||||
"/Users/tonytins/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"packagesConfigPath": "/Volumes/TheD/Projects/ztso/server/tso.common/packages.config"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Volumes/TheD/.nuget</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Volumes/TheD/.nuget</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.9.1</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="/Volumes/TheD/.nuget/" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.netcore.app/2.2.8/build/netcoreapp2.2/Microsoft.NETCore.App.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.netcore.app/2.2.8/build/netcoreapp2.2/Microsoft.NETCore.App.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.extensions.fileproviders.embedded/2.2.0/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.extensions.fileproviders.embedded/2.2.0/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.extensions.configuration.usersecrets/2.2.0/build/netstandard2.0/Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.extensions.configuration.usersecrets/2.2.0/build/netstandard2.0/Microsoft.Extensions.Configuration.UserSecrets.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.entityframeworkcore.design/2.2.6/build/netcoreapp2.0/Microsoft.EntityFrameworkCore.Design.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.entityframeworkcore.design/2.2.6/build/netcoreapp2.0/Microsoft.EntityFrameworkCore.Design.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.mvc.razor.extensions/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.mvc.razor.extensions/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.razor.design/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.razor.design/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.app/2.2.8/build/netcoreapp2.2/Microsoft.AspNetCore.App.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.app/2.2.8/build/netcoreapp2.2/Microsoft.AspNetCore.App.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.all/2.2.7/build/netcoreapp2.2/Microsoft.AspNetCore.All.props" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.all/2.2.7/build/netcoreapp2.2/Microsoft.AspNetCore.All.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgMicrosoft_EntityFrameworkCore_Tools Condition=" '$(PkgMicrosoft_EntityFrameworkCore_Tools)' == '' ">/Volumes/TheD/.nuget/microsoft.entityframeworkcore.tools/2.2.6</PkgMicrosoft_EntityFrameworkCore_Tools>
|
||||
<PkgMicrosoft_CodeAnalysis_Analyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_Analyzers)' == '' ">/Volumes/TheD/.nuget/microsoft.codeanalysis.analyzers/1.1.0</PkgMicrosoft_CodeAnalysis_Analyzers>
|
||||
<PkgMicrosoft_AspNetCore_Razor_Design Condition=" '$(PkgMicrosoft_AspNetCore_Razor_Design)' == '' ">/Volumes/TheD/.nuget/microsoft.aspnetcore.razor.design/2.2.0</PkgMicrosoft_AspNetCore_Razor_Design>
|
||||
<PkgAWSSDK_Core Condition=" '$(PkgAWSSDK_Core)' == '' ">/Volumes/TheD/.nuget/awssdk.core/3.3.103.48</PkgAWSSDK_Core>
|
||||
<PkgAWSSDK_S3 Condition=" '$(PkgAWSSDK_S3)' == '' ">/Volumes/TheD/.nuget/awssdk.s3/3.3.104.36</PkgAWSSDK_S3>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)/netstandard.library/2.0.3/build/netstandard2.0/NETStandard.Library.targets" Condition="Exists('$(NuGetPackageRoot)/netstandard.library/2.0.3/build/netstandard2.0/NETStandard.Library.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.netcore.app/2.2.8/build/netcoreapp2.2/Microsoft.NETCore.App.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.netcore.app/2.2.8/build/netcoreapp2.2/Microsoft.NETCore.App.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.extensions.fileproviders.embedded/2.2.0/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.extensions.fileproviders.embedded/2.2.0/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.extensions.configuration.usersecrets/2.2.0/build/netstandard2.0/Microsoft.Extensions.Configuration.UserSecrets.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.extensions.configuration.usersecrets/2.2.0/build/netstandard2.0/Microsoft.Extensions.Configuration.UserSecrets.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.mvc.razor.extensions/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.mvc.razor.extensions/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.server.iisintegration/2.2.1/build/netstandard2.0/Microsoft.AspNetCore.Server.IISIntegration.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.server.iisintegration/2.2.1/build/netstandard2.0/Microsoft.AspNetCore.Server.IISIntegration.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.server.iis/2.2.6/build/netstandard2.0/Microsoft.AspNetCore.Server.IIS.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.server.iis/2.2.6/build/netstandard2.0/Microsoft.AspNetCore.Server.IIS.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.mvc.razor.viewcompilation/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.mvc.razor.viewcompilation/2.2.0/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.app/2.2.8/build/netcoreapp2.2/Microsoft.AspNetCore.App.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.app/2.2.8/build/netcoreapp2.2/Microsoft.AspNetCore.App.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)/microsoft.aspnetcore.all/2.2.7/build/netcoreapp2.2/Microsoft.AspNetCore.All.targets" Condition="Exists('$(NuGetPackageRoot)/microsoft.aspnetcore.all/2.2.7/build/netcoreapp2.2/Microsoft.AspNetCore.All.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
16011
server/FSO.Server.Api.Core/obj/project.assets.json
Normal file
16011
server/FSO.Server.Api.Core/obj/project.assets.json
Normal file
File diff suppressed because it is too large
Load diff
343
server/FSO.Server.Api.Core/obj/project.nuget.cache
Normal file
343
server/FSO.Server.Api.Core/obj/project.nuget.cache
Normal file
|
@ -0,0 +1,343 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "O9uTcc8ul7R5Oy7wU0RviTvK+nfjiri5q/zad3hfpvfdHyjPko5E7PeIbBSDiQiPyuONvNRmcuzEhHPbXoSo6Q==",
|
||||
"success": true,
|
||||
"projectFilePath": "/Volumes/TheD/Projects/ztso/server/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"/Volumes/TheD/.nuget/awssdk.core/3.3.103.48/awssdk.core.3.3.103.48.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/awssdk.s3/3.3.104.36/awssdk.s3.3.3.104.36.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/libuv/1.10.0/libuv.1.10.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/messagepack/1.7.3.7/messagepack.1.7.3.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.applicationinsights/2.4.0/microsoft.applicationinsights.2.4.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.applicationinsights.aspnetcore/2.1.1/microsoft.applicationinsights.aspnetcore.2.1.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.applicationinsights.dependencycollector/2.4.1/microsoft.applicationinsights.dependencycollector.2.4.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnet.webapi.client/5.2.6/microsoft.aspnet.webapi.client.5.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore/2.2.0/microsoft.aspnetcore.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.all/2.2.7/microsoft.aspnetcore.all.2.2.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.antiforgery/2.2.0/microsoft.aspnetcore.antiforgery.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.app/2.2.8/microsoft.aspnetcore.app.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.applicationinsights.hostingstartup/2.2.0/microsoft.aspnetcore.applicationinsights.hostingstartup.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication/2.2.0/microsoft.aspnetcore.authentication.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.abstractions/2.2.0/microsoft.aspnetcore.authentication.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.cookies/2.2.0/microsoft.aspnetcore.authentication.cookies.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.core/2.2.0/microsoft.aspnetcore.authentication.core.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.facebook/2.2.0/microsoft.aspnetcore.authentication.facebook.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.google/2.2.2/microsoft.aspnetcore.authentication.google.2.2.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.jwtbearer/2.2.0/microsoft.aspnetcore.authentication.jwtbearer.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.microsoftaccount/2.2.0/microsoft.aspnetcore.authentication.microsoftaccount.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.oauth/2.2.0/microsoft.aspnetcore.authentication.oauth.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.openidconnect/2.2.0/microsoft.aspnetcore.authentication.openidconnect.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.twitter/2.2.0/microsoft.aspnetcore.authentication.twitter.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authentication.wsfederation/2.2.0/microsoft.aspnetcore.authentication.wsfederation.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authorization/2.2.0/microsoft.aspnetcore.authorization.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.authorization.policy/2.2.0/microsoft.aspnetcore.authorization.policy.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.azureappservices.hostingstartup/2.2.5/microsoft.aspnetcore.azureappservices.hostingstartup.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.azureappservicesintegration/2.2.5/microsoft.aspnetcore.azureappservicesintegration.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.connections.abstractions/2.2.0/microsoft.aspnetcore.connections.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.cookiepolicy/2.2.8/microsoft.aspnetcore.cookiepolicy.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.cors/2.2.0/microsoft.aspnetcore.cors.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.cryptography.internal/2.2.0/microsoft.aspnetcore.cryptography.internal.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.cryptography.keyderivation/2.2.0/microsoft.aspnetcore.cryptography.keyderivation.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.dataprotection/2.2.0/microsoft.aspnetcore.dataprotection.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.dataprotection.abstractions/2.2.0/microsoft.aspnetcore.dataprotection.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.dataprotection.azurekeyvault/2.2.0/microsoft.aspnetcore.dataprotection.azurekeyvault.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.dataprotection.azurestorage/2.2.7/microsoft.aspnetcore.dataprotection.azurestorage.2.2.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.dataprotection.extensions/2.2.0/microsoft.aspnetcore.dataprotection.extensions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.diagnostics/2.2.0/microsoft.aspnetcore.diagnostics.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.diagnostics.abstractions/2.2.0/microsoft.aspnetcore.diagnostics.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.diagnostics.entityframeworkcore/2.2.1/microsoft.aspnetcore.diagnostics.entityframeworkcore.2.2.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.diagnostics.healthchecks/2.2.0/microsoft.aspnetcore.diagnostics.healthchecks.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.hostfiltering/2.2.0/microsoft.aspnetcore.hostfiltering.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.hosting/2.2.7/microsoft.aspnetcore.hosting.2.2.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.hosting.abstractions/2.2.0/microsoft.aspnetcore.hosting.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.hosting.server.abstractions/2.2.0/microsoft.aspnetcore.hosting.server.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.html.abstractions/2.2.0/microsoft.aspnetcore.html.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.http/2.2.2/microsoft.aspnetcore.http.2.2.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.http.abstractions/2.2.0/microsoft.aspnetcore.http.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.http.connections/1.1.0/microsoft.aspnetcore.http.connections.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.http.connections.common/1.1.0/microsoft.aspnetcore.http.connections.common.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.http.extensions/2.2.0/microsoft.aspnetcore.http.extensions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.http.features/2.2.0/microsoft.aspnetcore.http.features.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.httpoverrides/2.2.0/microsoft.aspnetcore.httpoverrides.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.httpspolicy/2.2.0/microsoft.aspnetcore.httpspolicy.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.identity/2.2.0/microsoft.aspnetcore.identity.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.identity.entityframeworkcore/2.2.0/microsoft.aspnetcore.identity.entityframeworkcore.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.identity.ui/2.2.5/microsoft.aspnetcore.identity.ui.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.jsonpatch/2.2.0/microsoft.aspnetcore.jsonpatch.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.localization/2.2.0/microsoft.aspnetcore.localization.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.localization.routing/2.2.0/microsoft.aspnetcore.localization.routing.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.middlewareanalysis/2.2.0/microsoft.aspnetcore.middlewareanalysis.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc/2.2.0/microsoft.aspnetcore.mvc.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.abstractions/2.2.0/microsoft.aspnetcore.mvc.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.analyzers/2.2.0/microsoft.aspnetcore.mvc.analyzers.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.apiexplorer/2.2.0/microsoft.aspnetcore.mvc.apiexplorer.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.core/2.2.5/microsoft.aspnetcore.mvc.core.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.cors/2.2.0/microsoft.aspnetcore.mvc.cors.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.dataannotations/2.2.0/microsoft.aspnetcore.mvc.dataannotations.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.formatters.json/2.2.0/microsoft.aspnetcore.mvc.formatters.json.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.formatters.xml/2.2.0/microsoft.aspnetcore.mvc.formatters.xml.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.localization/2.2.0/microsoft.aspnetcore.mvc.localization.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.razor/2.2.0/microsoft.aspnetcore.mvc.razor.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.razor.extensions/2.2.0/microsoft.aspnetcore.mvc.razor.extensions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.razor.viewcompilation/2.2.0/microsoft.aspnetcore.mvc.razor.viewcompilation.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.razorpages/2.2.5/microsoft.aspnetcore.mvc.razorpages.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.taghelpers/2.2.0/microsoft.aspnetcore.mvc.taghelpers.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.mvc.viewfeatures/2.2.0/microsoft.aspnetcore.mvc.viewfeatures.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.nodeservices/2.2.0/microsoft.aspnetcore.nodeservices.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.owin/2.2.0/microsoft.aspnetcore.owin.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.razor/2.2.0/microsoft.aspnetcore.razor.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.razor.design/2.2.0/microsoft.aspnetcore.razor.design.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.razor.language/2.2.0/microsoft.aspnetcore.razor.language.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.razor.runtime/2.2.0/microsoft.aspnetcore.razor.runtime.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.responsecaching/2.2.0/microsoft.aspnetcore.responsecaching.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.responsecaching.abstractions/2.2.0/microsoft.aspnetcore.responsecaching.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.responsecompression/2.2.0/microsoft.aspnetcore.responsecompression.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.rewrite/2.2.0/microsoft.aspnetcore.rewrite.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.routing/2.2.2/microsoft.aspnetcore.routing.2.2.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.routing.abstractions/2.2.0/microsoft.aspnetcore.routing.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.httpsys/2.2.6/microsoft.aspnetcore.server.httpsys.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.iis/2.2.6/microsoft.aspnetcore.server.iis.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.iisintegration/2.2.1/microsoft.aspnetcore.server.iisintegration.2.2.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.kestrel/2.2.0/microsoft.aspnetcore.server.kestrel.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.kestrel.core/2.2.0/microsoft.aspnetcore.server.kestrel.core.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.kestrel.https/2.2.0/microsoft.aspnetcore.server.kestrel.https.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.kestrel.transport.abstractions/2.2.0/microsoft.aspnetcore.server.kestrel.transport.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.kestrel.transport.libuv/2.2.0/microsoft.aspnetcore.server.kestrel.transport.libuv.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.server.kestrel.transport.sockets/2.2.1/microsoft.aspnetcore.server.kestrel.transport.sockets.2.2.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.session/2.2.0/microsoft.aspnetcore.session.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.signalr/1.1.0/microsoft.aspnetcore.signalr.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.signalr.common/1.1.0/microsoft.aspnetcore.signalr.common.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.signalr.core/1.1.0/microsoft.aspnetcore.signalr.core.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.signalr.protocols.json/1.1.0/microsoft.aspnetcore.signalr.protocols.json.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.signalr.redis/1.1.5/microsoft.aspnetcore.signalr.redis.1.1.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.spaservices/2.2.7/microsoft.aspnetcore.spaservices.2.2.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.spaservices.extensions/2.2.0/microsoft.aspnetcore.spaservices.extensions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.staticfiles/2.2.0/microsoft.aspnetcore.staticfiles.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.websockets/2.2.1/microsoft.aspnetcore.websockets.2.2.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.aspnetcore.webutilities/2.2.0/microsoft.aspnetcore.webutilities.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.azure.keyvault/2.3.2/microsoft.azure.keyvault.2.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.azure.keyvault.webkey/2.0.7/microsoft.azure.keyvault.webkey.2.0.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.azure.services.appauthentication/1.0.1/microsoft.azure.services.appauthentication.1.0.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.codeanalysis.analyzers/1.1.0/microsoft.codeanalysis.analyzers.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.codeanalysis.common/2.8.0/microsoft.codeanalysis.common.2.8.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.codeanalysis.csharp/2.8.0/microsoft.codeanalysis.csharp.2.8.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.codeanalysis.razor/2.2.0/microsoft.codeanalysis.razor.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.csharp/4.5.0/microsoft.csharp.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.data.edm/5.8.4/microsoft.data.edm.5.8.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.data.odata/5.8.4/microsoft.data.odata.5.8.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.data.sqlite/2.2.6/microsoft.data.sqlite.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.data.sqlite.core/2.2.6/microsoft.data.sqlite.core.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.dotnet.platformabstractions/2.1.0/microsoft.dotnet.platformabstractions.2.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore/2.2.6/microsoft.entityframeworkcore.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.abstractions/2.2.6/microsoft.entityframeworkcore.abstractions.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.analyzers/2.2.6/microsoft.entityframeworkcore.analyzers.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.design/2.2.6/microsoft.entityframeworkcore.design.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.inmemory/2.2.6/microsoft.entityframeworkcore.inmemory.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.relational/2.2.6/microsoft.entityframeworkcore.relational.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.sqlite/2.2.6/microsoft.entityframeworkcore.sqlite.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.sqlite.core/2.2.6/microsoft.entityframeworkcore.sqlite.core.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.sqlserver/2.2.6/microsoft.entityframeworkcore.sqlserver.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.entityframeworkcore.tools/2.2.6/microsoft.entityframeworkcore.tools.2.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.caching.abstractions/2.2.0/microsoft.extensions.caching.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.caching.memory/2.2.0/microsoft.extensions.caching.memory.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.caching.redis/2.2.0/microsoft.extensions.caching.redis.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.caching.sqlserver/2.2.0/microsoft.extensions.caching.sqlserver.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration/2.2.0/microsoft.extensions.configuration.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.abstractions/2.2.0/microsoft.extensions.configuration.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.azurekeyvault/2.2.0/microsoft.extensions.configuration.azurekeyvault.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.binder/2.2.4/microsoft.extensions.configuration.binder.2.2.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.commandline/2.2.0/microsoft.extensions.configuration.commandline.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.environmentvariables/2.2.4/microsoft.extensions.configuration.environmentvariables.2.2.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.fileextensions/2.2.0/microsoft.extensions.configuration.fileextensions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.ini/2.2.0/microsoft.extensions.configuration.ini.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.json/2.2.0/microsoft.extensions.configuration.json.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.keyperfile/2.2.4/microsoft.extensions.configuration.keyperfile.2.2.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.usersecrets/2.2.0/microsoft.extensions.configuration.usersecrets.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.configuration.xml/2.2.0/microsoft.extensions.configuration.xml.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.dependencyinjection/2.2.0/microsoft.extensions.dependencyinjection.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.dependencyinjection.abstractions/2.2.0/microsoft.extensions.dependencyinjection.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.dependencymodel/2.1.0/microsoft.extensions.dependencymodel.2.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.diagnosticadapter/2.2.0/microsoft.extensions.diagnosticadapter.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.diagnostics.healthchecks/2.2.5/microsoft.extensions.diagnostics.healthchecks.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.diagnostics.healthchecks.abstractions/2.2.0/microsoft.extensions.diagnostics.healthchecks.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.fileproviders.abstractions/2.2.0/microsoft.extensions.fileproviders.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.fileproviders.composite/2.2.0/microsoft.extensions.fileproviders.composite.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.fileproviders.embedded/2.2.0/microsoft.extensions.fileproviders.embedded.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.fileproviders.physical/2.2.0/microsoft.extensions.fileproviders.physical.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.filesystemglobbing/2.2.0/microsoft.extensions.filesystemglobbing.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.hosting/2.2.0/microsoft.extensions.hosting.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.hosting.abstractions/2.2.0/microsoft.extensions.hosting.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.http/2.2.0/microsoft.extensions.http.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.identity.core/2.2.0/microsoft.extensions.identity.core.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.identity.stores/2.2.0/microsoft.extensions.identity.stores.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.localization/2.2.0/microsoft.extensions.localization.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.localization.abstractions/2.2.0/microsoft.extensions.localization.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging/2.2.0/microsoft.extensions.logging.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.abstractions/2.2.0/microsoft.extensions.logging.abstractions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.azureappservices/2.2.5/microsoft.extensions.logging.azureappservices.2.2.5.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.configuration/2.2.0/microsoft.extensions.logging.configuration.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.console/2.2.0/microsoft.extensions.logging.console.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.debug/2.2.0/microsoft.extensions.logging.debug.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.eventsource/2.2.0/microsoft.extensions.logging.eventsource.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.logging.tracesource/2.2.0/microsoft.extensions.logging.tracesource.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.objectpool/2.2.0/microsoft.extensions.objectpool.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.options/2.2.0/microsoft.extensions.options.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.options.configurationextensions/2.2.0/microsoft.extensions.options.configurationextensions.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.options.dataannotations/2.2.0/microsoft.extensions.options.dataannotations.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.platformabstractions/1.1.0/microsoft.extensions.platformabstractions.1.1.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.primitives/2.2.0/microsoft.extensions.primitives.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.extensions.webencoders/2.2.0/microsoft.extensions.webencoders.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.clients.activedirectory/3.19.8/microsoft.identitymodel.clients.activedirectory.3.19.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.jsonwebtokens/5.3.0/microsoft.identitymodel.jsonwebtokens.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.logging/5.3.0/microsoft.identitymodel.logging.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.protocols/5.3.0/microsoft.identitymodel.protocols.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.protocols.openidconnect/5.3.0/microsoft.identitymodel.protocols.openidconnect.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.protocols.wsfederation/5.3.0/microsoft.identitymodel.protocols.wsfederation.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.tokens/5.3.0/microsoft.identitymodel.tokens.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.tokens.saml/5.3.0/microsoft.identitymodel.tokens.saml.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.identitymodel.xml/5.3.0/microsoft.identitymodel.xml.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.net.http.headers/2.2.8/microsoft.net.http.headers.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.netcore.app/2.2.8/microsoft.netcore.app.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.netcore.dotnetapphost/2.2.8/microsoft.netcore.dotnetapphost.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.netcore.dotnethostpolicy/2.2.8/microsoft.netcore.dotnethostpolicy.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.netcore.dotnethostresolver/2.2.8/microsoft.netcore.dotnethostresolver.2.2.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.netcore.platforms/2.2.4/microsoft.netcore.platforms.2.2.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.netcore.targets/2.0.0/microsoft.netcore.targets.2.0.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.rest.clientruntime/2.3.8/microsoft.rest.clientruntime.2.3.8.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.rest.clientruntime.azure/3.3.7/microsoft.rest.clientruntime.azure.3.3.7.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.visualstudio.web.browserlink/2.2.0/microsoft.visualstudio.web.browserlink.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.win32.primitives/4.3.0/microsoft.win32.primitives.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/microsoft.win32.registry/4.5.0/microsoft.win32.registry.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/netstandard.library/2.0.3/netstandard.library.2.0.3.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/newtonsoft.json/11.0.2/newtonsoft.json.11.0.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/newtonsoft.json.bson/1.0.1/newtonsoft.json.bson.1.0.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/octokit/0.36.0/octokit.0.36.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/remotion.linq/2.2.0/remotion.linq.2.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system/4.3.0/runtime.native.system.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system.data.sqlclient.sni/4.5.0/runtime.native.system.data.sqlclient.sni.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system.io.compression/4.3.0/runtime.native.system.io.compression.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system.net.http/4.3.0/runtime.native.system.net.http.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system.net.security/4.3.0/runtime.native.system.net.security.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system.security.cryptography.apple/4.3.1/runtime.native.system.security.cryptography.apple.4.3.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.native.system.security.cryptography.openssl/4.3.2/runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.1/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.win-arm64.runtime.native.system.data.sqlclient.sni/4.4.0/runtime.win-arm64.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.win-x64.runtime.native.system.data.sqlclient.sni/4.4.0/runtime.win-x64.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/runtime.win-x86.runtime.native.system.data.sqlclient.sni/4.4.0/runtime.win-x86.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/sqlitepclraw.bundle_green/1.1.12/sqlitepclraw.bundle_green.1.1.12.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/sqlitepclraw.core/1.1.12/sqlitepclraw.core.1.1.12.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/sqlitepclraw.lib.e_sqlite3.linux/1.1.12/sqlitepclraw.lib.e_sqlite3.linux.1.1.12.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/sqlitepclraw.lib.e_sqlite3.osx/1.1.12/sqlitepclraw.lib.e_sqlite3.osx.1.1.12.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/sqlitepclraw.lib.e_sqlite3.v110_xp/1.1.12/sqlitepclraw.lib.e_sqlite3.v110_xp.1.1.12.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/sqlitepclraw.provider.e_sqlite3.netstandard11/1.1.12/sqlitepclraw.provider.e_sqlite3.netstandard11.1.1.12.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/stackexchange.redis.strongname/1.2.6/stackexchange.redis.strongname.1.2.6.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.appcontext/4.3.0/system.appcontext.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.buffers/4.5.0/system.buffers.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.collections/4.3.0/system.collections.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.collections.concurrent/4.3.0/system.collections.concurrent.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.collections.immutable/1.5.0/system.collections.immutable.1.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.collections.nongeneric/4.3.0/system.collections.nongeneric.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.collections.specialized/4.3.0/system.collections.specialized.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.componentmodel.annotations/4.5.0/system.componentmodel.annotations.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.console/4.3.0/system.console.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.data.sqlclient/4.6.1/system.data.sqlclient.4.6.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.contracts/4.3.0/system.diagnostics.contracts.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.debug/4.3.0/system.diagnostics.debug.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.diagnosticsource/4.5.1/system.diagnostics.diagnosticsource.4.5.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.fileversioninfo/4.3.0/system.diagnostics.fileversioninfo.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.process/4.3.0/system.diagnostics.process.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.stacktrace/4.3.0/system.diagnostics.stacktrace.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.tools/4.3.0/system.diagnostics.tools.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.diagnostics.tracing/4.3.0/system.diagnostics.tracing.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.dynamic.runtime/4.3.0/system.dynamic.runtime.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.globalization/4.3.0/system.globalization.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.globalization.calendars/4.3.0/system.globalization.calendars.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.globalization.extensions/4.3.0/system.globalization.extensions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.identitymodel.tokens.jwt/5.3.0/system.identitymodel.tokens.jwt.5.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.interactive.async/3.2.0/system.interactive.async.3.2.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.io/4.3.0/system.io.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.io.compression/4.3.0/system.io.compression.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.io.filesystem/4.3.0/system.io.filesystem.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.io.filesystem.primitives/4.3.0/system.io.filesystem.primitives.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.io.pipelines/4.5.3/system.io.pipelines.4.5.3.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.linq/4.3.0/system.linq.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.linq.expressions/4.3.0/system.linq.expressions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.linq.queryable/4.0.1/system.linq.queryable.4.0.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.memory/4.5.1/system.memory.4.5.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.net.http/4.3.0/system.net.http.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.net.nameresolution/4.3.0/system.net.nameresolution.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.net.primitives/4.3.0/system.net.primitives.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.net.security/4.3.0/system.net.security.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.net.sockets/4.3.0/system.net.sockets.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.net.websockets.websocketprotocol/4.5.3/system.net.websockets.websocketprotocol.4.5.3.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.numerics.vectors/4.5.0/system.numerics.vectors.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.objectmodel/4.3.0/system.objectmodel.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.private.datacontractserialization/4.3.0/system.private.datacontractserialization.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection/4.3.0/system.reflection.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.emit/4.3.0/system.reflection.emit.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.emit.ilgeneration/4.3.0/system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.emit.lightweight/4.3.0/system.reflection.emit.lightweight.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.extensions/4.3.0/system.reflection.extensions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.metadata/1.6.0/system.reflection.metadata.1.6.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.primitives/4.3.0/system.reflection.primitives.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.reflection.typeextensions/4.3.0/system.reflection.typeextensions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.resources.resourcemanager/4.3.0/system.resources.resourcemanager.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime/4.3.0/system.runtime.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.compilerservices.unsafe/4.5.1/system.runtime.compilerservices.unsafe.4.5.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.extensions/4.3.0/system.runtime.extensions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.handles/4.3.0/system.runtime.handles.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.interopservices/4.3.0/system.runtime.interopservices.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.interopservices.runtimeinformation/4.3.0/system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.numerics/4.3.0/system.runtime.numerics.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.serialization.json/4.3.0/system.runtime.serialization.json.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.serialization.primitives/4.3.0/system.runtime.serialization.primitives.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.runtime.serialization.xml/4.3.0/system.runtime.serialization.xml.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.accesscontrol/4.5.0/system.security.accesscontrol.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.claims/4.3.0/system.security.claims.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.algorithms/4.3.1/system.security.cryptography.algorithms.4.3.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.cng/4.5.0/system.security.cryptography.cng.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.csp/4.3.0/system.security.cryptography.csp.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.encoding/4.3.0/system.security.cryptography.encoding.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.openssl/4.3.0/system.security.cryptography.openssl.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.pkcs/4.5.0/system.security.cryptography.pkcs.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.primitives/4.3.0/system.security.cryptography.primitives.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.x509certificates/4.3.0/system.security.cryptography.x509certificates.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.cryptography.xml/4.5.0/system.security.cryptography.xml.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.permissions/4.5.0/system.security.permissions.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.principal/4.3.0/system.security.principal.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.security.principal.windows/4.5.0/system.security.principal.windows.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.spatial/5.8.4/system.spatial.5.8.4.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.text.encoding/4.3.0/system.text.encoding.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.text.encoding.codepages/4.5.0/system.text.encoding.codepages.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.text.encoding.extensions/4.3.0/system.text.encoding.extensions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.text.encodings.web/4.5.0/system.text.encodings.web.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.text.regularexpressions/4.3.0/system.text.regularexpressions.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading/4.3.0/system.threading.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.channels/4.5.0/system.threading.channels.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.tasks/4.3.0/system.threading.tasks.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.tasks.extensions/4.5.1/system.threading.tasks.extensions.4.5.1.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.tasks.parallel/4.3.0/system.threading.tasks.parallel.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.thread/4.3.0/system.threading.thread.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.threadpool/4.3.0/system.threading.threadpool.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.threading.timer/4.3.0/system.threading.timer.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.valuetuple/4.5.0/system.valuetuple.4.5.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.xml.readerwriter/4.3.0/system.xml.readerwriter.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.xml.xdocument/4.3.0/system.xml.xdocument.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.xml.xmldocument/4.3.0/system.xml.xmldocument.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.xml.xmlserializer/4.3.0/system.xml.xmlserializer.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.xml.xpath/4.3.0/system.xml.xpath.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/system.xml.xpath.xdocument/4.3.0/system.xml.xpath.xdocument.4.3.0.nupkg.sha512",
|
||||
"/Volumes/TheD/.nuget/windowsazure.storage/8.1.4/windowsazure.storage.8.1.4.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
46
server/FSO.Server.Common/ApiAbstract.cs
Executable file
46
server/FSO.Server.Common/ApiAbstract.cs
Executable file
|
@ -0,0 +1,46 @@
|
|||
namespace FSO.Server.Common
|
||||
{
|
||||
public class ApiAbstract
|
||||
{
|
||||
public event APIRequestShutdownDelegate OnRequestShutdown;
|
||||
public event APIBroadcastMessageDelegate OnBroadcastMessage;
|
||||
public event APIRequestUserDisconnectDelegate OnRequestUserDisconnect;
|
||||
public event APIRequestMailNotifyDelegate OnRequestMailNotify;
|
||||
|
||||
public delegate void APIRequestShutdownDelegate(uint time, ShutdownType type);
|
||||
public delegate void APIBroadcastMessageDelegate(string sender, string title, string message);
|
||||
public delegate void APIRequestUserDisconnectDelegate(uint user_id);
|
||||
public delegate void APIRequestMailNotifyDelegate(int message_id, string subject, string body, uint target_id);
|
||||
|
||||
public void RequestShutdown(uint time, ShutdownType type)
|
||||
{
|
||||
OnRequestShutdown?.Invoke(time, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the server to disconnect a user.
|
||||
/// </summary>
|
||||
/// <param name="user_id"></param>
|
||||
public void RequestUserDisconnect(uint user_id)
|
||||
{
|
||||
OnRequestUserDisconnect?.Invoke(user_id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the server to notify the client about the new message.
|
||||
/// </summary>
|
||||
/// <param name="message_id"></param>
|
||||
/// <param name="subject"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="target_id"></param>
|
||||
public void RequestMailNotify(int message_id, string subject, string body, uint target_id)
|
||||
{
|
||||
OnRequestMailNotify(message_id, subject, body, target_id);
|
||||
}
|
||||
|
||||
public void BroadcastMessage(string sender, string title, string message)
|
||||
{
|
||||
OnBroadcastMessage?.Invoke(sender, title, message);
|
||||
}
|
||||
}
|
||||
}
|
10
server/FSO.Server.Common/Config/AWSConfig.cs
Executable file
10
server/FSO.Server.Common/Config/AWSConfig.cs
Executable file
|
@ -0,0 +1,10 @@
|
|||
namespace FSO.Server.Common.Config
|
||||
{
|
||||
public class AWSConfig
|
||||
{
|
||||
public string Region { get; set; } = "eu-west-2";
|
||||
public string Bucket { get; set; } = "fso-updates";
|
||||
public string AccessKeyID { get; set; }
|
||||
public string SecretAccessKey { get; set; }
|
||||
}
|
||||
}
|
16
server/FSO.Server.Common/Config/GithubConfig.cs
Executable file
16
server/FSO.Server.Common/Config/GithubConfig.cs
Executable file
|
@ -0,0 +1,16 @@
|
|||
namespace FSO.Server.Common.Config
|
||||
{
|
||||
public class GithubConfig
|
||||
{
|
||||
public string AppName { get; set; } = "FreeSO";
|
||||
public string User { get; set; } = "riperiperi";
|
||||
public string Repository { get; set; } = "FreeSO";
|
||||
public string ClientID { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Must be generated by installing the app on your user account. Browse to the /github/ API endpoint when this is null. (yes, on this server)
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
}
|
||||
}
|
39
server/FSO.Server.Common/Epoch.cs
Executable file
39
server/FSO.Server.Common/Epoch.cs
Executable file
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
|
||||
namespace FSO.Server.Common
|
||||
{
|
||||
public class Epoch
|
||||
{
|
||||
public static uint Now
|
||||
{
|
||||
get
|
||||
{
|
||||
uint epoch = (uint)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
return epoch;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint FromDate(DateTime time)
|
||||
{
|
||||
return (uint)(time.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
}
|
||||
|
||||
public static DateTime ToDate(uint time)
|
||||
{
|
||||
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
return epoch.AddSeconds(time);
|
||||
}
|
||||
|
||||
public static string HMSRemaining(uint date)
|
||||
{
|
||||
TimeSpan span = (ToDate(date) - ToDate(Epoch.Now));
|
||||
|
||||
return String.Format("{0} hours, {1} minutes and {2} seconds", span.Hours, span.Minutes, span.Seconds);
|
||||
}
|
||||
|
||||
public static uint Default
|
||||
{
|
||||
get { return 0; }
|
||||
}
|
||||
}
|
||||
}
|
104
server/FSO.Server.Common/FSO.Server.Common.csproj
Executable file
104
server/FSO.Server.Common/FSO.Server.Common.csproj
Executable file
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{39B61962-FE43-4B64-8E57-8F793737FFFE}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>FSO.Server.Common</RootNamespace>
|
||||
<AssemblyName>FSO.Server.Common</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<UseVSHostingProcess>true</UseVSHostingProcess>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<UseVSHostingProcess>true</UseVSHostingProcess>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ServerRelease|AnyCPU'">
|
||||
<OutputPath>bin\ServerRelease\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="crypto, Version=1.8.0.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Portable.BouncyCastle.1.8.0\lib\net45\crypto.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="JWT, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Portable.JWT.1.0.5\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\JWT.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Ninject, Version=3.0.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ApiAbstract.cs" />
|
||||
<Compile Include="Config\AWSConfig.cs" />
|
||||
<Compile Include="Config\GithubConfig.cs" />
|
||||
<Compile Include="Epoch.cs" />
|
||||
<Compile Include="IAPIController.cs" />
|
||||
<Compile Include="IPAddress.cs" />
|
||||
<Compile Include="IPEndPointUtils.cs" />
|
||||
<Compile Include="JsonWebToken\JWTConfiguration.cs" />
|
||||
<Compile Include="JsonWebToken\JWTokenFactory.cs" />
|
||||
<Compile Include="JsonWebToken\JWTUser.cs" />
|
||||
<Compile Include="PacketLogger.cs" />
|
||||
<Compile Include="PasswordHasher.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="IServerDebugger.cs" />
|
||||
<Compile Include="ServerVersion.cs" />
|
||||
<Compile Include="Session\IAriesSession.cs" />
|
||||
<Compile Include="Session\IGluonSession.cs" />
|
||||
<Compile Include="ShutdownType.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\tso.common\FSO.Common.csproj">
|
||||
<Project>{c42962a1-8796-4f47-9dcd-79ed5904d8ca}</Project>
|
||||
<Name>FSO.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
7
server/FSO.Server.Common/IAPIController.cs
Executable file
7
server/FSO.Server.Common/IAPIController.cs
Executable file
|
@ -0,0 +1,7 @@
|
|||
namespace FSO.Server.Common
|
||||
{
|
||||
public interface IAPILifetime
|
||||
{
|
||||
void Stop();
|
||||
}
|
||||
}
|
21
server/FSO.Server.Common/IPAddress.cs
Executable file
21
server/FSO.Server.Common/IPAddress.cs
Executable file
|
@ -0,0 +1,21 @@
|
|||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Common
|
||||
{
|
||||
public class IPAddress
|
||||
{
|
||||
public static string Get(HttpListenerRequest httpRequest)
|
||||
{
|
||||
if (httpRequest.Headers["X-Forwarded-For"] != null)
|
||||
{
|
||||
return httpRequest.Headers["X-Forwarded-For"];
|
||||
}
|
||||
return Get(httpRequest.RemoteEndPoint);
|
||||
}
|
||||
|
||||
public static string Get(IPEndPoint endpoint)
|
||||
{
|
||||
return endpoint.Address.ToString();
|
||||
}
|
||||
}
|
||||
}
|
32
server/FSO.Server.Common/IPEndPointUtils.cs
Executable file
32
server/FSO.Server.Common/IPEndPointUtils.cs
Executable file
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Common
|
||||
{
|
||||
public class IPEndPointUtils
|
||||
{
|
||||
public static IPEndPoint CreateIPEndPoint(string endPoint)
|
||||
{
|
||||
string[] ep = endPoint.Split(':');
|
||||
if (ep.Length != 2) throw new FormatException("Invalid endpoint format");
|
||||
System.Net.IPAddress ip;
|
||||
if (!System.Net.IPAddress.TryParse(ep[0], out ip))
|
||||
{
|
||||
var addrs = Dns.GetHostEntry(ep[0]).AddressList;
|
||||
if (addrs.Length == 0)
|
||||
{
|
||||
throw new FormatException("Invalid ip-address");
|
||||
}
|
||||
else ip = addrs[0];
|
||||
}
|
||||
|
||||
int port;
|
||||
if (!int.TryParse(ep[1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
|
||||
{
|
||||
throw new FormatException("Invalid port");
|
||||
}
|
||||
return new IPEndPoint(ip, port);
|
||||
}
|
||||
}
|
||||
}
|
22
server/FSO.Server.Common/IServerDebugger.cs
Executable file
22
server/FSO.Server.Common/IServerDebugger.cs
Executable file
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Common
|
||||
{
|
||||
public interface IServerDebugger
|
||||
{
|
||||
IPacketLogger GetPacketLogger();
|
||||
void AddSocketServer(ISocketServer server);
|
||||
}
|
||||
|
||||
|
||||
public interface ISocketServer
|
||||
{
|
||||
List<ISocketSession> GetSocketSessions();
|
||||
}
|
||||
|
||||
|
||||
public interface ISocketSession
|
||||
{
|
||||
void Write(params object[] data);
|
||||
}
|
||||
}
|
8
server/FSO.Server.Common/JsonWebToken/JWTConfiguration.cs
Executable file
8
server/FSO.Server.Common/JsonWebToken/JWTConfiguration.cs
Executable file
|
@ -0,0 +1,8 @@
|
|||
namespace FSO.Server.Servers.Api.JsonWebToken
|
||||
{
|
||||
public class JWTConfiguration
|
||||
{
|
||||
public byte[] Key;
|
||||
public int TokenDuration = 3600;
|
||||
}
|
||||
}
|
19
server/FSO.Server.Common/JsonWebToken/JWTUser.cs
Executable file
19
server/FSO.Server.Common/JsonWebToken/JWTUser.cs
Executable file
|
@ -0,0 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Servers.Api.JsonWebToken
|
||||
{
|
||||
public class JWTUser
|
||||
{
|
||||
public uint UserID { get; set; }
|
||||
|
||||
public IEnumerable<string> Claims
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string UserName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
48
server/FSO.Server.Common/JsonWebToken/JWTokenFactory.cs
Executable file
48
server/FSO.Server.Common/JsonWebToken/JWTokenFactory.cs
Executable file
|
@ -0,0 +1,48 @@
|
|||
using FSO.Server.Common;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Servers.Api.JsonWebToken
|
||||
{
|
||||
public class JWTInstance
|
||||
{
|
||||
public string Token;
|
||||
public int ExpiresIn;
|
||||
}
|
||||
|
||||
public class JWTFactory
|
||||
{
|
||||
private JWTConfiguration Config;
|
||||
|
||||
public JWTFactory(JWTConfiguration config)
|
||||
{
|
||||
this.Config = config;
|
||||
}
|
||||
|
||||
public JWTUser DecodeToken(string token)
|
||||
{
|
||||
var payload = JWT.JsonWebToken.Decode(token, Config.Key, true);
|
||||
Dictionary<string, string> payloadParsed = JsonConvert.DeserializeObject<Dictionary<string, string>>(payload);
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<JWTUser>(payloadParsed["data"]);
|
||||
}
|
||||
|
||||
public JWTInstance CreateToken(JWTUser data)
|
||||
{
|
||||
var tokenData = Newtonsoft.Json.JsonConvert.SerializeObject(data);
|
||||
return CreateToken(tokenData, Config.TokenDuration);
|
||||
}
|
||||
|
||||
private JWTInstance CreateToken(string data, int expiresIn)
|
||||
{
|
||||
var expires = Epoch.Now + expiresIn;
|
||||
var payload = new Dictionary<string, object>()
|
||||
{
|
||||
{ "exp", expires },
|
||||
{ "data", data }
|
||||
};
|
||||
|
||||
var token = JWT.JsonWebToken.Encode(payload, Config.Key, JWT.JwtHashAlgorithm.HS384);
|
||||
return new JWTInstance { Token = token, ExpiresIn = expiresIn };
|
||||
}
|
||||
}
|
||||
}
|
29
server/FSO.Server.Common/PacketLogger.cs
Executable file
29
server/FSO.Server.Common/PacketLogger.cs
Executable file
|
@ -0,0 +1,29 @@
|
|||
namespace FSO.Server.Common
|
||||
{
|
||||
public interface IPacketLogger
|
||||
{
|
||||
void OnPacket(Packet packet);
|
||||
}
|
||||
|
||||
public class Packet
|
||||
{
|
||||
public PacketType Type;
|
||||
public uint SubType;
|
||||
|
||||
public byte[] Data;
|
||||
public PacketDirection Direction;
|
||||
}
|
||||
|
||||
public enum PacketType
|
||||
{
|
||||
ARIES,
|
||||
VOLTRON,
|
||||
ELECTRON
|
||||
}
|
||||
|
||||
public enum PacketDirection
|
||||
{
|
||||
OUTPUT,
|
||||
INPUT
|
||||
}
|
||||
}
|
103
server/FSO.Server.Common/PasswordHasher.cs
Executable file
103
server/FSO.Server.Common/PasswordHasher.cs
Executable file
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FSO.Server.Common
|
||||
{
|
||||
public class PasswordHasher
|
||||
{
|
||||
public static PasswordHash Hash(string password)
|
||||
{
|
||||
return Hash(password, "Rfc2898");
|
||||
}
|
||||
|
||||
public static PasswordHash Hash(string password, string scheme)
|
||||
{
|
||||
var schemeImpl = GetScheme(scheme);
|
||||
return schemeImpl.Hash(password);
|
||||
}
|
||||
|
||||
public static bool Verify(string password, PasswordHash hash)
|
||||
{
|
||||
return Verify(password, hash, "Rfc2898");
|
||||
}
|
||||
|
||||
public static bool Verify(string password, PasswordHash hash, string scheme)
|
||||
{
|
||||
var schemeImpl = GetScheme(scheme);
|
||||
return schemeImpl.Verify(password, hash);
|
||||
}
|
||||
|
||||
private static IPasswordHashScheme GetScheme(string scheme)
|
||||
{
|
||||
switch (scheme)
|
||||
{
|
||||
case "Rfc2898":
|
||||
return new DefaultPasswordHashScheme();
|
||||
}
|
||||
|
||||
throw new Exception("Unknown password hash scheme: " + scheme);
|
||||
}
|
||||
}
|
||||
|
||||
public class PasswordHash
|
||||
{
|
||||
public byte[] data;
|
||||
public string scheme;
|
||||
}
|
||||
|
||||
public class DefaultPasswordHashScheme : IPasswordHashScheme
|
||||
{
|
||||
public PasswordHash Hash(string password)
|
||||
{
|
||||
var salt_input = GetStrongRandomBytes(16);
|
||||
|
||||
|
||||
return new PasswordHash()
|
||||
{
|
||||
scheme = "Rfc2898",
|
||||
data = Hash(salt_input, password)
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] Hash(byte[] salt_input, string password)
|
||||
{
|
||||
var hasher = new Rfc2898DeriveBytes(System.Text.Encoding.UTF8.GetBytes(password), salt_input, 1000);
|
||||
var hash = hasher.GetBytes(64);
|
||||
|
||||
//Encode the salt + hash together
|
||||
var result = new byte[1 + 16 + hash.Length];
|
||||
result[0] = (byte)16;
|
||||
Array.Copy(salt_input, 0, result, 1, salt_input.Length);
|
||||
Array.Copy(hash, 0, result, salt_input.Length + 1, hash.Length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool Verify(string password, PasswordHash hash)
|
||||
{
|
||||
var salt_length = hash.data[0];
|
||||
var salt_input = new byte[salt_length];
|
||||
Array.Copy(hash.data, 1, salt_input, 0, salt_length);
|
||||
|
||||
var expected = Hash(salt_input, password);
|
||||
return expected.SequenceEqual(hash.data);
|
||||
}
|
||||
|
||||
private byte[] GetStrongRandomBytes(int numBytes)
|
||||
{
|
||||
var random_bytes = new byte[numBytes];
|
||||
using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
|
||||
{
|
||||
rngCsp.GetBytes(random_bytes);
|
||||
}
|
||||
return random_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPasswordHashScheme
|
||||
{
|
||||
PasswordHash Hash(string password);
|
||||
bool Verify(string password, PasswordHash hash);
|
||||
}
|
||||
}
|
36
server/FSO.Server.Common/Properties/AssemblyInfo.cs
Executable file
36
server/FSO.Server.Common/Properties/AssemblyInfo.cs
Executable file
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("FSO.Server.Common")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("FSO.Server.Common")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("39b61962-fe43-4b64-8e57-8f793737fffe")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
48
server/FSO.Server.Common/ServerVersion.cs
Executable file
48
server/FSO.Server.Common/ServerVersion.cs
Executable file
|
@ -0,0 +1,48 @@
|
|||
using System.IO;
|
||||
|
||||
namespace FSO.Server.Common
|
||||
{
|
||||
public class ServerVersion
|
||||
{
|
||||
public string Name;
|
||||
public string Number;
|
||||
public int? UpdateID;
|
||||
|
||||
public static ServerVersion Get()
|
||||
{
|
||||
var result = new ServerVersion()
|
||||
{
|
||||
Name = "unknown",
|
||||
Number = "0"
|
||||
};
|
||||
|
||||
if (File.Exists("version.txt"))
|
||||
{
|
||||
using (StreamReader Reader = new StreamReader(File.Open("version.txt", FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
var str = Reader.ReadLine();
|
||||
var split = str.LastIndexOf('-');
|
||||
|
||||
result.Name = str;
|
||||
if (split != -1)
|
||||
{
|
||||
result.Name = str.Substring(0, split);
|
||||
result.Number = str.Substring(split + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists("updateID.txt"))
|
||||
{
|
||||
var stringID = File.ReadAllText("updateID.txt");
|
||||
int id;
|
||||
if (int.TryParse(stringID, out id))
|
||||
{
|
||||
result.UpdateID = id;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
17
server/FSO.Server.Common/Session/IAriesSession.cs
Executable file
17
server/FSO.Server.Common/Session/IAriesSession.cs
Executable file
|
@ -0,0 +1,17 @@
|
|||
using FSO.Server.Common;
|
||||
|
||||
namespace FSO.Server.Framework.Aries
|
||||
{
|
||||
public interface IAriesSession : ISocketSession
|
||||
{
|
||||
bool IsAuthenticated { get; }
|
||||
uint LastRecv { get; set; }
|
||||
bool Connected { get; }
|
||||
|
||||
void Write(params object[] messages);
|
||||
void Close();
|
||||
|
||||
object GetAttribute(string key);
|
||||
void SetAttribute(string key, object value);
|
||||
}
|
||||
}
|
12
server/FSO.Server.Common/Session/IGluonSession.cs
Executable file
12
server/FSO.Server.Common/Session/IGluonSession.cs
Executable file
|
@ -0,0 +1,12 @@
|
|||
using FSO.Common.Security;
|
||||
using FSO.Server.Framework.Aries;
|
||||
|
||||
namespace FSO.Server.Framework.Gluon
|
||||
{
|
||||
public interface IGluonSession : IAriesSession, ISecurityContext
|
||||
{
|
||||
string CallSign { get; }
|
||||
string PublicHost { get; }
|
||||
string InternalHost { get; }
|
||||
}
|
||||
}
|
9
server/FSO.Server.Common/ShutdownType.cs
Executable file
9
server/FSO.Server.Common/ShutdownType.cs
Executable file
|
@ -0,0 +1,9 @@
|
|||
namespace FSO.Server.Common
|
||||
{
|
||||
public enum ShutdownType : byte
|
||||
{
|
||||
SHUTDOWN = 0,
|
||||
RESTART = 1,
|
||||
UPDATE = 2 //restart but runs an update task
|
||||
}
|
||||
}
|
19
server/FSO.Server.Common/app.config
Executable file
19
server/FSO.Server.Common/app.config
Executable file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Common.Logging.Core" publicKeyToken="af08829b84f0328e" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.4.1.0" newVersion="3.4.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Common.Logging" publicKeyToken="af08829b84f0328e" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.4.1.0" newVersion="3.4.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
7
server/FSO.Server.Common/packages.config
Executable file
7
server/FSO.Server.Common/packages.config
Executable file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net45" />
|
||||
<package id="Portable.BouncyCastle" version="1.8.0" targetFramework="net45" />
|
||||
<package id="Portable.JWT" version="1.0.5" targetFramework="net45" />
|
||||
<package id="Portable.Ninject" version="3.3.1" targetFramework="net45" />
|
||||
</packages>
|
12
server/FSO.Server.Database/DA/AbstractSqlDA.cs
Executable file
12
server/FSO.Server.Database/DA/AbstractSqlDA.cs
Executable file
|
@ -0,0 +1,12 @@
|
|||
namespace FSO.Server.Database.DA
|
||||
{
|
||||
public class AbstractSqlDA
|
||||
{
|
||||
protected ISqlContext Context;
|
||||
|
||||
public AbstractSqlDA(ISqlContext context)
|
||||
{
|
||||
this.Context = context;
|
||||
}
|
||||
}
|
||||
}
|
10
server/FSO.Server.Database/DA/AuthTickets/AuthTicket.cs
Executable file
10
server/FSO.Server.Database/DA/AuthTickets/AuthTicket.cs
Executable file
|
@ -0,0 +1,10 @@
|
|||
namespace FSO.Server.Database.DA.AuthTickets
|
||||
{
|
||||
public class AuthTicket
|
||||
{
|
||||
public string ticket_id { get; set; }
|
||||
public uint user_id { get; set; }
|
||||
public uint date { get; set; }
|
||||
public string ip { get; set; }
|
||||
}
|
||||
}
|
10
server/FSO.Server.Database/DA/AuthTickets/IAuthTickets.cs
Executable file
10
server/FSO.Server.Database/DA/AuthTickets/IAuthTickets.cs
Executable file
|
@ -0,0 +1,10 @@
|
|||
namespace FSO.Server.Database.DA.AuthTickets
|
||||
{
|
||||
public interface IAuthTickets
|
||||
{
|
||||
void Create(AuthTicket ticket);
|
||||
AuthTicket Get(string id);
|
||||
void Delete(string id);
|
||||
void Purge(uint time);
|
||||
}
|
||||
}
|
33
server/FSO.Server.Database/DA/AuthTickets/SqlAuthTickets.cs
Executable file
33
server/FSO.Server.Database/DA/AuthTickets/SqlAuthTickets.cs
Executable file
|
@ -0,0 +1,33 @@
|
|||
using System.Linq;
|
||||
using Dapper;
|
||||
|
||||
namespace FSO.Server.Database.DA.AuthTickets
|
||||
{
|
||||
public class SqlAuthTickets : AbstractSqlDA, IAuthTickets
|
||||
{
|
||||
public SqlAuthTickets(ISqlContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public void Create(AuthTicket ticket)
|
||||
{
|
||||
Context.Connection.Execute("INSERT INTO fso_auth_tickets VALUES (@ticket_id, @user_id, @date, @ip)", ticket);
|
||||
}
|
||||
|
||||
public void Delete(string id)
|
||||
{
|
||||
Context.Connection.Execute("DELETE FROM fso_auth_tickets WHERE ticket_id = @ticket_id", new { ticket_id = id });
|
||||
}
|
||||
|
||||
public AuthTicket Get(string id)
|
||||
{
|
||||
return
|
||||
Context.Connection.Query<AuthTicket>("SELECT * FROM fso_auth_tickets WHERE ticket_id = @ticket_id", new { ticket_id = id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void Purge(uint time)
|
||||
{
|
||||
Context.Connection.Execute("DELETE FROM fso_auth_tickets WHERE date < @time", new { time = time });
|
||||
}
|
||||
}
|
||||
}
|
17
server/FSO.Server.Database/DA/AvatarClaims/DbAvatarClaim.cs
Executable file
17
server/FSO.Server.Database/DA/AvatarClaims/DbAvatarClaim.cs
Executable file
|
@ -0,0 +1,17 @@
|
|||
namespace FSO.Server.Database.DA.AvatarClaims
|
||||
{
|
||||
public class DbAvatarClaim
|
||||
{
|
||||
public int avatar_claim_id { get; set; }
|
||||
public uint avatar_id { get; set; }
|
||||
public string owner { get; set; }
|
||||
public uint location { get; set; }
|
||||
}
|
||||
public class DbAvatarActive
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public byte privacy_mode { get; set; }
|
||||
public uint location { get; set; }
|
||||
}
|
||||
}
|
21
server/FSO.Server.Database/DA/AvatarClaims/IAvatarClaims.cs
Executable file
21
server/FSO.Server.Database/DA/AvatarClaims/IAvatarClaims.cs
Executable file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Database.DA.AvatarClaims
|
||||
{
|
||||
public interface IAvatarClaims
|
||||
{
|
||||
DbAvatarClaim Get(int id);
|
||||
IEnumerable<DbAvatarClaim> GetAll();
|
||||
IEnumerable<DbAvatarActive> GetAllActiveAvatars();
|
||||
int? GetAllActiveAvatarsCount();
|
||||
DbAvatarClaim GetByAvatarID(uint id);
|
||||
IEnumerable<DbAvatarClaim> GetAllByOwner(string owner);
|
||||
|
||||
int? TryCreate(DbAvatarClaim claim);
|
||||
bool Claim(int id, string previousOwner, string newOwner, uint location);
|
||||
void RemoveRemaining(string previousOwner, uint location);
|
||||
|
||||
void Delete(int id, string owner);
|
||||
void DeleteAll(string owner);
|
||||
}
|
||||
}
|
84
server/FSO.Server.Database/DA/AvatarClaims/SqlAvatarClaims.cs
Executable file
84
server/FSO.Server.Database/DA/AvatarClaims/SqlAvatarClaims.cs
Executable file
|
@ -0,0 +1,84 @@
|
|||
using Dapper;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Server.Database.DA.AvatarClaims
|
||||
{
|
||||
public class SqlAvatarClaims : AbstractSqlDA, IAvatarClaims
|
||||
{
|
||||
public SqlAvatarClaims(ISqlContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Claim(int id, string previousOwner, string newOwner, uint location)
|
||||
{
|
||||
try
|
||||
{
|
||||
Context.Connection.Query("UPDATE fso_avatar_claims SET owner = @owner, location = @location WHERE avatar_claim_id = @claim_id AND owner = @previous_owner", new { claim_id = (int)id, previous_owner = previousOwner, owner = newOwner, location = location });
|
||||
var newClaim = Context.Connection.Query<DbAvatarClaim>("SELECT * FROM fso_avatar_claims WHERE avatar_claim_id = @claim_id AND owner = @owner", new { claim_id = (int)id, owner = newOwner }).FirstOrDefault();
|
||||
return newClaim != null;
|
||||
}
|
||||
catch (MySqlException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRemaining(string previousOwner, uint location)
|
||||
{
|
||||
Context.Connection.Query("DELETE FROM fso_avatar_claims WHERE location = @location AND owner = @previous_owner", new { previous_owner = previousOwner, location = location });
|
||||
}
|
||||
|
||||
|
||||
public void Delete(int id, string owner)
|
||||
{
|
||||
Context.Connection.Query("DELETE FROM fso_avatar_claims WHERE owner = @owner AND avatar_claim_id = @claim_id", new { owner = owner, claim_id = (int)id });
|
||||
}
|
||||
|
||||
public void DeleteAll(string owner)
|
||||
{
|
||||
Context.Connection.Query("DELETE FROM fso_avatar_claims WHERE owner = @owner", new { owner = owner });
|
||||
}
|
||||
|
||||
public DbAvatarClaim Get(int id)
|
||||
{
|
||||
return Context.Connection.Query<DbAvatarClaim>("SELECT * FROM fso_avatar_claims WHERE avatar_claim_id = @claim_id", new { claim_id = (int)id }).FirstOrDefault();
|
||||
}
|
||||
public IEnumerable<DbAvatarClaim> GetAll()
|
||||
{
|
||||
return Context.Connection.Query<DbAvatarClaim>("SELECT * FROM fso_avatar_claims");
|
||||
}
|
||||
public IEnumerable<DbAvatarActive> GetAllActiveAvatars()
|
||||
{
|
||||
return Context.Connection.Query<DbAvatarActive>("SELECT b.*, a.location FROM fso.fso_avatar_claims as a "+
|
||||
"inner join fso.fso_avatars as b ON a.avatar_id = b.avatar_id;");
|
||||
}
|
||||
public int? GetAllActiveAvatarsCount()
|
||||
{
|
||||
return Context.Connection.Query<int>("SELECT COUNT(*) FROM fso_avatar_claims").FirstOrDefault();
|
||||
}
|
||||
public DbAvatarClaim GetByAvatarID(uint id)
|
||||
{
|
||||
return Context.Connection.Query<DbAvatarClaim>("SELECT * FROM fso_avatar_claims WHERE avatar_id = @id", new { id = id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public IEnumerable<DbAvatarClaim> GetAllByOwner(string owner)
|
||||
{
|
||||
return Context.Connection.Query<DbAvatarClaim>("SELECT * FROM fso_avatar_claims WHERE owner = @owner", new { owner = owner });
|
||||
}
|
||||
|
||||
public int? TryCreate(DbAvatarClaim claim)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Context.Connection.Query<int>("INSERT INTO fso_avatar_claims (avatar_id, owner, location) " +
|
||||
" VALUES (@avatar_id, @owner, @location); SELECT LAST_INSERT_ID();", claim).First();
|
||||
}
|
||||
catch (MySqlException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
server/FSO.Server.Database/DA/Avatars/DbAvatar.cs
Executable file
70
server/FSO.Server.Database/DA/Avatars/DbAvatar.cs
Executable file
|
@ -0,0 +1,70 @@
|
|||
namespace FSO.Server.Database.DA.Avatars
|
||||
{
|
||||
public class DbAvatar
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public uint user_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public DbAvatarGender gender { get; set; }
|
||||
public uint date { get; set; }
|
||||
public byte skin_tone { get; set; }
|
||||
public ulong head { get; set; }
|
||||
public ulong body { get; set; }
|
||||
public string description { get; set; }
|
||||
public int budget { get; set; }
|
||||
public byte privacy_mode { get; set; }
|
||||
|
||||
//lot persist state beyond this point (very mutable)
|
||||
|
||||
public byte[] motive_data { get; set; }
|
||||
//locks
|
||||
|
||||
public byte skilllock { get; set; } //number of skill locks we had at last avatar save. this is usually handled by data service.
|
||||
public ushort lock_mechanical { get; set; }
|
||||
public ushort lock_cooking { get; set; }
|
||||
public ushort lock_charisma { get; set; }
|
||||
public ushort lock_logic { get; set; }
|
||||
public ushort lock_body { get; set; }
|
||||
public ushort lock_creativity { get; set; }
|
||||
//skills
|
||||
public ushort skill_mechanical { get; set; }
|
||||
public ushort skill_cooking { get; set; }
|
||||
public ushort skill_charisma { get; set; }
|
||||
public ushort skill_logic { get; set; }
|
||||
public ushort skill_body { get; set; }
|
||||
public ushort skill_creativity { get; set; }
|
||||
|
||||
public ulong body_swimwear { get; set; }
|
||||
public ulong body_sleepwear { get; set; }
|
||||
public ulong body_current { get; set; }
|
||||
|
||||
public ushort current_job { get; set; }
|
||||
public ushort is_ghost { get; set; }
|
||||
public ushort ticker_death { get; set; }
|
||||
public ushort ticker_gardener { get; set; }
|
||||
public ushort ticker_maid { get; set; }
|
||||
public ushort ticker_repairman { get; set; }
|
||||
|
||||
public byte moderation_level { get; set; }
|
||||
public uint? custom_guid { get; set; }
|
||||
public uint move_date { get; set; }
|
||||
public uint name_date { get; set; }
|
||||
public int? mayor_nhood { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class DbTransactionResult
|
||||
{
|
||||
public bool success { get; set; }
|
||||
public int source_budget { get; set; }
|
||||
public int dest_budget { get; set; }
|
||||
public int amount { get; set; }
|
||||
}
|
||||
|
||||
public enum DbAvatarGender
|
||||
{
|
||||
male,
|
||||
female
|
||||
}
|
||||
}
|
22
server/FSO.Server.Database/DA/Avatars/DbAvatarSummary.cs
Executable file
22
server/FSO.Server.Database/DA/Avatars/DbAvatarSummary.cs
Executable file
|
@ -0,0 +1,22 @@
|
|||
namespace FSO.Server.Database.DA.Avatars
|
||||
{
|
||||
public class DbAvatarSummary
|
||||
{
|
||||
//fso_avatar data
|
||||
public uint avatar_id { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public uint user_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public DbAvatarGender gender { get; set; }
|
||||
public uint date { get; set; }
|
||||
public byte skin_tone { get; set; }
|
||||
public ulong head { get; set; }
|
||||
public ulong body { get; set; }
|
||||
public string description { get; set; }
|
||||
|
||||
//fso_lots
|
||||
public uint? lot_id { get; set; }
|
||||
public uint? lot_location { get; set; }
|
||||
public string lot_name { get; set; }
|
||||
}
|
||||
}
|
12
server/FSO.Server.Database/DA/Avatars/DbJobLevel.cs
Executable file
12
server/FSO.Server.Database/DA/Avatars/DbJobLevel.cs
Executable file
|
@ -0,0 +1,12 @@
|
|||
namespace FSO.Server.Database.DA.Avatars
|
||||
{
|
||||
public class DbJobLevel
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public ushort job_type { get; set; }
|
||||
public ushort job_experience { get; set; }
|
||||
public ushort job_level { get; set; }
|
||||
public ushort job_sickdays { get; set; }
|
||||
public ushort job_statusflags { get; set; }
|
||||
}
|
||||
}
|
51
server/FSO.Server.Database/DA/Avatars/IAvatars.cs
Executable file
51
server/FSO.Server.Database/DA/Avatars/IAvatars.cs
Executable file
|
@ -0,0 +1,51 @@
|
|||
using FSO.Server.Database.DA.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Database.DA.Avatars
|
||||
{
|
||||
public interface IAvatars
|
||||
{
|
||||
uint Create(DbAvatar avatar);
|
||||
|
||||
DbAvatar Get(uint id);
|
||||
List<DbAvatar> GetMultiple(uint[] id);
|
||||
bool Delete(uint id);
|
||||
int GetPrivacyMode(uint id);
|
||||
int GetModerationLevel(uint id);
|
||||
DbJobLevel GetCurrentJobLevel(uint avatar_id);
|
||||
List<DbJobLevel> GetJobLevels(uint avatar_id);
|
||||
IEnumerable<DbAvatar> All();
|
||||
IEnumerable<DbAvatar> All(int shard_id);
|
||||
PagedList<DbAvatar> AllByPage(int shard_id, int offset, int limit, string orderBy);
|
||||
List<uint> GetLivingInNhood(uint nhood_id);
|
||||
List<AvatarRating> GetPossibleCandidatesNhood(uint nhood_id);
|
||||
|
||||
List<DbAvatar> GetByUserId(uint user_id);
|
||||
List<DbAvatarSummary> GetSummaryByUserId(uint user_id);
|
||||
|
||||
int GetOtherLocks(uint avatar_id, string except);
|
||||
|
||||
int GetBudget(uint avatar_id);
|
||||
DbTransactionResult Transaction(uint source_id, uint avatar_id, int amount, short reason);
|
||||
DbTransactionResult Transaction(uint source_id, uint avatar_id, int amount, short reason, Func<bool> transactionInject);
|
||||
DbTransactionResult TestTransaction(uint source_id, uint avatar_id, int amount, short reason);
|
||||
|
||||
void UpdateDescription(uint id, string description);
|
||||
void UpdatePrivacyMode(uint id, byte privacy);
|
||||
void UpdateAvatarLotSave(uint id, DbAvatar avatar);
|
||||
void UpdateAvatarJobLevel(DbJobLevel jobLevel);
|
||||
void UpdateMoveDate(uint id, uint date);
|
||||
void UpdateMayorNhood(uint id, uint? nhood);
|
||||
|
||||
List<DbAvatar> SearchExact(int shard_id, string name, int limit);
|
||||
List<DbAvatar> SearchWildcard(int shard_id, string name, int limit);
|
||||
}
|
||||
|
||||
public class AvatarRating
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public float? rating { get; set; }
|
||||
}
|
||||
}
|
401
server/FSO.Server.Database/DA/Avatars/SqlAvatars.cs
Executable file
401
server/FSO.Server.Database/DA/Avatars/SqlAvatars.cs
Executable file
|
@ -0,0 +1,401 @@
|
|||
using Dapper;
|
||||
using FSO.Server.Database.DA.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Server.Database.DA.Avatars
|
||||
{
|
||||
public class SqlAvatars : AbstractSqlDA, IAvatars
|
||||
{
|
||||
public SqlAvatars(ISqlContext context) : base(context){
|
||||
}
|
||||
public PagedList<DbAvatar> AllByPage(int shard_id,int offset = 1, int limit = 100, string orderBy = "avatar_id")
|
||||
{
|
||||
var total = Context.Connection.Query<int>("SELECT COUNT(*) FROM fso_avatars WHERE shard_id = @shard_id",new { shard_id = shard_id }).FirstOrDefault();
|
||||
var results = Context.Connection.Query<DbAvatar>("SELECT * FROM fso_avatars WHERE shard_id = @shard_id ORDER BY @order DESC LIMIT @offset, @limit", new { shard_id = shard_id, order = orderBy, offset = offset, limit = limit });
|
||||
return new PagedList<DbAvatar>(results, offset, total);
|
||||
}
|
||||
|
||||
public IEnumerable<DbAvatar> All()
|
||||
{
|
||||
return Context.Connection.Query<DbAvatar>("SELECT * FROM fso_avatars");
|
||||
}
|
||||
|
||||
public IEnumerable<DbAvatar> All(int shard_id){
|
||||
return Context.Connection.Query<DbAvatar>("SELECT * FROM fso_avatars WHERE shard_id = @shard_id", new { shard_id = shard_id });
|
||||
}
|
||||
|
||||
public List<uint> GetLivingInNhood(uint neigh_id)
|
||||
{
|
||||
return Context.Connection.Query<uint>("SELECT avatar_id FROM fso_roommates r JOIN fso_lots l ON r.lot_id = l.lot_id "
|
||||
+ "WHERE neighborhood_id = @neigh_id", new { neigh_id = neigh_id }).ToList();
|
||||
}
|
||||
|
||||
public List<AvatarRating> GetPossibleCandidatesNhood(uint neigh_id)
|
||||
{
|
||||
return Context.Connection.Query<AvatarRating>("SELECT r.avatar_id, a.name, AVG(CAST(v.rating as DECIMAL(10,6))) AS rating " +
|
||||
"FROM (fso_roommates r JOIN fso_lots l ON r.lot_id = l.lot_id) " +
|
||||
"LEFT JOIN fso_mayor_ratings v ON v.to_avatar_id = r.avatar_id " +
|
||||
"JOIN fso_avatars a ON r.avatar_id = a.avatar_id " +
|
||||
"WHERE l.neighborhood_id = @neigh_id " +
|
||||
"GROUP BY avatar_id", new { neigh_id = neigh_id }).ToList();
|
||||
}
|
||||
|
||||
public DbAvatar Get(uint id){
|
||||
return Context.Connection.Query<DbAvatar>("SELECT * FROM fso_avatars WHERE avatar_id = @id", new { id = id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public bool Delete(uint id)
|
||||
{
|
||||
return Context.Connection.Execute("DELETE FROM fso_avatars WHERE avatar_id = @id", new { id = id }) > 0;
|
||||
}
|
||||
|
||||
public int GetPrivacyMode(uint id)
|
||||
{
|
||||
return Context.Connection.Query<int>("SELECT privacy_mode FROM fso_avatars WHERE avatar_id = @id", new { id = id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public int GetModerationLevel(uint id)
|
||||
{
|
||||
return Context.Connection.Query<int>("SELECT moderation_level FROM fso_avatars WHERE avatar_id = @id", new { id = id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public uint Create(DbAvatar avatar)
|
||||
{
|
||||
return (uint)Context.Connection.Query<int>("INSERT INTO fso_avatars (shard_id, user_id, name, " +
|
||||
"gender, date, skin_tone, head, body, description, budget, moderation_level, " +
|
||||
" body_swimwear, body_sleepwear) " +
|
||||
" VALUES (@shard_id, @user_id, @name, @gender, @date, " +
|
||||
" @skin_tone, @head, @body, @description, @budget, @moderation_level, "+
|
||||
" @body_swimwear, @body_sleepwear); SELECT LAST_INSERT_ID();", new
|
||||
{
|
||||
shard_id = avatar.shard_id,
|
||||
user_id = avatar.user_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender.ToString(),
|
||||
date = avatar.date,
|
||||
skin_tone = avatar.skin_tone,
|
||||
head = avatar.head,
|
||||
body = avatar.body,
|
||||
description = avatar.description,
|
||||
budget = avatar.budget,
|
||||
moderation_level = avatar.moderation_level,
|
||||
body_swimwear = avatar.body_swimwear,
|
||||
body_sleepwear = avatar.body_sleepwear
|
||||
}).First();
|
||||
//for now, everything else assumes default values.
|
||||
}
|
||||
|
||||
|
||||
public List<DbAvatar> GetByUserId(uint user_id)
|
||||
{
|
||||
return Context.Connection.Query<DbAvatar>(
|
||||
"SELECT * FROM fso_avatars WHERE user_id = @user_id",
|
||||
new { user_id = user_id }
|
||||
).ToList();
|
||||
}
|
||||
|
||||
public List<DbAvatar> GetMultiple(uint[] id)
|
||||
{
|
||||
String inClause = "IN (";
|
||||
for (int i = 0; i < id.Length; i++)
|
||||
{
|
||||
inClause = inClause + "'" + id.ElementAt(i) + "'" + ",";
|
||||
}
|
||||
inClause = inClause.Substring(0, inClause.Length - 1);
|
||||
inClause = inClause + ")";
|
||||
|
||||
return Context.Connection.Query<DbAvatar>(
|
||||
"Select * from fso_avatars Where avatar_id "+ inClause
|
||||
).ToList();
|
||||
}
|
||||
|
||||
public List<DbAvatar> SearchExact(int shard_id, string name, int limit)
|
||||
{
|
||||
return Context.Connection.Query<DbAvatar>(
|
||||
"SELECT avatar_id, name FROM fso_avatars WHERE shard_id = @shard_id AND name = @name LIMIT @limit",
|
||||
new { name = name, limit = limit, shard_id = shard_id }
|
||||
).ToList();
|
||||
}
|
||||
|
||||
public List<DbAvatar> SearchWildcard(int shard_id, string name, int limit)
|
||||
{
|
||||
name = name
|
||||
.Replace("!", "!!")
|
||||
.Replace("%", "!%")
|
||||
.Replace("_", "!_")
|
||||
.Replace("[", "!["); //must sanitize format...
|
||||
return Context.Connection.Query<DbAvatar>(
|
||||
"SELECT avatar_id, name FROM fso_avatars WHERE shard_id = @shard_id AND name LIKE @name LIMIT @limit",
|
||||
new { name = "%" + name + "%", limit = limit, shard_id = shard_id }
|
||||
).ToList();
|
||||
}
|
||||
|
||||
public void UpdateDescription(uint id, string description)
|
||||
{
|
||||
Context.Connection.Query("UPDATE fso_avatars SET description = @desc WHERE avatar_id = @id", new { id = id, desc = description });
|
||||
}
|
||||
|
||||
public void UpdatePrivacyMode(uint id, byte mode)
|
||||
{
|
||||
Context.Connection.Query("UPDATE fso_avatars SET privacy_mode = @privacy_mode WHERE avatar_id = @id", new { id = id, privacy_mode = mode });
|
||||
}
|
||||
|
||||
public void UpdateMoveDate(uint id, uint date)
|
||||
{
|
||||
Context.Connection.Query("UPDATE fso_avatars SET move_date = @date WHERE avatar_id = @id", new { id = id, date = date });
|
||||
}
|
||||
|
||||
public void UpdateMayorNhood(uint id, uint? nhood)
|
||||
{
|
||||
Context.Connection.Query("UPDATE fso_avatars SET mayor_nhood = @nhood WHERE avatar_id = @id", new { id = id, nhood = nhood });
|
||||
}
|
||||
|
||||
|
||||
public void UpdateAvatarLotSave(uint id, DbAvatar avatar)
|
||||
{
|
||||
avatar.avatar_id = id;
|
||||
Context.Connection.Query("UPDATE fso_avatars SET "
|
||||
+ "motive_data = @motive_data, "
|
||||
+ "skilllock = @skilllock, "
|
||||
+ "lock_mechanical = @lock_mechanical, "
|
||||
+ "lock_cooking = @lock_cooking, "
|
||||
+ "lock_charisma = @lock_charisma, "
|
||||
+ "lock_logic = @lock_logic, "
|
||||
+ "lock_body = @lock_body, "
|
||||
+ "lock_creativity = @lock_creativity, "
|
||||
+ "skill_mechanical = @skill_mechanical, "
|
||||
+ "skill_cooking = @skill_cooking, "
|
||||
+ "skill_charisma = @skill_charisma, "
|
||||
+ "skill_logic = @skill_logic, "
|
||||
+ "skill_body = @skill_body, "
|
||||
+ "skill_creativity = @skill_creativity, "
|
||||
+ "body = @body, "
|
||||
+ "body_swimwear = @body_swimwear, "
|
||||
+ "body_sleepwear = @body_sleepwear, "
|
||||
+ "body_current = @body_current, "
|
||||
+ "current_job = @current_job, "
|
||||
+ "is_ghost = @is_ghost, "
|
||||
+ "ticker_death = @ticker_death, "
|
||||
+ "ticker_gardener = @ticker_gardener, "
|
||||
+ "ticker_maid = @ticker_maid, "
|
||||
+ "ticker_repairman = @ticker_repairman WHERE avatar_id = @avatar_id", avatar);
|
||||
}
|
||||
|
||||
private static string[] LockNames = new string[]
|
||||
{
|
||||
"lock_mechanical",
|
||||
"lock_cooking",
|
||||
"lock_charisma",
|
||||
"lock_logic",
|
||||
"lock_body",
|
||||
"lock_creativity"
|
||||
};
|
||||
|
||||
public int GetOtherLocks(uint avatar_id, string except)
|
||||
{
|
||||
string columns = "(";
|
||||
foreach (var l in LockNames)
|
||||
{
|
||||
if (l == except) continue;
|
||||
columns += l;
|
||||
columns += " + ";
|
||||
}
|
||||
columns += "0) AS Sum";
|
||||
|
||||
return Context.Connection.Query<int>("SELECT "+columns+" FROM fso_avatars WHERE avatar_id = @id", new { id = avatar_id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
//budget and transactions
|
||||
public int GetBudget(uint avatar_id)
|
||||
{
|
||||
return Context.Connection.Query<int>("SELECT budget FROM fso_avatars WHERE avatar_id = @id", new { id = avatar_id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public DbTransactionResult Transaction(uint source_id, uint dest_id, int amount, short reason)
|
||||
{
|
||||
return Transaction(source_id, dest_id, amount, reason, null);
|
||||
}
|
||||
|
||||
public DbTransactionResult Transaction(uint source_id, uint dest_id, int amount, short reason, Func<bool> transactionInject)
|
||||
{
|
||||
var t = Context.Connection.BeginTransaction();
|
||||
var srcObj = (source_id >= 16777216);
|
||||
var dstObj = (dest_id >= 16777216);
|
||||
var success = true;
|
||||
try {
|
||||
int srcRes, dstRes;
|
||||
if (source_id != uint.MaxValue)
|
||||
{
|
||||
if (srcObj)
|
||||
{
|
||||
srcRes = Context.Connection.Execute("UPDATE fso_objects SET budget = budget - @amount WHERE object_id = @source_id;",
|
||||
new { source_id = source_id, amount = amount });
|
||||
}
|
||||
else
|
||||
{
|
||||
srcRes = Context.Connection.Execute("UPDATE fso_avatars SET budget = budget - @amount WHERE avatar_id = @source_id;",
|
||||
new { source_id = source_id, amount = amount });
|
||||
}
|
||||
if (srcRes == 0) throw new Exception("Source avatar/object does not exist!");
|
||||
}
|
||||
|
||||
if (dest_id != uint.MaxValue)
|
||||
{
|
||||
if (dstObj)
|
||||
{
|
||||
dstRes = Context.Connection.Execute("UPDATE fso_objects SET budget = budget + @amount WHERE object_id = @dest_id;",
|
||||
new { dest_id = dest_id, amount = amount });
|
||||
}
|
||||
else
|
||||
{
|
||||
dstRes = Context.Connection.Execute("UPDATE fso_avatars SET budget = budget + @amount WHERE avatar_id = @dest_id;",
|
||||
new { dest_id = dest_id, amount = amount });
|
||||
}
|
||||
if (dstRes == 0) throw new Exception("Dest avatar/object does not exist!");
|
||||
}
|
||||
|
||||
if (transactionInject != null)
|
||||
{
|
||||
if (!transactionInject()) throw new Exception("Transaction Cancelled");
|
||||
}
|
||||
|
||||
t.Commit();
|
||||
} catch (Exception)
|
||||
{
|
||||
success = false;
|
||||
t.Rollback();
|
||||
}
|
||||
|
||||
if (success && ((reason > 7 && reason != 9) || (source_id != uint.MaxValue && dest_id != uint.MaxValue))) {
|
||||
var days = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalDays;
|
||||
Context.Connection.Execute("INSERT INTO fso_transactions (from_id, to_id, transaction_type, day, value, count) "+
|
||||
"VALUES (@from_id, @to_id, @transaction_type, @day, @value, @count) " +
|
||||
"ON DUPLICATE KEY UPDATE value = value + @value, count = count+1", new
|
||||
{
|
||||
from_id = (amount>0)?source_id:dest_id,
|
||||
to_id = (amount>0)?dest_id:source_id,
|
||||
transaction_type = reason,
|
||||
day = (int)days,
|
||||
value = Math.Abs(amount),
|
||||
count = 1
|
||||
});
|
||||
}
|
||||
|
||||
var result = Context.Connection.Query<DbTransactionResult>("SELECT a1.budget AS source_budget, a2.budget AS dest_budget "
|
||||
+ "FROM"
|
||||
+ "(SELECT budget, count(budget) FROM " + (srcObj ? "fso_objects" : "fso_avatars") + " WHERE " + (srcObj ? "object_id" : "avatar_id") + " = @source_id) a1,"
|
||||
+ "(SELECT budget, count(budget) FROM " + (dstObj ? "fso_objects" : "fso_avatars") + " WHERE " + (dstObj ? "object_id" : "avatar_id") + " = @avatar_id) a2; ",
|
||||
new { avatar_id = dest_id, source_id = source_id }).FirstOrDefault();
|
||||
if (result != null)
|
||||
{
|
||||
result.amount = amount;
|
||||
result.success = success;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public DbTransactionResult TestTransaction(uint source_id, uint dest_id, int amount, short reason)
|
||||
{
|
||||
var success = true;
|
||||
var srcObj = (source_id >= 16777216);
|
||||
var dstObj = (dest_id >= 16777216);
|
||||
try
|
||||
{
|
||||
int? srcVal, dstVal;
|
||||
if (srcObj)
|
||||
{
|
||||
srcVal = Context.Connection.Query<int?>("SELECT budget FROM fso_objects WHERE object_id = @source_id;",
|
||||
new { source_id = source_id }).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
srcVal = Context.Connection.Query<int?>("SELECT budget FROM fso_avatars WHERE avatar_id = @source_id;",
|
||||
new { source_id = source_id }).FirstOrDefault();
|
||||
}
|
||||
if (source_id != uint.MaxValue)
|
||||
{
|
||||
if (srcVal == null) throw new Exception("Source avatar/object does not exist!");
|
||||
if (srcVal.Value - amount < 0) throw new Exception("Source does not have enough money!");
|
||||
}
|
||||
if (dstObj)
|
||||
{
|
||||
dstVal = Context.Connection.Query<int?>("SELECT budget FROM fso_objects WHERE object_id = @dest_id;",
|
||||
new { dest_id = dest_id }).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
dstVal = Context.Connection.Query<int?>("SELECT budget FROM fso_avatars WHERE avatar_id = @dest_id;",
|
||||
new { dest_id = dest_id }).FirstOrDefault();
|
||||
}
|
||||
if (dest_id != uint.MaxValue)
|
||||
{
|
||||
if (dstVal == null) throw new Exception("Dest avatar/object does not exist!");
|
||||
if (dstVal.Value + amount < 0) throw new Exception("Destination does not have enough money! (transaction accidentally debits)");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
var result = Context.Connection.Query<DbTransactionResult>("SELECT a1.budget AS source_budget, a2.budget AS dest_budget "
|
||||
+ "FROM"
|
||||
+ "(SELECT budget, count(budget) FROM "+(srcObj?"fso_objects":"fso_avatars")+" WHERE "+(srcObj?"object_id":"avatar_id")+" = @source_id) a1,"
|
||||
+ "(SELECT budget, count(budget) FROM " + (dstObj ? "fso_objects" : "fso_avatars") + " WHERE " + (dstObj ? "object_id" : "avatar_id") + " = @avatar_id) a2; ",
|
||||
new { avatar_id = dest_id, source_id = source_id }).FirstOrDefault();
|
||||
if (result != null)
|
||||
{
|
||||
result.amount = amount;
|
||||
result.success = success;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//JOB LEVELS
|
||||
|
||||
public DbJobLevel GetCurrentJobLevel(uint avatar_id)
|
||||
{
|
||||
return Context.Connection.Query<DbJobLevel>("SELECT * FROM fso_avatars a JOIN fso_joblevels b "
|
||||
+ "ON a.avatar_id = b.avatar_id AND a.current_job = b.job_type WHERE a.avatar_id = @id",
|
||||
new { id = avatar_id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<DbJobLevel> GetJobLevels(uint avatar_id)
|
||||
{
|
||||
return Context.Connection.Query<DbJobLevel>("SELECT * FROM fso_avatars a JOIN fso_joblevels b ON a.avatar_id = b.avatar_id WHERE a.avatar_id = @id", new { id = avatar_id }).ToList();
|
||||
}
|
||||
|
||||
public void UpdateAvatarJobLevel(DbJobLevel jobLevel)
|
||||
{
|
||||
Context.Connection.Query<DbJobLevel>("INSERT INTO fso_joblevels (avatar_id, job_type, job_experience, job_level, job_sickdays, job_statusflags) "
|
||||
+ "VALUES (@avatar_id, @job_type, @job_experience, @job_level, @job_sickdays, @job_statusflags) "
|
||||
+ "ON DUPLICATE KEY UPDATE job_experience=VALUES(`job_experience`), job_level=VALUES(`job_level`), "
|
||||
+" job_sickdays=VALUES(`job_sickdays`), job_statusflags=VALUES(`job_statusflags`); ", jobLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
public List<DbAvatarSummary> GetSummaryByUserId(uint user_id)
|
||||
{
|
||||
return Context.Connection.Query<DbAvatarSummary>(
|
||||
@"SELECT a.avatar_id,
|
||||
a.shard_id,
|
||||
a.user_id,
|
||||
a.name,
|
||||
a.gender,
|
||||
a.date,
|
||||
a.skin_tone,
|
||||
a.head,
|
||||
a.body,
|
||||
a.description,
|
||||
r.lot_id,
|
||||
l.name as lot_name,
|
||||
l.location as lot_location
|
||||
FROM fso_avatars a
|
||||
LEFT OUTER JOIN fso_roommates r on r.avatar_id = a.avatar_id
|
||||
LEFT OUTER JOIN fso_lots l on l.lot_id = r.lot_id AND r.is_pending = 0
|
||||
WHERE a.user_id = @user_id
|
||||
ORDER BY a.date ASC", new { user_id = user_id }).ToList();
|
||||
}
|
||||
}
|
||||
}
|
10
server/FSO.Server.Database/DA/Bans/DbBan.cs
Executable file
10
server/FSO.Server.Database/DA/Bans/DbBan.cs
Executable file
|
@ -0,0 +1,10 @@
|
|||
namespace FSO.Server.Database.DA.Bans
|
||||
{
|
||||
public class DbBan
|
||||
{
|
||||
public uint user_id { get; set; }
|
||||
public string ip_address { get; set; }
|
||||
public string ban_reason { get; set; }
|
||||
public int end_date { get; set; }
|
||||
}
|
||||
}
|
11
server/FSO.Server.Database/DA/Bans/IBans.cs
Executable file
11
server/FSO.Server.Database/DA/Bans/IBans.cs
Executable file
|
@ -0,0 +1,11 @@
|
|||
namespace FSO.Server.Database.DA.Bans
|
||||
{
|
||||
public interface IBans
|
||||
{
|
||||
DbBan GetByIP(string ip);
|
||||
void Add(string ip, uint userid, string reason, int enddate, string client_id);
|
||||
|
||||
DbBan GetByClientId(string client_id);
|
||||
void Remove(uint user_id);
|
||||
}
|
||||
}
|
51
server/FSO.Server.Database/DA/Bans/SqlBans.cs
Executable file
51
server/FSO.Server.Database/DA/Bans/SqlBans.cs
Executable file
|
@ -0,0 +1,51 @@
|
|||
using Dapper;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Server.Database.DA.Bans
|
||||
{
|
||||
public class SqlBans : AbstractSqlDA, IBans
|
||||
{
|
||||
public SqlBans(ISqlContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public DbBan GetByIP(string ip)
|
||||
{
|
||||
return Context.Connection.Query<DbBan>("SELECT * FROM fso_ip_ban WHERE ip_address = @ip", new { ip = ip }).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a ban by MAC Address.
|
||||
/// </summary>
|
||||
/// <param name="client_id"></param>
|
||||
/// <returns></returns>
|
||||
public DbBan GetByClientId(string client_id)
|
||||
{
|
||||
return Context.Connection.Query<DbBan>("SELECT * FROM fso_ip_ban WHERE client_id = @client_id", new { client_id = client_id }).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void Add(string ip, uint userid, string reason, int enddate, string client_id)
|
||||
{
|
||||
Context.Connection.Execute(
|
||||
"REPLACE INTO fso_ip_ban (user_id, ip_address, banreason, end_date, client_id) " +
|
||||
"VALUES (@user_id, @ip_address, @banreason, @end_date, @client_id)",
|
||||
new
|
||||
{
|
||||
user_id = userid,
|
||||
ip_address = ip,
|
||||
banreason = reason,
|
||||
end_date = enddate
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove ban by user_id.
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
public void Remove(uint user_id)
|
||||
{
|
||||
Context.Connection.Query("DELETE FROM fso_ip_ban WHERE user_id = @user_id", new { user_id = user_id });
|
||||
}
|
||||
}
|
||||
}
|
14
server/FSO.Server.Database/DA/Bonus/DbBonus.cs
Executable file
14
server/FSO.Server.Database/DA/Bonus/DbBonus.cs
Executable file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace FSO.Server.Database.DA.Bonus
|
||||
{
|
||||
public class DbBonus
|
||||
{
|
||||
public int bonus_id { get; set; }
|
||||
public uint avatar_id { get; set; }
|
||||
public DateTime period { get; set; }
|
||||
public int? bonus_visitor { get; set; }
|
||||
public int? bonus_property { get; set; }
|
||||
public int? bonus_sim { get; set; }
|
||||
}
|
||||
}
|
14
server/FSO.Server.Database/DA/Bonus/DbBonusMetrics.cs
Executable file
14
server/FSO.Server.Database/DA/Bonus/DbBonusMetrics.cs
Executable file
|
@ -0,0 +1,14 @@
|
|||
using FSO.Common.Enum;
|
||||
|
||||
namespace FSO.Server.Database.DA.Bonus
|
||||
{
|
||||
public class DbBonusMetrics
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public int lot_id { get; set; }
|
||||
public LotCategory category { get; set; }
|
||||
public int? visitor_minutes { get; set; }
|
||||
public byte? property_rank { get; set; }
|
||||
public byte? sim_rank { get; set; }
|
||||
}
|
||||
}
|
13
server/FSO.Server.Database/DA/Bonus/IBonus.cs
Executable file
13
server/FSO.Server.Database/DA/Bonus/IBonus.cs
Executable file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Database.DA.Bonus
|
||||
{
|
||||
public interface IBonus
|
||||
{
|
||||
IEnumerable<DbBonus> GetByAvatarId(uint avatar_id);
|
||||
IEnumerable<DbBonusMetrics> GetMetrics(DateTime date, int shard_id);
|
||||
void Insert(IEnumerable<DbBonus> bonus);
|
||||
void Purge(DateTime date);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue