first commit

This commit is contained in:
2026-05-10 03:16:55 +03:00
commit 4c82631733
27 changed files with 892 additions and 0 deletions
+30
View File
@@ -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/**
+4
View File
@@ -0,0 +1,4 @@
POSTGRES_USER=app
POSTGRES_PASSWORD=app
POSTGRES_DB=appdb
DB_HOST=db
+85
View File
@@ -0,0 +1,85 @@
# Build results
[Dd]ebug/
[Rr]elease/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
publish/
# Visual Studio
.vs/
*.user
*.suo
*.userosscache
*.sln.docstates
# Rider / JetBrains
.idea/
*.sln.iml
# VS Code
.vscode/
# ASP.NET Core
appsettings.Development.json
appsettings.Local.json
# Environment variables
.env
#.env.*
# Logs
logs/
*.log
# NuGet
*.nupkg
packages/
!packages/build/
# Entity Framework
*.db
*.db-shm
*.db-wal
# SQLite
*.sqlite
*.sqlite3
# OS files
.DS_Store
Thumbs.db
# Node modules
node_modules/
# Coverage / tests
coverage/
TestResults/
# Docker
docker-compose.override.yml
# Generated files
*.cache
*.tmp
*.temp
# Publish output
wwwroot/dist/
wwwroot/build/
# Secrets
secrets.json
# Certificates
*.pfx
*.pem
*.key
# Resharper
_ReSharper*/
*.DotSettings.user
+29
View File
@@ -0,0 +1,29 @@
# См. статью по ссылке https://aka.ms/customizecontainer, чтобы узнать как настроить контейнер отладки и как Visual Studio использует этот Dockerfile для создания образов для ускорения отладки.
# Этот этап используется при запуске из VS в быстром режиме (по умолчанию для конфигурации отладки)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# Этот этап используется для сборки проекта службы
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["URL-Shortener/URL-Shortener.csproj", "URL-Shortener/"]
RUN dotnet restore "./URL-Shortener/URL-Shortener.csproj"
COPY . .
WORKDIR "/src/URL-Shortener"
RUN dotnet build "./URL-Shortener.csproj" -c $BUILD_CONFIGURATION -o /app/build
# Этот этап используется для публикации проекта службы, который будет скопирован на последний этап
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./URL-Shortener.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Этот этап используется в рабочей среде или при запуске из VS в обычном режиме (по умолчанию, когда конфигурация отладки не используется)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "URL-Shortener.dll"]
+1
View File
@@ -0,0 +1 @@
# URL Shortener
+3
View File
@@ -0,0 +1,3 @@
<Solution>
<Project Path="URL-Shortener/URL-Shortener.csproj" />
</Solution>
+12
View File
@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;
using URL_Shortener.Models;
namespace URL_Shortener
{
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<ShortUrl> ShortUrls => Set<ShortUrl>();
public DbSet<ClickInfo> ClickInfos => Set<ClickInfo>();
public DbSet<User> Users => Set<User>();
}
}
@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using URL_Shortener.Models;
using URL_Shortener.DTO;
namespace URL_Shortener.Controllers
{
[ApiController]
[Route("clickInfo")]
public class ClickInfoController(AppDbContext db) : ControllerBase
{
private readonly AppDbContext _db = db;
[HttpGet("{id:int}", Name = nameof(GetClickInfoByIdAsync))]
public async Task<IActionResult> GetClickInfoByIdAsync(int id)
{
var clickInfo = await _db.ClickInfos.FindAsync(id);
if (clickInfo == null)
return NotFound();
return Ok(clickInfo);
}
[HttpGet("by-short-url/{shortUrlId:int}")]
public async Task<IActionResult> GetByShortUrlIdAsync(int shortUrlId)
{
var clickInfos = await _db.ClickInfos.Where(c => c.ShortUrlId == shortUrlId).ToListAsync();
if (clickInfos.Count == 0)
return NotFound();
return Ok(clickInfos);
}
}
}
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using URL_Shortener.Models;
namespace URL_Shortener.Controllers
{
[ApiController]
[Route("/go")]
public class RedirectController(AppDbContext db) : ControllerBase
{
private readonly AppDbContext _db = db;
[HttpGet("{shortCode}")]
public async Task<IActionResult> RedirectToOriginalAsync(string shortCode)
{
var shortUrl = await _db.ShortUrls.FirstOrDefaultAsync(s => s.ShortCode == shortCode);
if (shortUrl == null)
return NotFound("Short URL not found");
if (shortUrl.ExpirationDateTime.HasValue && shortUrl.ExpirationDateTime.Value < DateTime.UtcNow ||
shortUrl.ClickLimit > 0 && shortUrl.TotalClickCount >= shortUrl.ClickLimit)
return BadRequest("Short URL has expired");
var clickInfo = new ClickInfo
{
ShortUrlId = shortUrl.Id,
ClickDateTime = DateTime.UtcNow
};
shortUrl.TotalClickCount++;
_db.ClickInfos.Add(clickInfo);
await _db.SaveChangesAsync();
return Redirect(shortUrl.OriginalUrl);
}
}
}
@@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using URL_Shortener.DTO;
using URL_Shortener.Models;
using HashidsNet;
namespace URL_Shortener.Controllers
{
[ApiController]
[Route("shortUrl")]
public class ShortUrlController(AppDbContext db, Hashids hashids) : ControllerBase
{
private readonly AppDbContext _db = db;
private readonly Hashids _hashids = hashids;
[HttpGet("all")]
public async Task<IActionResult> GetAll() =>
Ok(await _db.ShortUrls.ToListAsync());
[HttpGet("{id:int}", Name = nameof(GetShortUrlByIdAsync))]
public async Task<IActionResult> GetShortUrlByIdAsync(int id)
{
var shortUrl = await _db.ShortUrls.FindAsync(id);
if (shortUrl == null)
return NotFound();
return Ok(shortUrl);
}
[HttpGet("by-user-id/{userId:int}")]
public async Task<IActionResult> GetByUserIdAsync(int userId)
{
var shortUrls = await _db.ShortUrls.Where(s => s.UserId == userId).ToListAsync();
if (shortUrls.Count == 0)
return NotFound();
return Ok(shortUrls);
}
[HttpPost]
public async Task<IActionResult> CreateShortUrlAsync(CreateShortUrlDto dto)
{
var shortUrl = new ShortUrl
{
OriginalUrl = dto.OriginalUrl,
ExpirationDateTime = dto.ExpirationDateTime,
ClickLimit = dto.ClickLimit
};
await _db.ShortUrls.AddAsync(shortUrl);
await _db.SaveChangesAsync();
shortUrl.ShortCode = _hashids.Encode(shortUrl.Id);
await _db.SaveChangesAsync();
return CreatedAtRoute(nameof(GetShortUrlByIdAsync), new { id = shortUrl.Id }, shortUrl);
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteShortUrlByIdAsync(int id)
{
var shortUrl = await _db.ShortUrls.FindAsync(id);
if (shortUrl == null)
return NotFound();
_db.ShortUrls.Remove(shortUrl);
var clickInfos = _db.ClickInfos.Where(c => c.ShortUrlId == id);
if (clickInfos != null)
_db.ClickInfos.RemoveRange(clickInfos);
await _db.SaveChangesAsync();
return NoContent();
}
}
}
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using URL_Shortener.Models;
using URL_Shortener.DTO;
namespace URL_Shortener.Controllers
{
[ApiController]
[Route("user")]
public class UserController(AppDbContext db) : ControllerBase
{
private readonly AppDbContext _db = db;
[HttpGet("email")]
public async Task<IActionResult> GetUserByEmailAsync(string email)
{
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email);
if (user == null)
return NotFound("User not found");
return Ok(user);
}
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace URL_Shortener.DTO
{
public class CreateShortUrlDto
{
[Required]
[Url]
public string OriginalUrl { get; set; } = string.Empty;
public DateTime? ExpirationDateTime { get; set; } = null;
public int ClickLimit { get; set; } = 0;
}
}
+29
View File
@@ -0,0 +1,29 @@
# См. статью по ссылке https://aka.ms/customizecontainer, чтобы узнать как настроить контейнер отладки и как Visual Studio использует этот Dockerfile для создания образов для ускорения отладки.
# Этот этап используется при запуске из VS в быстром режиме (по умолчанию для конфигурации отладки)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# Этот этап используется для сборки проекта службы
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["URL-Shortener/URL-Shortener.csproj", "URL-Shortener/"]
RUN dotnet restore "./URL-Shortener/URL-Shortener.csproj"
COPY . .
WORKDIR "/src/URL-Shortener"
RUN dotnet build "./URL-Shortener.csproj" -c $BUILD_CONFIGURATION -o /app/build
# Этот этап используется для публикации проекта службы, который будет скопирован на последний этап
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./URL-Shortener.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Этот этап используется в рабочей среде или при запуске из VS в обычном режиме (по умолчанию, когда конфигурация отладки не используется)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "URL-Shortener.dll"]
@@ -0,0 +1,116 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using URL_Shortener;
#nullable disable
namespace URL_Shortener.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260510000847_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("URL_Shortener.Models.ClickInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ClickDateTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("ShortUrlId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("ClickInfos");
});
modelBuilder.Entity("URL_Shortener.Models.ShortUrl", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ClickLimit")
.HasColumnType("integer");
b.Property<DateTime>("CreationDateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationDateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("OriginalUrl")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ShortCode")
.IsRequired()
.HasColumnType("text");
b.Property<int>("TotalClickCount")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("ShortUrls");
});
modelBuilder.Entity("URL_Shortener.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("HashedPassword")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("LastLoginDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("RegisterDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,79 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace URL_Shortener.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ClickInfos",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ShortUrlId = table.Column<int>(type: "integer", nullable: false),
ClickDateTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClickInfos", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ShortUrls",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
OriginalUrl = table.Column<string>(type: "text", nullable: false),
ShortCode = table.Column<string>(type: "text", nullable: false),
TotalClickCount = table.Column<int>(type: "integer", nullable: false),
CreationDateTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExpirationDateTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
ClickLimit = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ShortUrls", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
Email = table.Column<string>(type: "text", nullable: false),
HashedPassword = table.Column<string>(type: "text", nullable: false),
RegisterDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
LastLoginDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ClickInfos");
migrationBuilder.DropTable(
name: "ShortUrls");
migrationBuilder.DropTable(
name: "Users");
}
}
}
@@ -0,0 +1,113 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using URL_Shortener;
#nullable disable
namespace URL_Shortener.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("URL_Shortener.Models.ClickInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ClickDateTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("ShortUrlId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("ClickInfos");
});
modelBuilder.Entity("URL_Shortener.Models.ShortUrl", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ClickLimit")
.HasColumnType("integer");
b.Property<DateTime>("CreationDateTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationDateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("OriginalUrl")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ShortCode")
.IsRequired()
.HasColumnType("text");
b.Property<int>("TotalClickCount")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("ShortUrls");
});
modelBuilder.Entity("URL_Shortener.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("HashedPassword")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("LastLoginDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("RegisterDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace URL_Shortener.Models
{
public class ClickInfo
{
public int Id { get; set; }
public int ShortUrlId { get; set; }
public DateTime ClickDateTime { get; set; } = DateTime.UtcNow;
}
}
+14
View File
@@ -0,0 +1,14 @@
namespace URL_Shortener.Models
{
public class ShortUrl
{
public int Id { get; set; }
public int UserId { get; set; }
public string OriginalUrl { get; set; } = string.Empty;
public string ShortCode { get; set; } = string.Empty;
public int TotalClickCount { get; set; }
public DateTime CreationDateTime { get; set; } = DateTime.UtcNow;
public DateTime? ExpirationDateTime { get; set; } = null;
public int ClickLimit { get; set; } = 0;
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace URL_Shortener.Models
{
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string HashedPassword { get; set; } = string.Empty;
public DateTime RegisterDate { get; set; }
public DateTime LastLoginDate { get; set; }
}
}
+47
View File
@@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore;
using URL_Shortener;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Чтобы короткие коды генерировать
builder.Services.AddSingleton<HashidsNet.Hashids>();
builder.Services.AddDbContext<AppDbContext>(options =>
{
var connString = builder.Configuration.GetConnectionString("DefaultConnection");
options.UseNpgsql(connString, o => o.EnableRetryOnFailure());
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseSwagger();
app.UseSwaggerUI();
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.Migrate();
}
//app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
@@ -0,0 +1,31 @@
{
"profiles": {
"http": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5212"
},
"https": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7180;http://localhost:5212"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}
+10
View File
@@ -0,0 +1,10 @@
namespace URL_Shortener.Services
{
public class UserService
{
public async Task Register(string userName, string email, string hashedPassword)
{
throw new NotImplementedException();
}
}
}
+29
View File
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>URL_Shortener</RootNamespace>
<UserSecretsId>8eedc3e1-0d87-47c1-9bd3-32550f156e73</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hashids.net" Version="1.7.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
</ItemGroup>
</Project>
+6
View File
@@ -0,0 +1,6 @@
@URL_Shortener_HostAddress = http://localhost:5212
GET {{URL_Shortener_HostAddress}}/weatherforecast/
Accept: application/json
###
+12
View File
@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Host=db;Database=appdb;Username=app;Password=app"
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.7",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
+24
View File
@@ -0,0 +1,24 @@
services:
api:
build: .
ports:
- "5000:8080"
depends_on:
- db
environment:
ConnectionStrings__DefaultConnection: Host=${DB_HOST};Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}
db:
image: postgres:16
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data: