Skip to content

Commit 7a3b393

Browse files
committed
V14 Integrations (Semrush)
- Add management api for Semrush
1 parent 4229bf7 commit 7a3b393

25 files changed

+745
-465
lines changed

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ src/Umbraco.Cms.Integrations.Crm.Dynamics/wwwroot/*
1515
src/Umbraco.Cms.Integrations.Crm.ActiveCampaign/wwwroot/*
1616
!src/Umbraco.Cms.Integrations.Crm.ActiveCampaign/wwwroot/umbraco-package.json
1717
src/Umbraco.Cms.Integrations.Automation.Zapier/wwwroot
18-
src/Umbraco.Cms.Integrations.Commerce.Shopify/wwwroot
19-
src/Umbraco.Cms.Integrations.Crm.Hubspot/wwwroot
20-
18+
src/Umbraco.Cms.Integrations.SEO.Semrush/wwwroot/*
19+
!src/Umbraco.Cms.Integrations.SEO.Semrush/wwwroot/umbraco-package.json
2120

2221
# User-specific files
2322
*.suo
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Models;
13+
using Umbraco.Cms.Integrations.SEO.Semrush.Models.Dtos;
14+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
15+
16+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
17+
{
18+
[ApiVersion("1.0")]
19+
[ApiExplorerSettings(GroupName = Constants.ManagementApi.SemrushGroupName)]
20+
public class AuthorizationController : SemrushControllerBase
21+
{
22+
public AuthorizationController(IOptions<SemrushSettings> options, IWebHostEnvironment webHostEnvironment, ISemrushTokenService semrushTokenService, ICacheHelper cacheHelper, TokenBuilder tokenBuilder, SemrushComposer.AuthorizationImplementationFactory authorizationImplementationFactory) : base(options, webHostEnvironment, semrushTokenService, cacheHelper, tokenBuilder, authorizationImplementationFactory)
23+
{
24+
}
25+
26+
[HttpGet("auth")]
27+
[ProducesResponseType(typeof(ContentResult), StatusCodes.Status200OK)]
28+
public IActionResult OAuth(string code)
29+
{
30+
return Ok(new ContentResult
31+
{
32+
Content = string.IsNullOrEmpty(code)
33+
? JavascriptResponse.Fail("Authorization process failed.")
34+
: JavascriptResponse.Ok(code),
35+
ContentType = "text/html"
36+
});
37+
}
38+
}
39+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
13+
14+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
15+
{
16+
[ApiVersion("1.0")]
17+
[ApiExplorerSettings(GroupName = Constants.ManagementApi.SemrushGroupName)]
18+
public class GetAuthorizationUrlController : SemrushControllerBase
19+
{
20+
public GetAuthorizationUrlController(IOptions<SemrushSettings> options, IWebHostEnvironment webHostEnvironment, ISemrushTokenService semrushTokenService, ICacheHelper cacheHelper, TokenBuilder tokenBuilder, SemrushComposer.AuthorizationImplementationFactory authorizationImplementationFactory) : base(options, webHostEnvironment, semrushTokenService, cacheHelper, tokenBuilder, authorizationImplementationFactory)
21+
{
22+
}
23+
24+
[HttpGet("auth-url")]
25+
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
26+
public IActionResult GetAuthorizationUrl() => Ok(_authorizationService.GetAuthorizationUrl());
27+
}
28+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Text.Json;
11+
using System.Threading.Tasks;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
13+
using Umbraco.Cms.Integrations.SEO.Semrush.Models.Dtos;
14+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
15+
16+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
17+
{
18+
[ApiVersion("1.0")]
19+
[ApiExplorerSettings(GroupName = Constants.ManagementApi.SemrushGroupName)]
20+
public class GetColumnsController : SemrushControllerBase
21+
{
22+
public GetColumnsController(IOptions<SemrushSettings> options, IWebHostEnvironment webHostEnvironment, ISemrushTokenService semrushTokenService, ICacheHelper cacheHelper, TokenBuilder tokenBuilder, SemrushComposer.AuthorizationImplementationFactory authorizationImplementationFactory) : base(options, webHostEnvironment, semrushTokenService, cacheHelper, tokenBuilder, authorizationImplementationFactory)
23+
{
24+
}
25+
26+
[HttpGet("columns")]
27+
[ProducesResponseType(typeof(IEnumerable<ColumnDto>), StatusCodes.Status200OK)]
28+
public IActionResult GetColumns()
29+
{
30+
string semrushColumnsPath = $"{_webHostEnvironment.ContentRootPath}/App_Plugins/UmbracoCms.Integrations/SEO/Semrush/semrushColumns.json";
31+
32+
_lock.EnterReadLock();
33+
34+
try
35+
{
36+
if (!System.IO.File.Exists(semrushColumnsPath))
37+
{
38+
var fs = System.IO.File.Create(semrushColumnsPath);
39+
fs.Close();
40+
41+
return Ok(Enumerable.Empty<ColumnDto>());
42+
}
43+
44+
var content = System.IO.File.ReadAllText(semrushColumnsPath);
45+
var deserializeContent = JsonSerializer.Deserialize<IEnumerable<ColumnDto>>(content).Select(p =>
46+
new ColumnDto
47+
{
48+
Name = p.Name,
49+
Value = p.Value,
50+
Description = p.Description
51+
});
52+
53+
return Ok(deserializeContent);
54+
55+
}
56+
catch (FileNotFoundException ex)
57+
{
58+
return Ok(Enumerable.Empty<ColumnDto>());
59+
}
60+
finally
61+
{
62+
_lock.ExitReadLock();
63+
}
64+
}
65+
}
66+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using Newtonsoft.Json;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
13+
using Umbraco.Cms.Integrations.SEO.Semrush.Models.Dtos;
14+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
15+
16+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
17+
{
18+
[ApiVersion("1.0")]
19+
[ApiExplorerSettings(GroupName = Constants.ManagementApi.SemrushGroupName)]
20+
public class GetDataSourcesController : SemrushControllerBase
21+
{
22+
public GetDataSourcesController(IOptions<SemrushSettings> options, IWebHostEnvironment webHostEnvironment, ISemrushTokenService semrushTokenService, ICacheHelper cacheHelper, TokenBuilder tokenBuilder, SemrushComposer.AuthorizationImplementationFactory authorizationImplementationFactory) : base(options, webHostEnvironment, semrushTokenService, cacheHelper, tokenBuilder, authorizationImplementationFactory)
23+
{
24+
}
25+
26+
[HttpGet("datasources")]
27+
[ProducesResponseType(typeof(DataSourceDto), StatusCodes.Status200OK)]
28+
public IActionResult GetDataSources()
29+
{
30+
string semrushDataSourcesPath = $"{_webHostEnvironment.ContentRootPath}/App_Plugins/UmbracoCms.Integrations/SEO/Semrush/semrushDataSources.json";
31+
32+
_lock.EnterReadLock();
33+
34+
try
35+
{
36+
if (!System.IO.File.Exists(semrushDataSourcesPath))
37+
{
38+
var fs = System.IO.File.Create(semrushDataSourcesPath);
39+
fs.Close();
40+
41+
return Ok(new DataSourceDto());
42+
}
43+
44+
var content = System.IO.File.ReadAllText(semrushDataSourcesPath);
45+
var dataSourceDto = new DataSourceDto
46+
{
47+
Items = JsonConvert.DeserializeObject<List<DataSourceItemDto>>(content).Select(p =>
48+
new DataSourceItemDto
49+
{
50+
Code = p.Code,
51+
Region = p.Region,
52+
ResearchTypes = p.ResearchTypes,
53+
GoogleSearchDomain = p.GoogleSearchDomain
54+
})
55+
};
56+
57+
return Ok(dataSourceDto);
58+
}
59+
catch (FileNotFoundException ex)
60+
{
61+
return Ok(new DataSourceDto());
62+
}
63+
finally
64+
{
65+
_lock.ExitReadLock();
66+
}
67+
}
68+
}
69+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Text.Json;
11+
using System.Threading.Tasks;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
13+
using Umbraco.Cms.Integrations.SEO.Semrush.Models.Dtos;
14+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
15+
16+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
17+
{
18+
[ApiVersion("1.0")]
19+
[ApiExplorerSettings(GroupName = Constants.ManagementApi.SemrushGroupName)]
20+
public class GetRelatedPhrasesController : SemrushControllerBase
21+
{
22+
public GetRelatedPhrasesController(IOptions<SemrushSettings> options, IWebHostEnvironment webHostEnvironment, ISemrushTokenService semrushTokenService, ICacheHelper cacheHelper, TokenBuilder tokenBuilder, SemrushComposer.AuthorizationImplementationFactory authorizationImplementationFactory) : base(options, webHostEnvironment, semrushTokenService, cacheHelper, tokenBuilder, authorizationImplementationFactory)
23+
{
24+
}
25+
26+
[HttpGet("related-phrases")]
27+
[ProducesResponseType(typeof(RelatedPhrasesDto), StatusCodes.Status200OK)]
28+
public async Task<IActionResult> GetRelatedPhrases(string phrase, int pageNumber, string dataSource, string method)
29+
{
30+
string cacheKey = $"{dataSource}-{method}-{phrase}";
31+
32+
if (_cacheHelper.TryGetCachedItem<RelatedPhrasesDto>(cacheKey, out var relatedPhrasesDto) && relatedPhrasesDto.Data != null)
33+
{
34+
relatedPhrasesDto.TotalPages = relatedPhrasesDto.Data.Rows.Count / Constants.DefaultPageSize;
35+
relatedPhrasesDto.Data.Rows = relatedPhrasesDto.Data.Rows
36+
.Skip((pageNumber - 1) * Constants.DefaultPageSize)
37+
.Take(Constants.DefaultPageSize)
38+
.ToList();
39+
40+
return Ok(relatedPhrasesDto);
41+
}
42+
43+
_semrushTokenService.TryGetParameters(Constants.TokenDbKey, out TokenDto token);
44+
45+
var response = await ClientFactory()
46+
.GetAsync(string.Format(Constants.SemrushKeywordsEndpoint, _settings.BaseUrl, method, token.AccessToken, phrase, dataSource));
47+
48+
if (response.IsSuccessStatusCode)
49+
{
50+
var responseContent = await response.Content.ReadAsStringAsync();
51+
52+
var relatedPhrasesDeserialized = JsonSerializer.Deserialize<RelatedPhrasesDto>(responseContent);
53+
54+
if (!relatedPhrasesDeserialized.IsSuccessful) return Ok(relatedPhrasesDeserialized);
55+
56+
_cacheHelper.AddCachedItem(cacheKey, responseContent);
57+
58+
relatedPhrasesDeserialized.TotalPages = relatedPhrasesDeserialized.Data.Rows.Count / Constants.DefaultPageSize;
59+
relatedPhrasesDeserialized.Data.Rows = relatedPhrasesDeserialized.Data.Rows
60+
.Skip((pageNumber - 1) * Constants.DefaultPageSize)
61+
.Take(Constants.DefaultPageSize)
62+
.ToList();
63+
64+
return Ok(relatedPhrasesDeserialized);
65+
}
66+
67+
return Ok(relatedPhrasesDto);
68+
}
69+
}
70+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
13+
14+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
15+
{
16+
[ApiVersion("1.0")]
17+
[ApiExplorerSettings(GroupName = Constants.ManagementApi.SemrushGroupName)]
18+
public class PingController : SemrushControllerBase
19+
{
20+
public PingController(IOptions<SemrushSettings> options, IWebHostEnvironment webHostEnvironment, ISemrushTokenService semrushTokenService, ICacheHelper cacheHelper, TokenBuilder tokenBuilder, SemrushComposer.AuthorizationImplementationFactory authorizationImplementationFactory) : base(options, webHostEnvironment, semrushTokenService, cacheHelper, tokenBuilder, authorizationImplementationFactory)
21+
{
22+
}
23+
24+
[HttpGet("ping")]
25+
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
26+
public IActionResult Ping() => Ok("test API");
27+
}
28+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Umbraco.Cms.Api.Common.Attributes;
12+
using Umbraco.Cms.Integrations.SEO.Semrush.Configuration;
13+
using Umbraco.Cms.Integrations.SEO.Semrush.Models.Dtos;
14+
using Umbraco.Cms.Integrations.SEO.Semrush.Services;
15+
using Umbraco.Cms.Web.Common.Authorization;
16+
using Umbraco.Cms.Web.Common.Routing;
17+
using static Umbraco.Cms.Integrations.SEO.Semrush.SemrushComposer;
18+
19+
namespace Umbraco.Cms.Integrations.SEO.Semrush.Api.Management.Controllers
20+
{
21+
[ApiController]
22+
[BackOfficeRoute($"{Constants.ManagementApi.RootPath}/v{{version:apiVersion}}")]
23+
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
24+
[MapToApi(Constants.ManagementApi.ApiName)]
25+
public class SemrushControllerBase : Controller
26+
{
27+
// Using a static HttpClient (see: https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/).
28+
protected readonly static HttpClient s_client = new HttpClient();
29+
30+
// Access to the client within the class is via ClientFactory(), allowing us to mock the responses in tests.
31+
protected static Func<HttpClient> ClientFactory = () => s_client;
32+
33+
protected static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
34+
35+
protected readonly ISemrushTokenService _semrushTokenService;
36+
37+
protected readonly ICacheHelper _cacheHelper;
38+
39+
protected readonly TokenBuilder _tokenBuilder;
40+
41+
protected readonly SemrushSettings _settings;
42+
43+
protected readonly ISemrushAuthorizationService _authorizationService;
44+
45+
protected readonly IWebHostEnvironment _webHostEnvironment;
46+
47+
public SemrushControllerBase(IOptions<SemrushSettings> options,
48+
IWebHostEnvironment webHostEnvironment,
49+
ISemrushTokenService semrushTokenService,
50+
ICacheHelper cacheHelper, TokenBuilder tokenBuilder,
51+
AuthorizationImplementationFactory authorizationImplementationFactory)
52+
{
53+
_settings = options.Value;
54+
_webHostEnvironment = webHostEnvironment;
55+
_semrushTokenService = semrushTokenService;
56+
_cacheHelper = cacheHelper;
57+
_tokenBuilder = tokenBuilder;
58+
_authorizationService = authorizationImplementationFactory(_settings.UseUmbracoAuthorization);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)