Skip to content

Commit

Permalink
Update API to v1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
arumie committed Mar 3, 2024
1 parent 300425e commit 99f5e2d
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 29 deletions.
55 changes: 47 additions & 8 deletions AiTestimonials/Api/AiTestimonialsApi.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,71 @@
using AiTestimonials.Models;
using AiTestimonials.Repository;
using AiTestimonials.Services;
using AiTestimonials.Util;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace AiTestimonials.Api;

public static class AiTestimonialsApi
{
public static void RegisterAiTestimonialsEndpoints(this IEndpointRouteBuilder routes)
public static void RegisterAiTestimonialsEndpoints(this IEndpointRouteBuilder routes)
{
var route = routes.MapGroup("/api/v1/ai-testimonials").WithOpenApi();
route.MapPost("/generate", PostGenerateAiTestimonialAsync);
route.MapPost("/redo", PostRedoTestimonialsAsync);
route.MapPost("/save", PostSaveTestimonialsAsync);
route.MapGet("", GetTestimonialsAsync);
}

private static async Task<Ok<TestimonialResult>> PostGenerateAiTestimonialAsync(string name, string skills, [FromHeader(Name = "OPENAI_KEY")] string? openAIKey, AiTestimonialsService service, VercelPostgresRepository repo)
private static async Task<Ok<Identity>> PostGenerateAiTestimonialAsync(TestimonialInput input, [FromHeader(Name = "OPENAI_KEY")] string? openAIKey, AiTestimonialsService service, VercelPostgresRepository repo)
{
service.SetupOpenAIService(openAIKey);
var res = await service.GenerateAiTestimonialAsync(name, skills);
await repo.CreatTestimonialsTableAsync();
await repo.AddTestimonialAsync(res);
return TypedResults.Ok(res);
var testimonialId = (await repo.CreateNewTestimonialAsync(input)).ToString();
try
{
GenerateAiTestimonialAsync(testimonialId, input.Name, input.Skills, service, repo).Forget();
return TypedResults.Ok(new Identity() { Id = testimonialId.ToString() });
}
catch
{
await repo.UpdateTestimonialAsync(TestimonialStatus.FAILED, testimonialId);
throw;
}
}

private static async Task<IResult> PostRedoTestimonialsAsync(string id, AiTestimonialsService service, VercelPostgresRepository repo)
{
var entity = await repo.GetTestimonialsEntityAsync(id);
if (entity != null && entity.Status != TestimonialStatus.PENDING && entity.Status != TestimonialStatus.SAVED && entity.Input != null)
{
await repo.UpdateTestimonialAsync(TestimonialStatus.PENDING, id);
GenerateAiTestimonialAsync(id, entity.Input.Name, entity.Input.Skills, service, repo).Forget();
}
else
{
return TypedResults.BadRequest();
};
return TypedResults.Ok();
}

private static async Task<Ok> PostSaveTestimonialsAsync(string id, VercelPostgresRepository repo)
{
await repo.UpdateTestimonialAsync(TestimonialStatus.SAVED, id);
return TypedResults.Ok();
}

private static async Task<Ok<List<TestimonialResult>>> GetTestimonialsAsync(VercelPostgresRepository repo)
private static async Task<Ok<List<TestimonialResult>>> GetTestimonialsAsync(string? id, VercelPostgresRepository repo)
{
var res = await repo.GetTestimonialsAsync();
var res = await repo.GetTestimonialsAsync(id);
return TypedResults.Ok(res);
}

private static async Task GenerateAiTestimonialAsync(string id, string name, string skills, AiTestimonialsService service, VercelPostgresRepository repo)
{
var res = await service.GenerateAiTestimonialAsync(name, skills);
await repo.CreatTestimonialsTableAsync();
await repo.AddTestimonialAsync(res, id);
await repo.UpdateTestimonialAsync(TestimonialStatus.SUCCESSFUL, id);
}
}
27 changes: 27 additions & 0 deletions AiTestimonials/Models/AiTestmonialModels.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
namespace AiTestimonials.Models;

public record TestimonialEntity
{
public required string Id { get; init; }
public required TestimonialStatus Status { get; init; }
public required TestimonialInput Input { get; init; }
public TestimonialResult? Testimonial { get; init; }
}

