Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
all variable names should be in snake_case

ALL variable names should start with the prefix "my_"
2 changes: 2 additions & 0 deletions src/Common/Database/AppDBContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Mythology = MythApi.Common.Database.Models.Mythology;
using God = MythApi.Common.Database.Models.God;
using User = MythApi.Common.Database.Models.User;
using MythApi.Common.Database.Models;

namespace MythApi.Common.Database;
Expand All @@ -11,6 +12,7 @@ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<God> Gods { get; set; } = null!;
public DbSet<Mythology> Mythologies { get; set; } = null!;
public DbSet<Alias> Aliases { get; set; } = null!;
public DbSet<User> Users { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder modelBuilder) {
// Map entities to tables
Expand Down
12 changes: 12 additions & 0 deletions src/Common/Database/Models/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

namespace MythApi.Common.Database.Models;

public class User {

public int Id { get; set; }

public string Name { get; set; } = null!;

public string Bio { get; set; } = null!;

}
21 changes: 13 additions & 8 deletions src/Endpoints/v1/Gods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@
namespace MythApi.Endpoints.v1;
public static class Gods {
public static void RegisterGodEndpoints(this IEndpointRouteBuilder endpoints) {

var gods = endpoints.MapGroup("/api/v1/gods");


gods.MapGet("", GetAlllGods);
gods.MapGet("{id}", (int id, IGodRepository repository) => repository.GetGodAsync(new GodParameter(id)));
gods.MapGet("search/{name}", (string name, IGodRepository repository, [FromQuery] bool includeAliases = false) => repository.GetGodByNameAsync(new GodByNameParameter(name, includeAliases)));
gods.MapPost("", AddOrUpdateGods);
gods.MapGet("", GetAllGodsHandler);
gods.MapGet("{id}", GetGodByIdHandler);
gods.MapGet("search/{name}", SearchGodsByNameHandler);
gods.MapPost("", AddOrUpdateGodsHandler);
}

public static Task<List<God>> AddOrUpdateGods(List<GodInput> gods, IGodRepository repository) => repository.AddOrUpdateGods(gods);
private static Task<IList<God>> GetAllGodsHandler(IGodRepository repository) => repository.GetAllGodsAsync();

private static Task<God?> GetGodByIdHandler(int id, IGodRepository repository) => repository.GetGodAsync(new GodParameter(id));

Check warning on line 19 in src/Endpoints/v1/Gods.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Nullability of reference types in value of type 'Task<God>' doesn't match target type 'Task<God?>'.

Check warning on line 19 in src/Endpoints/v1/Gods.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Nullability of reference types in value of type 'Task<God>' doesn't match target type 'Task<God?>'.

private static Task<IList<God>> SearchGodsByNameHandler(string name, IGodRepository repository, [FromQuery] bool includeAliases = false) =>
repository.GetGodByNameAsync(new GodByNameParameter(name, includeAliases))
.ContinueWith(task => (IList<God>)task.Result);

public static Task<IList<God>> GetAlllGods(IGodRepository repository) => repository.GetAllGodsAsync();
private static Task<List<God>> AddOrUpdateGodsHandler(List<GodInput> gods, IGodRepository repository) =>
repository.AddOrUpdateGods(gods);
}
19 changes: 19 additions & 0 deletions src/Endpoints/v1/Users.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

using System.Text.Encodings.Web;
using MythApi.Common.Database.Models;
using MythApi.Users.Interfaces;
using MythApi.Users.Models;

namespace MythApi.Endpoints.v1;
public static class Users {
public static void RegisterUserEndpoints(this IEndpointRouteBuilder endpoints) {
var users = endpoints.MapGroup("/api/v1/users");

users.MapPost("", AddOrUpdateUserInformation);
}
public static Task<User> AddOrUpdateUserInformation(UserInput user, IUserRepository repository) {
user.Name = HtmlEncoder.Default.Encode(user.Name);
user.Bio = HtmlEncoder.Default.Encode(user.Bio);
return repository.AddOrUpdateUserInformation(user);
}
}
38 changes: 38 additions & 0 deletions src/Users/DbRepositories/UserRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

using Microsoft.EntityFrameworkCore;
using MythApi.Common.Database;
using MythApi.Common.Database.Models;
using MythApi.Users.Interfaces;
using MythApi.Users.Models;

namespace MythApi.Users.DbRepositories;

public class UserRepository : IUserRepository
{

private readonly AppDbContext _context;
public UserRepository(AppDbContext context)
{
_context = context;
}
public Task<User> AddOrUpdateUserInformation(UserInput user)
{
if (user.Id.HasValue && _context.Users.Any(x => x.Id == user.Id))
{
_context.Users.FromSqlRaw($"UPDATE Users SET Name = '{user.Name}', Bio = '{user.Bio}' WHERE Id = {user.Id}");

Check warning on line 22 in src/Users/DbRepositories/UserRepository.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Method 'FromSqlRaw' inserts interpolated strings directly into the SQL, without any protection against SQL injection. Consider using 'FromSql' instead, which protects against SQL injection, or make sure that the value is sanitized and suppress the warning.
Copy link

Copilot AI Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of FromSqlRaw with string interpolation can lead to SQL injection vulnerabilities. Use parameterized queries or Entity Framework's built-in methods to avoid this risk.

Suggested change
_context.Users.FromSqlRaw($"UPDATE Users SET Name = '{user.Name}', Bio = '{user.Bio}' WHERE Id = {user.Id}");
var existingUser = _context.Users.First(x => x.Id == user.Id); existingUser.Name = user.Name; existingUser.Bio = user.Bio;

Copilot uses AI. Check for mistakes.
}
else
{
var newUser = new User
{
Name = user.Name,
Bio = user.Bio
};
_context.Users.FromSqlRaw($"INSERT INTO Users (Name, Bio) VALUES ('{user.Name}', '{user.Bio}')");

Check warning on line 31 in src/Users/DbRepositories/UserRepository.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Method 'FromSqlRaw' inserts interpolated strings directly into the SQL, without any protection against SQL injection. Consider using 'FromSql' instead, which protects against SQL injection, or make sure that the value is sanitized and suppress the warning.
Copy link

Copilot AI Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of FromSqlRaw with string interpolation can lead to SQL injection vulnerabilities. Use parameterized queries or Entity Framework's built-in methods to avoid this risk.

Suggested change
_context.Users.FromSqlRaw($"INSERT INTO Users (Name, Bio) VALUES ('{user.Name}', '{user.Bio}')");
_context.Users.Add(newUser);

Copilot uses AI. Check for mistakes.
}

_context.SaveChangesAsync();

return Task.FromResult(_context.Users.First(x => x.Name == user.Name));
}
}
10 changes: 10 additions & 0 deletions src/Users/Interfaces/IUserRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using MythApi.Common.Database.Models;
using MythApi.Users.Models;

namespace MythApi.Users.Interfaces
{
public interface IUserRepository
{
Task<User> AddOrUpdateUserInformation(UserInput userInput);
}
}
10 changes: 10 additions & 0 deletions src/Users/Models/UserInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

namespace MythApi.Users.Models;
public class UserInput {
public int? Id { get; set; }

public string Name { get; set; } = null!;

public string Bio { get; set; } = null!;

}
57 changes: 57 additions & 0 deletions tests/UnitTests/UsersTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// FILE: src/Endpoints/v1/UsersTest.cs
using NUnit.Framework;
using Moq;
using MythApi.Users.Interfaces;
using MythApi.Users.Models;
using MythApi.Endpoints.v1;
using System.Threading.Tasks;
using MythApi.Common.Database.Models;
using System.Text.Encodings.Web;

namespace MythApi.Tests.Endpoints.v1
{
[TestFixture]
public class UsersTest
{
private Mock<IUserRepository> _userRepositoryMock;

[SetUp]
public void SetUp()
{
_userRepositoryMock = new Mock<IUserRepository>();
}

[Test]
public async Task AddOrUpdateUserInformation_CallsRepositoryMethod()
{
// Arrange
var userInput = new UserInput { Name = "Test User", Bio = "test@example.com" };
var user = new User { Id = 1, Name = "Test User", Bio = "test@example.com" };
_userRepositoryMock.Setup(repo => repo.AddOrUpdateUserInformation(userInput)).ReturnsAsync(user);

// Act
var result = await MythApi.Endpoints.v1.Users.AddOrUpdateUserInformation(userInput, _userRepositoryMock.Object);

// Assert
Assert.AreEqual(user, result);

Check warning on line 36 in tests/UnitTests/UsersTest.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Consider using the constraint model, Assert.That(actual, Is.EqualTo(expected)), instead of the classic model, Assert.AreEqual(expected, actual) (https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2005.md)
_userRepositoryMock.Verify(repo => repo.AddOrUpdateUserInformation(userInput), Times.Once);
}

[Test]
public async Task AddOrUpdateUserInformation_EncodesInputToPreventXSS()
{
// Arrange
var userInput = new UserInput { Name = "<script>alert('xss')</script>", Bio = "test@example.com" };
var encodedName = HtmlEncoder.Default.Encode(userInput.Name);
var user = new User { Id = 1, Name = encodedName, Bio = "test@example.com" };
_userRepositoryMock.Setup(repo => repo.AddOrUpdateUserInformation(It.Is<UserInput>(u => u.Name == encodedName))).ReturnsAsync(user);

// Act
var result = await MythApi.Endpoints.v1.Users.AddOrUpdateUserInformation(userInput, _userRepositoryMock.Object);

// Assert
_userRepositoryMock.Verify(repo => repo.AddOrUpdateUserInformation(It.Is<UserInput>(u => u.Name == encodedName)), Times.Once);
Assert.AreEqual(encodedName, result.Name);

Check warning on line 54 in tests/UnitTests/UsersTest.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Consider using the constraint model, Assert.That(actual, Is.EqualTo(expected)), instead of the classic model, Assert.AreEqual(expected, actual) (https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit2005.md)
}
}
}
Loading