diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..fe1152b
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,30 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
+!**/.gitignore
+!.git/HEAD
+!.git/config
+!.git/packed-refs
+!.git/refs/heads/**
\ No newline at end of file
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
new file mode 100644
index 0000000..d7915f5
--- /dev/null
+++ b/.github/workflows/docker-image.yml
@@ -0,0 +1,41 @@
+name: Publish Docker image
+
+on:
+ release:
+ types: [published]
+ push:
+ branches: [ "master" ]
+
+
+jobs:
+ push_to_registries:
+ name: Push Docker image to multiple registries
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ contents: read
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Log in to the Container registry
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
+ with:
+ images: |
+ ghcr.io/${{ github.repository }}
+
+ - name: Build and push Docker images
+ uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
+ with:
+ context: .
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 0000000..c62b908
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,28 @@
+# This workflow will build a .NET project
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
+
+name: .NET
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+ - name: Restore dependencies
+ run: dotnet restore
+ - name: Build
+ run: dotnet build --no-restore
+ - name: Test
+ run: dotnet test --no-build --verbosity normal
diff --git a/Controllers/v1/SkinsController.cs b/Controllers/v1/SkinsController.cs
new file mode 100644
index 0000000..aae22e1
--- /dev/null
+++ b/Controllers/v1/SkinsController.cs
@@ -0,0 +1,60 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.RateLimiting;
+using SkinsApi.Interfaces.Services;
+
+namespace SkinsApi.Controllers.v1
+{
+ [Route("/api/v1/skin/")]
+ [ApiController]
+
+ public class SkinsController (ISkinService skinService): ControllerBase
+ {
+ ///
+ /// Get user`s skin
+ ///
+ /// nickname or UUID
+ /// Full skin
+ [HttpGet("{user}")]
+ [ProducesResponseType(200)]
+ [Produces("image/png")]
+ public async Task GetSkinByUser(string user, [FromQuery(Name = "w")] int width = 64)
+ {
+
+ return File((await skinService.GetSkinStreamAsync(user)).GetAllSkin(width), "image/png");
+ }
+
+ ///
+ /// Get user`s face
+ ///
+ /// nickname or UUID
+ /// Face
+ [HttpGet("{user}/face")]
+ [ProducesResponseType(200)]
+ [Produces("image/png")]
+ public async Task GetSkinFaceByUser(string user, [FromQuery(Name = "w")] int width = 8)
+ {
+
+ return File((await skinService.GetSkinStreamAsync(user)).GetFace(width), "image/png");
+ }
+
+ ///
+ /// Get user`s front
+ ///
+ /// nickname or UUID
+ /// Face
+ [HttpGet("{user}/front")]
+ [ProducesResponseType(200)]
+ [Produces("image/png")]
+ public async Task GetSkinFrontByUser(string user, [FromQuery(Name = "w")] int width = 128)
+ {
+ try
+ {
+ return File((await skinService.GetSkinStreamAsync(user)).GetBody(width), "image/png");
+ } catch(Exception ex)
+ {
+ return NotFound();
+ }
+
+ }
+ }
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f80e44a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,24 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER app
+WORKDIR /app
+EXPOSE 80
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["SkinsApi.csproj", "."]
+RUN dotnet restore "./SkinsApi.csproj"
+COPY . .
+WORKDIR "/src/."
+RUN dotnet build "./SkinsApi.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./SkinsApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "SkinsApi.dll"]
\ No newline at end of file
diff --git a/Interfaces/Services/ISkinService.cs b/Interfaces/Services/ISkinService.cs
new file mode 100644
index 0000000..c772cf9
--- /dev/null
+++ b/Interfaces/Services/ISkinService.cs
@@ -0,0 +1,9 @@
+using SkinsApi.Sources;
+
+namespace SkinsApi.Interfaces.Services
+{
+ public interface ISkinService
+ {
+ Task GetSkinStreamAsync(string data);
+ }
+}
diff --git a/Interfaces/SkinService/IProfile.cs b/Interfaces/SkinService/IProfile.cs
new file mode 100644
index 0000000..504fd63
--- /dev/null
+++ b/Interfaces/SkinService/IProfile.cs
@@ -0,0 +1,9 @@
+namespace SkinsApi.Interfaces.SkinService
+{
+ public interface IProfile
+ {
+ public string id { get; set; }
+ public string name { get; set; }
+ }
+
+}
diff --git a/Models/DecodedSkinProperty.cs b/Models/DecodedSkinProperty.cs
new file mode 100644
index 0000000..59965c9
--- /dev/null
+++ b/Models/DecodedSkinProperty.cs
@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace SkinsApi.Models
+{
+ public class Metadata
+ {
+ [JsonPropertyName("model")]
+ public string Model { get; set; }
+ }
+
+ public class DecodedSkinProperty
+ {
+ [JsonPropertyName("timestamp")]
+ public long Timestamp { get; set; }
+
+ [JsonPropertyName("profileId")]
+ public string ProfileId { get; set; }
+
+ [JsonPropertyName("profileName")]
+ public string ProfileName { get; set; }
+
+ [JsonPropertyName("textures")]
+ public Textures Textures { get; set; }
+ }
+
+ public class SKIN
+ {
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+
+ [JsonPropertyName("metadata")]
+ public Metadata Metadata { get; set; }
+ }
+
+ public class Textures
+ {
+ [JsonPropertyName("SKIN")]
+ public SKIN SKIN { get; set; }
+ }
+
+
+}
diff --git a/Models/SkinService/MojangProfile.cs b/Models/SkinService/MojangProfile.cs
new file mode 100644
index 0000000..b388783
--- /dev/null
+++ b/Models/SkinService/MojangProfile.cs
@@ -0,0 +1,10 @@
+using SkinsApi.Interfaces.SkinService;
+
+namespace SkinsApi.Models.SkinService
+{
+ public class MojangProfile : IProfile
+ {
+ public string id { get; set; }
+ public string name { get; set; }
+ }
+}
diff --git a/Models/SkinService/MojangSessionProperty.cs b/Models/SkinService/MojangSessionProperty.cs
new file mode 100644
index 0000000..50c8a62
--- /dev/null
+++ b/Models/SkinService/MojangSessionProperty.cs
@@ -0,0 +1,16 @@
+namespace SkinsApi.Models.SkinService
+{
+ public class Property
+ {
+ public string name { get; set; }
+ public string value { get; set; }
+ }
+
+ public class MojangSessionProperty
+ {
+ public string id { get; set; }
+ public string name { get; set; }
+ public List properties { get; set; }
+ public List