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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

/src/shortenurl.unit.test/bin/Debug/netcoreapp2.1
/src/shortenurl.unit.test/obj
/src/Shortenurl.Model/bin/Debug/netstandard2.0
/src/Shortenurl.Model/obj
/src/ShortenUrl.API/obj
/src/ShortenUrl.API/bin/Debug/netcoreapp2.1
/src/.vs
20 changes: 20 additions & 0 deletions Install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Installation Guide for Shorten Urls Service

### Required Software

- MS SQL 2012+
- Visual Studio 2017 , you can download it from [Here](https://pages.github.com/).

### Install Steps

- Download Code from GitHub
- Create a DB on SQL and run the script src/Shorten.DB/DDL/CREATE SCHEMA AND SPs.sql on it
- Open the solution src/ShortenUrl.sln
- Change the "DefaultConnection" value with your DB details on shortenurl.api/appsettings.json file
- Run shortenurl.api project


### Endpoints
Base Url: https://localhost:[Port]/api/ShortenUrl


64 changes: 64 additions & 0 deletions src/Shorten.DB/DDL/CREATE SCHEMA AND SPs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

CREATE TABLE ShortUrl
(
ShortUrlId int primary key identity (1,1) not null,
Code varchar(6) COLLATE Latin1_General_CS_AS unique not null,
OriginalUrl nvarchar(2083) not null,
CreatedAt Datetime not null,
LastUsage Datetime,
UsageCount int
)

GO

CREATE PROCEDURE uspGetShortUrl
(
@Code varchar(7)
)
AS

BEGIN

Select ShortUrlId, Code, OriginalUrl, CreatedAt, LastUsage, UsageCount
FROM ShortUrl
where Code = @Code

END

GO


CREATE PROCEDURE uspInsertShortUrl
(
@Code varchar(6),
@OriginalUrl nvarchar(2083)
)
AS
BEGIN

Insert into ShortUrl (Code, OriginalUrl, CreatedAt, UsageCount) values (@Code, @OriginalUrl, GETUTCDATE(), 0)

END

GO


CREATE PROCEDURE uspUpdateUsage
(
@ShortUrlId int
)
AS

BEGIN

Update ShortUrl set LastUsage = GETUTCDATE(), UsageCount = UsageCount + 1
where ShortUrlId = @ShortUrlId

END







42 changes: 42 additions & 0 deletions src/ShortenUrl.API/Controllers/ShortenUrlController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using shortenurl.model.DTOs;
using shortenurl.model.Interfaces.Services;
using shortenurl.model.ViewModels;

namespace shortenurl.api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ShortenUrlController : ControllerBase
{

[HttpPost]
[Route("urls")]
public async Task<ActionResult<ShortUrlCreatedViewModel>> CreateShortenUrl(ShortenUrlDTO shortenUrlDTO,[FromServices]IShortenUrlService shortenUrlService)
{

var code = await shortenUrlService.CreateShortenUrl(shortenUrlDTO);
return Created(string.Empty,code);
}

[HttpGet("{code}")]
public async Task<IActionResult> Get(string code, [FromServices]IShortenUrlService shortenUrlService)
{
var location = await shortenUrlService.GetUrlByCode(code);
return Redirect(location);
}


[HttpGet("{code}/stats")]
public async Task<ActionResult<UrlStatsViewModel>> GetCodeStats(string code, [FromServices]IShortenUrlService shortenUrlService)
{
var result = await shortenUrlService.GetStatsByCode(code);
return Ok(result);
}

}
}
39 changes: 39 additions & 0 deletions src/ShortenUrl.API/Filters/CustomExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using shortenurl.model.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace shortenurl.api.Filters
{
public class CustomExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var msg = context.Exception.GetBaseException().Message;
string stack = context.Exception.StackTrace;

switch (context.Exception.GetType().Name)
{
case "NotFoundException":
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
break;
case "UnprocessableEntityException":
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity;
break;
case "ConflictException":
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
break;
default:
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}

context.Result = new JsonResult(msg);
base.OnException(context);
}
}
}
24 changes: 24 additions & 0 deletions src/ShortenUrl.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace shortenurl.api
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
30 changes: 30 additions & 0 deletions src/ShortenUrl.API/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8497",
"sslPort": 44314
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"shortenurl.api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
70 changes: 70 additions & 0 deletions src/ShortenUrl.API/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using shortenurl.api.Filters;
using shortenurl.model;
using shortenurl.model.Interfaces;
using shortenurl.model.Interfaces.Repositories;
using shortenurl.model.Interfaces.Services;
using shortenurl.model.Repositories;
using shortenurl.model.Services;
using shortenurl.model.Settings;

namespace shortenurl.api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(
options =>
options.Filters.Add(new CustomExceptionFilter())
)
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options =>
{
//Set date configurations
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

});


var connectionStringsSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionStringsSection);

services.AddSingleton<IShortenUrlService, ShortenUrlService>();
services.AddSingleton<ISqlDataAccess, SqlDataAccess>();
services.AddSingleton<IShortenUrlRepository, ShortenUrlRepository>();
services.AddSingleton<ICodeService, CodeService>();

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseMvc();
}
}
}
9 changes: 9 additions & 0 deletions src/ShortenUrl.API/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
11 changes: 11 additions & 0 deletions src/ShortenUrl.API/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=SHORTEN_URL2;Integrated Security=True;"
}
}
20 changes: 20 additions & 0 deletions src/ShortenUrl.API/shortenurl.api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\shortenurl.model\shortenurl.model.csproj" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions src/ShortenUrl.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.489
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "shortenurl.api", "ShortenUrl.API\shortenurl.api.csproj", "{DBAF0C7F-4D0A-4267-A5D6-32BB25C27E9E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "shortenurl.model", "shortenurl.model\shortenurl.model.csproj", "{FA20AC8E-8930-4BA3-86FE-A4BEAECA283B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "shortenurl.unit.test", "shortenurl.unit.test\shortenurl.unit.test.csproj", "{C460CBD3-D275-4A5C-8DA4-50DB84E83994}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DBAF0C7F-4D0A-4267-A5D6-32BB25C27E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBAF0C7F-4D0A-4267-A5D6-32BB25C27E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBAF0C7F-4D0A-4267-A5D6-32BB25C27E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBAF0C7F-4D0A-4267-A5D6-32BB25C27E9E}.Release|Any CPU.Build.0 = Release|Any CPU
{FA20AC8E-8930-4BA3-86FE-A4BEAECA283B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA20AC8E-8930-4BA3-86FE-A4BEAECA283B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA20AC8E-8930-4BA3-86FE-A4BEAECA283B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA20AC8E-8930-4BA3-86FE-A4BEAECA283B}.Release|Any CPU.Build.0 = Release|Any CPU
{C460CBD3-D275-4A5C-8DA4-50DB84E83994}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C460CBD3-D275-4A5C-8DA4-50DB84E83994}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C460CBD3-D275-4A5C-8DA4-50DB84E83994}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C460CBD3-D275-4A5C-8DA4-50DB84E83994}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {258CE533-A4D3-4752-89F1-7CBD40C131FB}
EndGlobalSection
EndGlobal
Loading