Skip to content

oznakdn/Practical-GraphQL

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hot Chocolate documentation
Examples documentation
Example documentation

Nuget Package

HotChocolate.AspNetCore
HotChocolate.AspNetCore.Authorization
Microsoft.AspNetCore.Authentication.JwtBearer
HotChocolate.Data.EntityFramework
Microsoft.EntityFrameworkCore.InMemory

Program.cs

builder.Services.AddScoped<TokenHelper>();

builder.Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("ApiDb"));


builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidIssuer = "http://localhost:5054",
                        ValidAudience = "http://localhost:5054",
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("c082a53a-938b-4504-8cff-def4667e8854"))
                    };
                });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("HasRole", policy =>
    policy.RequireAssertion(context =>
        context.User.HasClaim(c => c.Type == ClaimTypes.Role)));
});

builder.Services.AddGraphQLServer()
                .AddAuthorization()
                .AddProjections()
                .AddFiltering()
                .AddSorting()
                .AddQueryType<Query>()
                .AddMutationType<Mutation>()
                .AddSubscriptionType<Subscription>();


var app = builder.Build();

app.SeedData();

app.UseAuthentication();

app.UseAuthorization();

app.UseWebSockets();

app.MapGraphQL();

app.Run();

Models

public class Project
{
    public string Id { get; set; }
    public string Name { get; set; }

    public ICollection<Member> Members { get; set; } = new HashSet<Member>();
}
public class Member
{
    
    public string Id { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Title { get; set; }

    public string ProjectId { get; set; }
    public Project? Project { get; set; }
}
public class User
{
    public string Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string Username { get; set; }   

    public string? RoleId { get; set; }
    public Role? Role { get; set; }

}
public class Role
{
    public string Id { get; set; }
    public string Name { get; set; }

    public ICollection<User> Users { get; set; }  = new HashSet<User>();
}

Context

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext>options) : base(options)
    {
        
    }

    public DbSet<Project>Projects { get; set; }
    public DbSet<Member> Members { get; set; }
    public DbSet<User> Users { get; set; }
    public DbSet<Role>Roles { get; set; }

}

Seeding Data by Bogus

public static class DataSeeding
{
    public static void SeedData(this WebApplication app)
    {
        var scope = app.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();


        // Seeding project
        string projectId = Guid.NewGuid().ToString();
        var fakerProject = new Faker<Project>()
        .RuleFor(x => x.Name, f => f.Company.Bs())
        .RuleFor(x => x.Id, f => projectId);

        Project project = fakerProject.Generate();
        db.Projects.Add(project);


        // Seeding member
        var fakerMember = new Faker<Member>()
            .RuleFor(x => x.ProjectId, f => projectId)
            .RuleFor(x => x.Name, f => f.Name.FirstName())
            .RuleFor(x => x.LastName, f => f.Name.LastName())
            .RuleFor(x => x.Title, f => f.Name.JobTitle())
            .RuleFor(x => x.Id, f => Guid.NewGuid().ToString());


        List<Member> members = fakerMember.Generate(20);
        db.Members.AddRange(members);


        // Seeding role
        string adminId = Guid.NewGuid().ToString();
        var fakeRoleAdmin = new Faker<Role>()
            .RuleFor(x => x.Name, f => "ADMIN")
            .RuleFor(x => x.Id, f => adminId);

        string managerId = Guid.NewGuid().ToString();
        var fakeRoleManager = new Faker<Role>()
            .RuleFor(x => x.Name, f => "MANAGER")
            .RuleFor(x => x.Id, f => managerId);


        var admin = fakeRoleAdmin.Generate();
        var manager = fakeRoleManager.Generate();
        db.Roles.AddRange(admin, manager);

        // Seeding User
        var fakeManager = new Faker<User>()
         .RuleFor(x => x.Id, f => Guid.NewGuid().ToString())
          .RuleFor(x => x.Email, f => f.Person.Email)
          .RuleFor(x => x.Password, f => "123456")
          .RuleFor(x => x.Username, f => f.Person.UserName)
          .RuleFor(x => x.RoleId, f => managerId);

        var user = fakeManager.Generate(1);
        db.Users.AddRange(user);

        var fakeUser = new Faker<User>()
            .RuleFor(x => x.Id, f => Guid.NewGuid().ToString())
            .RuleFor(x => x.Email, f => f.Person.Email)
            .RuleFor(x => x.Password, f => "123456")
            .RuleFor(x => x.Username, f => f.Person.UserName)
            .RuleFor(x => x.RoleId, f => adminId);

        List<User> users = fakeUser.Generate(5);

        db.Users.AddRange(users);

        db.SaveChanges();
    }

Schemas

Query.cs

public class Query
{

    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public async Task<IQueryable<Project>> GetProjectAsync([Service] AppDbContext context)
    {
        return await Task.FromResult(context.Projects);
    }

    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public async Task<IQueryable<Member>> GetMemberAsync([Service] AppDbContext context)
    {
        return await Task.FromResult(context.Members);
    }

    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public async Task<IQueryable<User>> GetUserAsync([Service] AppDbContext context)
    {
        return await Task.FromResult(context.Users);
    }