public record TestimonialResult
{
public string? Testimonial { get; init; }
Expand All @@ -8,4 +16,23 @@ public record TestimonialResult
public string? TestifierPosition { get; init; }
public string? LogoUrl { get; set; }
public string? LogoB64 { get; set; }
}

public record TestimonialInput
{
public required string Name { get; init; }
public required string Skills { get; init; }
}

public record Identity
{
public string Id { get; init; }
}

public enum TestimonialStatus
{
PENDING = 0,
SAVED = 1,
SUCCESSFUL = 2,
FAILED = 3
}
67 changes: 61 additions & 6 deletions AiTestimonials/Repository/VercelPostgresRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,86 @@ public async Task CreatTestimonialsTableAsync()
$@"
CREATE TABLE testimonials (
id SERIAL PRIMARY KEY,
status int,
input JSONB,
testimonial JSONB
);"
);
await cmd.ExecuteReaderAsync();
}
}

public async Task<bool> AddTestimonialAsync(TestimonialResult testimonialResult)
public async Task<int> CreateNewTestimonialAsync(TestimonialInput input)
{
await using var cmd = _db.CreateCommand($"INSERT INTO testimonials (testimonial) VALUES (@value);");
var param = new NpgsqlParameter() { ParameterName = "value", NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Jsonb, Value = testimonialResult };
await using var cmd = _db.CreateCommand($"INSERT INTO testimonials (status, input) VALUES (@status, @input) RETURNING id;");
var param1 = new NpgsqlParameter() { ParameterName = "value", NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Integer, Value = (int)TestimonialStatus.PENDING };
var param2 = new NpgsqlParameter() { ParameterName = "input", NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Jsonb, Value = input };
cmd.Parameters.Add(param1);
cmd.Parameters.Add(param2);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var id = reader.GetFieldValue<int>(0);
return id;
}
throw new InvalidOperationException();
}

public async Task AddTestimonialAsync(TestimonialResult testimonialResult, string id)
{
await using var cmd = _db.CreateCommand($"UPDATE testimonials SET testimonial = @testimonial WHERE id = {id};");
var param = new NpgsqlParameter() { ParameterName = "testimonial", NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Jsonb, Value = testimonialResult };
cmd.Parameters.Add(param);
await using var reader = await cmd.ExecuteReaderAsync();
return await TableExistsAsync("testimonials");
}

public async Task<List<TestimonialResult>> GetTestimonialsAsync()
public async Task UpdateTestimonialAsync(TestimonialStatus status, string id)
{
await using var cmd = _db.CreateCommand($"UPDATE testimonials SET status = @status WHERE id = {id};");
var param = new NpgsqlParameter() { ParameterName = "status", NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Integer, Value = (int)status };
cmd.Parameters.Add(param);
await using var reader = await cmd.ExecuteReaderAsync();
}

public async Task<TestimonialEntity?> GetTestimonialsEntityAsync(string id)
{

var tableExists = await TableExistsAsync("testimonials");

if (tableExists)
{
var query = $"SELECT status, input, testimonial FROM testimonials WHERE id = {id}";
await using var cmd = _db.CreateCommand(query);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var status = (TestimonialStatus)reader.GetFieldValue<int>(0);
var input = reader.GetFieldValue<TestimonialInput>(1);
var testimonial = reader.GetFieldValue<TestimonialResult>(2);
return new TestimonialEntity()
{
Id = id,
Status = status,
Input = input,
Testimonial = testimonial
};
}
}
return null;
}

