From 9fa468b45ba374dcf9a3e2655e98e58b36eb4f2e Mon Sep 17 00:00:00 2001 From: Jeppe Bijker Date: Wed, 10 Jun 2020 22:47:31 +0200 Subject: [PATCH 01/25] Instagram module --- .../InstagramWall/InstagramWall.module.scss | 58 ++++++++++++++++ .../InstagramWall/InstagramWall.tsx | 69 +++++++++++++++++++ Frontend/src/pages/Home/Home.tsx | 5 ++ .../Detail/CrowdactionDetails.tsx | 11 +++ Frontend/src/translations/en/common.json | 3 + 5 files changed, 146 insertions(+) create mode 100644 Frontend/src/components/InstagramWall/InstagramWall.module.scss create mode 100644 Frontend/src/components/InstagramWall/InstagramWall.tsx diff --git a/Frontend/src/components/InstagramWall/InstagramWall.module.scss b/Frontend/src/components/InstagramWall/InstagramWall.module.scss new file mode 100644 index 000000000..0ca19b881 --- /dev/null +++ b/Frontend/src/components/InstagramWall/InstagramWall.module.scss @@ -0,0 +1,58 @@ +.container { + $size: 200px; + $padding: 20px; + + display: flex; + flex-wrap: wrap; + justify-content: center; + + a { + position: relative; + color: #000; + font-weight: normal; + margin: 10px 5px; + text-align: center; + transition: transform var(--transition-duration); + + &:hover { + text-decoration: none; + transform: scale(1.05); + + .caption { + opacity: 1; + } + } + } + + img { + width: $size; + } + + .date { + font-size: 9pt; + font-weight: var(--font-weight-bold); + color: #555; + } + + .caption { + position: absolute; + transition: opacity var(--transition-duration); + top: 0; + left: 0; + right: 0; + opacity: 0; + height: $size; + overflow: hidden; + font-size: 8pt; + line-height: 1.2; + background-color: rgba(255, 255, 255, 0.8); + + .padding { + padding: $padding; + height: $size - $padding; + white-space: pre-wrap; + overflow: hidden; + } + } + +} \ No newline at end of file diff --git a/Frontend/src/components/InstagramWall/InstagramWall.tsx b/Frontend/src/components/InstagramWall/InstagramWall.tsx new file mode 100644 index 000000000..d99e4f045 --- /dev/null +++ b/Frontend/src/components/InstagramWall/InstagramWall.tsx @@ -0,0 +1,69 @@ +import React, {useState} from "react"; + +import styles from "./InstagramWall.module.scss"; +import Formatter from "../../formatter"; +import moment from "moment"; + + +export interface IInstagramWallProps { + user: string; +} + +export class InstagramWall extends React.Component { + constructor(props: Readonly) { + super(props); + this.state = {items: []} + } + + //@todo: thumbnail quality + //@todo: video's: is_video + //@todo: check faulty returns + + componentDidMount() { + + let url = "https://www.instagram.com/" + this.props.user + "/?__a=1"; + let _this = this; + + fetch(url).then(res => res.json()) + .then(res => { + _this.setState({ + items: res.graphql.user.edge_owner_to_timeline_media.edges.map((edge: { node: any; }) => { + let { + shortcode, + thumbnail_src, + accessibility_caption, + edge_media_to_caption, + taken_at_timestamp, + } = edge.node; + return { + shortcode, + thumbnail_src, + accessibility_caption, + date: Formatter.date(moment.unix(taken_at_timestamp).toDate()), + caption: edge_media_to_caption && + edge_media_to_caption.edges && + edge_media_to_caption.edges[0] && + edge_media_to_caption.edges[0].node && + edge_media_to_caption.edges[0].node.text, + link: "https://www.instagram.com/p/" + shortcode, + } + }) + }); + }) + } + + + render() { + return
+ { + // @ts-ignore + this.state.items.map(item => + {item.accessibility_caption}/ +
+
{item.caption}
+
+
{item.date}
+
)} +
; + } +} diff --git a/Frontend/src/pages/Home/Home.tsx b/Frontend/src/pages/Home/Home.tsx index d1940bf5e..7fd34d748 100644 --- a/Frontend/src/pages/Home/Home.tsx +++ b/Frontend/src/pages/Home/Home.tsx @@ -9,6 +9,7 @@ import TimeToAct from "../../components/TimeToAct/TimeToAct"; import { useTranslation } from 'react-i18next'; import { Helmet } from "react-helmet"; +import {InstagramWall} from "../../components/InstagramWall/InstagramWall"; const HomePage = () => { const { t } = useTranslation(); @@ -36,6 +37,10 @@ const HomePage = () => { {t('home.crowdactions.button')} +
+ Instagram: @collaction_org + +
) } diff --git a/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx b/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx index 9155c107a..733932b21 100644 --- a/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx +++ b/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx @@ -26,6 +26,7 @@ import Formatter from '../../../formatter'; import LazyImage from '../../../components/LazyImage/LazyImage'; import { TextField } from 'formik-material-ui'; import CrowdactionComments from '../../../components/CrowdactionComments/CrowdactionComments'; +import {InstagramWall} from "../../../components/InstagramWall/InstagramWall"; type TParams = { slug: string; @@ -171,6 +172,8 @@ const CrowdactionDetailsPage = ({ crowdaction.categories[0] ? crowdaction.categories[0].category : 'OTHER' }.jpg`); + let instagramUser = "slowfashionseason"; + return ( <> @@ -262,6 +265,14 @@ const CrowdactionDetailsPage = ({ > )} + + {instagramUser &&
+ +

Instagram @{instagramUser}

+
+ +
} + diff --git a/Frontend/src/translations/en/common.json b/Frontend/src/translations/en/common.json index b07469cf7..e2bd83099 100644 --- a/Frontend/src/translations/en/common.json +++ b/Frontend/src/translations/en/common.json @@ -40,6 +40,9 @@ }, "share" : { "title" : "Share now" + }, + "follow": { + "title": "Follow us" } }, "about": { From 7a88802f70c824cc79a1ec0a126b84ff5380c821 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 21:39:28 +0200 Subject: [PATCH 02/25] Backend for instagram API + image proxy --- .../Integration/Endpoint/GraphQlTests.cs | 2 +- .../Service/CrowdactionServiceTests.cs | 11 +- ...eServicesTests.cs => ImageServiceTests.cs} | 4 +- .../Service/InstagramServiceTests.cs | 29 + .../Integration/Service/UserServiceTests.cs | 2 +- CollAction/Controllers/ProxyController.cs | 24 + .../Input/NewCrowdactionInputGraph.cs | 1 + .../Input/UpdatedCrowdactionInputGraph.cs | 1 + .../Queries/CrowdactionCommentGraph.cs | 2 +- .../GraphQl/Queries/CrowdactionGraph.cs | 22 + .../Queries/InstagramTimelineItemGraph.cs | 18 + .../20200612174558_InstagramName.Designer.cs | 688 ++++++++++++++++++ .../20200612174558_InstagramName.cs | 41 ++ .../ApplicationDbContextModelSnapshot.cs | 9 +- CollAction/Models/Crowdaction.cs | 10 +- CollAction/Models/CrowdactionComment.cs | 2 +- CollAction/Services/AnalyticsOptions.cs | 2 +- .../Crowdactions/CrowdactionService.cs | 3 + .../Crowdactions/Models/NewCrowdaction.cs | 4 + .../Models/NewCrowdactionInternal.cs | 10 +- .../Crowdactions/Models/UpdatedCrowdaction.cs | 4 + .../Initialization/InitializationService.cs | 3 +- .../Services/Instagram/IInstagramService.cs | 12 + .../Services/Instagram/InstagramService.cs | 73 ++ .../Instagram/Models/InstagramTimelineItem.cs | 29 + CollAction/Services/Proxy/IProxyService.cs | 12 + CollAction/Services/Proxy/ProxyService.cs | 37 + CollAction/Startup.cs | 12 +- 28 files changed, 1046 insertions(+), 21 deletions(-) rename CollAction.Tests/Integration/Service/{ImageServicesTests.cs => ImageServiceTests.cs} (95%) create mode 100644 CollAction.Tests/Integration/Service/InstagramServiceTests.cs create mode 100644 CollAction/Controllers/ProxyController.cs create mode 100644 CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs create mode 100644 CollAction/Migrations/20200612174558_InstagramName.Designer.cs create mode 100644 CollAction/Migrations/20200612174558_InstagramName.cs create mode 100644 CollAction/Services/Instagram/IInstagramService.cs create mode 100644 CollAction/Services/Instagram/InstagramService.cs create mode 100644 CollAction/Services/Instagram/Models/InstagramTimelineItem.cs create mode 100644 CollAction/Services/Proxy/IProxyService.cs create mode 100644 CollAction/Services/Proxy/ProxyService.cs diff --git a/CollAction.Tests/Integration/Endpoint/GraphQlTests.cs b/CollAction.Tests/Integration/Endpoint/GraphQlTests.cs index cd0cb2bef..4a61892f4 100644 --- a/CollAction.Tests/Integration/Endpoint/GraphQlTests.cs +++ b/CollAction.Tests/Integration/Endpoint/GraphQlTests.cs @@ -34,7 +34,7 @@ public GraphQlTests() [Fact] public async Task TestCrowdactionList() { - var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow, DateTime.UtcNow.AddDays(1), null, null, null, null, new[] { Category.Community }, Array.Empty(), CrowdactionDisplayPriority.Bottom, CrowdactionStatus.Running, 0, null); + var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow, DateTime.UtcNow.AddDays(1), null, null, null, null, null, new[] { Category.Community }, Array.Empty(), CrowdactionDisplayPriority.Bottom, CrowdactionStatus.Running, 0, null); Crowdaction createdCrowdaction = await crowdactionService.CreateCrowdactionInternal(newCrowdaction, CancellationToken.None).ConfigureAwait(false); Assert.NotNull(createdCrowdaction); diff --git a/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs b/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs index 3bb322377..56e767ede 100644 --- a/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs +++ b/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs @@ -70,7 +70,7 @@ public async Task TestCrowdactionCreate() public async Task TestCrowdactionUpdate() { var user = await context.Users.FirstAsync().ConfigureAwait(false); - var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow, DateTime.UtcNow.AddDays(1), null, null, null, null, new[] { Category.Community }, Array.Empty(), CrowdactionDisplayPriority.Bottom, CrowdactionStatus.Running, 0, user.Id); + var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow, DateTime.UtcNow.AddDays(1), null, null, null, null, null, new[] { Category.Community }, Array.Empty(), CrowdactionDisplayPriority.Bottom, CrowdactionStatus.Running, 0, user.Id); Crowdaction crowdaction = await crowdactionService.CreateCrowdactionInternal(newCrowdaction, CancellationToken.None).ConfigureAwait(false); Assert.NotNull(crowdaction); @@ -117,7 +117,7 @@ public async Task TestCrowdactionUpdate() [Fact] public async Task TestCommentCreate() { - var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow, DateTime.UtcNow.AddDays(1), null, null, null, null, new[] { Category.Community }, Array.Empty(), CrowdactionDisplayPriority.Bottom, CrowdactionStatus.Running, 0, null); + var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow, DateTime.UtcNow.AddDays(1), null, null, null, null, null, new[] { Category.Community }, Array.Empty(), CrowdactionDisplayPriority.Bottom, CrowdactionStatus.Running, 0, null); Crowdaction crowdaction = await crowdactionService.CreateCrowdactionInternal(newCrowdaction, CancellationToken.None).ConfigureAwait(false); Assert.NotNull(crowdaction); @@ -143,7 +143,7 @@ public async Task TestCrowdactionCommitAnonymous() { // Setup var user = await context.Users.FirstAsync().ConfigureAwait(false); - Crowdaction crowdaction = new Crowdaction($"test-{Guid.NewGuid()}", CrowdactionStatus.Running, user.Id, 10, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), "t", "t", "t", null, null); + Crowdaction crowdaction = new Crowdaction($"test-{Guid.NewGuid()}", CrowdactionStatus.Running, user.Id, 10, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), "t", "t", "t", "t", null, null); context.Crowdactions.Add(crowdaction); await context.SaveChangesAsync().ConfigureAwait(false); @@ -161,7 +161,7 @@ public async Task TestCrowdactionCommitLoggedIn() // Setup var user = await context.Users.FirstAsync().ConfigureAwait(false); var userClaim = await signInManager.CreateUserPrincipalAsync(user).ConfigureAwait(false); - var crowdaction = new Crowdaction($"test-{Guid.NewGuid()}", CrowdactionStatus.Running, user.Id, 10, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), "t", "t", "t", null, null); + var crowdaction = new Crowdaction($"test-{Guid.NewGuid()}", CrowdactionStatus.Running, user.Id, 10, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), "t", "t", "t", "t", null, null); context.Crowdactions.Add(crowdaction); await context.SaveChangesAsync().ConfigureAwait(false); @@ -187,6 +187,7 @@ public async Task TestCrowdactionEmail() start: DateTime.Now.AddDays(-10), end: DateTime.Now.AddDays(30), goal: Guid.NewGuid().ToString(), + instagramName: "test", creatorComments: Guid.NewGuid().ToString(), proposal: Guid.NewGuid().ToString(), target: 40, @@ -217,7 +218,7 @@ public async Task TestCrowdactionSearch() Category searchCategory = (Category)r.Next(7); for (int i = 0; i < r.Next(10, 30); i++) { - var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow.AddDays(r.Next(-20, 20)), DateTime.UtcNow.AddDays(r.Next(21, 50)), null, null, null, null, new[] { searchCategory }, Array.Empty(), CrowdactionDisplayPriority.Bottom, (CrowdactionStatus)r.Next(3), 0, null); + var newCrowdaction = new NewCrowdactionInternal("test" + Guid.NewGuid(), 100, "test", "test", "test", null, DateTime.UtcNow.AddDays(r.Next(-20, 20)), DateTime.UtcNow.AddDays(r.Next(21, 50)), null, null, null, null, null, new[] { searchCategory }, Array.Empty(), CrowdactionDisplayPriority.Bottom, (CrowdactionStatus)r.Next(3), 0, null); Crowdaction crowdaction = await crowdactionService.CreateCrowdactionInternal(newCrowdaction, CancellationToken.None).ConfigureAwait(false); } diff --git a/CollAction.Tests/Integration/Service/ImageServicesTests.cs b/CollAction.Tests/Integration/Service/ImageServiceTests.cs similarity index 95% rename from CollAction.Tests/Integration/Service/ImageServicesTests.cs rename to CollAction.Tests/Integration/Service/ImageServiceTests.cs index 3408f1277..0d84cbbe5 100644 --- a/CollAction.Tests/Integration/Service/ImageServicesTests.cs +++ b/CollAction.Tests/Integration/Service/ImageServiceTests.cs @@ -13,14 +13,14 @@ namespace CollAction.Tests.Integration.Service { [Trait("Category", "Integration")] - public sealed class ImageServicesTests : IntegrationTestBase + public sealed class ImageServiceTests : IntegrationTestBase { private readonly byte[] testImage = new byte[] { 0x42, 0x4D, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0xFF, 0x00 }; private readonly MemoryStream imageMs; private readonly IImageService imageService; private readonly Mock upload; - public ImageServicesTests() : base(false) + public ImageServiceTests() : base(false) { imageMs = new MemoryStream(testImage); imageService = Scope.ServiceProvider.GetRequiredService(); diff --git a/CollAction.Tests/Integration/Service/InstagramServiceTests.cs b/CollAction.Tests/Integration/Service/InstagramServiceTests.cs new file mode 100644 index 000000000..c4d0ca094 --- /dev/null +++ b/CollAction.Tests/Integration/Service/InstagramServiceTests.cs @@ -0,0 +1,29 @@ +using CollAction.Services.Instagram; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace CollAction.Tests.Integration.Service +{ + [Trait("Category", "Integration")] + public sealed class InstagramServiceTests : IntegrationTestBase + { + private readonly IInstagramService instagramService; + + public InstagramServiceTests(): base(false) + { + instagramService = Scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task TestInstagramApi() + { + var result = await instagramService.GetItems("slowfashionseason", CancellationToken.None).ConfigureAwait(false); + Assert.True(result.Any()); + } + } +} diff --git a/CollAction.Tests/Integration/Service/UserServiceTests.cs b/CollAction.Tests/Integration/Service/UserServiceTests.cs index 5fa5614d4..6cd39731e 100644 --- a/CollAction.Tests/Integration/Service/UserServiceTests.cs +++ b/CollAction.Tests/Integration/Service/UserServiceTests.cs @@ -124,7 +124,7 @@ public async Task TestUserManagement() public async Task TestFinishRegistration() { // Setup - var crowdaction = new Crowdaction($"test-{Guid.NewGuid()}", CrowdactionStatus.Running, await context.Users.Select(u => u.Id).FirstAsync().ConfigureAwait(false), 10, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), "t", "t", "t", null, null); + var crowdaction = new Crowdaction($"test-{Guid.NewGuid()}", CrowdactionStatus.Running, await context.Users.Select(u => u.Id).FirstAsync().ConfigureAwait(false), 10, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), "t", "t", "t", "t", null, null); context.Crowdactions.Add(crowdaction); await context.SaveChangesAsync().ConfigureAwait(false); diff --git a/CollAction/Controllers/ProxyController.cs b/CollAction/Controllers/ProxyController.cs new file mode 100644 index 000000000..c4053d7b5 --- /dev/null +++ b/CollAction/Controllers/ProxyController.cs @@ -0,0 +1,24 @@ +using CollAction.Services.Proxy; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CollAction.Controllers +{ + [Route("proxy")] + [ApiController] + public sealed class ProxyController + { + private readonly IProxyService proxyService; + + public ProxyController(IProxyService proxyService) + { + this.proxyService = proxyService; + } + + [HttpGet] + public Task Proxy(Uri url, CancellationToken token) + => proxyService.Proxy(url, token); + } +} diff --git a/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs b/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs index 583b7b6d5..987b26f9b 100644 --- a/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs +++ b/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs @@ -16,6 +16,7 @@ public NewCrowdactionInputGraph() Field(x => x.CreatorComments, true); Field(x => x.Start); Field(x => x.End); + Field(x => x.InstagramName, true); Field(x => x.BannerImageFileId, true); Field(x => x.CardImageFileId, true); Field(x => x.DescriptiveImageFileId, true); diff --git a/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs b/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs index 273ff7a9e..a11d5a600 100644 --- a/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs +++ b/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs @@ -20,6 +20,7 @@ public UpdatedCrowdactionInputGraph() Field(x => x.BannerImageFileId, true); Field(x => x.CardImageFileId, true); Field(x => x.DescriptiveImageFileId, true); + Field(x => x.InstagramName, true); Field(x => x.DescriptionVideoLink, true); Field(x => x.Tags); Field(x => x.DisplayPriority); diff --git a/CollAction/GraphQl/Queries/CrowdactionCommentGraph.cs b/CollAction/GraphQl/Queries/CrowdactionCommentGraph.cs index 58deaf6ad..92a83c920 100644 --- a/CollAction/GraphQl/Queries/CrowdactionCommentGraph.cs +++ b/CollAction/GraphQl/Queries/CrowdactionCommentGraph.cs @@ -6,7 +6,7 @@ namespace CollAction.GraphQl.Queries { - public class CrowdactionCommentGraph : EfObjectGraphType + public sealed class CrowdactionCommentGraph : EfObjectGraphType { public CrowdactionCommentGraph(IEfGraphQLService graphService) : base(graphService) { diff --git a/CollAction/GraphQl/Queries/CrowdactionGraph.cs b/CollAction/GraphQl/Queries/CrowdactionGraph.cs index 2415feb65..60f18f2ee 100644 --- a/CollAction/GraphQl/Queries/CrowdactionGraph.cs +++ b/CollAction/GraphQl/Queries/CrowdactionGraph.cs @@ -2,11 +2,16 @@ using CollAction.Helpers; using CollAction.Models; using CollAction.Services.Crowdactions; +using CollAction.Services.Instagram; +using CollAction.Services.Instagram.Models; using GraphQL.Authorization; using GraphQL.EntityFramework; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace CollAction.GraphQl.Queries { @@ -98,6 +103,23 @@ public CrowdactionGraph(IEfGraphQLService entityFrameworkG return c.Source.Percentage; }); + FieldAsync>>, IEnumerable>( + "instagramTimeline", + resolve: c => + { + string? instagramName = c.Source.InstagramName; + if (instagramName != null) + { + return c.GetUserContext() + .ServiceProvider + .GetRequiredService() + .GetItems(instagramName, c.CancellationToken); + } + else + { + return Task.FromResult(Enumerable.Empty()); + } + }); AddNavigationField(nameof(Crowdaction.DescriptiveImage), c => c.Source.DescriptiveImage); AddNavigationField(nameof(Crowdaction.BannerImage), c => c.Source.BannerImage); AddNavigationField(nameof(Crowdaction.CardImage), c => c.Source.CardImage); diff --git a/CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs b/CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs new file mode 100644 index 000000000..6dd103094 --- /dev/null +++ b/CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs @@ -0,0 +1,18 @@ +using CollAction.Services.Instagram.Models; +using GraphQL.Types; + +namespace CollAction.GraphQl.Queries +{ + public sealed class InstagramTimelineItemGraph : ObjectGraphType + { + public InstagramTimelineItemGraph() + { + Field(x => x.ShortCode); + Field(x => x.Link); + Field(x => x.Date); + Field(x => x.AccessibilityCaption, true); + Field(x => x.Caption, true); + Field(x => x.ThumbnailSrc); + } + } +} diff --git a/CollAction/Migrations/20200612174558_InstagramName.Designer.cs b/CollAction/Migrations/20200612174558_InstagramName.Designer.cs new file mode 100644 index 000000000..23b19c17d --- /dev/null +++ b/CollAction/Migrations/20200612174558_InstagramName.Designer.cs @@ -0,0 +1,688 @@ +// +using System; +using CollAction.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace CollAction.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20200612174558_InstagramName")] + partial class InstagramName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("CollAction.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("character varying(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .HasColumnType("character varying(250)") + .HasMaxLength(250); + + b.Property("LastName") + .HasColumnType("character varying(250)") + .HasMaxLength(250); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("character varying(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("character varying(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RegistrationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RepresentsNumberParticipants") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("character varying(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("CollAction.Models.Crowdaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AnonymousUserParticipants") + .HasColumnType("integer"); + + b.Property("BannerImageFileId") + .HasColumnType("integer"); + + b.Property("CardImageFileId") + .HasColumnType("integer"); + + b.Property("CreatorComments") + .HasColumnType("character varying(20000)") + .HasMaxLength(20000); + + b.Property("Description") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("DescriptionVideoLink") + .HasColumnType("character varying(2048)") + .HasMaxLength(2048); + + b.Property("DescriptiveImageFileId") + .HasColumnType("integer"); + + b.Property("DisplayPriority") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("End") + .HasColumnType("timestamp without time zone"); + + b.Property("FinishJobId") + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.Property("Goal") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("InstagramName") + .HasColumnType("character varying(30)") + .HasMaxLength(30); + + b.Property("Name") + .IsRequired() + .HasColumnType("character varying(50)") + .HasMaxLength(50); + + b.Property("NumberCrowdactionEmailsSent") + .HasColumnType("integer"); + + b.Property("OwnerId") + .HasColumnType("text"); + + b.Property("Proposal") + .IsRequired() + .HasColumnType("character varying(300)") + .HasMaxLength(300); + + b.Property("Start") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Target") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BannerImageFileId"); + + b.HasIndex("CardImageFileId"); + + b.HasIndex("DescriptiveImageFileId"); + + b.HasIndex("Name") + .IsUnique() + .HasName("IX_Crowdactions_Name"); + + b.HasIndex("OwnerId"); + + b.ToTable("Crowdactions"); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionCategory", b => + { + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CrowdactionId") + .HasColumnType("integer"); + + b.HasKey("Category", "CrowdactionId"); + + b.HasIndex("CrowdactionId"); + + b.ToTable("CrowdactionCategories"); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Comment") + .IsRequired() + .HasColumnType("text"); + + b.Property("CommentedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CrowdactionId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CrowdactionId"); + + b.HasIndex("UserId"); + + b.ToTable("CrowdactionComments"); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionParticipant", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("CrowdactionId") + .HasColumnType("integer"); + + b.Property("ParticipationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SubscribedToCrowdactionEmails") + .HasColumnType("boolean"); + + b.Property("UnsubscribeToken") + .HasColumnType("uuid"); + + b.HasKey("UserId", "CrowdactionId"); + + b.HasIndex("CrowdactionId"); + + b.ToTable("CrowdactionParticipants"); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionParticipantCount", b => + { + b.Property("CrowdactionId") + .HasColumnType("integer"); + + b.Property("Count") + .HasColumnType("integer"); + + b.HasKey("CrowdactionId"); + + b.ToTable("CrowdactionParticipantCounts"); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionTag", b => + { + b.Property("TagId") + .HasColumnType("integer"); + + b.Property("CrowdactionId") + .HasColumnType("integer"); + + b.HasKey("TagId", "CrowdactionId"); + + b.HasIndex("CrowdactionId"); + + b.ToTable("CrowdactionTags"); + }); + + modelBuilder.Entity("CollAction.Models.DonationEventLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("EventData") + .IsRequired() + .HasColumnType("json"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("DonationEventLog"); + }); + + modelBuilder.Entity("CollAction.Models.ImageFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Date") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Filepath") + .IsRequired() + .HasColumnType("text"); + + b.Property("Height") + .HasColumnType("integer"); + + b.Property("Width") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ImageFiles"); + }); + + modelBuilder.Entity("CollAction.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("character varying(30)") + .HasMaxLength(30); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("CollAction.Models.UserEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("EventData") + .IsRequired() + .HasColumnType("json"); + + b.Property("EventLoggedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEvents"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("character varying(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("character varying(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("CollAction.Models.Crowdaction", b => + { + b.HasOne("CollAction.Models.ImageFile", "BannerImage") + .WithMany() + .HasForeignKey("BannerImageFileId"); + + b.HasOne("CollAction.Models.ImageFile", "CardImage") + .WithMany() + .HasForeignKey("CardImageFileId"); + + b.HasOne("CollAction.Models.ImageFile", "DescriptiveImage") + .WithMany() + .HasForeignKey("DescriptiveImageFileId"); + + b.HasOne("CollAction.Models.ApplicationUser", "Owner") + .WithMany("Crowdactions") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionCategory", b => + { + b.HasOne("CollAction.Models.Crowdaction", "Crowdaction") + .WithMany("Categories") + .HasForeignKey("CrowdactionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionComment", b => + { + b.HasOne("CollAction.Models.Crowdaction", "Crowdaction") + .WithMany("Comments") + .HasForeignKey("CrowdactionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CollAction.Models.ApplicationUser", "User") + .WithMany("CrowdactionComments") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionParticipant", b => + { + b.HasOne("CollAction.Models.Crowdaction", "Crowdaction") + .WithMany("Participants") + .HasForeignKey("CrowdactionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CollAction.Models.ApplicationUser", "User") + .WithMany("Participates") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionParticipantCount", b => + { + b.HasOne("CollAction.Models.Crowdaction", "Crowdaction") + .WithOne("ParticipantCounts") + .HasForeignKey("CollAction.Models.CrowdactionParticipantCount", "CrowdactionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollAction.Models.CrowdactionTag", b => + { + b.HasOne("CollAction.Models.Crowdaction", "Crowdaction") + .WithMany("Tags") + .HasForeignKey("CrowdactionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CollAction.Models.Tag", "Tag") + .WithMany("CrowdactionTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollAction.Models.DonationEventLog", b => + { + b.HasOne("CollAction.Models.ApplicationUser", "User") + .WithMany("DonationEvents") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("CollAction.Models.UserEvent", b => + { + b.HasOne("CollAction.Models.ApplicationUser", "User") + .WithMany("UserEvents") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("CollAction.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("CollAction.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CollAction.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("CollAction.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CollAction/Migrations/20200612174558_InstagramName.cs b/CollAction/Migrations/20200612174558_InstagramName.cs new file mode 100644 index 000000000..edcf73623 --- /dev/null +++ b/CollAction/Migrations/20200612174558_InstagramName.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace CollAction.Migrations +{ + public partial class InstagramName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "DescriptionVideoLink", + table: "Crowdactions", + maxLength: 2048, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "InstagramName", + table: "Crowdactions", + maxLength: 30, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstagramName", + table: "Crowdactions"); + + migrationBuilder.AlterColumn( + name: "DescriptionVideoLink", + table: "Crowdactions", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 2048, + oldNullable: true); + } + } +} diff --git a/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs b/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs index ea5479a13..74d48c427 100644 --- a/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs @@ -16,7 +16,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("ProductVersion", "3.1.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("CollAction.Models.ApplicationUser", b => @@ -125,7 +125,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(10000); b.Property("DescriptionVideoLink") - .HasColumnType("text"); + .HasColumnType("character varying(2048)") + .HasMaxLength(2048); b.Property("DescriptiveImageFileId") .HasColumnType("integer"); @@ -147,6 +148,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(10000)") .HasMaxLength(10000); + b.Property("InstagramName") + .HasColumnType("character varying(30)") + .HasMaxLength(30); + b.Property("Name") .IsRequired() .HasColumnType("character varying(50)") diff --git a/CollAction/Models/Crowdaction.cs b/CollAction/Models/Crowdaction.cs index a0ab2da4c..752a0f68b 100644 --- a/CollAction/Models/Crowdaction.cs +++ b/CollAction/Models/Crowdaction.cs @@ -11,11 +11,11 @@ namespace CollAction.Models { public sealed class Crowdaction { - public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) : this(name, status, ownerId, target, start, end, description, goal, proposal, creatorComments, descriptionVideoLink, new List(), new List(), displayPriority, bannerImageFileId, descriptiveImageFileId, cardImageFileId, anonymousUserParticipants) + public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string? instagramName, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) : this(name, status, ownerId, target, start, end, instagramName, description, goal, proposal, creatorComments, descriptionVideoLink, new List(), new List(), displayPriority, bannerImageFileId, descriptiveImageFileId, cardImageFileId, anonymousUserParticipants) { } - public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, ICollection categories, ICollection tags, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) + public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string? instagramName, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, ICollection categories, ICollection tags, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) { Name = name; Status = status; @@ -35,6 +35,7 @@ public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int t Categories = categories; Tags = tags; AnonymousUserParticipants = anonymousUserParticipants; + InstagramName = instagramName; } [NotMapped] @@ -78,6 +79,7 @@ public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int t [MaxLength(20000)] public string? CreatorComments { get; set; } + [MaxLength(2048)] public string? DescriptionVideoLink { get; set; } public CrowdactionDisplayPriority DisplayPriority { get; set; } @@ -102,6 +104,10 @@ public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int t [ForeignKey("CardImageFileId")] public ImageFile? CardImage { get; set; } + [MinLength(1)] + [MaxLength(30)] + public string? InstagramName { get; set; } + public CrowdactionParticipantCount? ParticipantCounts { get; set; } public ICollection Participants { get; set; } = new List(); diff --git a/CollAction/Models/CrowdactionComment.cs b/CollAction/Models/CrowdactionComment.cs index 9af2e129f..00de33371 100644 --- a/CollAction/Models/CrowdactionComment.cs +++ b/CollAction/Models/CrowdactionComment.cs @@ -3,7 +3,7 @@ namespace CollAction.Models { - public class CrowdactionComment + public sealed class CrowdactionComment { public CrowdactionComment(string comment, string userId, int crowdactionId, DateTime commentedAt) { diff --git a/CollAction/Services/AnalyticsOptions.cs b/CollAction/Services/AnalyticsOptions.cs index 6450c461e..76e643eb6 100644 --- a/CollAction/Services/AnalyticsOptions.cs +++ b/CollAction/Services/AnalyticsOptions.cs @@ -2,7 +2,7 @@ namespace CollAction.Services { - public class AnalyticsOptions + public sealed class AnalyticsOptions { [Required] public string GoogleAnalyticsID { get; set; } = null!; diff --git a/CollAction/Services/Crowdactions/CrowdactionService.cs b/CollAction/Services/Crowdactions/CrowdactionService.cs index fa44e6fbf..26fc17863 100644 --- a/CollAction/Services/Crowdactions/CrowdactionService.cs +++ b/CollAction/Services/Crowdactions/CrowdactionService.cs @@ -106,6 +106,7 @@ await context.Tags description: newCrowdaction.Description, goal: newCrowdaction.Goal, proposal: newCrowdaction.Proposal, + instagramName: newCrowdaction.InstagramName, creatorComments: newCrowdaction.CreatorComments, descriptionVideoLink: newCrowdaction.DescriptionVideoLink?.Replace("www.youtube.com", "www.youtube-nocookie.com", StringComparison.Ordinal), displayPriority: newCrowdaction.DisplayPriority, @@ -188,6 +189,7 @@ await context.Tags description: newCrowdaction.Description, goal: newCrowdaction.Goal, proposal: newCrowdaction.Proposal, + instagramName: newCrowdaction.InstagramName, creatorComments: newCrowdaction.CreatorComments, descriptionVideoLink: newCrowdaction.DescriptionVideoLink?.Replace("www.youtube.com", "www.youtube-nocookie.com", StringComparison.Ordinal), displayPriority: CrowdactionDisplayPriority.Medium, @@ -271,6 +273,7 @@ public async Task UpdateCrowdaction(UpdatedCrowdaction update crowdaction.Proposal = updatedCrowdaction.Proposal; crowdaction.Goal = updatedCrowdaction.Goal; crowdaction.CreatorComments = updatedCrowdaction.CreatorComments; + crowdaction.InstagramName = updatedCrowdaction.InstagramName; crowdaction.Target = updatedCrowdaction.Target; crowdaction.Start = updatedCrowdaction.Start; crowdaction.End = updatedCrowdaction.End.Date.AddHours(23).AddMinutes(59).AddSeconds(59); diff --git a/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs b/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs index 3d4e87601..e4b9dda28 100644 --- a/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs +++ b/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs @@ -42,6 +42,10 @@ public sealed class NewCrowdaction [WithinMonthsAfterDateProperty(12, "Start", ErrorMessage = "The deadline must be within a year of the start date")] public DateTime End { get; set; } + [MinLength(1)] + [MaxLength(30)] + public string? InstagramName { get; set; } + public int? CardImageFileId { get; set; } public int? BannerImageFileId { get; set; } diff --git a/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs b/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs index bb22e41de..b09de79cb 100644 --- a/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs +++ b/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs @@ -1,13 +1,14 @@ -using CollAction.Models; +using CollAction.Migrations; +using CollAction.Models; using System; using System.Collections.Generic; using System.Linq; namespace CollAction.Services.Crowdactions.Models { - public class NewCrowdactionInternal + public sealed class NewCrowdactionInternal { - public NewCrowdactionInternal(string name, int target, string proposal, string description, string goal, string? creatorComments, DateTime start, DateTime end, int? cardImageFileId, int? bannerImageFileId, int? descriptiveImageFileId, string? descriptionVideoLink, IEnumerable categories, IEnumerable tags, CrowdactionDisplayPriority displayPriority, CrowdactionStatus status, int anonymousUserParticipants, string? ownerId) + public NewCrowdactionInternal(string name, int target, string proposal, string description, string goal, string? creatorComments, DateTime start, DateTime end, string? instagramName, int? cardImageFileId, int? bannerImageFileId, int? descriptiveImageFileId, string? descriptionVideoLink, IEnumerable categories, IEnumerable tags, CrowdactionDisplayPriority displayPriority, CrowdactionStatus status, int anonymousUserParticipants, string? ownerId) { Name = name; Target = target; @@ -21,6 +22,7 @@ public NewCrowdactionInternal(string name, int target, string proposal, string d BannerImageFileId = bannerImageFileId; DescriptiveImageFileId = descriptiveImageFileId; DescriptionVideoLink = descriptionVideoLink; + InstagramName = instagramName; Categories = categories; Tags = tags; DisplayPriority = displayPriority; @@ -53,6 +55,8 @@ public NewCrowdactionInternal(string name, int target, string proposal, string d public string? DescriptionVideoLink { get; set; } + public string? InstagramName { get; } + public IEnumerable Categories { get; set; } public IEnumerable Tags { get; set; } = Enumerable.Empty(); diff --git a/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs b/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs index 2fa7cfee8..8e95143d5 100644 --- a/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs +++ b/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs @@ -43,6 +43,10 @@ public sealed class UpdatedCrowdaction [DataType(DataType.Date)] public DateTime End { get; set; } + [MinLength(1)] + [MaxLength(30)] + public string? InstagramName { get; set; } + public int? CardImageFileId { get; set; } public int? BannerImageFileId { get; set; } diff --git a/CollAction/Services/Initialization/InitializationService.cs b/CollAction/Services/Initialization/InitializationService.cs index ef5e91959..be13ed2b2 100644 --- a/CollAction/Services/Initialization/InitializationService.cs +++ b/CollAction/Services/Initialization/InitializationService.cs @@ -19,7 +19,7 @@ namespace CollAction.Services.Initialization { - public class InitializationService : IInitializationService + public sealed class InitializationService : IInitializationService { private readonly UserManager userManager; private readonly RoleManager roleManager; @@ -248,6 +248,7 @@ private async Task SeedRandomCrowdactions(IEnumerable users, Ca bannerImageFileId: bannerImage?.Id, descriptiveImageFileId: descriptiveImage?.Id, cardImageFileId: cardImage?.Id, + instagramName: "slowfashionseason", creatorComments: r.Next(4) == 0 ? null : $"

{string.Join("

", Faker.Lorem.Paragraphs(r.Next(3) + 1))}

", goal: Faker.Company.CatchPhrase(), proposal: Faker.Company.BS(), diff --git a/CollAction/Services/Instagram/IInstagramService.cs b/CollAction/Services/Instagram/IInstagramService.cs new file mode 100644 index 000000000..9d30cd312 --- /dev/null +++ b/CollAction/Services/Instagram/IInstagramService.cs @@ -0,0 +1,12 @@ +using CollAction.Services.Instagram.Models; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace CollAction.Services.Instagram +{ + public interface IInstagramService + { + public Task> GetItems(string instagramName, CancellationToken token); + } +} diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs new file mode 100644 index 000000000..b7046dd20 --- /dev/null +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -0,0 +1,73 @@ +using CollAction.Services.Instagram.Models; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace CollAction.Services.Instagram +{ + public sealed class InstagramService : IInstagramService + { + private readonly IMemoryCache cache; + private readonly HttpClient instagramClient; + private readonly ILogger logger; + private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(10); + + public InstagramService(IMemoryCache cache, HttpClient instagramClient, ILogger logger) + { + this.cache = cache; + this.instagramClient = instagramClient; + this.logger = logger; + } + + public async Task> GetItems(string instagramName, CancellationToken token) + { + // TODO: Stop using the instagram private api + Uri url = new Uri($"/{instagramName}/?__a=1", UriKind.Relative); + try + { + return await cache.GetOrCreateAsync( + url, + async (ICacheEntry entry) => + { + entry.SlidingExpiration = CacheExpiration; + logger.LogInformation("Retrieving instagram timeline for {0}", instagramName); + var result = await instagramClient.GetAsync(url, token).ConfigureAwait(false); + result.EnsureSuccessStatusCode(); + return ParseInstagramResponse(await result.Content.ReadAsStringAsync().ConfigureAwait(false)); + }).ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogError(e, "Error retrieving items from instagram timeline"); + return Enumerable.Empty(); // External APIs can fail here, lets be a little robust here. Don't cache the failures though.. so catch outside the cache + } + } + + private IEnumerable ParseInstagramResponse(string json) + { + dynamic deserialized = JsonConvert.DeserializeObject(json); + var edges = (IEnumerable)(deserialized.graphql?.user?.edge_owner_to_timeline_media?.edges ?? Enumerable.Empty()); + logger.LogInformation("Retrieving instagram timeline, found {0} items", edges.Count()); + return edges + .Select(e => e.node) + .Where(e => e.shortcode != null && e.thumbnail_src != null && e.taken_at_timestamp != null) + .Select(e => + { + var captionEdges = (IEnumerable?)e.edge_media_to_caption?.edges; + string? caption = (string?)captionEdges?.FirstOrDefault()?.node?.text; + return new InstagramTimelineItem( + (string)e.shortcode, + (string)e.thumbnail_src, + (string?)e.accessibility_caption, + caption, + DateTimeOffset.FromUnixTimeSeconds((long)e.taken_at_timestamp)); + }); + } + } +} diff --git a/CollAction/Services/Instagram/Models/InstagramTimelineItem.cs b/CollAction/Services/Instagram/Models/InstagramTimelineItem.cs new file mode 100644 index 000000000..dcee2d0e7 --- /dev/null +++ b/CollAction/Services/Instagram/Models/InstagramTimelineItem.cs @@ -0,0 +1,29 @@ +using System; + +namespace CollAction.Services.Instagram.Models +{ + public sealed class InstagramTimelineItem + { + public InstagramTimelineItem(string shortCode, string thumbnailSrc, string? accessibilityCaption, string? caption, DateTimeOffset date) + { + ShortCode = shortCode; + ThumbnailSrc = thumbnailSrc; + AccessibilityCaption = accessibilityCaption; + Caption = caption; + Date = date; + } + + public string ShortCode { get; } + + public string ThumbnailSrc { get; } + + public string? AccessibilityCaption { get; } + + public string? Caption { get; } + + public DateTimeOffset Date { get; } + + public string Link + => $"https://www.instagram.com/p/{ShortCode}"; + } +} diff --git a/CollAction/Services/Proxy/IProxyService.cs b/CollAction/Services/Proxy/IProxyService.cs new file mode 100644 index 000000000..f594a454e --- /dev/null +++ b/CollAction/Services/Proxy/IProxyService.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CollAction.Services.Proxy +{ + public interface IProxyService + { + public Task Proxy(Uri url, CancellationToken token); + } +} diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs new file mode 100644 index 000000000..636b37b76 --- /dev/null +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace CollAction.Services.Proxy +{ + public sealed class ProxyService : IProxyService + { + private readonly HttpClient proxyClient; + + public ProxyService(HttpClient proxyClient) + { + this.proxyClient = proxyClient; + } + + public async Task Proxy(Uri url, CancellationToken token) + { + if (CanProxy(url)) + { + var result = await proxyClient.GetAsync(url, token).ConfigureAwait(false); + result.EnsureSuccessStatusCode(); + var stream = await result.Content.ReadAsStreamAsync().ConfigureAwait(false); + return new FileStreamResult(stream, new MediaTypeHeaderValue(result.Content.Headers.ContentType.MediaType)); + } + else + { + return new StatusCodeResult(405); + } + } + + private static bool CanProxy(Uri url) + => url.Host.EndsWith("fbcdn.net", StringComparison.Ordinal); + } +} diff --git a/CollAction/Startup.cs b/CollAction/Startup.cs index b8fc7349d..2b062ff20 100644 --- a/CollAction/Startup.cs +++ b/CollAction/Startup.cs @@ -1,4 +1,5 @@ using AspNetCore.IServiceCollection.AddIUrlHelper; +using CollAction.Controllers; using CollAction.Data; using CollAction.GraphQl; using CollAction.Helpers; @@ -10,7 +11,9 @@ using CollAction.Services.HtmlValidator; using CollAction.Services.Image; using CollAction.Services.Initialization; +using CollAction.Services.Instagram; using CollAction.Services.Newsletter; +using CollAction.Services.Proxy; using CollAction.Services.Statistics; using CollAction.Services.User; using CollAction.Services.ViewRender; @@ -26,7 +29,6 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Rewrite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -138,6 +140,14 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddHttpClient( + c => + { + c.BaseAddress = new Uri("https://www.instagram.com"); + }); + services.AddHttpClient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); From d89e0ae5a3f7e6ee517dfdf5510e3b2759b570ac Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 21:58:52 +0200 Subject: [PATCH 03/25] instagram wall for root queries as well --- .../Service/CrowdactionServiceTests.cs | 2 +- .../Mutations/Input/NewCrowdactionInputGraph.cs | 2 +- .../Input/UpdatedCrowdactionInputGraph.cs | 2 +- CollAction/GraphQl/Queries/CrowdactionGraph.cs | 13 +++++++------ ...ineItemGraph.cs => InstagramWallItemGraph.cs} | 4 ++-- CollAction/GraphQl/Queries/QueryGraph.cs | 16 ++++++++++++++++ .../20200612174558_InstagramName.Designer.cs | 6 +++--- .../Migrations/20200612174558_InstagramName.cs | 6 +++--- .../ApplicationDbContextModelSnapshot.cs | 2 +- CollAction/Models/Crowdaction.cs | 8 ++++---- .../Services/Crowdactions/CrowdactionService.cs | 6 +++--- .../Crowdactions/Models/NewCrowdaction.cs | 2 +- .../Models/NewCrowdactionInternal.cs | 6 +++--- .../Crowdactions/Models/UpdatedCrowdaction.cs | 2 +- .../Initialization/InitializationService.cs | 2 +- .../Services/Instagram/IInstagramService.cs | 2 +- .../Services/Instagram/InstagramService.cs | 12 ++++++------ ...agramTimelineItem.cs => InstagramWallItem.cs} | 4 ++-- 18 files changed, 57 insertions(+), 40 deletions(-) rename CollAction/GraphQl/Queries/{InstagramTimelineItemGraph.cs => InstagramWallItemGraph.cs} (73%) rename CollAction/Services/Instagram/Models/{InstagramTimelineItem.cs => InstagramWallItem.cs} (76%) diff --git a/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs b/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs index 56e767ede..3aa22926a 100644 --- a/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs +++ b/CollAction.Tests/Integration/Service/CrowdactionServiceTests.cs @@ -187,7 +187,7 @@ public async Task TestCrowdactionEmail() start: DateTime.Now.AddDays(-10), end: DateTime.Now.AddDays(30), goal: Guid.NewGuid().ToString(), - instagramName: "test", + instagramUser: "test", creatorComments: Guid.NewGuid().ToString(), proposal: Guid.NewGuid().ToString(), target: 40, diff --git a/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs b/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs index 987b26f9b..524841355 100644 --- a/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs +++ b/CollAction/GraphQl/Mutations/Input/NewCrowdactionInputGraph.cs @@ -16,7 +16,7 @@ public NewCrowdactionInputGraph() Field(x => x.CreatorComments, true); Field(x => x.Start); Field(x => x.End); - Field(x => x.InstagramName, true); + Field(x => x.InstagramUser, true); Field(x => x.BannerImageFileId, true); Field(x => x.CardImageFileId, true); Field(x => x.DescriptiveImageFileId, true); diff --git a/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs b/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs index a11d5a600..9081f574b 100644 --- a/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs +++ b/CollAction/GraphQl/Mutations/Input/UpdatedCrowdactionInputGraph.cs @@ -20,7 +20,7 @@ public UpdatedCrowdactionInputGraph() Field(x => x.BannerImageFileId, true); Field(x => x.CardImageFileId, true); Field(x => x.DescriptiveImageFileId, true); - Field(x => x.InstagramName, true); + Field(x => x.InstagramUser, true); Field(x => x.DescriptionVideoLink, true); Field(x => x.Tags); Field(x => x.DisplayPriority); diff --git a/CollAction/GraphQl/Queries/CrowdactionGraph.cs b/CollAction/GraphQl/Queries/CrowdactionGraph.cs index 60f18f2ee..6a8d23b74 100644 --- a/CollAction/GraphQl/Queries/CrowdactionGraph.cs +++ b/CollAction/GraphQl/Queries/CrowdactionGraph.cs @@ -35,6 +35,7 @@ public CrowdactionGraph(IEfGraphQLService entityFrameworkG Field(x => x.IsComingSoon); Field(x => x.Name); Field(x => x.NumberCrowdactionEmailsSent); + Field(x => x.InstagramUser, true); Field(nameof(Crowdaction.OwnerId)).Resolve(x => x.Source.OwnerId); Field(x => x.Proposal); Field(x => x.RemainingTime); @@ -103,21 +104,21 @@ public CrowdactionGraph(IEfGraphQLService entityFrameworkG return c.Source.Percentage; }); - FieldAsync>>, IEnumerable>( - "instagramTimeline", + FieldAsync>>, IEnumerable>( + "instagramWall", resolve: c => { - string? instagramName = c.Source.InstagramName; - if (instagramName != null) + string? instagramUser = c.Source.InstagramUser; + if (instagramUser != null) { return c.GetUserContext() .ServiceProvider .GetRequiredService() - .GetItems(instagramName, c.CancellationToken); + .GetItems(instagramUser, c.CancellationToken); } else { - return Task.FromResult(Enumerable.Empty()); + return Task.FromResult(Enumerable.Empty()); } }); AddNavigationField(nameof(Crowdaction.DescriptiveImage), c => c.Source.DescriptiveImage); diff --git a/CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs b/CollAction/GraphQl/Queries/InstagramWallItemGraph.cs similarity index 73% rename from CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs rename to CollAction/GraphQl/Queries/InstagramWallItemGraph.cs index 6dd103094..728d40e9c 100644 --- a/CollAction/GraphQl/Queries/InstagramTimelineItemGraph.cs +++ b/CollAction/GraphQl/Queries/InstagramWallItemGraph.cs @@ -3,9 +3,9 @@ namespace CollAction.GraphQl.Queries { - public sealed class InstagramTimelineItemGraph : ObjectGraphType + public sealed class InstagramWallItemGraph : ObjectGraphType { - public InstagramTimelineItemGraph() + public InstagramWallItemGraph() { Field(x => x.ShortCode); Field(x => x.Link); diff --git a/CollAction/GraphQl/Queries/QueryGraph.cs b/CollAction/GraphQl/Queries/QueryGraph.cs index cdb545850..3360ca7bb 100644 --- a/CollAction/GraphQl/Queries/QueryGraph.cs +++ b/CollAction/GraphQl/Queries/QueryGraph.cs @@ -3,14 +3,18 @@ using CollAction.Helpers; using CollAction.Models; using CollAction.Services.Crowdactions; +using CollAction.Services.Instagram; +using CollAction.Services.Instagram.Models; using GraphQL.Authorization; using GraphQL.EntityFramework; using GraphQL.Types; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Claims; +using System.Threading.Tasks; namespace CollAction.GraphQl.Queries { @@ -119,6 +123,18 @@ public QueryGraph(IEfGraphQLService entityFrameworkGraphQl c => c.DbContext.Users, typeof(ApplicationUserGraph)).AuthorizeWith(AuthorizationConstants.GraphQlAdminPolicy); + FieldAsync>>, IEnumerable>( + "instagramWall", + arguments: new QueryArguments(new QueryArgument>() { Name = "user" }), + resolve: c => + { + string instagramUser = c.GetArgument("user"); + return c.GetUserContext() + .ServiceProvider + .GetRequiredService() + .GetItems(instagramUser, c.CancellationToken); + }); + AddSingleField( name: "user", resolve: c => c.DbContext.Users, diff --git a/CollAction/Migrations/20200612174558_InstagramName.Designer.cs b/CollAction/Migrations/20200612174558_InstagramName.Designer.cs index 23b19c17d..b9351cddb 100644 --- a/CollAction/Migrations/20200612174558_InstagramName.Designer.cs +++ b/CollAction/Migrations/20200612174558_InstagramName.Designer.cs @@ -10,8 +10,8 @@ namespace CollAction.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20200612174558_InstagramName")] - partial class InstagramName + [Migration("20200612174558_InstagramUser")] + partial class InstagramUser { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -150,7 +150,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("character varying(10000)") .HasMaxLength(10000); - b.Property("InstagramName") + b.Property("InstagramUser") .HasColumnType("character varying(30)") .HasMaxLength(30); diff --git a/CollAction/Migrations/20200612174558_InstagramName.cs b/CollAction/Migrations/20200612174558_InstagramName.cs index edcf73623..e4ea4dc8e 100644 --- a/CollAction/Migrations/20200612174558_InstagramName.cs +++ b/CollAction/Migrations/20200612174558_InstagramName.cs @@ -2,7 +2,7 @@ namespace CollAction.Migrations { - public partial class InstagramName : Migration + public partial class InstagramUser : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) oldNullable: true); migrationBuilder.AddColumn( - name: "InstagramName", + name: "InstagramUser", table: "Crowdactions", maxLength: 30, nullable: true); @@ -25,7 +25,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "InstagramName", + name: "InstagramUser", table: "Crowdactions"); migrationBuilder.AlterColumn( diff --git a/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs b/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs index 74d48c427..557be68e1 100644 --- a/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/CollAction/Migrations/ApplicationDbContextModelSnapshot.cs @@ -148,7 +148,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(10000)") .HasMaxLength(10000); - b.Property("InstagramName") + b.Property("InstagramUser") .HasColumnType("character varying(30)") .HasMaxLength(30); diff --git a/CollAction/Models/Crowdaction.cs b/CollAction/Models/Crowdaction.cs index 752a0f68b..399862652 100644 --- a/CollAction/Models/Crowdaction.cs +++ b/CollAction/Models/Crowdaction.cs @@ -11,11 +11,11 @@ namespace CollAction.Models { public sealed class Crowdaction { - public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string? instagramName, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) : this(name, status, ownerId, target, start, end, instagramName, description, goal, proposal, creatorComments, descriptionVideoLink, new List(), new List(), displayPriority, bannerImageFileId, descriptiveImageFileId, cardImageFileId, anonymousUserParticipants) + public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string? instagramUser, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) : this(name, status, ownerId, target, start, end, instagramUser, description, goal, proposal, creatorComments, descriptionVideoLink, new List(), new List(), displayPriority, bannerImageFileId, descriptiveImageFileId, cardImageFileId, anonymousUserParticipants) { } - public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string? instagramName, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, ICollection categories, ICollection tags, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) + public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int target, DateTime start, DateTime end, string? instagramUser, string description, string goal, string proposal, string? creatorComments, string? descriptionVideoLink, ICollection categories, ICollection tags, CrowdactionDisplayPriority displayPriority = CrowdactionDisplayPriority.Medium, int? bannerImageFileId = null, int? descriptiveImageFileId = null, int? cardImageFileId = null, int anonymousUserParticipants = 0) { Name = name; Status = status; @@ -35,7 +35,7 @@ public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int t Categories = categories; Tags = tags; AnonymousUserParticipants = anonymousUserParticipants; - InstagramName = instagramName; + InstagramUser = instagramUser; } [NotMapped] @@ -106,7 +106,7 @@ public Crowdaction(string name, CrowdactionStatus status, string? ownerId, int t [MinLength(1)] [MaxLength(30)] - public string? InstagramName { get; set; } + public string? InstagramUser { get; set; } public CrowdactionParticipantCount? ParticipantCounts { get; set; } diff --git a/CollAction/Services/Crowdactions/CrowdactionService.cs b/CollAction/Services/Crowdactions/CrowdactionService.cs index 26fc17863..319a83e33 100644 --- a/CollAction/Services/Crowdactions/CrowdactionService.cs +++ b/CollAction/Services/Crowdactions/CrowdactionService.cs @@ -106,7 +106,7 @@ await context.Tags description: newCrowdaction.Description, goal: newCrowdaction.Goal, proposal: newCrowdaction.Proposal, - instagramName: newCrowdaction.InstagramName, + instagramUser: newCrowdaction.InstagramUser, creatorComments: newCrowdaction.CreatorComments, descriptionVideoLink: newCrowdaction.DescriptionVideoLink?.Replace("www.youtube.com", "www.youtube-nocookie.com", StringComparison.Ordinal), displayPriority: newCrowdaction.DisplayPriority, @@ -189,7 +189,7 @@ await context.Tags description: newCrowdaction.Description, goal: newCrowdaction.Goal, proposal: newCrowdaction.Proposal, - instagramName: newCrowdaction.InstagramName, + instagramUser: newCrowdaction.InstagramUser, creatorComments: newCrowdaction.CreatorComments, descriptionVideoLink: newCrowdaction.DescriptionVideoLink?.Replace("www.youtube.com", "www.youtube-nocookie.com", StringComparison.Ordinal), displayPriority: CrowdactionDisplayPriority.Medium, @@ -273,7 +273,7 @@ public async Task UpdateCrowdaction(UpdatedCrowdaction update crowdaction.Proposal = updatedCrowdaction.Proposal; crowdaction.Goal = updatedCrowdaction.Goal; crowdaction.CreatorComments = updatedCrowdaction.CreatorComments; - crowdaction.InstagramName = updatedCrowdaction.InstagramName; + crowdaction.InstagramUser = updatedCrowdaction.InstagramUser; crowdaction.Target = updatedCrowdaction.Target; crowdaction.Start = updatedCrowdaction.Start; crowdaction.End = updatedCrowdaction.End.Date.AddHours(23).AddMinutes(59).AddSeconds(59); diff --git a/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs b/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs index e4b9dda28..b7a864392 100644 --- a/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs +++ b/CollAction/Services/Crowdactions/Models/NewCrowdaction.cs @@ -44,7 +44,7 @@ public sealed class NewCrowdaction [MinLength(1)] [MaxLength(30)] - public string? InstagramName { get; set; } + public string? InstagramUser { get; set; } public int? CardImageFileId { get; set; } diff --git a/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs b/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs index b09de79cb..515b87fd3 100644 --- a/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs +++ b/CollAction/Services/Crowdactions/Models/NewCrowdactionInternal.cs @@ -8,7 +8,7 @@ namespace CollAction.Services.Crowdactions.Models { public sealed class NewCrowdactionInternal { - public NewCrowdactionInternal(string name, int target, string proposal, string description, string goal, string? creatorComments, DateTime start, DateTime end, string? instagramName, int? cardImageFileId, int? bannerImageFileId, int? descriptiveImageFileId, string? descriptionVideoLink, IEnumerable categories, IEnumerable tags, CrowdactionDisplayPriority displayPriority, CrowdactionStatus status, int anonymousUserParticipants, string? ownerId) + public NewCrowdactionInternal(string name, int target, string proposal, string description, string goal, string? creatorComments, DateTime start, DateTime end, string? instagramUser, int? cardImageFileId, int? bannerImageFileId, int? descriptiveImageFileId, string? descriptionVideoLink, IEnumerable categories, IEnumerable tags, CrowdactionDisplayPriority displayPriority, CrowdactionStatus status, int anonymousUserParticipants, string? ownerId) { Name = name; Target = target; @@ -22,7 +22,7 @@ public NewCrowdactionInternal(string name, int target, string proposal, string d BannerImageFileId = bannerImageFileId; DescriptiveImageFileId = descriptiveImageFileId; DescriptionVideoLink = descriptionVideoLink; - InstagramName = instagramName; + InstagramUser = instagramUser; Categories = categories; Tags = tags; DisplayPriority = displayPriority; @@ -55,7 +55,7 @@ public NewCrowdactionInternal(string name, int target, string proposal, string d public string? DescriptionVideoLink { get; set; } - public string? InstagramName { get; } + public string? InstagramUser { get; } public IEnumerable Categories { get; set; } diff --git a/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs b/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs index 8e95143d5..913bd3265 100644 --- a/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs +++ b/CollAction/Services/Crowdactions/Models/UpdatedCrowdaction.cs @@ -45,7 +45,7 @@ public sealed class UpdatedCrowdaction [MinLength(1)] [MaxLength(30)] - public string? InstagramName { get; set; } + public string? InstagramUser { get; set; } public int? CardImageFileId { get; set; } diff --git a/CollAction/Services/Initialization/InitializationService.cs b/CollAction/Services/Initialization/InitializationService.cs index be13ed2b2..1f34ab53d 100644 --- a/CollAction/Services/Initialization/InitializationService.cs +++ b/CollAction/Services/Initialization/InitializationService.cs @@ -248,7 +248,7 @@ private async Task SeedRandomCrowdactions(IEnumerable users, Ca bannerImageFileId: bannerImage?.Id, descriptiveImageFileId: descriptiveImage?.Id, cardImageFileId: cardImage?.Id, - instagramName: "slowfashionseason", + instagramUser: "slowfashionseason", creatorComments: r.Next(4) == 0 ? null : $"

{string.Join("

", Faker.Lorem.Paragraphs(r.Next(3) + 1))}

", goal: Faker.Company.CatchPhrase(), proposal: Faker.Company.BS(), diff --git a/CollAction/Services/Instagram/IInstagramService.cs b/CollAction/Services/Instagram/IInstagramService.cs index 9d30cd312..3e6063f4e 100644 --- a/CollAction/Services/Instagram/IInstagramService.cs +++ b/CollAction/Services/Instagram/IInstagramService.cs @@ -7,6 +7,6 @@ namespace CollAction.Services.Instagram { public interface IInstagramService { - public Task> GetItems(string instagramName, CancellationToken token); + public Task> GetItems(string instagramUser, CancellationToken token); } } diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs index b7046dd20..55286a62e 100644 --- a/CollAction/Services/Instagram/InstagramService.cs +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -25,10 +25,10 @@ public InstagramService(IMemoryCache cache, HttpClient instagramClient, ILogger< this.logger = logger; } - public async Task> GetItems(string instagramName, CancellationToken token) + public async Task> GetItems(string instagramUser, CancellationToken token) { // TODO: Stop using the instagram private api - Uri url = new Uri($"/{instagramName}/?__a=1", UriKind.Relative); + Uri url = new Uri($"/{instagramUser}/?__a=1", UriKind.Relative); try { return await cache.GetOrCreateAsync( @@ -36,7 +36,7 @@ public async Task> GetItems(string instagramN async (ICacheEntry entry) => { entry.SlidingExpiration = CacheExpiration; - logger.LogInformation("Retrieving instagram timeline for {0}", instagramName); + logger.LogInformation("Retrieving instagram timeline for {0}", instagramUser); var result = await instagramClient.GetAsync(url, token).ConfigureAwait(false); result.EnsureSuccessStatusCode(); return ParseInstagramResponse(await result.Content.ReadAsStringAsync().ConfigureAwait(false)); @@ -45,11 +45,11 @@ public async Task> GetItems(string instagramN catch (Exception e) { logger.LogError(e, "Error retrieving items from instagram timeline"); - return Enumerable.Empty(); // External APIs can fail here, lets be a little robust here. Don't cache the failures though.. so catch outside the cache + return Enumerable.Empty(); // External APIs can fail here, lets be a little robust here. Don't cache the failures though.. so catch outside the cache } } - private IEnumerable ParseInstagramResponse(string json) + private IEnumerable ParseInstagramResponse(string json) { dynamic deserialized = JsonConvert.DeserializeObject(json); var edges = (IEnumerable)(deserialized.graphql?.user?.edge_owner_to_timeline_media?.edges ?? Enumerable.Empty()); @@ -61,7 +61,7 @@ private IEnumerable ParseInstagramResponse(string json) { var captionEdges = (IEnumerable?)e.edge_media_to_caption?.edges; string? caption = (string?)captionEdges?.FirstOrDefault()?.node?.text; - return new InstagramTimelineItem( + return new InstagramWallItem( (string)e.shortcode, (string)e.thumbnail_src, (string?)e.accessibility_caption, diff --git a/CollAction/Services/Instagram/Models/InstagramTimelineItem.cs b/CollAction/Services/Instagram/Models/InstagramWallItem.cs similarity index 76% rename from CollAction/Services/Instagram/Models/InstagramTimelineItem.cs rename to CollAction/Services/Instagram/Models/InstagramWallItem.cs index dcee2d0e7..1b88f2c60 100644 --- a/CollAction/Services/Instagram/Models/InstagramTimelineItem.cs +++ b/CollAction/Services/Instagram/Models/InstagramWallItem.cs @@ -2,9 +2,9 @@ namespace CollAction.Services.Instagram.Models { - public sealed class InstagramTimelineItem + public sealed class InstagramWallItem { - public InstagramTimelineItem(string shortCode, string thumbnailSrc, string? accessibilityCaption, string? caption, DateTimeOffset date) + public InstagramWallItem(string shortCode, string thumbnailSrc, string? accessibilityCaption, string? caption, DateTimeOffset date) { ShortCode = shortCode; ThumbnailSrc = thumbnailSrc; From 6d28d931c0ca525642c11efd93bc2caa285880da Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 22:35:06 +0200 Subject: [PATCH 04/25] Display instagram wall using new api --- CollAction/Controllers/GraphQlController.cs | 2 +- Frontend/src/api/fragments.ts | 8 +++ Frontend/src/api/types.ts | 11 +++ .../InstagramWall/InstagramWall.tsx | 72 +++++-------------- Frontend/src/pages/Home/Home.tsx | 29 +++++++- .../Detail/CrowdactionDetails.tsx | 12 ++-- 6 files changed, 68 insertions(+), 66 deletions(-) diff --git a/CollAction/Controllers/GraphQlController.cs b/CollAction/Controllers/GraphQlController.cs index b5c0b6377..627119861 100644 --- a/CollAction/Controllers/GraphQlController.cs +++ b/CollAction/Controllers/GraphQlController.cs @@ -140,7 +140,7 @@ private async Task Execute(string query, string? operationName, UserContext = new UserContext(User, context, serviceProvider), ComplexityConfiguration = new ComplexityConfiguration() { - MaxDepth = 20 + MaxDepth = 21 }, ValidationRules = validationRules, CancellationToken = cancellation, diff --git a/Frontend/src/api/fragments.ts b/Frontend/src/api/fragments.ts index 86f442788..f607c7e89 100644 --- a/Frontend/src/api/fragments.ts +++ b/Frontend/src/api/fragments.ts @@ -32,6 +32,14 @@ export const Fragments = { name } } + instagramWall { + shortCode + thumbnailSrc + caption + accessibilityCaption + link + } + instagramUser goal start end diff --git a/Frontend/src/api/types.ts b/Frontend/src/api/types.ts index b60d91514..c01dc784d 100644 --- a/Frontend/src/api/types.ts +++ b/Frontend/src/api/types.ts @@ -60,6 +60,15 @@ export enum CrowdactionDisplayPriority { BOTTOM = "BOTTOM", } +export interface IInstagramWallItem { + shortCode: string; + thumbnailSrc: string; + caption: string | null; + accessibilityCaption: string | null; + link: string; + date: string; +} + export interface ICrowdaction { anonymousUserParticipants: number; bannerImage: IImageFile; @@ -74,6 +83,8 @@ export interface ICrowdaction { descriptiveImage: IImageFile; descriptiveImageFileId: string; displayPriority: CrowdactionDisplayPriority; + instagramWall: IInstagramWallItem[]; + instagramUser: string | null; start: string; end: string; goal: string; diff --git a/Frontend/src/components/InstagramWall/InstagramWall.tsx b/Frontend/src/components/InstagramWall/InstagramWall.tsx index d99e4f045..9bb046d8f 100644 --- a/Frontend/src/components/InstagramWall/InstagramWall.tsx +++ b/Frontend/src/components/InstagramWall/InstagramWall.tsx @@ -2,68 +2,28 @@ import React, {useState} from "react"; import styles from "./InstagramWall.module.scss"; import Formatter from "../../formatter"; -import moment from "moment"; +import { IInstagramWallItem } from "../../api/types"; +import LazyImage from "../LazyImage/LazyImage"; export interface IInstagramWallProps { - user: string; + wallItems: IInstagramWallItem[]; } -export class InstagramWall extends React.Component { - constructor(props: Readonly) { - super(props); - this.state = {items: []} - } +const getProxiedUrl = (url: string) => + `${process.env.REACT_APP_BACKEND_URL}/proxy?url=${encodeURIComponent(url)}`; - //@todo: thumbnail quality - //@todo: video's: is_video - //@todo: check faulty returns - - componentDidMount() { - - let url = "https://www.instagram.com/" + this.props.user + "/?__a=1"; - let _this = this; - - fetch(url).then(res => res.json()) - .then(res => { - _this.setState({ - items: res.graphql.user.edge_owner_to_timeline_media.edges.map((edge: { node: any; }) => { - let { - shortcode, - thumbnail_src, - accessibility_caption, - edge_media_to_caption, - taken_at_timestamp, - } = edge.node; - return { - shortcode, - thumbnail_src, - accessibility_caption, - date: Formatter.date(moment.unix(taken_at_timestamp).toDate()), - caption: edge_media_to_caption && - edge_media_to_caption.edges && - edge_media_to_caption.edges[0] && - edge_media_to_caption.edges[0].node && - edge_media_to_caption.edges[0].node.text, - link: "https://www.instagram.com/p/" + shortcode, - } - }) - }); - }) - } - - - render() { - return
- { - // @ts-ignore - this.state.items.map(item => - {item.accessibility_caption}/ +export default (props: IInstagramWallProps) => ( + ; - } -} +
{Formatter.date(new Date(item.date))}
+ )) + } +
+); diff --git a/Frontend/src/pages/Home/Home.tsx b/Frontend/src/pages/Home/Home.tsx index 7fd34d748..dc6f694c1 100644 --- a/Frontend/src/pages/Home/Home.tsx +++ b/Frontend/src/pages/Home/Home.tsx @@ -9,10 +9,34 @@ import TimeToAct from "../../components/TimeToAct/TimeToAct"; import { useTranslation } from 'react-i18next'; import { Helmet } from "react-helmet"; -import {InstagramWall} from "../../components/InstagramWall/InstagramWall"; +import { gql, useQuery } from "@apollo/client"; +import InstagramWall from "../../components/InstagramWall/InstagramWall"; +import { Alert } from "../../components/Alert/Alert"; +import { IInstagramWallItem } from "../../api/types"; + +const GET_INSTAGRAM_WALL = gql` + query GetInstagramWall($user: String!) { + instagramWall(user: $user) { + shortCode + thumbnailSrc + caption + accessibilityCaption + link + } + } +`; const HomePage = () => { const { t } = useTranslation(); + const { data, error, loading } = useQuery( + GET_INSTAGRAM_WALL, + { + variables: { + user: "collaction_org" + } + } + ); + const wallData = data?.instagramWall as IInstagramWallItem[] | null; return ( <> @@ -39,7 +63,8 @@ const HomePage = () => {
Instagram: @collaction_org - + + { wallData && }
) diff --git a/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx b/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx index 733932b21..89c7d2aa3 100644 --- a/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx +++ b/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx @@ -26,7 +26,7 @@ import Formatter from '../../../formatter'; import LazyImage from '../../../components/LazyImage/LazyImage'; import { TextField } from 'formik-material-ui'; import CrowdactionComments from '../../../components/CrowdactionComments/CrowdactionComments'; -import {InstagramWall} from "../../../components/InstagramWall/InstagramWall"; +import InstagramWall from '../../../components/InstagramWall/InstagramWall'; type TParams = { slug: string; @@ -172,8 +172,6 @@ const CrowdactionDetailsPage = ({ crowdaction.categories[0] ? crowdaction.categories[0].category : 'OTHER' }.jpg`); - let instagramUser = "slowfashionseason"; - return ( <> @@ -266,11 +264,11 @@ const CrowdactionDetailsPage = ({ )} - {instagramUser &&
- -

Instagram @{instagramUser}

+ {crowdaction.instagramUser &&
} From 8a0b34452c947c71a3f083fdcf06e5cd3c38566a Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 22:52:20 +0200 Subject: [PATCH 05/25] Seed instagram users better --- CollAction/Services/Initialization/InitializationService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CollAction/Services/Initialization/InitializationService.cs b/CollAction/Services/Initialization/InitializationService.cs index 1f34ab53d..5feec74ab 100644 --- a/CollAction/Services/Initialization/InitializationService.cs +++ b/CollAction/Services/Initialization/InitializationService.cs @@ -189,6 +189,8 @@ private async Task SeedRandomCrowdactions(IEnumerable users, Ca "https://collaction-production.s3.eu-central-1.amazonaws.com/6e6c12b1-eaae-4811-aa1c-c169d10f1a59.png", }.Select(b => new Uri(b)).Select(b => (b, DownloadFile(b, cancellationToken))).ToList(); + string?[] instagramUsers = new string?[] { "slowfashionseason", "collaction_org", null }; + await Task.WhenAll(descriptiveImages.Select(d => d.descriptiveImageBytes).Concat(bannerImages.Select(b => b.bannerImageBytes))).ConfigureAwait(false); List tags = Enumerable.Range(10, r.Next(60)) @@ -248,7 +250,7 @@ private async Task SeedRandomCrowdactions(IEnumerable users, Ca bannerImageFileId: bannerImage?.Id, descriptiveImageFileId: descriptiveImage?.Id, cardImageFileId: cardImage?.Id, - instagramUser: "slowfashionseason", + instagramUser: instagramUsers[r.Next(instagramUsers.Length)], creatorComments: r.Next(4) == 0 ? null : $"

{string.Join("

", Faker.Lorem.Paragraphs(r.Next(3) + 1))}

", goal: Faker.Company.CatchPhrase(), proposal: Faker.Company.BS(), From 6e19ec4f940133fe5d9021610a42de9066508468 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 22:52:30 +0200 Subject: [PATCH 06/25] Fix insta display issues --- Frontend/src/api/fragments.ts | 1 + Frontend/src/components/InstagramWall/InstagramWall.tsx | 2 +- Frontend/src/pages/Home/Home.tsx | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Frontend/src/api/fragments.ts b/Frontend/src/api/fragments.ts index f607c7e89..0e3bc7898 100644 --- a/Frontend/src/api/fragments.ts +++ b/Frontend/src/api/fragments.ts @@ -38,6 +38,7 @@ export const Fragments = { caption accessibilityCaption link + date } instagramUser goal diff --git a/Frontend/src/components/InstagramWall/InstagramWall.tsx b/Frontend/src/components/InstagramWall/InstagramWall.tsx index 9bb046d8f..b1eb2d1dc 100644 --- a/Frontend/src/components/InstagramWall/InstagramWall.tsx +++ b/Frontend/src/components/InstagramWall/InstagramWall.tsx @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React from "react"; import styles from "./InstagramWall.module.scss"; import Formatter from "../../formatter"; diff --git a/Frontend/src/pages/Home/Home.tsx b/Frontend/src/pages/Home/Home.tsx index dc6f694c1..6b2430dfc 100644 --- a/Frontend/src/pages/Home/Home.tsx +++ b/Frontend/src/pages/Home/Home.tsx @@ -13,6 +13,7 @@ import { gql, useQuery } from "@apollo/client"; import InstagramWall from "../../components/InstagramWall/InstagramWall"; import { Alert } from "../../components/Alert/Alert"; import { IInstagramWallItem } from "../../api/types"; +import Loader from "../../components/Loader/Loader"; const GET_INSTAGRAM_WALL = gql` query GetInstagramWall($user: String!) { @@ -22,6 +23,7 @@ const GET_INSTAGRAM_WALL = gql` caption accessibilityCaption link + date } } `; @@ -64,6 +66,7 @@ const HomePage = () => {
Instagram: @collaction_org + { loading && } { wallData && }
From 79367036d7ac353c387e1711f3706ee0653d8058 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 23:04:27 +0200 Subject: [PATCH 07/25] Allow editting instagram in admin ui --- .../Admin/Crowdactions/AdminEditCrowdaction.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx index 8a096eafa..8a1cea93e 100644 --- a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx +++ b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx @@ -76,6 +76,7 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { goal: "", status: "", proposal: "", + instagramUser: "", target: "", anonymousUserParticipants: 0, firstCategory: "", @@ -102,6 +103,7 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { target: Yup.number().required("Must be filled in").min(1, "Must be a positive number"), anonymousUserParticipants: Yup.number().required("Must be filled in").min(0, "Must be a positive number"), firstCategory: Yup.string(), + instagramUser: Yup.string(), secondCategory: Yup.string(), tags: Yup.string(), numberCrowdactionEmailsSent: Yup.number().required("Must be filled in").min(0, "Must be a positive number"), @@ -140,6 +142,7 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { creatorComments: values.creatorComments === "" ? null : values.creatorComments, start: values.start, end: values.end, + instagramUser: values.instagramUser ? values.instagramUser : null, descriptionVideoLink: values.descriptionVideoLink === "" ? null : values.descriptionVideoLink, displayPriority: values.displayPriority, numberCrowdactionEmailsSent: values.numberCrowdactionEmailsSent, @@ -168,6 +171,7 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { displayPriority: data.crowdaction.displayPriority, start: data.crowdaction.start, end: data.crowdaction.end, + instagramUser: data.crowdaction.instagramUser ?? "", ownerEmail: data.crowdaction.ownerWithEmail?.email ?? "", ownerId: formik.values.ownerId, status: data.crowdaction.status, @@ -301,6 +305,15 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { /> + + + + Date: Fri, 12 Jun 2020 23:16:10 +0200 Subject: [PATCH 08/25] Add instagram user to start project form --- Frontend/src/pages/crowdactions/Create/Create.tsx | 11 +++++++++++ Frontend/src/pages/crowdactions/Create/form.ts | 3 +++ 2 files changed, 14 insertions(+) diff --git a/Frontend/src/pages/crowdactions/Create/Create.tsx b/Frontend/src/pages/crowdactions/Create/Create.tsx index ba06dd4b0..c5d076729 100644 --- a/Frontend/src/pages/crowdactions/Create/Create.tsx +++ b/Frontend/src/pages/crowdactions/Create/Create.tsx @@ -81,6 +81,7 @@ const CreateCrowdactionPage = () => { start: form.startDate, end: form.endDate, goal: form.goal, + instagramUser: form.instagramUser || null, tags: form.tags ? form.tags.split(';') : [], creatorComments: form.comments || null, descriptiveImageFileId: imageId || null, @@ -292,6 +293,16 @@ const CreateCrowdactionPage = () => { fullWidth > + + + diff --git a/Frontend/src/pages/crowdactions/Create/form.ts b/Frontend/src/pages/crowdactions/Create/form.ts index 74bf360a6..be8889886 100644 --- a/Frontend/src/pages/crowdactions/Create/form.ts +++ b/Frontend/src/pages/crowdactions/Create/form.ts @@ -9,6 +9,7 @@ export interface ICrowdactionForm { startDate: string; endDate: string; tags: string; + instagramUser: string; description: string; goal: string; image: any | null; @@ -27,6 +28,7 @@ export const initialValues: ICrowdactionForm = { tags: '', endDate: '', description: '', + instagramUser: '', goal: '', image: null, imageDescription: '', @@ -84,6 +86,7 @@ export const validations = Yup.object({ hashtags: Yup.string() .max(30, 'Please keep the number of hashtags civil, no more then 30 characters') .matches(/^[a-zA-Z_0-9]+(;[a-zA-Z_0-9]+)*$/, 'Don\'t use spaces or #, must contain a letter, can contain digits and underscores. Separate multiple tags with a colon \';\''), + instagramUser: Yup.string(), description: Yup.string() .required('Give a succinct description of what you are gathering participants for') .max(10000, 'Please use no more then 10.000 characters'), From 0c028b17f8c7d6d77d025470f416e829de128955 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 12 Jun 2020 23:22:40 +0200 Subject: [PATCH 09/25] tweak submit --- .../components/Admin/Crowdactions/AdminEditCrowdaction.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx index 8a1cea93e..2855482d7 100644 --- a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx +++ b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx @@ -139,10 +139,10 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { proposal: values.proposal, description: values.description, goal: values.goal, - creatorComments: values.creatorComments === "" ? null : values.creatorComments, + creatorComments: values.creatorComments || null, start: values.start, end: values.end, - instagramUser: values.instagramUser ? values.instagramUser : null, + instagramUser: values.instagramUser || null, descriptionVideoLink: values.descriptionVideoLink === "" ? null : values.descriptionVideoLink, displayPriority: values.displayPriority, numberCrowdactionEmailsSent: values.numberCrowdactionEmailsSent, From c10f881b6a901db432f43ffb9932cedac766b07d Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 00:48:01 +0200 Subject: [PATCH 10/25] Add id for caching, add last-modified for caching --- CollAction/GraphQl/Queries/InstagramWallItemGraph.cs | 1 + CollAction/Services/Proxy/ProxyService.cs | 5 ++++- Frontend/src/api/fragments.ts | 1 + Frontend/src/api/types.ts | 1 + Frontend/src/pages/Home/Home.tsx | 1 + 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CollAction/GraphQl/Queries/InstagramWallItemGraph.cs b/CollAction/GraphQl/Queries/InstagramWallItemGraph.cs index 728d40e9c..f55c6806a 100644 --- a/CollAction/GraphQl/Queries/InstagramWallItemGraph.cs +++ b/CollAction/GraphQl/Queries/InstagramWallItemGraph.cs @@ -7,6 +7,7 @@ public sealed class InstagramWallItemGraph : ObjectGraphType { public InstagramWallItemGraph() { + Field("id", resolve: x => x.Source.ShortCode); Field(x => x.ShortCode); Field(x => x.Link); Field(x => x.Date); diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs index 636b37b76..6411f8494 100644 --- a/CollAction/Services/Proxy/ProxyService.cs +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -23,7 +23,10 @@ public async Task Proxy(Uri url, CancellationToken token) var result = await proxyClient.GetAsync(url, token).ConfigureAwait(false); result.EnsureSuccessStatusCode(); var stream = await result.Content.ReadAsStreamAsync().ConfigureAwait(false); - return new FileStreamResult(stream, new MediaTypeHeaderValue(result.Content.Headers.ContentType.MediaType)); + return new FileStreamResult(stream, new MediaTypeHeaderValue(result.Content.Headers.ContentType.MediaType)) + { + LastModified = result.Content.Headers.LastModified + }; } else { diff --git a/Frontend/src/api/fragments.ts b/Frontend/src/api/fragments.ts index 0e3bc7898..02b453033 100644 --- a/Frontend/src/api/fragments.ts +++ b/Frontend/src/api/fragments.ts @@ -33,6 +33,7 @@ export const Fragments = { } } instagramWall { + id shortCode thumbnailSrc caption diff --git a/Frontend/src/api/types.ts b/Frontend/src/api/types.ts index c01dc784d..1b4f01ba1 100644 --- a/Frontend/src/api/types.ts +++ b/Frontend/src/api/types.ts @@ -61,6 +61,7 @@ export enum CrowdactionDisplayPriority { } export interface IInstagramWallItem { + id: string; shortCode: string; thumbnailSrc: string; caption: string | null; diff --git a/Frontend/src/pages/Home/Home.tsx b/Frontend/src/pages/Home/Home.tsx index 6b2430dfc..25dcc9760 100644 --- a/Frontend/src/pages/Home/Home.tsx +++ b/Frontend/src/pages/Home/Home.tsx @@ -18,6 +18,7 @@ import Loader from "../../components/Loader/Loader"; const GET_INSTAGRAM_WALL = gql` query GetInstagramWall($user: String!) { instagramWall(user: $user) { + id shortCode thumbnailSrc caption From 26b7b04c61f09f754e4e7bd484c87dafd255373f Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 01:13:03 +0200 Subject: [PATCH 11/25] Formattnig differently --- Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx b/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx index 89c7d2aa3..ed8f0dea8 100644 --- a/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx +++ b/Frontend/src/pages/crowdactions/Detail/CrowdactionDetails.tsx @@ -265,7 +265,7 @@ const CrowdactionDetailsPage = ({ )} {crowdaction.instagramUser &&
- +

Instagram @{crowdaction.instagramUser}

From b97181d562f0f9c56c96e5c9447eccb1597b177a Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 01:32:29 +0200 Subject: [PATCH 12/25] Add debug logging why json is failing --- CollAction/Services/Instagram/InstagramService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs index 55286a62e..f0afe2a7f 100644 --- a/CollAction/Services/Instagram/InstagramService.cs +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -51,6 +51,7 @@ public async Task> GetItems(string instagramUser, private IEnumerable ParseInstagramResponse(string json) { + logger.LogDebug(json); dynamic deserialized = JsonConvert.DeserializeObject(json); var edges = (IEnumerable)(deserialized.graphql?.user?.edge_owner_to_timeline_media?.edges ?? Enumerable.Empty()); logger.LogInformation("Retrieving instagram timeline, found {0} items", edges.Count()); From 0af0aa5d6b6389d7f3e47e3043123f19e41fc3c9 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 01:37:57 +0200 Subject: [PATCH 13/25] Better logging.. --- CollAction/Services/Instagram/InstagramService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs index f0afe2a7f..bf21b24aa 100644 --- a/CollAction/Services/Instagram/InstagramService.cs +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -51,7 +51,7 @@ public async Task> GetItems(string instagramUser, private IEnumerable ParseInstagramResponse(string json) { - logger.LogDebug(json); + logger.LogDebug("Instagram JSON: {0}", json); dynamic deserialized = JsonConvert.DeserializeObject(json); var edges = (IEnumerable)(deserialized.graphql?.user?.edge_owner_to_timeline_media?.edges ?? Enumerable.Empty()); logger.LogInformation("Retrieving instagram timeline, found {0} items", edges.Count()); From 487aacd4ae0bc5e00985bdf705a1a7d457ca8b25 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 01:38:21 +0200 Subject: [PATCH 14/25] To loginfo so it doesn't get lost --- CollAction/Services/Instagram/InstagramService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs index bf21b24aa..f08c09c39 100644 --- a/CollAction/Services/Instagram/InstagramService.cs +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -51,7 +51,7 @@ public async Task> GetItems(string instagramUser, private IEnumerable ParseInstagramResponse(string json) { - logger.LogDebug("Instagram JSON: {0}", json); + logger.LogInformation("Instagram JSON: {0}", json); dynamic deserialized = JsonConvert.DeserializeObject(json); var edges = (IEnumerable)(deserialized.graphql?.user?.edge_owner_to_timeline_media?.edges ?? Enumerable.Empty()); logger.LogInformation("Retrieving instagram timeline, found {0} items", edges.Count()); From 8e08a16501c728e6abf8a2cc03f29b45ac8c415e Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 01:51:52 +0200 Subject: [PATCH 15/25] Only debug logging --- CollAction/Services/Instagram/InstagramService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs index f08c09c39..bf21b24aa 100644 --- a/CollAction/Services/Instagram/InstagramService.cs +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -51,7 +51,7 @@ public async Task> GetItems(string instagramUser, private IEnumerable ParseInstagramResponse(string json) { - logger.LogInformation("Instagram JSON: {0}", json); + logger.LogDebug("Instagram JSON: {0}", json); dynamic deserialized = JsonConvert.DeserializeObject(json); var edges = (IEnumerable)(deserialized.graphql?.user?.edge_owner_to_timeline_media?.edges ?? Enumerable.Empty()); logger.LogInformation("Retrieving instagram timeline, found {0} items", edges.Count()); From 05da4970f783fd21536f4b0e525411c995d699dc Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sat, 13 Jun 2020 23:56:33 +0200 Subject: [PATCH 16/25] Add cndinstagram to allowed proxy hosts --- CollAction/Services/Proxy/ProxyService.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs index 6411f8494..59add4205 100644 --- a/CollAction/Services/Proxy/ProxyService.cs +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Net.Http.Headers; using System; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -10,6 +12,12 @@ namespace CollAction.Services.Proxy public sealed class ProxyService : IProxyService { private readonly HttpClient proxyClient; + private static string[] AllowedHosts = + new string[] + { + "fbcdn.net", + "cdninstagram.com" + }; public ProxyService(HttpClient proxyClient) { @@ -35,6 +43,6 @@ public async Task Proxy(Uri url, CancellationToken token) } private static bool CanProxy(Uri url) - => url.Host.EndsWith("fbcdn.net", StringComparison.Ordinal); + => AllowedHosts.Any(h => url.Host.EndsWith(h, StringComparison.Ordinal)); } } From 6dceedf6c116fbb71a0543ab7b2c492523a5d255 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sun, 14 Jun 2020 21:46:59 +0200 Subject: [PATCH 17/25] Caching timeout a little higher --- CollAction/Services/Instagram/InstagramService.cs | 2 +- CollAction/Services/Proxy/ProxyService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CollAction/Services/Instagram/InstagramService.cs b/CollAction/Services/Instagram/InstagramService.cs index bf21b24aa..51758a3f6 100644 --- a/CollAction/Services/Instagram/InstagramService.cs +++ b/CollAction/Services/Instagram/InstagramService.cs @@ -16,7 +16,7 @@ public sealed class InstagramService : IInstagramService private readonly IMemoryCache cache; private readonly HttpClient instagramClient; private readonly ILogger logger; - private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(10); + private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(30); public InstagramService(IMemoryCache cache, HttpClient instagramClient, ILogger logger) { diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs index 59add4205..1cfc33f29 100644 --- a/CollAction/Services/Proxy/ProxyService.cs +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -12,7 +12,7 @@ namespace CollAction.Services.Proxy public sealed class ProxyService : IProxyService { private readonly HttpClient proxyClient; - private static string[] AllowedHosts = + private static readonly string[] AllowedHosts = new string[] { "fbcdn.net", From 59e78fab8179b5b2117600c269912384069aede1 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sun, 14 Jun 2020 21:58:06 +0200 Subject: [PATCH 18/25] Exclude test in github actions because instagram is blocked --- .github/workflows/on_push.yml | 2 +- CollAction.Tests/Integration/Service/InstagramServiceTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml index af54405d1..b1bb734fe 100644 --- a/.github/workflows/on_push.yml +++ b/.github/workflows/on_push.yml @@ -55,7 +55,7 @@ jobs: - name: Install Dependencies run: dotnet restore - name: Test - run: dotnet test --no-restore + run: dotnet test --no-restore --filter "SkipInActions!=true" build_frontend: runs-on: ubuntu-latest diff --git a/CollAction.Tests/Integration/Service/InstagramServiceTests.cs b/CollAction.Tests/Integration/Service/InstagramServiceTests.cs index c4d0ca094..237288bd4 100644 --- a/CollAction.Tests/Integration/Service/InstagramServiceTests.cs +++ b/CollAction.Tests/Integration/Service/InstagramServiceTests.cs @@ -10,6 +10,7 @@ namespace CollAction.Tests.Integration.Service { [Trait("Category", "Integration")] + [Trait("SkipInActions", "true")] public sealed class InstagramServiceTests : IntegrationTestBase { private readonly IInstagramService instagramService; From 2eea7861868c312fea9a724c392386aaf3abd4a3 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sun, 14 Jun 2020 21:59:06 +0200 Subject: [PATCH 19/25] Add comment why we're skipping --- CollAction.Tests/Integration/Service/InstagramServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollAction.Tests/Integration/Service/InstagramServiceTests.cs b/CollAction.Tests/Integration/Service/InstagramServiceTests.cs index 237288bd4..49a10e654 100644 --- a/CollAction.Tests/Integration/Service/InstagramServiceTests.cs +++ b/CollAction.Tests/Integration/Service/InstagramServiceTests.cs @@ -10,7 +10,7 @@ namespace CollAction.Tests.Integration.Service { [Trait("Category", "Integration")] - [Trait("SkipInActions", "true")] + [Trait("SkipInActions", "true")] // Instagram blocks github actions ips, probably marked as spammer public sealed class InstagramServiceTests : IntegrationTestBase { private readonly IInstagramService instagramService; From f40a98c22a943bf62992164920edd6327d7183c3 Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sun, 14 Jun 2020 22:08:00 +0200 Subject: [PATCH 20/25] Add more cdns --- CollAction/Services/Proxy/ProxyService.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs index 1cfc33f29..59b1e2400 100644 --- a/CollAction/Services/Proxy/ProxyService.cs +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -9,14 +9,32 @@ namespace CollAction.Services.Proxy { + /* + * We're using this as a simple (probably incomplete) privacy proxy, + * to stop people from being directly seen/fingerprinted/tracked by facebook + * when visiting our site when we need to display stuff from instagram/facebook + */ public sealed class ProxyService : IProxyService { private readonly HttpClient proxyClient; + // As a complete list as I can get of facebook/instagram cdns private static readonly string[] AllowedHosts = new string[] { "fbcdn.net", - "cdninstagram.com" + "cdninstagram.com", + "fbsbx.com", + "tfbnw.net", + "fb.me", + "facebook.com.edgesuite.net", + "facebook.com.edgekey.net", + "facebook.net.edgekey.net", + "facebook-web-clients.appspot.com", + "fbcdn-profile-a.akamaihd.net", + "fbsbx.com.online-metrix.net", + "instagramstatic-a.akamaihd.net", + "akamaihd.net.edgesuite.net", + "internet.org" }; public ProxyService(HttpClient proxyClient) From 6c4a01abecec455f6ebab25b949e84fa608f84df Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sun, 14 Jun 2020 22:12:35 +0200 Subject: [PATCH 21/25] Tweak comments --- CollAction/Services/Proxy/ProxyService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs index 59b1e2400..e07464b77 100644 --- a/CollAction/Services/Proxy/ProxyService.cs +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -17,7 +17,11 @@ namespace CollAction.Services.Proxy public sealed class ProxyService : IProxyService { private readonly HttpClient proxyClient; - // As a complete list as I can get of facebook/instagram cdns + /* + * As a complete list as I can get of facebook/instagram cdns + * We don't want to be an open proxy (for security/liability), + * so lets restrict what hosts are allowed + */ private static readonly string[] AllowedHosts = new string[] { From 85be25e06341eef334efd6649e0fbd15dbb7eb8f Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Sun, 14 Jun 2020 22:17:51 +0200 Subject: [PATCH 22/25] Enable range processing for proxying videos --- CollAction/Services/Proxy/ProxyService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CollAction/Services/Proxy/ProxyService.cs b/CollAction/Services/Proxy/ProxyService.cs index e07464b77..6c6241188 100644 --- a/CollAction/Services/Proxy/ProxyService.cs +++ b/CollAction/Services/Proxy/ProxyService.cs @@ -55,7 +55,8 @@ public async Task Proxy(Uri url, CancellationToken token) var stream = await result.Content.ReadAsStreamAsync().ConfigureAwait(false); return new FileStreamResult(stream, new MediaTypeHeaderValue(result.Content.Headers.ContentType.MediaType)) { - LastModified = result.Content.Headers.LastModified + LastModified = result.Content.Headers.LastModified, + EnableRangeProcessing = true }; } else From 6d9902df07d4645d69d158836f14b9225fa4ced2 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 15 Jun 2020 19:00:03 +0200 Subject: [PATCH 23/25] Code review comments --- CollAction/Controllers/GraphQlController.cs | 3 ++- .../src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx | 2 +- Frontend/src/pages/crowdactions/Create/form.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CollAction/Controllers/GraphQlController.cs b/CollAction/Controllers/GraphQlController.cs index 627119861..58f09d8c6 100644 --- a/CollAction/Controllers/GraphQlController.cs +++ b/CollAction/Controllers/GraphQlController.cs @@ -30,6 +30,7 @@ public sealed class GraphQlController : Controller private readonly IServiceProvider serviceProvider; private readonly IMemoryCache cache; private readonly ISchema schema; + private const int MaxGraphQlQueryComplexity = 21; private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1); private class CacheKey : IEquatable @@ -140,7 +141,7 @@ private async Task Execute(string query, string? operationName, UserContext = new UserContext(User, context, serviceProvider), ComplexityConfiguration = new ComplexityConfiguration() { - MaxDepth = 21 + MaxDepth = MaxGraphQlQueryComplexity }, ValidationRules = validationRules, CancellationToken = cancellation, diff --git a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx index 2855482d7..b17c7c129 100644 --- a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx +++ b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx @@ -103,7 +103,7 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { target: Yup.number().required("Must be filled in").min(1, "Must be a positive number"), anonymousUserParticipants: Yup.number().required("Must be filled in").min(0, "Must be a positive number"), firstCategory: Yup.string(), - instagramUser: Yup.string(), + instagramUser: Yup.string().max(30, "An instagram username has a maximum length of 30 characters"), secondCategory: Yup.string(), tags: Yup.string(), numberCrowdactionEmailsSent: Yup.number().required("Must be filled in").min(0, "Must be a positive number"), diff --git a/Frontend/src/pages/crowdactions/Create/form.ts b/Frontend/src/pages/crowdactions/Create/form.ts index be8889886..052adfe5f 100644 --- a/Frontend/src/pages/crowdactions/Create/form.ts +++ b/Frontend/src/pages/crowdactions/Create/form.ts @@ -86,7 +86,7 @@ export const validations = Yup.object({ hashtags: Yup.string() .max(30, 'Please keep the number of hashtags civil, no more then 30 characters') .matches(/^[a-zA-Z_0-9]+(;[a-zA-Z_0-9]+)*$/, 'Don\'t use spaces or #, must contain a letter, can contain digits and underscores. Separate multiple tags with a colon \';\''), - instagramUser: Yup.string(), + instagramUser: Yup.string().max(30, "An instagram username has a maximum length of 30 characters"), description: Yup.string() .required('Give a succinct description of what you are gathering participants for') .max(10000, 'Please use no more then 10.000 characters'), From 857f461f01d2f73544198d435de41f264f56549a Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 15 Jun 2020 19:07:23 +0200 Subject: [PATCH 24/25] Naming --- CollAction/Controllers/GraphQlController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CollAction/Controllers/GraphQlController.cs b/CollAction/Controllers/GraphQlController.cs index 58f09d8c6..8b852cb4e 100644 --- a/CollAction/Controllers/GraphQlController.cs +++ b/CollAction/Controllers/GraphQlController.cs @@ -30,7 +30,7 @@ public sealed class GraphQlController : Controller private readonly IServiceProvider serviceProvider; private readonly IMemoryCache cache; private readonly ISchema schema; - private const int MaxGraphQlQueryComplexity = 21; + private const int MaxGraphQlQueryDepth = 21; private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1); private class CacheKey : IEquatable @@ -141,7 +141,7 @@ private async Task Execute(string query, string? operationName, UserContext = new UserContext(User, context, serviceProvider), ComplexityConfiguration = new ComplexityConfiguration() { - MaxDepth = MaxGraphQlQueryComplexity + MaxDepth = MaxGraphQlQueryDepth }, ValidationRules = validationRules, CancellationToken = cancellation, From 62279245f5a4cfc2cd176522f560d415ff2acdfe Mon Sep 17 00:00:00 2001 From: Tim Stokman Date: Fri, 10 Jul 2020 14:57:19 +0200 Subject: [PATCH 25/25] Fix build issues --- .../src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx index f5eb1b719..713427305 100644 --- a/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx +++ b/Frontend/src/components/Admin/Crowdactions/AdminEditCrowdaction.tsx @@ -298,7 +298,6 @@ export default ({ crowdactionId } : IEditCrowdactionProps): any => { {