private async Task InternalSaveTranscript(int resourceId, string transcript) { var entity = new GameCopycatEntity2 { PartitionKey = KeyUtils.IntToKey(resourceId), RowKey = KeyUtils.DateTimeToDescKey(DateTime.UtcNow), Transcript = transcript, UserId = this.GetUserId(), UserDisplayName = this.GetUserDisplayName(), }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.GameCopycat, entity); }
public async Task <IHttpActionResult> PostResourceView([FromUri] int id, [FromBody] JObject value) { // A suggestion may have resourceId == null(0?) when the resource is made on the client in code and points to Google // TODO. Idea: Get the unauthenicated user's session cookie and log views for the session in a separate datastore. var userId = this.GetUserId(); // GetUserId() returns 0 for an unauthenticated user. That's fine. We log every view. var logEnity = new DynamicTableEntity(KeyUtils.GetCurrentTimeKey(), KeyUtils.IntToKey(userId)); logEnity["Json"] = new EntityProperty(value.ToString()); var logTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryLog, logEnity); if (userId != 0 && id != 0) { var viewTask = DapperHelper.ExecuteResilientlyAsync("dbo.libPostResourceView", new { UserId = userId, ResourceId = id, }, CommandType.StoredProcedure); // We use KeyUtils.LocalTimeToInvertedKey() to keep the local time and to order last records first for retrieval. var localTime = (string)value["localTime"]; var rowKey = KeyUtils.LocalTimeToInvertedKey(localTime); if (String.IsNullOrEmpty(rowKey)) { throw new ArgumentOutOfRangeException(localTime); } var historyEntity = new LibraryHistoryEntity { PartitionKey = KeyUtils.IntToKey(userId), RowKey = rowKey, ResourceId = id, }; var historyTask = AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.LibraryHistory, historyEntity); // Do all the tasks in parallel. await Task.WhenAll(viewTask, historyTask, logTask); } else { await logTask; } return(StatusCode(HttpStatusCode.NoContent)); }
private async Task SendSmsMessage(string text, string tablePartitionKey) { // Send SMS var accountSid = ConfigurationManager.AppSettings["Twilio.AccountSid"]; var authToken = ConfigurationManager.AppSettings["Twilio.AuthToken"]; var fromPhoneNumber = ConfigurationManager.AppSettings["Twilio.PhoneNumber.SMS"]; var twilio = new TwilioRestClient(accountSid, authToken); var message = twilio.SendMessage(fromPhoneNumber, "+16477711715", text); var entity = new ExternalSessionLogEntity { PartitionKey = tablePartitionKey, RowKey = "TwilioMessage", Data = JsonUtils.SerializeAsJson(new { Message = message, }), }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); }
public static async Task <string> SaveMessageAndGetExtId(string message) { string extId = null; // We cannot use a transaction in heterogeneous storage mediums. An orphaned message on Azure Table is not a problem. The main message metadata is in the database anyway. if (!String.IsNullOrWhiteSpace(message)) { extId = KeyUtils.GetTwelveBase32Digits(); var entity = new UserMessageEntity { PartitionKey = extId, RowKey = String.Empty, Text = message, }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.UserMessages, entity); } return(extId); }
public async Task <IHttpActionResult> InboundWebhook(InboundWebhookFormModel form) { // The form is empty on a HEAD request if (form != null) { var mandrill_events = form.mandrill_events; var partitionKey = KeyUtils.GetCurrentTimeKey(); var entity = new ExternalSessionLogEntity { PartitionKey = partitionKey, RowKey = "InboundWebhook", Data = mandrill_events, }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); await SendSmsMessage("InboundWebhook " + partitionKey, partitionKey); } return(StatusCode(HttpStatusCode.NoContent)); }
public async Task <IHttpActionResult> GetCollection(string q) { if (String.IsNullOrWhiteSpace(q)) { return(BadRequest("Empty query.")); } var collectionName = GeneralUtils.SanitizeSpaceSeparatedWords(q); // Try to find the picture collection for the query in the internal Table storage. var table = AzureStorageUtils.GetCloudTable(AzureStorageUtils.TableNames.GamePicapick); var operation = TableOperation.Retrieve <GamePickapicEntity>(collectionName, String.Empty); var entity = (await table.ExecuteAsync(operation)).Result as GamePickapicEntity; string json = entity != null ? entity.Json : null; // If the data not found in our internal Table, query the YouTube API. if (json == null) { // We do not use the Google API Client library to materialize result as a POCO. Anyway the API itself is RESTful, and JSON can be parsed easily. Avoid overhead, overkill, bloatware etc. // +https://developers.google.com/apis-explorer/#p/youtube/v3/youtube.search.list var youtubeParams = HttpUtility.ParseQueryString(String.Empty); // returns System.Web.HttpValueCollection: System.Collections.Specialized.NameValueCollection youtubeParams["key"] = ConfigurationManager.AppSettings["YoutubeApiKey"]; youtubeParams["part"] = "snippet"; youtubeParams["type"] = "video"; youtubeParams["maxResults"] = "50"; youtubeParams["q"] = collectionName; youtubeParams["fields"] = "items(id,snippet(thumbnails(high)))"; var youtubeQueryString = youtubeParams.ToString(); var url = "https://www.googleapis.com/youtube/v3/search?" + youtubeQueryString; var handler = new HttpClientHandler(); if (handler.SupportsAutomaticDecompression) { handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } string response = null; using (var client = new HttpClient(handler)) { // If User-Agent is not sent, the server ignores "Accept-Encoding: gzip, deflate" and does not compress the response. The observed compression is 10kB -> 1kB. client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "google-api-dotnet-client/1.8.1.31685 (gzip)"); response = await client.GetStringAsync(url); } var urls = JObject.Parse(response) .GetValue("items") .Children() .Select(i => i["snippet"]["thumbnails"]["high"]["url"].Value <string>()) .OrderBy(i => Guid.NewGuid()) // Shuffle .ToArray(); var controlNumber = Math.Abs(String.Join(String.Empty, urls).GetHashCode()) % 100; json = JsonUtils.SerializeAsJson(new { CollectionName = collectionName, ControlNumber = controlNumber, Urls = urls, }); entity = new GamePickapicEntity(collectionName, json); await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.GamePicapick, entity); } return(new RawStringResult(this, json, RawStringResult.TextMediaType.Json)); }
public async Task <long?> BookSession(int courseId, DateTime start, string referer, string tablePartitionKey) { // Time slot is encoded in a weired way. var origin = DateTime.UtcNow.Date; var diff = start - origin; var day = Convert.ToInt32(Math.Floor(diff.TotalDays)) - 1; // Day number for the next day is 0. if (day < 0) { throw new Exception(ItalkiHelper.TimeSlotUnavailableError); } var halfHour = (diff.Hours * 2) + (diff.Minutes == 30 ? 1 : 0); // Convert.ToInt32(1.0 * diff.Minutes / 30); // We expect minutes to be either 00 or 30. var timeText = String.Format("{0},{1}", day, halfHour); // We will write to the log. var partitionKey0 = KeyUtils.GetCurrentTimeKey(); // Pre-booking validation. It might be redundand. We may want to do it just in case. // Note that the validation call does not send the courseId. That means the server uses a session(???) or a cookie or the Referer header. It may cause problems in a multi-teacher scenario, until we use a separate HttpClient for each teacher. //var content1 = new FormUrlEncodedContent(new[] //{ // new KeyValuePair<string, string>("MethodName", "ValidSessionTime"), // new KeyValuePair<string, string>("time", timeText), //}); //var request1 = new HttpRequestMessage //{ // RequestUri = new Uri(ItalkiWebServicesUrl), // Method = HttpMethod.Post, // Content = content1, //}; //request1.Headers.Add("Referer", referer); //var response1 = await httpClient.SendAsync(request1); //var statusCode1 = (int)response1.StatusCode; //string responseContent1 = null; //if (response1.IsSuccessStatusCode) //{ // responseContent1 = await response1.Content.ReadAsStringAsync(); //} //var entity1 = new ExternalSessionLogEntity //{ // PartitionKey = partitionKey, // RowKey = "ValidationResponse", // Data = responseContent1, // HttpStatus = statusCode1, //}; //await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity1); //response1.EnsureSuccessStatusCode(); // Send the booking request #if DEBUG var password = "******"; #else var password = ConfigurationManager.AppSettings["Italki.Password1"]; #endif var content = new FormUrlEncodedContent(new[] { new KeyValuePair <string, string>("MethodName", "RequestSession"), new KeyValuePair <string, string>("time", timeText), // It will be Url-encoded by the form oject. new KeyValuePair <string, string>("tool", "1"), new KeyValuePair <string, string>("sim", "andrey.fomin3"), new KeyValuePair <string, string>("msg", ":)"), // ":-) :) :D :o) :] :3 :c) :> =] 8) =) :} :^)" new KeyValuePair <string, string>("pwd", password), new KeyValuePair <string, string>("cid", courseId.ToString()), }); var request = new HttpRequestMessage { RequestUri = new Uri(ItalkiWebServicesUrl), Method = HttpMethod.Post, Content = content, }; request.Headers.Add("Referer", referer); // Write to the log var entity = new ExternalSessionLogEntity { PartitionKey = tablePartitionKey, RowKey = "ItalkiRequest", Data = JsonUtils.SerializeAsJson(new { CourseId = courseId, Start = start, Time = timeText, }), }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); // Book the session with Italki var response = await httpClient.SendAsync(request); var statusCode = (int)response.StatusCode; string responseContent = null; if (response.IsSuccessStatusCode) { responseContent = await response.Content.ReadAsStringAsync(); } // Write the response to the log entity = new ExternalSessionLogEntity { PartitionKey = tablePartitionKey, RowKey = "ItalkiResponse", Data = responseContent, HttpStatus = statusCode, }; await AzureStorageUtils.InsertEntityAsync(AzureStorageUtils.TableNames.ExternalSessions, entity); response.EnsureSuccessStatusCode(); long?extSessionId = null; try { var items = JArray.Parse(responseContent); /* +https://secure.italki.com/sessions/schedule_20131106.js?v=150302 , Line 853 * if(result[0]=="-2") $get("MsgPrompt").innerHTML=TS312; * if(result[0]=="-1") $get("MsgPrompt").innerHTML=TS313; * if(result[0]=="1") $get("MsgPrompt").innerHTML=TS314; * if(result[0]=="-3") $get("MsgPrompt").innerHTML=TS315; * In HTML * var TS312="The time you\'re scheduling was taken by other students. Please try other time."; * var TS313="Your payment account balance is not enough to pay for the session(s) you are scheduling."; * var TS314="Trial session is not available."; * var TS315="Incorrect password"; */ var result = (string)items.FirstOrDefault(); if ((result == "Y") || (result == "Y1")) { var idArr = items[3]; if (idArr is JArray) { extSessionId = (long)idArr.First(); } } } // We may get an HTML instead of JSON in response. catch (JsonReaderException) { } return(extSessionId); }