    [UseProjection]
    [UseFiltering]
    [UseSorting]
    [Authorize("HasRole", Roles = ["MANAGER"])]
    public async Task<IQueryable<Role>> GetRoleAsync([Service] AppDbContext context)
    {
        return await Task.FromResult(context.Roles);
    }


    public async Task<LoginResponse> LoginAsync(LoginInput input, [Service] AppDbContext context, [Service] TokenHelper tokenHelper)
    {
        var user = await context.Users.Include(x=>x.Role).SingleOrDefaultAsync(x => x.Username == input.Username && x.Password == input.Password);

        if (user is null)
        {
            throw new GraphQLException("Username or password is wrong!");
        }

        var token = tokenHelper.GenerateToken(user);

        return new LoginResponse(token.Token, token.TokenExpire, user);
    }
}

Mutation.cs

public class Mutation
{
    public async Task<CreateProjectOutput> CreateProjectAsync(CreateProjectInput input, CancellationToken cancellationToken, [Service] AppDbContext context)
    {
        var project = new Project
        {
            Id = Guid.NewGuid().ToString(),
            Name = input.Name
        };

        context.Add(project);
        await context.SaveChangesAsync(cancellationToken);
        return new CreateProjectOutput(project);
    }

    public async Task<bool> UpdateProjectAsync(UpdateProjectInput input, CancellationToken cancellationToken, [Service] AppDbContext context)
    {
        var project = await context.Projects.SingleOrDefaultAsync(p => p.Id == input.Id);

        if (project is null)
            throw new GraphQLException("Project not found!");

        project.Name = input.Name ?? default!;

        context.Update(project);
        return await context.SaveChangesAsync(cancellationToken) > 0;
    }

    public async Task<bool> RemoveProjectAsync(string id, CancellationToken cancellationToken, [Service] AppDbContext context)
    {
        var project = await context.Projects.SingleOrDefaultAsync(p => p.Id == id);

        if (project is null)
            throw new GraphQLException("Project not found!");

        context.Remove(project);
        return await context.SaveChangesAsync(cancellationToken) > 0;
    }

    // This method is for the Subscription's OnProjectCreated event
    public async Task<CreateProjectOutput> AddProjectWithEventAsync(CreateProjectInput input, [Service] AppDbContext context, [Service] ITopicEventSender eventSender, CancellationToken cancellationToken)
    {
        var project = new Project
        {
            Id = Guid.NewGuid().ToString(),
            Name = input.Name
        };

        context.Add(project);
        await context.SaveChangesAsync(cancellationToken);

        await eventSender.SendAsync(nameof(Subscription.OnProjectCreated), project, cancellationToken);

        return new CreateProjectOutput(project);
    }
}

Subscription.cs

public class Subscription
{
    [Subscribe]
    [Topic]
    public Project OnProjectCreated([EventMessage] Project project)
    {
        return project;
    }
}

Requests and responses

Attention! All the requests must to call by the Http POST method for usage the GraphQL

User-Role requests and responses

Screenshot_1 Screenshot_2

Project requests and responses

Screenshot_1 Screenshot_2 Screenshot_3 Screenshot_4 Screenshot_5 Screenshot_6

Member requests and responses

Screenshot_7 Screenshot_8 Screenshot_9 Screenshot_10