public async Task<List<TestimonialResult>> GetTestimonialsAsync(string? id)
{

var tableExists = await TableExistsAsync("testimonials");
List<TestimonialResult> res = [];

if (tableExists)
{
await using var cmd = _db.CreateCommand("SELECT testimonial FROM testimonials");
var query = id != null
? $"SELECT testimonial FROM testimonials WHERE id = {id} AND status = {(int)TestimonialStatus.SUCCESSFUL}"
: $"SELECT testimonial FROM testimonials WHERE status = {(int)TestimonialStatus.SUCCESSFUL}";
await using var cmd = _db.CreateCommand(query);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
Expand Down
4 changes: 2 additions & 2 deletions AiTestimonials/Services/AiTestimonialsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ private async Task<TestimonialResult> CreateTestimonialAsync(string developer, s
Messages =
[
ChatMessage.FromSystem("You generate random short testimonials from people and companies You output is valid JSON format: { Testimonial: string, TestifierName: string, TestifierCompany: string, TestifierPosition: string }"),
ChatMessage.FromUser($"Create a short ({numWords} words) random testimonial from a company praising the following software developer \"{developer}\". Focus on the the following skills: \"{work}\". The name of the testifier should be a plausible name (not John Doe).")
ChatMessage.FromUser($"Create a ~{numWords} words long random testimonial from a company praising the following software developer \"{developer}\". Focus on the the following skills: \"{work}\". The name of the testifier should be a plausible name (not John Doe).")
],
MaxTokens = 1000,
Temperature = 1.2f
Temperature = 0.9f

});
if (res.Successful)
Expand Down
36 changes: 36 additions & 0 deletions AiTestimonials/Util/TaskExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace AiTestimonials.Util;

public static class TaskExtensions
{
/// <summary>
/// Observes the task to avoid the UnobservedTaskException event to be raised.
/// </summary>
public static void Forget(this Task task)
{
// note: this code is inspired by a tweet from Ben Adams: https://twitter.com/ben_a_adams/status/1045060828700037125
// Only care about tasks that may fault (not completed) or are faulted,
// so fast-path for SuccessfullyCompleted and Canceled tasks.
if (!task.IsCompleted || task.IsFaulted)
{
// use "_" (Discard operation) to remove the warning IDE0058: Because this call is not awaited, execution of the current method continues before the call is completed
// https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards?WT.mc_id=DT-MVP-5003978#a-standalone-discard
_ = ForgetAwaited(task);
}

// Allocate the async/await state machine only when needed for performance reasons.
// More info about the state machine: https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/?WT.mc_id=DT-MVP-5003978
async static Task ForgetAwaited(Task task)
{

try
{
// No need to resume on the original SynchronizationContext, so use ConfigureAwait(false)
await task.ConfigureAwait(false);
}
catch
{
// Nothing to do here
}
}
}
}
122 changes: 122 additions & 0 deletions AiTestimonials/openapi/gcpApiConfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
swagger: '2.0'
info:
title: AI Testimonials
description: API for generating fake testimonials
version: 1.1.0
schemes:
- https
produces:
- application/json
x-google-backend:
address: https://ai-testimonials-trtv3l63ra-ey.a.run.app
paths:
/api/v1/ai-testimonials/generate:
post:
tags:
- AiTestimonialsApi
operationId: "GenerateAiTestimonial"
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: OPENAI_KEY
type: string
- in: body
name: body
required: true
schema:
$ref: '#/definitions/TestimonialInput'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/Identity'
/api/v1/ai-testimonials/redo:
post:
tags:
- AiTestimonialsApi
operationId: "RedoAiTestimonial"
parameters:
- in: query
name: id
required: true
type: string
collectionFormat: multi
responses:
'200':
description: OK
/api/v1/ai-testimonials/save:
post:
tags:
- AiTestimonialsApi
operationId: "SaveAiTestimonial"
parameters:
- in: query
name: id
required: true
type: string
collectionFormat: multi
responses:
'200':
description: OK
/api/v1/ai-testimonials:
get:
tags:
- AiTestimonialsApi
operationId: "GetAiTestimonials"
produces:
- application/json
parameters:
- in: query
name: id
type: string
collectionFormat: multi
responses:
'200':
description: OK
schema:
type: array
items:
$ref: '#/definitions/TestimonialResult'
definitions:
Identity:
type: object
properties:
id:
type: string
additionalProperties: false
TestimonialInput:
type: object
properties:
name:
type: string
skills:
type: string
additionalProperties: false
TestimonialResult:
type: object
properties:
testimonial:
type: string
testifierName:
type: string
testifierCompany:
type: string
testifierPosition:
type: string
logoUrl:
type: string
logoB64:
type: string
additionalProperties: false
securityDefinitions:
API_KEY:
type: apiKey
name: x-api-key
in: header
description: ApiKey must appear in header
security:
- API_KEY: [ ]
Loading

0 comments on commit 99f5e2d

Please sign in to comment.