generated from ricardo/MVCLogin
Compare commits
2 Commits
2e64ee836f
...
511b538ad3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
511b538ad3 | ||
|
|
7b3c63ff37 |
@ -0,0 +1,19 @@
|
|||||||
|
using SumaTube.Infra.MongoDB.Documents.UserPlan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.Contracts.Repositories.UserPlan
|
||||||
|
{
|
||||||
|
public interface IPersonUserRepository
|
||||||
|
{
|
||||||
|
Task<PersonUserDocument> GetByIdAsync(string id);
|
||||||
|
Task<PersonUserDocument> GetByEmailAsync(string email);
|
||||||
|
Task<IEnumerable<PersonUserDocument>> GetAllAsync();
|
||||||
|
Task CreateAsync(PersonUserDocument PersonUserDocument);
|
||||||
|
Task UpdateAsync(PersonUserDocument PersonUserDocument);
|
||||||
|
Task DeleteAsync(string id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using SumaTube.Domain.Entities.Videos;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.Contracts.Repositories.Videos
|
||||||
|
{
|
||||||
|
public interface IVideoSummaryRepository
|
||||||
|
{
|
||||||
|
Task<VideoSummary> GetByIdAsync(string id);
|
||||||
|
Task<List<VideoSummary>> GetByUserIdAsync(string userId);
|
||||||
|
Task AddAsync(VideoSummary videoSummary);
|
||||||
|
Task UpdateAsync(VideoSummary videoSummary);
|
||||||
|
Task<bool> ExistsAsync(string videoId, string userId, string language);
|
||||||
|
Task<VideoSummary> GetByVideoIdAndUserIdAndLanguageAsync(string videoId, string userId, string language);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SumaTuba.Infra/Messages.cs
Normal file
20
SumaTuba.Infra/Messages.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra
|
||||||
|
{
|
||||||
|
public class VideoProcessingCompletedMessage
|
||||||
|
{
|
||||||
|
public string SessionId { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Summary { get; set; }
|
||||||
|
public string Transcription { get; set; }
|
||||||
|
public int Duration { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
using MongoDB.Bson;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB.Documents.UserPlan
|
||||||
|
{
|
||||||
|
public class PersonUserDocument
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
|
public string LastName { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public DateTime LastChanged { get; set; }
|
||||||
|
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
|
||||||
|
public bool IsProfileCompleted { get; set; }
|
||||||
|
|
||||||
|
public int CountryId { get; set; }
|
||||||
|
|
||||||
|
public int BusinessAreaId { get; set; }
|
||||||
|
|
||||||
|
public string DesiredName { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public UserPaymentDocument CurrentPlan { get; set; }
|
||||||
|
|
||||||
|
public List<UserPaymentDocument> PastPlans { get; set; } = new List<UserPaymentDocument>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB.Documents.UserPlan
|
||||||
|
{
|
||||||
|
public class UserPaymentDocument
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public decimal Value { get; set; }
|
||||||
|
|
||||||
|
public DateTime StartDate { get; set; }
|
||||||
|
|
||||||
|
public DateTime EndDate { get; set; }
|
||||||
|
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB.Documents.Videos
|
||||||
|
{
|
||||||
|
public class VideoProcessingMessageDocument
|
||||||
|
{
|
||||||
|
public string SessionId { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string Language { get; set; }
|
||||||
|
|
||||||
|
public VideoProcessingMessageDocument(string sessionId, string url, string language)
|
||||||
|
{
|
||||||
|
SessionId = sessionId;
|
||||||
|
Url = url;
|
||||||
|
Language = language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SumaTuba.Infra/MongoDB/Mappers/UserPlan/PersonUserMapper.cs
Normal file
19
SumaTuba.Infra/MongoDB/Mappers/UserPlan/PersonUserMapper.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using SumaTube.Domain.Entities.UserPlan;
|
||||||
|
using SumaTube.Crosscutting.Mappers;
|
||||||
|
using SumaTube.Infra.MongoDB.Documents.UserPlan;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB.Mappers.UserPlan
|
||||||
|
{
|
||||||
|
public static class PersonUserMapper
|
||||||
|
{
|
||||||
|
public static PersonUserDocument ToDocument(this PersonUser entity)
|
||||||
|
{
|
||||||
|
return entity.MapTo<PersonUser, PersonUserDocument>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PersonUser ToDomain(this PersonUserDocument document)
|
||||||
|
{
|
||||||
|
return document.MapTo<PersonUserDocument, PersonUser>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
SumaTuba.Infra/MongoDB/MongoConfig.cs
Normal file
76
SumaTuba.Infra/MongoDB/MongoConfig.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using SumaTube.Infra.MongoDB;
|
||||||
|
using MongoDB.Bson.Serialization.Conventions;
|
||||||
|
using MongoDB.Bson.Serialization;
|
||||||
|
using MongoDB.Bson.Serialization.Serializers;
|
||||||
|
using MongoDB.Bson.Serialization.IdGenerators;
|
||||||
|
using MongoDB.Bson;
|
||||||
|
using SumaTube.Domain;
|
||||||
|
using SumaTube.Domain.Entities.UserPlan;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB
|
||||||
|
{
|
||||||
|
public static class MongoConfig
|
||||||
|
{
|
||||||
|
public static void Configure()
|
||||||
|
{
|
||||||
|
var conventionPack = new ConventionPack { new IgnoreIfNullConvention(true) };
|
||||||
|
ConventionRegistry.Register("IgnoreIfNull", conventionPack, t => true);
|
||||||
|
|
||||||
|
if (!BsonClassMap.IsClassMapRegistered(typeof(PersonUser)))
|
||||||
|
{
|
||||||
|
BsonClassMap.RegisterClassMap<PersonUser>(cm =>
|
||||||
|
{
|
||||||
|
cm.MapIdProperty(p => p.Id)
|
||||||
|
.SetSerializer(new StringSerializer(BsonType.ObjectId))
|
||||||
|
.SetIdGenerator(StringObjectIdGenerator.Instance);
|
||||||
|
|
||||||
|
cm.MapProperty(p => p.Username);
|
||||||
|
cm.MapProperty(p => p.Name);
|
||||||
|
cm.MapProperty(p => p.Email);
|
||||||
|
cm.MapProperty(p => p.DateChanged);
|
||||||
|
cm.MapProperty(p => p.IsProfileCompleted);
|
||||||
|
cm.MapProperty(p => p.CountryId);
|
||||||
|
cm.MapProperty(p => p.BusinessAreaId);
|
||||||
|
cm.MapProperty(p => p.DesiredName);
|
||||||
|
cm.MapProperty(p => p.CreatedAt);
|
||||||
|
cm.MapProperty(p => p.Plano);
|
||||||
|
cm.MapProperty(p => p.PastPlans);
|
||||||
|
|
||||||
|
cm.MapCreator(p => new PersonUser(
|
||||||
|
p.Id.Value,
|
||||||
|
p.Username,
|
||||||
|
p.Name,
|
||||||
|
p.Email,
|
||||||
|
p.DateChanged,
|
||||||
|
p.IsProfileCompleted,
|
||||||
|
p.CountryId,
|
||||||
|
p.BusinessAreaId,
|
||||||
|
p.DesiredName,
|
||||||
|
p.CreatedAt,
|
||||||
|
p.Plano,
|
||||||
|
p.PastPlans
|
||||||
|
));
|
||||||
|
|
||||||
|
cm.AutoMap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure também as classes ValueObject se necessário
|
||||||
|
ConfigureValueObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureValueObjects()
|
||||||
|
{
|
||||||
|
if (!BsonClassMap.IsClassMapRegistered(typeof(Name)))
|
||||||
|
{
|
||||||
|
BsonClassMap.RegisterClassMap<Name>(cm =>
|
||||||
|
{
|
||||||
|
cm.AutoMap();
|
||||||
|
cm.MapCreator(n => new Name(n.FirstName, n.LastName));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adicione mapeamento para outros value objects conforme necessário
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,8 +4,9 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
|
using SumaTube.Domain.Entities.UserPlan;
|
||||||
|
|
||||||
namespace Blinks.Infra.MongoDB
|
namespace SumaTube.Infra.MongoDB
|
||||||
{
|
{
|
||||||
public class MongoDbContext
|
public class MongoDbContext
|
||||||
{
|
{
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
using MongoDB.Driver;
|
||||||
|
using SumaTube.Infra.MongoDB.Documents.UserPlan;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB.Repositories.UserPlan
|
||||||
|
{
|
||||||
|
public class PersonUserRepository
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<PersonUserDocument> _collection;
|
||||||
|
|
||||||
|
public PersonUserRepository(IMongoDatabase database)
|
||||||
|
{
|
||||||
|
_collection = database.GetCollection<PersonUserDocument>("PersonUsers");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PersonUserDocument> GetByIdAsync(string id)
|
||||||
|
{
|
||||||
|
return await _collection.Find(p => p.Id == id).FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PersonUserDocument>> GetAllAsync()
|
||||||
|
{
|
||||||
|
return await _collection.Find(_ => true).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateAsync(PersonUserDocument PersonUserDocument)
|
||||||
|
{
|
||||||
|
await _collection.InsertOneAsync(PersonUserDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(PersonUserDocument PersonUserDocument)
|
||||||
|
{
|
||||||
|
await _collection.ReplaceOneAsync(p => p.Id == PersonUserDocument.Id, PersonUserDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(string id)
|
||||||
|
{
|
||||||
|
await _collection.DeleteOneAsync(p => p.Id == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using SumaTube.Domain.Entities.Videos;
|
||||||
|
using SumaTube.Infra.Contracts.Repositories.Videos;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.MongoDB.Repositories.Videos
|
||||||
|
{
|
||||||
|
public class VideoSummaryRepository : IVideoSummaryRepository
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<VideoSummary> _collection;
|
||||||
|
private readonly ILogger<VideoSummaryRepository> _logger;
|
||||||
|
|
||||||
|
public VideoSummaryRepository(IMongoDatabase database, ILogger<VideoSummaryRepository> logger)
|
||||||
|
{
|
||||||
|
_collection = database.GetCollection<VideoSummary>("VideoSummaries");
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VideoSummary> GetByIdAsync(string id)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Obtendo resumo por ID: {Id}", id);
|
||||||
|
return await _collection.Find(x => x.Id == id).FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<VideoSummary>> GetByUserIdAsync(string userId)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Obtendo resumos do usuário: {UserId}", userId);
|
||||||
|
return await _collection.Find(x => x.UserId == userId)
|
||||||
|
.SortByDescending(x => x.RequestDate)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddAsync(VideoSummary videoSummary)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Adicionando novo resumo. ID: {Id}, VideoId: {VideoId}",
|
||||||
|
videoSummary.Id, videoSummary.VideoId);
|
||||||
|
await _collection.InsertOneAsync(videoSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(VideoSummary videoSummary)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Atualizando resumo. ID: {Id}, Status: {Status}",
|
||||||
|
videoSummary.Id, videoSummary.Status);
|
||||||
|
await _collection.ReplaceOneAsync(x => x.Id == videoSummary.Id, videoSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsAsync(string videoId, string userId, string language)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Verificando existência de resumo. VideoId: {VideoId}, UserId: {UserId}, Language: {Language}",
|
||||||
|
videoId, userId, language);
|
||||||
|
|
||||||
|
var count = await _collection.CountDocumentsAsync(x =>
|
||||||
|
x.VideoId == videoId &&
|
||||||
|
x.UserId == userId &&
|
||||||
|
x.Language == language &&
|
||||||
|
(x.Status == "REALIZADO" || x.Status == "PROCESSANDO"));
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VideoSummary> GetByVideoIdAndUserIdAndLanguageAsync(string videoId, string userId, string language)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Obtendo resumo por VideoId: {VideoId}, UserId: {UserId}, Language: {Language}",
|
||||||
|
videoId, userId, language);
|
||||||
|
|
||||||
|
return await _collection.Find(x =>
|
||||||
|
x.VideoId == videoId &&
|
||||||
|
x.UserId == userId &&
|
||||||
|
x.Language == language)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
using global::MongoDB.Bson.Serialization.Attributes;
|
using global::MongoDB.Bson.Serialization.Attributes;
|
||||||
using global::MongoDB.Bson;
|
using global::MongoDB.Bson;
|
||||||
|
|
||||||
namespace Blinks.Infra.MongoDB
|
namespace SumaTube.Infra.MongoDB
|
||||||
{
|
{
|
||||||
|
|
||||||
public class PersonUser
|
public class UserPerson
|
||||||
{
|
{
|
||||||
[BsonId]
|
[BsonId]
|
||||||
[BsonRepresentation(BsonType.ObjectId)]
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
|
|||||||
39
SumaTuba.Infra/Register/InfraServicesRegister.cs
Normal file
39
SumaTuba.Infra/Register/InfraServicesRegister.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using SumaTube.Infra.Contracts.Repositories.Videos;
|
||||||
|
using SumaTube.Infra.MongoDB.Repositories.Videos;
|
||||||
|
using SumaTube.Infra.VideoSumarizer.Contracts;
|
||||||
|
using SumaTube.Infra.VideoSumarizer.Videos;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.Register
|
||||||
|
{
|
||||||
|
public static class InfraServicesRegister
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddInfraServices(this IServiceCollection services, ConfigurationManager configuration)
|
||||||
|
{
|
||||||
|
var mongoConnectionString = configuration.GetConnectionString("MongoDB") ?? "mongodb://localhost:27017";
|
||||||
|
var mongoDatabaseName = configuration.GetValue<string>("MongoDB:DatabaseName") ?? "SumaTube";
|
||||||
|
|
||||||
|
services.AddSingleton<IMongoClient>(sp =>
|
||||||
|
{
|
||||||
|
return new MongoClient(mongoConnectionString);
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddScoped<IMongoDatabase>(sp =>
|
||||||
|
{
|
||||||
|
var client = sp.GetRequiredService<IMongoClient>();
|
||||||
|
return client.GetDatabase(mongoDatabaseName);
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddScoped<IVideoSumarizerService, VideoSumarizerService>();
|
||||||
|
services.AddScoped<IVideoSummaryRepository, VideoSummaryRepository>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,11 +9,21 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MongoDB.Bson" Version="2.28.0" />
|
<PackageReference Include="MongoDB.Bson" Version="2.28.0" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="2.28.0" />
|
<PackageReference Include="MongoDB.Driver" Version="2.28.0" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
|
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
|
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SumaTube.Crosscutting\SumaTube.Crosscutting.csproj" />
|
||||||
|
<ProjectReference Include="..\SumaTube.Domain\SumaTube.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Stripe\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.VideoSumarizer.Contracts
|
||||||
|
{
|
||||||
|
public interface IVideoSumarizerService
|
||||||
|
{
|
||||||
|
Task RequestVideoSummarization(string sessionId, string url, string language);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using RabbitMQ.Client.Events;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using SumaTube.Infra.Contracts.Repositories.Videos;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static MongoDB.Driver.WriteConcern;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.VideoSumarizer.Videos
|
||||||
|
{
|
||||||
|
public class VideoProcessingCompletedListener : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IVideoSummaryRepository _repository;
|
||||||
|
private readonly ILogger<VideoProcessingCompletedListener> _logger;
|
||||||
|
private IConnection _connection;
|
||||||
|
//private IModel _channel;
|
||||||
|
|
||||||
|
private static string _hostName = Environment.GetEnvironmentVariable("RABBITMQ_HOST") ?? "localhost";
|
||||||
|
private static string _userName = Environment.GetEnvironmentVariable("RABBITMQ_USER") ?? "guest";
|
||||||
|
private static string _password = Environment.GetEnvironmentVariable("RABBITMQ_PASSWORD") ?? "guest";
|
||||||
|
private static string _queueName = "video-processing-completed";
|
||||||
|
|
||||||
|
public VideoProcessingCompletedListener(
|
||||||
|
IVideoSummaryRepository repository,
|
||||||
|
ILogger<VideoProcessingCompletedListener> logger)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
InitializeRabbitMQ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRabbitMQ()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Inicializando conexão com RabbitMQ em {HostName}...", _hostName);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//var factory = new ConnectionFactory()
|
||||||
|
//{
|
||||||
|
// HostName = _hostName,
|
||||||
|
// UserName = _userName,
|
||||||
|
// Password = _password,
|
||||||
|
// DispatchConsumersAsync = true
|
||||||
|
//};
|
||||||
|
|
||||||
|
//_connection = factory.CreateConnection();
|
||||||
|
//_channel = _connection.CreateModel();
|
||||||
|
|
||||||
|
//_channel.QueueDeclare(
|
||||||
|
// queue: _queueName,
|
||||||
|
// durable: true,
|
||||||
|
// exclusive: false,
|
||||||
|
// autoDelete: false,
|
||||||
|
// arguments: null);
|
||||||
|
|
||||||
|
_logger.LogInformation("Conexão com RabbitMQ estabelecida. Aguardando mensagens na fila: {QueueName}", _queueName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao inicializar conexão com RabbitMQ: {Message}", ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
//stoppingToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
//var consumer = new AsyncEventingBasicConsumer(_channel);
|
||||||
|
|
||||||
|
//consumer.Received += async (model, ea) =>
|
||||||
|
//{
|
||||||
|
// var body = ea.Body.ToArray();
|
||||||
|
// var message = Encoding.UTF8.GetString(body);
|
||||||
|
|
||||||
|
// _logger.LogInformation("Mensagem recebida: {Message}", message);
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var completedMessage = JsonConvert.DeserializeObject<VideoProcessingCompletedMessage>(message);
|
||||||
|
|
||||||
|
// // Buscar o resumo pelo sessionId
|
||||||
|
// var summaries = await _repository.GetByUserIdAsync(completedMessage.UserId);
|
||||||
|
// var summary = summaries.Find(s => s.SessionId == completedMessage.SessionId);
|
||||||
|
|
||||||
|
// if (summary != null)
|
||||||
|
// {
|
||||||
|
// _logger.LogInformation("Atualizando resumo. ID: {Id}, SessionId: {SessionId}",
|
||||||
|
// summary.Id, completedMessage.SessionId);
|
||||||
|
|
||||||
|
// if (completedMessage.Success)
|
||||||
|
// {
|
||||||
|
// summary.SetAsCompleted(
|
||||||
|
// completedMessage.Title,
|
||||||
|
// completedMessage.Summary,
|
||||||
|
// completedMessage.Transcription,
|
||||||
|
// completedMessage.Duration);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// summary.SetAsFailed(completedMessage.ErrorMessage);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await _repository.UpdateAsync(summary);
|
||||||
|
// _logger.LogInformation("Resumo atualizado com sucesso. ID: {Id}, Status: {Status}",
|
||||||
|
// summary.Id, summary.Status);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// _logger.LogWarning("Resumo não encontrado para SessionId: {SessionId}", completedMessage.SessionId);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _channel.BasicAck(ea.DeliveryTag, false);
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// _logger.LogError(ex, "Erro ao processar mensagem: {Message}", ex.Message);
|
||||||
|
// _channel.BasicNack(ea.DeliveryTag, false, true);
|
||||||
|
// }
|
||||||
|
//};
|
||||||
|
|
||||||
|
//_channel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
//_channel?.Close();
|
||||||
|
//_connection?.Close();
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.VideoSumarizer.Videos
|
||||||
|
{
|
||||||
|
public class VideoProcessingMessageDocument
|
||||||
|
{
|
||||||
|
public VideoProcessingMessageDocument(string sessionId, string url, string language)
|
||||||
|
{
|
||||||
|
SessionId = sessionId;
|
||||||
|
Url = url;
|
||||||
|
Language = language;
|
||||||
|
}
|
||||||
|
public string SessionId { get; private set; }
|
||||||
|
public string Url { get; private set; }
|
||||||
|
public string Language { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using SumaTube.Infra.VideoSumarizer.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Infra.VideoSumarizer.Videos
|
||||||
|
{
|
||||||
|
public class VideoSumarizerService : IVideoSumarizerService
|
||||||
|
{
|
||||||
|
private static string _hostName = Environment.GetEnvironmentVariable("RABBITMQ_HOST") ?? "localhost";
|
||||||
|
private static string _userName = Environment.GetEnvironmentVariable("RABBITMQ_USER") ?? "guest";
|
||||||
|
private static string _password = Environment.GetEnvironmentVariable("RABBITMQ_PASSWORD") ?? "guest";
|
||||||
|
private static string _queueName = "video-processing-queue";
|
||||||
|
|
||||||
|
private readonly ILogger<VideoSumarizerService> _logger;
|
||||||
|
|
||||||
|
public VideoSumarizerService(ILogger<VideoSumarizerService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RequestVideoSummarization(string sessionId, string url, string language)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("### Video Processor Publisher ###");
|
||||||
|
_logger.LogInformation("Conectando ao RabbitMQ em {HostName}...", _hostName);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var factory = new ConnectionFactory()
|
||||||
|
{
|
||||||
|
HostName = _hostName,
|
||||||
|
UserName = _userName,
|
||||||
|
Password = _password
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var connection = await factory.CreateConnectionAsync())
|
||||||
|
using (var channel = await connection.CreateChannelAsync())
|
||||||
|
{
|
||||||
|
await channel.QueueDeclareAsync(
|
||||||
|
queue: _queueName,
|
||||||
|
durable: true,
|
||||||
|
exclusive: false,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: null);
|
||||||
|
|
||||||
|
var properties = new BasicProperties
|
||||||
|
{
|
||||||
|
Persistent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation("Conexão estabelecida com RabbitMQ");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(language))
|
||||||
|
language = "pt";
|
||||||
|
|
||||||
|
// Criar objeto de mensagem
|
||||||
|
var message = new VideoProcessingMessageDocument(
|
||||||
|
sessionId,
|
||||||
|
url,
|
||||||
|
language);
|
||||||
|
|
||||||
|
// Serializar para JSON
|
||||||
|
var messageJson = JsonSerializer.Serialize(message);
|
||||||
|
var body = Encoding.UTF8.GetBytes(messageJson);
|
||||||
|
|
||||||
|
// Publicar mensagem
|
||||||
|
await channel.BasicPublishAsync(
|
||||||
|
exchange: string.Empty,
|
||||||
|
routingKey: _queueName,
|
||||||
|
mandatory: true,
|
||||||
|
basicProperties: properties,
|
||||||
|
body: body);
|
||||||
|
|
||||||
|
_logger.LogInformation("Mensagem enviada: {Message}", messageJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao enviar mensagem para RabbitMQ: {Message}", ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SumaTube.Application/Register/ApplicationServiceRegister.cs
Normal file
20
SumaTube.Application/Register/ApplicationServiceRegister.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SumaTube.Application.Videos.ApplicationServices;
|
||||||
|
using SumaTube.Application.Videos.Contracts;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Application.Register
|
||||||
|
{
|
||||||
|
public static class ApplicationServiceRegister
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IVideoApplicationService, VideoApplicationService>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SumaTube.Application/SumaTube.Application.csproj
Normal file
20
SumaTube.Application/SumaTube.Application.csproj
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BaseDomain\BaseDomain.csproj" />
|
||||||
|
<ProjectReference Include="..\SumaTuba.Infra\SumaTube.Infra.csproj" />
|
||||||
|
<ProjectReference Include="..\SumaTube.Crosscutting\SumaTube.Crosscutting.csproj" />
|
||||||
|
<ProjectReference Include="..\SumaTube.Domain\SumaTube.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="UserPlan\Contracts\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
using SumaTube.Domain.Entities.UserPlan;
|
||||||
|
using SumaTube.Infra.Contracts.Repositories.UserPlan;
|
||||||
|
using SumaTube.Infra.MongoDB.Mappers.UserPlan;
|
||||||
|
|
||||||
|
namespace SumaTube.Application.UserPlan.ApplicationServices
|
||||||
|
{
|
||||||
|
public class PersonUserAppService
|
||||||
|
{
|
||||||
|
private readonly IPersonUserRepository _personUserRepository;
|
||||||
|
|
||||||
|
public PersonUserAppService(IPersonUserRepository personUserRepository)
|
||||||
|
{
|
||||||
|
_personUserRepository = personUserRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PersonUser> GetPersonUserByEmailAsync(string email)
|
||||||
|
{
|
||||||
|
var document = await _personUserRepository.GetByEmailAsync(email);
|
||||||
|
|
||||||
|
return document.ToDomain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SumaTube.Application.Videos.Contracts;
|
||||||
|
using SumaTube.Domain.Entities.Videos;
|
||||||
|
using SumaTube.Infra.Contracts.Repositories.Videos;
|
||||||
|
using SumaTube.Infra.VideoSumarizer.Contracts;
|
||||||
|
using SumaTube.Infra.VideoSumarizer.Videos;
|
||||||
|
|
||||||
|
namespace SumaTube.Application.Videos.ApplicationServices
|
||||||
|
{
|
||||||
|
public class VideoApplicationService : IVideoApplicationService
|
||||||
|
{
|
||||||
|
private readonly IVideoSummaryRepository _repository;
|
||||||
|
private readonly IVideoSumarizerService _sumarizerService;
|
||||||
|
private readonly ILogger<VideoApplicationService> _logger;
|
||||||
|
|
||||||
|
public VideoApplicationService(
|
||||||
|
IVideoSummaryRepository repository,
|
||||||
|
IVideoSumarizerService sumarizerService,
|
||||||
|
ILogger<VideoApplicationService> logger)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_sumarizerService = sumarizerService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<VideoSummary>> GetUserVideosAsync(string userId)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Obtendo videos do usuário: {UserId}", userId);
|
||||||
|
return await _repository.GetByUserIdAsync(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VideoSummary> GetVideoSummaryByIdAsync(string id, string userId)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Obtendo resumo com ID: {SummaryId} para usuário: {UserId}", id, userId);
|
||||||
|
var summary = await _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
if (summary == null || summary.UserId != userId)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Resumo não encontrado ou acesso não autorizado. ID: {SummaryId}, UserId: {UserId}", id, userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VideoSummary> RequestVideoSummaryAsync(string youtubeUrl, string language, string userId)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Solicitando resumo para URL: {Url}, idioma: {Language}, usuário: {UserId}", youtubeUrl, language, userId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Extrair ID do vídeo
|
||||||
|
string videoId = ExtractVideoId(youtubeUrl);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(videoId))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("URL do YouTube inválida: {Url}", youtubeUrl);
|
||||||
|
throw new ArgumentException("URL do YouTube inválida");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se já existe um resumo para este vídeo/usuário/idioma
|
||||||
|
bool exists = await _repository.ExistsAsync(videoId, userId, language);
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Resumo já existente para vídeo: {VideoId}, usuário: {UserId}, idioma: {Language}",
|
||||||
|
videoId, userId, language);
|
||||||
|
return await _repository.GetByVideoIdAndUserIdAndLanguageAsync(videoId, userId, language);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criar novo resumo
|
||||||
|
var sessionId = Guid.NewGuid().ToString();
|
||||||
|
var summary = VideoSummary.Create(videoId, userId, language, sessionId);
|
||||||
|
|
||||||
|
// Salvar no repositório
|
||||||
|
await _repository.AddAsync(summary);
|
||||||
|
|
||||||
|
// Enviar para processamento via RabbitMQ
|
||||||
|
_logger.LogInformation("Enviando solicitação para processamento. SessionId: {SessionId}", sessionId);
|
||||||
|
await _sumarizerService.RequestVideoSummarization(sessionId, youtubeUrl, language);
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao solicitar resumo de vídeo: {Message}", ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<object> CheckSummaryStatusAsync(string id, string userId)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Verificando status do resumo. ID: {SummaryId}, usuário: {UserId}", id, userId);
|
||||||
|
|
||||||
|
var summary = await _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
if (summary == null || summary.UserId != userId)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Resumo não encontrado ou acesso não autorizado. ID: {SummaryId}, UserId: {UserId}", id, userId);
|
||||||
|
return new { status = "NOT_FOUND" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
status = summary.Status,
|
||||||
|
title = summary.Title,
|
||||||
|
thumbnailUrl = summary.ThumbnailUrl,
|
||||||
|
errorMessage = summary.ErrorMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExtractVideoId(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var uri = new Uri(url);
|
||||||
|
|
||||||
|
if (uri.Host.Contains("youtube.com"))
|
||||||
|
{
|
||||||
|
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||||
|
return query["v"];
|
||||||
|
}
|
||||||
|
else if (uri.Host.Contains("youtu.be"))
|
||||||
|
{
|
||||||
|
var segments = uri.Segments;
|
||||||
|
return segments[segments.Length - 1].TrimEnd('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao extrair ID do vídeo da URL: {Url}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using SumaTube.Domain.Entities.Videos;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Application.Videos.Contracts
|
||||||
|
{
|
||||||
|
public interface IVideoApplicationService
|
||||||
|
{
|
||||||
|
Task<List<VideoSummary>> GetUserVideosAsync(string userId);
|
||||||
|
Task<VideoSummary> GetVideoSummaryByIdAsync(string id, string userId);
|
||||||
|
Task<VideoSummary> RequestVideoSummaryAsync(string youtubeUrl, string language, string userId);
|
||||||
|
Task<object> CheckSummaryStatusAsync(string id, string userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Serilog.Extensions.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace SumaTube.Crosscutting.Logging.Configuration
|
||||||
|
{
|
||||||
|
public static class SerilogConfiguration
|
||||||
|
{
|
||||||
|
public static LoggerConfiguration SetLoggerConfiguration(this WebApplicationBuilder builder, LoggerConfiguration config, IServiceProvider services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var workspace = configuration["Serilog:Properties:Workspace"];
|
||||||
|
var seqServer = configuration.GetValue<string>("Serilog:WriteTo:2:Args:serverUrl"); ;
|
||||||
|
|
||||||
|
config
|
||||||
|
.ReadFrom.Configuration(configuration)
|
||||||
|
.ReadFrom.Services(services)
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithEnvironmentName()
|
||||||
|
.Enrich.WithMachineName()
|
||||||
|
.Enrich.WithProperty("Application", "SumaTube")
|
||||||
|
.Enrich.WithProperty("Workspace", workspace)
|
||||||
|
.WriteTo.Seq(seqServer)
|
||||||
|
;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static IServiceCollection AddSerilogServices(this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment)
|
||||||
|
{
|
||||||
|
// Obtenha o workspace do Seq baseado no ambiente
|
||||||
|
//var workspace = environment.IsDevelopment() ? "Dev" : "Prod";
|
||||||
|
|
||||||
|
services.AddSingleton<DiagnosticContext>();
|
||||||
|
|
||||||
|
var workspace = configuration["Serilog:Properties:Workspace"];
|
||||||
|
|
||||||
|
// Crie o logger usando a configuração
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.ReadFrom.Configuration(configuration)
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithMachineName()
|
||||||
|
.Enrich.WithEnvironmentName()
|
||||||
|
//.Enrich.WithProperty("Workspace", workspace) // Adiciona explicitamente o workspace
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
// Registra o logger no container de DI
|
||||||
|
services.AddSingleton(Log.Logger);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método opcional para configuração direta sem usar appsettings.json
|
||||||
|
public static IServiceCollection AddSerilogServicesWithCode(
|
||||||
|
this IServiceCollection services,
|
||||||
|
IHostEnvironment environment)
|
||||||
|
{
|
||||||
|
services.AddSingleton<DiagnosticContext>();
|
||||||
|
|
||||||
|
// Defina as configurações do Seq baseado no ambiente
|
||||||
|
var (workspace, apiKey) = environment.IsDevelopment()
|
||||||
|
? ("Dev", "sua-api-key-dev")
|
||||||
|
: ("Prod", "sua-api-key-prod");
|
||||||
|
|
||||||
|
// Configuração básica para ambos ambientes
|
||||||
|
var loggerConfig = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Debug()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithMachineName()
|
||||||
|
.Enrich.WithEnvironmentUserName()
|
||||||
|
.Enrich.WithThreadId()
|
||||||
|
.Enrich.WithProperty("Application", "SumaTube")
|
||||||
|
.Enrich.WithProperty("Environment", environment.EnvironmentName)
|
||||||
|
.Enrich.WithProperty("Workspace", workspace);
|
||||||
|
|
||||||
|
// Adicione destinos específicos por ambiente
|
||||||
|
if (environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
loggerConfig
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||||
|
.WriteTo.Console()
|
||||||
|
.WriteTo.File("logs/dev-app-.log", rollingInterval: RollingInterval.Day);
|
||||||
|
}
|
||||||
|
else // Produção ou outros ambientes
|
||||||
|
{
|
||||||
|
loggerConfig
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||||
|
.MinimumLevel.Override("System", LogEventLevel.Error)
|
||||||
|
.WriteTo.File("logs/prod-app-.log", rollingInterval: RollingInterval.Day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adicione o Seq para ambos ambientes, mas com configurações diferentes
|
||||||
|
//loggerConfig.WriteTo.Seq(
|
||||||
|
// "http://logs-ingest.carneiro.ddnsfree.com",
|
||||||
|
// apiKey: apiKey);
|
||||||
|
loggerConfig.WriteTo.Seq(
|
||||||
|
"http://logs.carneiro.ddnsfree.com:5341");
|
||||||
|
|
||||||
|
Log.Logger = loggerConfig.CreateLogger();
|
||||||
|
|
||||||
|
// Registra o logger no container de DI
|
||||||
|
services.AddSingleton(Log.Logger);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
SumaTube.Crosscutting/Logging/Extensions/LoggerExtensions.cs
Normal file
28
SumaTube.Crosscutting/Logging/Extensions/LoggerExtensions.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace SumaTube.Crosscutting.Logging.Extensions
|
||||||
|
{
|
||||||
|
public static class LoggerExtensions
|
||||||
|
{
|
||||||
|
public static void LogMethodEntry<T>(this ILogger<T> logger, string methodName, params object[] parameters)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Entering method {MethodName} with parameters {@Parameters}", methodName, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogMethodExit<T>(this ILogger<T> logger, string methodName, object result = null)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Exiting method {MethodName} with result {@Result}", methodName, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogException<T>(this ILogger<T> logger, Exception exception, string message = null)
|
||||||
|
{
|
||||||
|
logger.LogError(exception, message ?? "An error occurred: {ErrorMessage}", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogPerformance<T>(this ILogger<T> logger, string operation, long elapsedMilliseconds)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Performance: {Operation} took {ElapsedMilliseconds} ms", operation, elapsedMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
254
SumaTube.Crosscutting/Mapper/GenericMapper.cs
Normal file
254
SumaTube.Crosscutting/Mapper/GenericMapper.cs
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
namespace SumaTube.Crosscutting.Mappers
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
public static class GenericMapper
|
||||||
|
{
|
||||||
|
public static TDestination MapTo<TSource, TDestination>(this TSource source)
|
||||||
|
where TDestination : class
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var destType = typeof(TDestination);
|
||||||
|
|
||||||
|
// Verifica se o tipo de destino tem um construtor público
|
||||||
|
var constructors = destType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.OrderByDescending(c => c.GetParameters().Length)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Se o tipo de destino tem um construtor com parâmetros, tenta mapear para ele
|
||||||
|
if (constructors.Any() && constructors[0].GetParameters().Length > 0)
|
||||||
|
{
|
||||||
|
return MapToImmutableObject<TSource, TDestination>(source, constructors);
|
||||||
|
}
|
||||||
|
// Caso contrário, usa a abordagem de mapeamento de propriedades para objetos mutáveis
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return MapToMutableObject<TSource, TDestination>(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TDestination MapToImmutableObject<TSource, TDestination>(TSource source, List<ConstructorInfo> constructors)
|
||||||
|
where TDestination : class
|
||||||
|
{
|
||||||
|
var sourceType = typeof(TSource);
|
||||||
|
var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
|
||||||
|
// Tenta cada construtor, começando pelo que tem mais parâmetros
|
||||||
|
foreach (var constructor in constructors)
|
||||||
|
{
|
||||||
|
var parameters = constructor.GetParameters();
|
||||||
|
if (parameters.Length == 0)
|
||||||
|
{
|
||||||
|
// Construtor sem parâmetros, cria instância diretamente
|
||||||
|
var instance = constructor.Invoke(null);
|
||||||
|
return (TDestination)instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameterValues = new object[parameters.Length];
|
||||||
|
bool canUseConstructor = true;
|
||||||
|
|
||||||
|
// Tenta mapear os parâmetros do construtor
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
var param = parameters[i];
|
||||||
|
|
||||||
|
// Procura propriedade com o mesmo nome (case insensitive)
|
||||||
|
var matchingProp = sourceProps.FirstOrDefault(p =>
|
||||||
|
string.Equals(p.Name, param.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (matchingProp != null)
|
||||||
|
{
|
||||||
|
var value = matchingProp.GetValue(source);
|
||||||
|
|
||||||
|
// Se os tipos são compatíveis, usa o valor diretamente
|
||||||
|
if (param.ParameterType.IsAssignableFrom(matchingProp.PropertyType))
|
||||||
|
{
|
||||||
|
parameterValues[i] = value;
|
||||||
|
}
|
||||||
|
// Verifica se existe uma conversão implícita
|
||||||
|
else if (value != null && TryImplicitConversion(value, param.ParameterType, out var convertedValue))
|
||||||
|
{
|
||||||
|
parameterValues[i] = convertedValue;
|
||||||
|
}
|
||||||
|
// Se o valor é um tipo complexo, tenta mapear recursivamente
|
||||||
|
else if (value != null && !matchingProp.PropertyType.IsPrimitive &&
|
||||||
|
!matchingProp.PropertyType.Namespace.StartsWith("System"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var method = typeof(GenericMapper).GetMethod(nameof(MapTo));
|
||||||
|
var genericMethod = method.MakeGenericMethod(matchingProp.PropertyType, param.ParameterType);
|
||||||
|
parameterValues[i] = genericMethod.Invoke(null, new[] { value });
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
canUseConstructor = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
canUseConstructor = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Se não encontrou uma propriedade correspondente, verifica se o parâmetro é opcional
|
||||||
|
if (param.IsOptional)
|
||||||
|
{
|
||||||
|
parameterValues[i] = param.DefaultValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
canUseConstructor = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canUseConstructor)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var instance = constructor.Invoke(parameterValues);
|
||||||
|
return (TDestination)instance;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Se falhou ao criar a instância, tenta o próximo construtor
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se não conseguiu usar nenhum construtor, lança exceção
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Não foi possível mapear {sourceType.Name} para {typeof(TDestination).Name} " +
|
||||||
|
$"usando os construtores disponíveis. Verifique se os nomes das propriedades " +
|
||||||
|
$"correspondem aos nomes dos parâmetros do construtor (case insensitive).");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TDestination MapToMutableObject<TSource, TDestination>(TSource source)
|
||||||
|
where TDestination : class
|
||||||
|
{
|
||||||
|
var destType = typeof(TDestination);
|
||||||
|
|
||||||
|
// Tenta criar uma instância usando o construtor sem parâmetros
|
||||||
|
TDestination destination;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
destination = Activator.CreateInstance<TDestination>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Não foi possível criar uma instância de {destType.Name}. " +
|
||||||
|
$"Certifique-se de que a classe tenha um construtor sem parâmetros acessível.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceType = typeof(TSource);
|
||||||
|
var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
var destProps = destType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);
|
||||||
|
|
||||||
|
foreach (var destProp in destProps)
|
||||||
|
{
|
||||||
|
if (!destProp.CanWrite)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Procura propriedade com o mesmo nome (case insensitive)
|
||||||
|
var sourceProp = sourceProps.FirstOrDefault(p =>
|
||||||
|
string.Equals(p.Name, destProp.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (sourceProp != null)
|
||||||
|
{
|
||||||
|
var value = sourceProp.GetValue(source);
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// Se os tipos são compatíveis, atribui diretamente
|
||||||
|
if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
|
||||||
|
{
|
||||||
|
destProp.SetValue(destination, value);
|
||||||
|
}
|
||||||
|
// Verifica se existe uma conversão implícita
|
||||||
|
else if (TryImplicitConversion(value, destProp.PropertyType, out var convertedValue))
|
||||||
|
{
|
||||||
|
destProp.SetValue(destination, convertedValue);
|
||||||
|
}
|
||||||
|
// Se o valor é um tipo complexo, tenta mapear recursivamente
|
||||||
|
else if (!sourceProp.PropertyType.IsPrimitive &&
|
||||||
|
!sourceProp.PropertyType.Namespace.StartsWith("System"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var method = typeof(GenericMapper).GetMethod(nameof(MapTo));
|
||||||
|
var genericMethod = method.MakeGenericMethod(sourceProp.PropertyType, destProp.PropertyType);
|
||||||
|
var mappedValue = genericMethod.Invoke(null, new[] { value });
|
||||||
|
destProp.SetValue(destination, mappedValue);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignora se não conseguir mapear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryImplicitConversion(object source, Type destinationType, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (source == null) return false;
|
||||||
|
|
||||||
|
var sourceType = source.GetType();
|
||||||
|
|
||||||
|
// Verifica operador de conversão implícita no tipo de origem
|
||||||
|
var methodSource = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.FirstOrDefault(m =>
|
||||||
|
m.Name == "op_Implicit" &&
|
||||||
|
m.ReturnType == destinationType &&
|
||||||
|
m.GetParameters().Length == 1 &&
|
||||||
|
m.GetParameters()[0].ParameterType == sourceType);
|
||||||
|
|
||||||
|
if (methodSource != null)
|
||||||
|
{
|
||||||
|
result = methodSource.Invoke(null, new[] { source });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica operador de conversão implícita no tipo de destino
|
||||||
|
var methodDest = destinationType.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.FirstOrDefault(m =>
|
||||||
|
m.Name == "op_Implicit" &&
|
||||||
|
m.ReturnType == destinationType &&
|
||||||
|
m.GetParameters().Length == 1 &&
|
||||||
|
m.GetParameters()[0].ParameterType == sourceType);
|
||||||
|
|
||||||
|
if (methodDest != null)
|
||||||
|
{
|
||||||
|
result = methodDest.Invoke(null, new[] { source });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tenta converter usando Convert.ChangeType
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (destinationType.IsValueType || destinationType == typeof(string))
|
||||||
|
{
|
||||||
|
result = Convert.ChangeType(source, destinationType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SumaTube.Crosscutting/SumaTube.Crosscutting.csproj
Normal file
19
SumaTube.Crosscutting/SumaTube.Crosscutting.csproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
|
||||||
{
|
|
||||||
public class BusinessAreas
|
|
||||||
{
|
|
||||||
public BusinessAreas() { }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
|
||||||
{
|
|
||||||
internal class BusinessAres
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
|
||||||
{
|
|
||||||
public class LinkBio
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Ordem de exibição
|
|
||||||
/// </summary>
|
|
||||||
public int OrderNum { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parte customizada do link
|
|
||||||
/// </summary>
|
|
||||||
public string UrlData { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parte fixa do link (obter do service links quando cadastrar)
|
|
||||||
/// </summary>
|
|
||||||
public string ServiceUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Url/caminho do PNG do link
|
|
||||||
/// </summary>
|
|
||||||
public string ServiceIcon { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exibir/nao exibir
|
|
||||||
/// </summary>
|
|
||||||
public bool IsVisible { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
|
||||||
{
|
|
||||||
public class PageBio
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string UrlParte1 { get; set; }
|
|
||||||
public string UrlParte2 { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public List<LinkBio>? Links { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Blinks.Domain.ValueObjects;
|
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
|
||||||
{
|
|
||||||
public class PersonUser
|
|
||||||
{
|
|
||||||
public Id id { get; private set; }
|
|
||||||
public Username Username { get; private set; }
|
|
||||||
public Name Name { get; private set; }
|
|
||||||
public string FirstName => Name.FirstName;
|
|
||||||
public string LastName => Name.LastName;
|
|
||||||
public Email Email { get; private set; }
|
|
||||||
public DateChanged DateChanged { get; private set; }
|
|
||||||
public bool IsProfileCompleted { get; private set; }
|
|
||||||
public int CountryId { get; private set; }
|
|
||||||
public int BusinessAreaId { get; private set; }
|
|
||||||
public string DesiredName { get; private set; }
|
|
||||||
public DateTime CreatedAt { get; private set; }
|
|
||||||
public UserPlan? Plano { get; private set; }
|
|
||||||
public List<UserPlan>? PastPlans { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
55
SumaTube.Domain/Entities/UserPlan/PersonUser.cs
Normal file
55
SumaTube.Domain/Entities/UserPlan/PersonUser.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SumaTube.Domain.ValueObjects;
|
||||||
|
|
||||||
|
namespace SumaTube.Domain.Entities.UserPlan
|
||||||
|
{
|
||||||
|
public class PersonUser
|
||||||
|
{
|
||||||
|
public PersonUser(string id, Username username, Name name, Email email,
|
||||||
|
DateChanged dateChanged, bool isProfileCompleted, int countryId,
|
||||||
|
int businessAreaId, string desiredName, DateTime createdAt,
|
||||||
|
UserPayment plano = null, List<UserPayment> pastPlans = null)
|
||||||
|
{
|
||||||
|
Id = id ?? Guid.NewGuid().ToString("N");
|
||||||
|
Username = username;
|
||||||
|
Name = name;
|
||||||
|
Email = email;
|
||||||
|
DateChanged = dateChanged;
|
||||||
|
IsProfileCompleted = isProfileCompleted;
|
||||||
|
CountryId = countryId;
|
||||||
|
BusinessAreaId = businessAreaId;
|
||||||
|
DesiredName = desiredName;
|
||||||
|
CreatedAt = createdAt;
|
||||||
|
Plano = plano;
|
||||||
|
PastPlans = pastPlans ?? new List<UserPayment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Id Id { get; private set; }
|
||||||
|
public Username Username { get; private set; }
|
||||||
|
public Name Name { get; private set; }
|
||||||
|
public string FirstName => Name.FirstName;
|
||||||
|
public string LastName => Name.LastName;
|
||||||
|
public Email Email { get; private set; }
|
||||||
|
public DateChanged DateChanged { get; private set; }
|
||||||
|
public bool IsProfileCompleted { get; private set; }
|
||||||
|
public int CountryId { get; private set; }
|
||||||
|
public int BusinessAreaId { get; private set; }
|
||||||
|
public string DesiredName { get; private set; }
|
||||||
|
public DateTime CreatedAt { get; private set; }
|
||||||
|
public UserPayment? Plano { get; private set; }
|
||||||
|
public List<UserPayment>? PastPlans { get; private set; }
|
||||||
|
|
||||||
|
public PersonUser AddPlano(UserPayment plano)
|
||||||
|
{
|
||||||
|
if (plano == null) throw new ArgumentNullException(nameof(plano));
|
||||||
|
PastPlans.Add(plano);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
namespace SumaTube.Domain.Entities.UserPlan.UserPlan
|
||||||
{
|
{
|
||||||
public class ServiceLinkPart
|
public class ServiceLinkPart
|
||||||
{
|
{
|
||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
namespace SumaTube.Domain.Entities.UserPlan.UserPlan
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Vai ler uma lista estatica do BD com os links dos serviços
|
/// Vai ler uma lista estatica do BD com os links dos serviços
|
||||||
@ -4,9 +4,9 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Blinks.Domain.Entities
|
namespace SumaTube.Domain.Entities.UserPlan
|
||||||
{
|
{
|
||||||
public class UserPlan
|
public class UserPayment
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
48
SumaTube.Domain/Entities/Videos/UserVideo.cs
Normal file
48
SumaTube.Domain/Entities/Videos/UserVideo.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using BaseDomain.Results;
|
||||||
|
using SumaTube.Domain.ValueObjects;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Domain.Entities.Videos
|
||||||
|
{
|
||||||
|
public class UserVideo
|
||||||
|
{
|
||||||
|
|
||||||
|
public Id Id { get; private set; }
|
||||||
|
public string Name { get; private set; }
|
||||||
|
public List<VideoGroup> VideoGroups { get; private set; } = new List<VideoGroup>();
|
||||||
|
|
||||||
|
public UserVideo(int userId, string name)
|
||||||
|
{
|
||||||
|
this.Id = Guid.NewGuid();
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<bool> AddVideoGroup(string name, string description)
|
||||||
|
{
|
||||||
|
if (!VideoGroups.Any(v => v.Name == name))
|
||||||
|
{
|
||||||
|
this.VideoGroups.Add(new VideoGroup(name, description));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<bool> AddVideo(string collectionName, string url)
|
||||||
|
{
|
||||||
|
var videoCollectionItem = VideoGroups.Find(v => v.Name == collectionName);
|
||||||
|
if (videoCollectionItem != null)
|
||||||
|
{
|
||||||
|
if (videoCollectionItem.Videos.Any(v => v.Url == url)) return false;
|
||||||
|
videoCollectionItem.AddVideo(this.Id.Value, url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserVideo Create(int userId, string name) => new UserVideo(userId, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
SumaTube.Domain/Entities/Videos/VideoData.cs
Normal file
43
SumaTube.Domain/Entities/Videos/VideoData.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Domain.Entities.Videos
|
||||||
|
{
|
||||||
|
public enum VideoSiteEnum
|
||||||
|
{
|
||||||
|
Youtube
|
||||||
|
}
|
||||||
|
public class VideoData
|
||||||
|
{
|
||||||
|
public string SessionId { get; private set; }
|
||||||
|
public string Title { get; private set; }
|
||||||
|
public string Image { get; private set; }
|
||||||
|
public string Url { get; private set; }
|
||||||
|
public VideoSiteEnum Site { get; private set; }
|
||||||
|
public VideoResult VideoResult { get; private set; }
|
||||||
|
|
||||||
|
public VideoData(string sessionId, string url)
|
||||||
|
{
|
||||||
|
SessionId = sessionId;
|
||||||
|
Url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVideoResult(VideoResult videoResult)
|
||||||
|
{
|
||||||
|
VideoResult = videoResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateData(string title, string iamge)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Image = iamge;
|
||||||
|
}
|
||||||
|
public void ChangeStatus(VideoStatusEnum videoStatus)
|
||||||
|
{
|
||||||
|
this.VideoResult.ChangeStatus(videoStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
SumaTube.Domain/Entities/Videos/VideoGroup.cs
Normal file
45
SumaTube.Domain/Entities/Videos/VideoGroup.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using SumaTube.Domain.ValueObjects;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Domain.Entities.Videos
|
||||||
|
{
|
||||||
|
public class VideoGroup
|
||||||
|
{
|
||||||
|
public Id Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public List<VideoData> Videos { get; private set; }
|
||||||
|
|
||||||
|
public VideoGroup(string name, string description)
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
CreatedAt = DateTime.Now;
|
||||||
|
Videos = new List<VideoData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoGroup(string id, string name, string description, DateTime createdAt, List<VideoData> videos)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
CreatedAt = createdAt;
|
||||||
|
Videos = videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddVideo(string sessionId, string url)
|
||||||
|
{
|
||||||
|
Videos.Add(new VideoData(sessionId, url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
SumaTube.Domain/Entities/Videos/VideoResult.cs
Normal file
29
SumaTube.Domain/Entities/Videos/VideoResult.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Domain.Entities.Videos
|
||||||
|
{
|
||||||
|
public enum VideoStatusEnum
|
||||||
|
{
|
||||||
|
Requested,
|
||||||
|
Processed,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
public class VideoResult
|
||||||
|
{
|
||||||
|
public string VideoUrl { get; private set; }
|
||||||
|
public string Summary { get; private set; }
|
||||||
|
public string ErrorMessage { get; private set; }
|
||||||
|
public VideoStatusEnum VideoStatus { get; private set; }
|
||||||
|
public DateTime LastStatusDate { get; private set; }
|
||||||
|
|
||||||
|
public void ChangeStatus(VideoStatusEnum status)
|
||||||
|
{
|
||||||
|
LastStatusDate = DateTime.Now;
|
||||||
|
VideoStatus = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
SumaTube.Domain/Entities/Videos/VideoSummary.cs
Normal file
62
SumaTube.Domain/Entities/Videos/VideoSummary.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SumaTube.Domain.Entities.Videos
|
||||||
|
{
|
||||||
|
public class VideoSummary
|
||||||
|
{
|
||||||
|
public string Id { get; private set; }
|
||||||
|
public string VideoId { get; private set; }
|
||||||
|
public string Title { get; private set; }
|
||||||
|
public string ThumbnailUrl { get; private set; }
|
||||||
|
public string Status { get; private set; }
|
||||||
|
public DateTime RequestDate { get; private set; }
|
||||||
|
public string Language { get; private set; }
|
||||||
|
public string UserId { get; private set; }
|
||||||
|
public string Summary { get; private set; }
|
||||||
|
public string Transcription { get; private set; }
|
||||||
|
public int Duration { get; private set; }
|
||||||
|
public string ErrorMessage { get; private set; }
|
||||||
|
public string SessionId { get; private set; }
|
||||||
|
|
||||||
|
// Factory method
|
||||||
|
public static VideoSummary Create(string videoId, string userId, string language, string sessionId)
|
||||||
|
{
|
||||||
|
return new VideoSummary
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid().ToString(),
|
||||||
|
VideoId = videoId,
|
||||||
|
Title = "Carregando...",
|
||||||
|
ThumbnailUrl = $"https://img.youtube.com/vi/{videoId}/maxresdefault.jpg",
|
||||||
|
Status = "PROCESSANDO",
|
||||||
|
RequestDate = DateTime.UtcNow,
|
||||||
|
Language = language,
|
||||||
|
UserId = userId,
|
||||||
|
SessionId = sessionId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods to update state
|
||||||
|
public void SetAsCompleted(string title, string summary, string transcription, int duration)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Summary = summary;
|
||||||
|
Transcription = transcription;
|
||||||
|
Duration = duration;
|
||||||
|
Status = "REALIZADO";
|
||||||
|
ErrorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAsFailed(string errorMessage)
|
||||||
|
{
|
||||||
|
Status = "ERRO";
|
||||||
|
ErrorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For MongoDB/ORM
|
||||||
|
private VideoSummary() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,4 +18,9 @@
|
|||||||
<ProjectReference Include="..\BaseDomain\BaseDomain.csproj" />
|
<ProjectReference Include="..\BaseDomain\BaseDomain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Contracts\UserPlan\" />
|
||||||
|
<Folder Include="DomainServices\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Diagnostics.Metrics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
namespace Blinks.Domain
|
namespace SumaTube.Domain
|
||||||
{
|
{
|
||||||
public class DateBirth : AValueObject
|
public class DateBirth : AValueObject
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
using BaseDomain;
|
using BaseDomain;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Blinks.Domain
|
namespace SumaTube.Domain
|
||||||
{
|
{
|
||||||
public class DateChanged : AValueObject
|
public class DateChanged : AValueObject
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
using BaseDomain;
|
using BaseDomain;
|
||||||
using System.Net.Mail;
|
using System.Net.Mail;
|
||||||
|
|
||||||
namespace Blinks.Domain
|
namespace SumaTube.Domain
|
||||||
{
|
{
|
||||||
public class Email : AValueObject
|
public class Email : AValueObject
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,22 +1,66 @@
|
|||||||
using BaseDomain;
|
using BaseDomain;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Blinks.Domain.ValueObjects
|
namespace SumaTube.Domain.ValueObjects
|
||||||
{
|
{
|
||||||
public class Id : AValueObject
|
public class Id : AValueObject
|
||||||
{
|
{
|
||||||
|
private string _value;
|
||||||
|
public string Value => _value;
|
||||||
|
|
||||||
public override bool GetValidationExpression()
|
public override bool GetValidationExpression()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return string.IsNullOrWhiteSpace(_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<object> GetEqualityComponents()
|
protected override IEnumerable<object> GetEqualityComponents()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Id(string valor, Guid id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(valor))
|
||||||
|
throw new ArgumentException("O valor não pode ser nulo ou vazio", nameof(valor));
|
||||||
|
|
||||||
|
_value = valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Id(Guid id)
|
||||||
|
{
|
||||||
|
_value = id.ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Id(string valor) : this(valor, Guid.NewGuid())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Id(string valor)
|
||||||
|
{
|
||||||
|
return new Id(valor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Id(Guid id)
|
||||||
|
{
|
||||||
|
return new Id(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static explicit operator string(Id vo)
|
||||||
|
{
|
||||||
|
return vo.Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static explicit operator Guid(Id vo)
|
||||||
|
{
|
||||||
|
return Guid.Parse(vo.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
using BaseDomain;
|
using BaseDomain;
|
||||||
|
|
||||||
namespace Blinks.Domain
|
namespace SumaTube.Domain
|
||||||
{
|
{
|
||||||
public class Name : AValueObject
|
public class Name : AValueObject
|
||||||
{
|
{
|
||||||
@ -15,9 +15,15 @@ namespace Blinks.Domain
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FullName { get; set; }
|
public Name(string firstName, string lastName)
|
||||||
public string FirstName { get; set; }
|
{
|
||||||
public string LastName { get; set; }
|
this.FirstName = firstName;
|
||||||
|
this.LastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FullName { get; private set; }
|
||||||
|
public string FirstName { get; private set; }
|
||||||
|
public string LastName { get; private set; }
|
||||||
|
|
||||||
public override bool GetValidationExpression()
|
public override bool GetValidationExpression()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
using BaseDomain;
|
using BaseDomain;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Blinks.Domain
|
namespace SumaTube.Domain
|
||||||
{
|
{
|
||||||
public class Phone : AValueObject
|
public class Phone : AValueObject
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Blinks.Domain.ValueObjects
|
namespace SumaTube.Domain.ValueObjects
|
||||||
{
|
{
|
||||||
public class Username
|
public class Username
|
||||||
{
|
{
|
||||||
|
|||||||
12
SumaTube.sln
12
SumaTube.sln
@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SumaTube", "SumaTube\SumaTu
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseDomain", "BaseDomain\BaseDomain.csproj", "{8DEA200D-FF43-0D75-15A2-7DA8831449C9}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseDomain", "BaseDomain\BaseDomain.csproj", "{8DEA200D-FF43-0D75-15A2-7DA8831449C9}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SumaTube.Crosscutting", "SumaTube.Crosscutting\SumaTube.Crosscutting.csproj", "{46EE417D-A974-4011-9799-646F31C9C146}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SumaTube.Application", "SumaTube.Application\SumaTube.Application.csproj", "{142F15AA-CD0E-CC29-6972-3FE9A1DB283F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -33,6 +37,14 @@ Global
|
|||||||
{8DEA200D-FF43-0D75-15A2-7DA8831449C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{8DEA200D-FF43-0D75-15A2-7DA8831449C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{8DEA200D-FF43-0D75-15A2-7DA8831449C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{8DEA200D-FF43-0D75-15A2-7DA8831449C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8DEA200D-FF43-0D75-15A2-7DA8831449C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{8DEA200D-FF43-0D75-15A2-7DA8831449C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{46EE417D-A974-4011-9799-646F31C9C146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{46EE417D-A974-4011-9799-646F31C9C146}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{46EE417D-A974-4011-9799-646F31C9C146}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{46EE417D-A974-4011-9799-646F31C9C146}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{142F15AA-CD0E-CC29-6972-3FE9A1DB283F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{142F15AA-CD0E-CC29-6972-3FE9A1DB283F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{142F15AA-CD0E-CC29-6972-3FE9A1DB283F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{142F15AA-CD0E-CC29-6972-3FE9A1DB283F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
public class CartHomeController : Controller
|
public class CartHomeController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Blinks.Models;
|
using SumaTube.Crosscutting.Logging.Extensions;
|
||||||
|
using SumaTube.Models;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<HomeController> logger;
|
private readonly ILogger<HomeController> _logger;
|
||||||
|
|
||||||
public HomeController(ILogger<HomeController> logger)
|
public HomeController(ILogger<HomeController> logger)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this._logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Home carregada!");
|
_logger.LogInformation("Home carregada!");
|
||||||
|
_logger.LogMethodEntry(nameof(Index));
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
public class LanguageController : Controller
|
public class LanguageController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
public class LoginController : Controller
|
public class LoginController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
using Stripe;
|
using Stripe;
|
||||||
using Stripe.Checkout;
|
using Stripe.Checkout;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
[Route("Pay")]
|
[Route("Pay")]
|
||||||
public class PayController : Controller
|
public class PayController : Controller
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
using Stripe.Checkout;
|
using Stripe.Checkout;
|
||||||
using Blinks.Models;
|
using SumaTube.Models;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
public class PlansController : Controller
|
public class PlansController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
[Route("signin-microsoft")]
|
[Route("signin-microsoft")]
|
||||||
public class SignInController : Controller
|
public class SignInController : Controller
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Blinks.Controllers
|
namespace SumaTube.Controllers
|
||||||
{
|
{
|
||||||
public class StartupController : Controller
|
public class StartupController : Controller
|
||||||
{
|
{
|
||||||
|
|||||||
114
SumaTube/Controllers/VideoController.cs
Normal file
114
SumaTube/Controllers/VideoController.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SumaTube.Application.Videos.ApplicationServices;
|
||||||
|
using SumaTube.Application.Videos.Contracts;
|
||||||
|
using SumaTube.Domain.Entities.Videos;
|
||||||
|
using SumaTube.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SumaTube.Controllers
|
||||||
|
{
|
||||||
|
public class VideoController : Controller
|
||||||
|
{
|
||||||
|
private readonly IVideoApplicationService _videoApplicationService;
|
||||||
|
private readonly ILogger<VideoController> _logger;
|
||||||
|
|
||||||
|
public VideoController(
|
||||||
|
IVideoApplicationService videoApplicationService,
|
||||||
|
ILogger<VideoController> logger)
|
||||||
|
{
|
||||||
|
_videoApplicationService = videoApplicationService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Extract()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> VideoSummary(string id)
|
||||||
|
{
|
||||||
|
// Aqui você chamaria sua WebAPI para processar o vídeo
|
||||||
|
// e retornaria o modelo para a view
|
||||||
|
var model = new VideoSummaryViewModel
|
||||||
|
{
|
||||||
|
VideoId = id,
|
||||||
|
VideoTitle = "Título do Vídeo",
|
||||||
|
ChannelName = "Nome do Canal",
|
||||||
|
ChannelThumbnail = "URL da Thumbnail do Canal",
|
||||||
|
PublishedDate = "Data de Publicação",
|
||||||
|
ViewCount = "Quantidade de Visualizações",
|
||||||
|
LikeCount = "Quantidade de Curtidas",
|
||||||
|
CommentCount = "Quantidade de Comentarios",
|
||||||
|
SummaryText = "Resumo do Vídeo",
|
||||||
|
KeyPoints = new List<object> { "Ponto 1", "Ponto 2", "Ponto 3" },
|
||||||
|
Captions = new List<object> { "Legenda 1", "Legenda 2", "Legenda 3" },
|
||||||
|
Keywords = new List<string> { "Palavra 1", "Palavra 2", "Palavra 3" },
|
||||||
|
RelatedTopics = new List<string> { "Tópico 1", "Tópico 2", "Tópico 3" },
|
||||||
|
RelatedVideos = new List<object> { "Vídeo 1", "Vídeo 2", "Vídeo 3" }
|
||||||
|
};
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> MySummary()
|
||||||
|
{
|
||||||
|
var userId = User.Identity.Name; // Ou outra forma de obter o ID do usuário
|
||||||
|
_logger.LogInformation("Carregando página 'Meus Resumos' para usuário: {UserId}", userId);
|
||||||
|
|
||||||
|
var videos = new List<VideoSummary>();
|
||||||
|
if (userId != null)
|
||||||
|
videos = await _videoApplicationService.GetUserVideosAsync(userId);
|
||||||
|
|
||||||
|
return View(videos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint para enviar solicitação de novo resumo
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> RequestSummary(string youtubeUrl, string language)
|
||||||
|
{
|
||||||
|
var userId = User.Identity.Name; // Ou outra forma de obter o ID do usuário
|
||||||
|
_logger.LogInformation("Recebido pedido de resumo. URL: {Url}, Idioma: {Language}, Usuário: {UserId}",
|
||||||
|
youtubeUrl, language, userId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _videoApplicationService.RequestVideoSummaryAsync(youtubeUrl, language, userId);
|
||||||
|
_logger.LogInformation("Resumo solicitado com sucesso. ID: {Id}", result.Id);
|
||||||
|
|
||||||
|
return Json(new { success = true, videoId = result.VideoId, summaryId = result.Id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Erro ao solicitar resumo: {Message}", ex.Message);
|
||||||
|
return Json(new { success = false, error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Página para exibir um resumo específico
|
||||||
|
public async Task<IActionResult> Summary(string id)
|
||||||
|
{
|
||||||
|
var userId = User.Identity.Name; // Ou outra forma de obter o ID do usuário
|
||||||
|
_logger.LogInformation("Acessando resumo. ID: {Id}, Usuário: {UserId}", id, userId);
|
||||||
|
|
||||||
|
var summary = await _videoApplicationService.GetVideoSummaryByIdAsync(id, userId);
|
||||||
|
|
||||||
|
if (summary == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Resumo não encontrado. ID: {Id}", id);
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return View(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint para verificar status de processamento
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> CheckStatus(string id)
|
||||||
|
{
|
||||||
|
var userId = User.Identity.Name; // Ou outra forma de obter o ID do usuário
|
||||||
|
_logger.LogInformation("Verificando status do resumo. ID: {Id}, Usuário: {UserId}", id, userId);
|
||||||
|
|
||||||
|
var status = await _videoApplicationService.CheckSummaryStatusAsync(id, userId);
|
||||||
|
return Json(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,16 +7,16 @@ EXPOSE 443
|
|||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ["Blinks.csproj", "."]
|
COPY ["SumaTube.csproj", "."]
|
||||||
RUN dotnet restore "./Blinks.csproj"
|
RUN dotnet restore "./SumaTube.csproj"
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR "/src/."
|
WORKDIR "/src/."
|
||||||
RUN dotnet build "Blinks.csproj" -c Release -o /app/build
|
RUN dotnet build "SumaTube.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
RUN dotnet publish "Blinks.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
RUN dotnet publish "SumaTube.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
ENTRYPOINT ["dotnet", "Blinks.dll"]
|
ENTRYPOINT ["dotnet", "SumaTube.dll"]
|
||||||
@ -1,6 +1,6 @@
|
|||||||
//using Serilog.Sinks.Loki;
|
//using Serilog.Sinks.Loki;
|
||||||
|
|
||||||
//namespace Blinks.LogConfig
|
//namespace SumaTube.LogConfig
|
||||||
//{
|
//{
|
||||||
// public class LogCredentials : LokiCredentials
|
// public class LogCredentials : LokiCredentials
|
||||||
// {
|
// {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//using Serilog.Sinks.Loki.Labels;
|
//using Serilog.Sinks.Loki.Labels;
|
||||||
|
|
||||||
namespace Blinks.LogConfig
|
namespace SumaTube.LogConfig
|
||||||
{
|
{
|
||||||
//public class LogLabelProvider : ILogLabelProvider
|
//public class LogLabelProvider : ILogLabelProvider
|
||||||
//{
|
//{
|
||||||
@ -8,7 +8,7 @@ namespace Blinks.LogConfig
|
|||||||
// {
|
// {
|
||||||
// return new List<LokiLabel>
|
// return new List<LokiLabel>
|
||||||
// {
|
// {
|
||||||
// new LokiLabel("app", "blinks"),
|
// new LokiLabel("app", "SumaTube"),
|
||||||
// new LokiLabel("namespace", "test")
|
// new LokiLabel("namespace", "test")
|
||||||
// };
|
// };
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Blinks.Middle
|
namespace SumaTube.Middle
|
||||||
{
|
{
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Blinks.Models
|
namespace SumaTube.Models
|
||||||
{
|
{
|
||||||
public class ChangeViewModel
|
public class ChangeViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Blinks.Models
|
namespace SumaTube.Models
|
||||||
{
|
{
|
||||||
public class ErrorViewModel
|
public class ErrorViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Blinks.Models
|
namespace SumaTube.Models
|
||||||
{
|
{
|
||||||
public class Payment
|
public class Payment
|
||||||
{
|
{
|
||||||
|
|||||||
23
SumaTube/Models/VideoSummaryItem.cs
Normal file
23
SumaTube/Models/VideoSummaryItem.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SumaTube.Models
|
||||||
|
{
|
||||||
|
public class VideoSummaryItem
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string VideoId { get; set; } // ID do vídeo do YouTube
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string ThumbnailUrl { get; set; }
|
||||||
|
public string Status { get; set; } // PROCESSANDO, REALIZADO, ERRO
|
||||||
|
public DateTime RequestDate { get; set; }
|
||||||
|
public string Language { get; set; }
|
||||||
|
public string UserId { get; set; } // ID do usuário que solicitou o resumo
|
||||||
|
|
||||||
|
// Propriedades para quando o resumo for concluído
|
||||||
|
public string Summary { get; set; }
|
||||||
|
public string Transcription { get; set; }
|
||||||
|
public int Duration { get; set; } // Duração do vídeo em segundos
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
20
SumaTube/Models/VideoSummaryViewModel.cs
Normal file
20
SumaTube/Models/VideoSummaryViewModel.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace SumaTube.Models
|
||||||
|
{
|
||||||
|
public class VideoSummaryViewModel
|
||||||
|
{
|
||||||
|
public string VideoId { get; set; }
|
||||||
|
public string VideoTitle { get; set; }
|
||||||
|
public string ChannelName { get; set; }
|
||||||
|
public string ChannelThumbnail { get; set; }
|
||||||
|
public string PublishedDate { get; set; }
|
||||||
|
public string ViewCount { get; set; }
|
||||||
|
public string LikeCount { get; set; }
|
||||||
|
public string CommentCount { get; set; }
|
||||||
|
public string SummaryText { get; set; }
|
||||||
|
public List<object> KeyPoints { get; set; }
|
||||||
|
public List<object> Captions { get; set; }
|
||||||
|
public List<string> Keywords { get; set; }
|
||||||
|
public List<string> RelatedTopics { get; set; }
|
||||||
|
public List<object> RelatedVideos { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"path": "Blinks.csproj",
|
"path": "SumaTube.csproj",
|
||||||
"startingProject": true,
|
"startingProject": true,
|
||||||
"issues": 1,
|
"issues": 1,
|
||||||
"storyPoints": 1,
|
"storyPoints": 1,
|
||||||
@ -40,7 +40,7 @@
|
|||||||
{
|
{
|
||||||
"incidentId": "1161414d-b2e4-4447-9d34-e811b563e1c5",
|
"incidentId": "1161414d-b2e4-4447-9d34-e811b563e1c5",
|
||||||
"ruleId": "NuGet.0001",
|
"ruleId": "NuGet.0001",
|
||||||
"projectPath": "Blinks.csproj",
|
"projectPath": "SumaTube.csproj",
|
||||||
"state": "Active",
|
"state": "Active",
|
||||||
"location": {
|
"location": {
|
||||||
"snippetModel": {
|
"snippetModel": {
|
||||||
@ -48,7 +48,7 @@
|
|||||||
"protected": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets, 1.19.4\n\nRecommendation:\n\nNo supported version found"
|
"protected": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets, 1.19.4\n\nRecommendation:\n\nNo supported version found"
|
||||||
},
|
},
|
||||||
"kind": "File",
|
"kind": "File",
|
||||||
"path": "Blinks.csproj",
|
"path": "SumaTube.csproj",
|
||||||
"snippet": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets, 1.19.4\n\nRecommendation:\n\nNo supported version found",
|
"snippet": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets, 1.19.4\n\nRecommendation:\n\nNo supported version found",
|
||||||
"protectedSnippet": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets, 1.19.4\n\nRecommendation:\n\nNo supported version found",
|
"protectedSnippet": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets, 1.19.4\n\nRecommendation:\n\nNo supported version found",
|
||||||
"label": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets 1.19.4"
|
"label": "Microsoft.VisualStudio.Azure.Containers.Tools.Targets 1.19.4"
|
||||||
@ -57,7 +57,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "C:\\vscode\\Blinks.me.mvc\\Blinks.Domain\\Blinks.Domain.csproj",
|
"path": "C:\\vscode\\SumaTube.me.mvc\\SumaTube.Domain\\SumaTube.Domain.csproj",
|
||||||
"startingProject": true,
|
"startingProject": true,
|
||||||
"issues": 1,
|
"issues": 1,
|
||||||
"storyPoints": 1,
|
"storyPoints": 1,
|
||||||
@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"incidentId": "2fdd89a6-9f8d-4428-8845-41d048dcaf73",
|
"incidentId": "2fdd89a6-9f8d-4428-8845-41d048dcaf73",
|
||||||
"ruleId": "Project.0002",
|
"ruleId": "Project.0002",
|
||||||
"projectPath": "C:\\vscode\\Blinks.me.mvc\\Blinks.Domain\\Blinks.Domain.csproj",
|
"projectPath": "C:\\vscode\\SumaTube.me.mvc\\SumaTube.Domain\\SumaTube.Domain.csproj",
|
||||||
"state": "Active",
|
"state": "Active",
|
||||||
"location": {
|
"location": {
|
||||||
"snippetModel": {
|
"snippetModel": {
|
||||||
@ -73,7 +73,7 @@
|
|||||||
"protected": "Current: net7.0\nNew: net8.0"
|
"protected": "Current: net7.0\nNew: net8.0"
|
||||||
},
|
},
|
||||||
"kind": "File",
|
"kind": "File",
|
||||||
"path": "C:\\vscode\\Blinks.me.mvc\\Blinks.Domain\\Blinks.Domain.csproj",
|
"path": "C:\\vscode\\SumaTube.me.mvc\\SumaTube.Domain\\SumaTube.Domain.csproj",
|
||||||
"snippet": "Current: net7.0\nNew: net8.0",
|
"snippet": "Current: net7.0\nNew: net8.0",
|
||||||
"protectedSnippet": "Current: net7.0\nNew: net8.0"
|
"protectedSnippet": "Current: net7.0\nNew: net8.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using Blinks.LogConfig;
|
using SumaTube.LogConfig;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authentication.Google;
|
using Microsoft.AspNetCore.Authentication.Google;
|
||||||
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
||||||
@ -11,30 +11,23 @@ using Stripe;
|
|||||||
using Stripe.Forwarding;
|
using Stripe.Forwarding;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Policy;
|
using System.Security.Policy;
|
||||||
|
using SumaTube.Crosscutting.Logging.Configuration;
|
||||||
|
using SumaTube.Application.Register;
|
||||||
|
using SumaTube.Infra.Register;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
//var credentials = new BasicAuthCredentials("http://192.168.0.82:3100");
|
builder.Host.UseSerilog((context, services, configuration) =>
|
||||||
//var credentials = new LogCredentials("http://192.168.0.82:3100");
|
{
|
||||||
|
builder.SetLoggerConfiguration(configuration, services, context.Configuration);
|
||||||
Log.Logger = new LoggerConfiguration()
|
});
|
||||||
.MinimumLevel.Information()
|
|
||||||
.Enrich.FromLogContext()
|
|
||||||
.Enrich.WithProperty("app", "blinks")
|
|
||||||
.WriteTo.Console()
|
|
||||||
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
|
|
||||||
.WriteTo.GrafanaLoki(
|
|
||||||
uri: "http://192.168.0.82:3100",
|
|
||||||
propertiesAsLabels: new List<string> { "app", "blinks" },
|
|
||||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug
|
|
||||||
)
|
|
||||||
.CreateLogger();
|
|
||||||
|
|
||||||
builder.Host.UseSerilog();
|
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
|
builder.Services.AddApplicationServices();
|
||||||
|
builder.Services.AddInfraServices(builder.Configuration);
|
||||||
|
|
||||||
var config = builder.Configuration;
|
var config = builder.Configuration;
|
||||||
|
|
||||||
//builder.Services.AddAuthentication()
|
//builder.Services.AddAuthentication()
|
||||||
@ -86,21 +79,29 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
|||||||
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
|
StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"];
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
builder.Services.AddSerilog();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseSerilogRequestLogging(options =>
|
||||||
|
{
|
||||||
|
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
|
||||||
|
{
|
||||||
|
diagnosticContext.Set("UserAgent", httpContext.Request.Headers["User-Agent"]);
|
||||||
|
diagnosticContext.Set("ClientIP", httpContext.Connection.RemoteIpAddress);
|
||||||
|
diagnosticContext.Set("UserName", httpContext.User?.Identity?.Name ?? "Anonymous");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
var locOptions = app.Services.GetService<IOptions<RequestLocalizationOptions>>();
|
var locOptions = app.Services.GetService<IOptions<RequestLocalizationOptions>>();
|
||||||
app.UseRequestLocalization(locOptions.Value);
|
app.UseRequestLocalization(locOptions.Value);
|
||||||
|
|
||||||
app.UseMiddleware<RequestLocalizationMiddleware>();
|
app.UseMiddleware<RequestLocalizationMiddleware>();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
app.UseExceptionHandler("/Home/Error");
|
app.UseExceptionHandler("/Home/Error");
|
||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,8 +117,11 @@ app.MapControllerRoute(
|
|||||||
name: "default",
|
name: "default",
|
||||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||||
|
|
||||||
app.UseSerilogRequestLogging();
|
|
||||||
|
|
||||||
app.UseRequestLocalization();
|
app.UseRequestLocalization();
|
||||||
|
|
||||||
|
Log.Information("Aplicação iniciando");
|
||||||
|
Log.Warning("Este é um aviso de teste");
|
||||||
|
Log.Error("Este é um erro de teste para verificar o Seq");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
4
SumaTube/Resource.Designer.cs
generated
4
SumaTube/Resource.Designer.cs
generated
@ -8,7 +8,7 @@
|
|||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace Blinks {
|
namespace SumaTube {
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ namespace Blinks {
|
|||||||
public static global::System.Resources.ResourceManager ResourceManager {
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
get {
|
get {
|
||||||
if (object.ReferenceEquals(resourceMan, null)) {
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Blinks.Resource", typeof(Resource).Assembly);
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SumaTube.Resource", typeof(Resource).Assembly);
|
||||||
resourceMan = temp;
|
resourceMan = temp;
|
||||||
}
|
}
|
||||||
return resourceMan;
|
return resourceMan;
|
||||||
|
|||||||
2
SumaTube/Resource.pt-BR.Designer.cs
generated
2
SumaTube/Resource.pt-BR.Designer.cs
generated
@ -8,7 +8,7 @@
|
|||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace Blinks {
|
namespace SumaTube {
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,22 +9,38 @@
|
|||||||
<DockerfileContext>.</DockerfileContext>
|
<DockerfileContext>.</DockerfileContext>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Views\CartHome\**" />
|
||||||
|
<Content Remove="Views\CartHome\**" />
|
||||||
|
<EmbeddedResource Remove="Views\CartHome\**" />
|
||||||
|
<None Remove="Views\CartHome\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.7" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
|
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
|
||||||
|
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
|
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
<PackageReference Include="Stripe.net" Version="45.13.0" />
|
<PackageReference Include="Stripe.net" Version="45.13.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Views\CartHome\" />
|
|
||||||
<Folder Include="wwwroot\img\" />
|
<Folder Include="wwwroot\img\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SumaTube.Application\SumaTube.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\SumaTube.Crosscutting\SumaTube.Crosscutting.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Resource.Designer.cs">
|
<Compile Update="Resource.Designer.cs">
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
|
|||||||
@ -1,79 +1,279 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Home Page";
|
ViewBag.Title = "Resumos Inteligentes de Vídeos";
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Scripts {
|
<!-- Hero Banner -->
|
||||||
<script type="text/javascript">
|
<div class="hero-banner mb-5">
|
||||||
</script>
|
<div class="container">
|
||||||
}
|
<div class="row align-items-center">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h1>Transforme qualquer vídeo em um resumo inteligente</h1>
|
||||||
|
<p class="lead">Extraímos as legendas e criamos resumos precisos para você economizar tempo e maximizar o aprendizado.</p>
|
||||||
|
|
||||||
@section Styles {
|
<a href="@Url.Action("Index", "Login")" class="btn btn-hero">
|
||||||
<style type="text/css">
|
<i class="bi bi-magic mr-2"></i> Fazer login e resumir um vídeo
|
||||||
.text-responsive {
|
|
||||||
font-size: calc(100% + 1vw + 1vh);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="div-body">
|
|
||||||
<div class="container">
|
|
||||||
<div id="myCarousel" class="carousel slide" data-ride="carousel">
|
|
||||||
<ol class="carousel-indicators">
|
|
||||||
<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
|
|
||||||
<li data-target="#myCarousel" data-slide-to="1"></li>
|
|
||||||
<li data-target="#myCarousel" data-slide-to="2"></li>
|
|
||||||
<li data-target="#myCarousel" data-slide-to="3"></li>
|
|
||||||
</ol>
|
|
||||||
<div class="carousel-inner" role="listbox">
|
|
||||||
<div class="carousel-item active">
|
|
||||||
<img class="first-slide" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="First slide">
|
|
||||||
<div class="container">
|
|
||||||
<div class="carousel-caption d-md-block text-left">
|
|
||||||
<h1 class="text-responsive">Example headline.</h1>
|
|
||||||
<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
|
|
||||||
<p><a class="btn btn-lg btn-primary" href="#" role="button">Sign up today</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="carousel-item">
|
|
||||||
<img class="second-slide" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Second slide">
|
|
||||||
<div class="container">
|
|
||||||
<div class="carousel-caption d-md-block">
|
|
||||||
<h1 class="text-responsive">Another example headline.</h1>
|
|
||||||
<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
|
|
||||||
<p><a class="btn btn-lg btn-primary" href="#" role="button">Learn more</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="carousel-item">
|
|
||||||
<img class="third-slide" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Third slide">
|
|
||||||
<div class="container">
|
|
||||||
<div class="carousel-caption d-md-block text-right">
|
|
||||||
<h1 class="text-responsive">One more for good measure.</h1>
|
|
||||||
<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
|
|
||||||
<p><a class="btn btn-lg btn-primary" href="#" role="button">Browse gallery</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="carousel-item">
|
|
||||||
<img class="fourth-slide" src="/img/teste1.png" alt="Fourth slide">
|
|
||||||
<div class="container">
|
|
||||||
<div class="carousel-caption d-md-block text-right text-dark">
|
|
||||||
<h1 class="text-responsive">One more for good measure.</h1>
|
|
||||||
<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
|
|
||||||
<p><a class="btn btn-lg btn-primary" href="#" role="button">Browse gallery</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a class="carousel-control-prev" href="#myCarousel" role="button" data-slide="prev">
|
|
||||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
|
||||||
<span class="sr-only">Anterior</span>
|
|
||||||
</a>
|
</a>
|
||||||
<a class="carousel-control-next" href="#myCarousel" role="button" data-slide="next">
|
</div>
|
||||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
<div class="col-lg-6 mt-4 mt-lg-0 text-center">
|
||||||
<span class="sr-only">Próximo</span>
|
<div class="hero-image">
|
||||||
|
<img src="/img/video-summary-demo.png" alt="Ilustração de resumo de vídeo" class="img-fluid" onerror="this.src='https://via.placeholder.com/600x350?text=SumaTube';" />
|
||||||
|
<span class="demo-badge">
|
||||||
|
<i class="bi bi-play-circle mr-1"></i> Ver demonstração
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Como funciona -->
|
||||||
|
<section class="mb-5">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="text-center mb-4">Como funciona</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100 text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-youtube text-danger" style="font-size: 3rem;"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="suma-card-title">1. Cole o link do YouTube</h3>
|
||||||
|
<p class="suma-card-body">
|
||||||
|
Basta colar o URL do vídeo do YouTube que você deseja resumir. Funciona com qualquer vídeo que tenha legendas disponíveis.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100 text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-cpu text-danger" style="font-size: 3rem;"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="suma-card-title">2. Processamento inteligente</h3>
|
||||||
|
<p class="suma-card-body">
|
||||||
|
Nossa tecnologia extrai as legendas e utiliza inteligência artificial para identificar os pontos mais importantes do vídeo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100 text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-file-earmark-text text-danger" style="font-size: 3rem;"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="suma-card-title">3. Receba seu resumo</h3>
|
||||||
|
<p class="suma-card-body">
|
||||||
|
Em poucos segundos, você recebe um resumo completo e organizado com os principais pontos abordados no vídeo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Recursos -->
|
||||||
|
<section class="mb-5 bg-light py-5">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="text-center mb-4">Economize tempo com nossos recursos</h2>
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-lg-6 mb-4 mb-lg-0 order-lg-2">
|
||||||
|
@*
|
||||||
|
<img src="/images/features-illustration.png" alt="Recursos do SumaTube" class="img-fluid rounded shadow" onerror="this.src='https://via.placeholder.com/600x400?text=Recursos+SumaTube';" />
|
||||||
|
|
||||||
|
*@
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 order-lg-1">
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<div class="mr-3">
|
||||||
|
<i class="bi bi-clock text-danger" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Economize tempo</h3>
|
||||||
|
<p>Assista apenas o que realmente importa com nossos resumos personalizados.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<div class="mr-3">
|
||||||
|
<i class="bi bi-translate text-danger" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Suporte multi-idioma</h3>
|
||||||
|
<p>Resuma vídeos em diversos idiomas com a mesma precisão e qualidade.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<div class="mr-3">
|
||||||
|
<i class="bi bi-bookmark-check text-danger" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Pontos-chave destacados</h3>
|
||||||
|
<p>Identificamos automaticamente os conceitos e informações mais importantes em cada vídeo.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="mr-3">
|
||||||
|
<i class="bi bi-cloud-download text-danger" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Salve para consultar depois</h3>
|
||||||
|
<p>Mantenha todos seus resumos organizados em sua biblioteca pessoal.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Planos -->
|
||||||
|
<section class="mb-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2>Escolha o plano ideal para você</h2>
|
||||||
|
<p class="lead">Oferecemos opções flexíveis para atender às suas necessidades</p>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100 text-center">
|
||||||
|
<span class="badge badge-pill badge-light mb-3">Gratuito</span>
|
||||||
|
<h3 class="suma-card-title">Basic</h3>
|
||||||
|
<div class="my-4">
|
||||||
|
<span class="h1">R$ 0</span>
|
||||||
|
<span class="text-muted">/mês</span>
|
||||||
|
</div>
|
||||||
|
<ul class="list-unstyled mb-4">
|
||||||
|
<li class="mb-2">3 resumos por mês</li>
|
||||||
|
<li class="mb-2">Resumos básicos</li>
|
||||||
|
<li class="mb-2">Suporte por e-mail</li>
|
||||||
|
<li class="mb-2 text-muted"><del>Biblioteca personalizada</del></li>
|
||||||
|
<li class="mb-2 text-muted"><del>Exportação em PDF</del></li>
|
||||||
|
</ul>
|
||||||
|
<a href="#" class="btn btn-outline-danger">Começar agora</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100 text-center" style="transform: scale(1.05); border: 2px solid var(--suma-red);">
|
||||||
|
<span class="badge badge-pill badge-danger mb-3">Popular</span>
|
||||||
|
<h3 class="suma-card-title">Pro</h3>
|
||||||
|
<div class="my-4">
|
||||||
|
<span class="h1">R$ 19,90</span>
|
||||||
|
<span class="text-muted">/mês</span>
|
||||||
|
</div>
|
||||||
|
<ul class="list-unstyled mb-4">
|
||||||
|
<li class="mb-2">30 resumos por mês</li>
|
||||||
|
<li class="mb-2">Resumos detalhados</li>
|
||||||
|
<li class="mb-2">Suporte prioritário</li>
|
||||||
|
<li class="mb-2">Biblioteca personalizada</li>
|
||||||
|
<li class="mb-2">Exportação em PDF</li>
|
||||||
|
</ul>
|
||||||
|
<a href="#" class="btn btn-danger">Escolher Pro</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100 text-center">
|
||||||
|
<span class="badge badge-pill badge-light mb-3">Premium</span>
|
||||||
|
<h3 class="suma-card-title">Business</h3>
|
||||||
|
<div class="my-4">
|
||||||
|
<span class="h1">R$ 49,90</span>
|
||||||
|
<span class="text-muted">/mês</span>
|
||||||
|
</div>
|
||||||
|
<ul class="list-unstyled mb-4">
|
||||||
|
<li class="mb-2">Resumos ilimitados</li>
|
||||||
|
<li class="mb-2">Resumos avançados</li>
|
||||||
|
<li class="mb-2">Suporte VIP</li>
|
||||||
|
<li class="mb-2">Biblioteca com categorias</li>
|
||||||
|
<li class="mb-2">Exportação em vários formatos</li>
|
||||||
|
</ul>
|
||||||
|
<a href="#" class="btn btn-outline-danger">Escolher Business</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Depoimentos -->
|
||||||
|
<section class="mb-5 bg-light py-5">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="text-center mb-5">O que nossos usuários dizem</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100">
|
||||||
|
<div class="d-flex mb-3 align-items-center">
|
||||||
|
<div class="rounded-circle overflow-hidden mr-3" style="width: 50px; height: 50px;">
|
||||||
|
<img src="https://via.placeholder.com/50x50" alt="Usuário" class="img-fluid" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Mariana Silva</h5>
|
||||||
|
<small class="text-muted">Estudante de Medicina</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="suma-card-body">
|
||||||
|
"O SumaTube revolucionou meus estudos. Consigo extrair o essencial de aulas longas em minutos, o que me ajudou a otimizar muito meu tempo de estudo."
|
||||||
|
</p>
|
||||||
|
<div class="text-warning">
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100">
|
||||||
|
<div class="d-flex mb-3 align-items-center">
|
||||||
|
<div class="rounded-circle overflow-hidden mr-3" style="width: 50px; height: 50px;">
|
||||||
|
<img src="https://via.placeholder.com/50x50" alt="Usuário" class="img-fluid" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Carlos Mendes</h5>
|
||||||
|
<small class="text-muted">Profissional de Marketing</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="suma-card-body">
|
||||||
|
"Uso o SumaTube para acompanhar webinars e palestras do meu setor. Os resumos são precisos e me ajudam a extrair insights valiosos sem gastar horas assistindo vídeos."
|
||||||
|
</p>
|
||||||
|
<div class="text-warning">
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-half"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="suma-card h-100">
|
||||||
|
<div class="d-flex mb-3 align-items-center">
|
||||||
|
<div class="rounded-circle overflow-hidden mr-3" style="width: 50px; height: 50px;">
|
||||||
|
<img src="https://via.placeholder.com/50x50" alt="Usuário" class="img-fluid" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Júlia Santos</h5>
|
||||||
|
<small class="text-muted">Professora</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="suma-card-body">
|
||||||
|
"Recomendo o SumaTube para todos meus alunos. É uma ferramenta incrível para complementar os estudos e revisar conteúdos de forma eficiente."
|
||||||
|
</p>
|
||||||
|
<div class="text-warning">
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
<i class="bi bi-star-fill"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
|
<section class="mb-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="suma-card bg-danger text-white text-center py-5">
|
||||||
|
<h2 class="mb-4">Comece a economizar tempo hoje mesmo</h2>
|
||||||
|
<p class="lead mb-4">Transforme a maneira como você assiste vídeos e absorve conhecimento</p>
|
||||||
|
<a href="@Url.Action("Index", "Login")" class="btn btn-light btn-lg px-4">
|
||||||
|
<i class="bi bi-play-circle mr-2"></i> Começar gratuitamente
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
@ -1,102 +0,0 @@
|
|||||||
.btn-primary {
|
|
||||||
background-color: darkgreen;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-red {
|
|
||||||
background-color: darkred;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.div-body {
|
|
||||||
padding-top: 3rem;
|
|
||||||
padding-bottom: 3rem;
|
|
||||||
color: #5a5a5a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-inverse {
|
|
||||||
background-color: #292b2c !important;
|
|
||||||
}
|
|
||||||
/* CUSTOMIZE THE CAROUSEL
|
|
||||||
-------------------------------------------------- */
|
|
||||||
|
|
||||||
/* Carousel base class */
|
|
||||||
.carousel {
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
}
|
|
||||||
/* Since positioning the image, we need to help out the caption */
|
|
||||||
.carousel-caption {
|
|
||||||
z-index: 10;
|
|
||||||
bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Declare heights because of positioning of img element */
|
|
||||||
.carousel-item {
|
|
||||||
height: 32rem;
|
|
||||||
background-color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-item > img {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
min-width: 100%;
|
|
||||||
height: 32rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* MARKETING CONTENT
|
|
||||||
-------------------------------------------------- */
|
|
||||||
|
|
||||||
/* Center align the text within the three columns below the carousel */
|
|
||||||
.marketing .col-lg-4 {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.marketing h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.marketing .col-lg-4 p {
|
|
||||||
margin-right: .75rem;
|
|
||||||
margin-left: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Featurettes
|
|
||||||
------------------------- */
|
|
||||||
|
|
||||||
.featurette-divider {
|
|
||||||
margin: 5rem 0; /* Space out the Bootstrap <hr> more */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Thin out the marketing headings */
|
|
||||||
.featurette-heading {
|
|
||||||
font-weight: 300;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: -.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* RESPONSIVE CSS
|
|
||||||
-------------------------------------------------- */
|
|
||||||
|
|
||||||
@media (min-width: 40em) {
|
|
||||||
/* Bump up size of carousel content */
|
|
||||||
.carousel-caption p {
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featurette-heading {
|
|
||||||
font-size: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 62em) {
|
|
||||||
.featurette-heading {
|
|
||||||
margin-top: 7rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,127 +1,111 @@
|
|||||||
@{
|
@{
|
||||||
ViewBag.Title = "Login / Registro";
|
ViewBag.Title = "Login / Registro";
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Styles {
|
@section Styles {
|
||||||
<style>
|
<style>
|
||||||
.box {
|
.login-container {
|
||||||
width: 500px;
|
max-width: 500px;
|
||||||
margin: 200px 0;
|
margin: 60px auto;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape1 {
|
.login-icon {
|
||||||
position: relative;
|
font-size: 2.5rem;
|
||||||
height: 150px;
|
color: var(--suma-red);
|
||||||
width: 150px;
|
margin-bottom: 25px;
|
||||||
background-color: #0074d9;
|
|
||||||
border-radius: 80px;
|
|
||||||
float: left;
|
|
||||||
margin-right: -50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape2 {
|
.login-title {
|
||||||
position: relative;
|
font-size: 28px;
|
||||||
height: 150px;
|
font-weight: 700;
|
||||||
width: 150px;
|
color: var(--suma-red);
|
||||||
background-color: #0074d9;
|
margin-bottom: 15px;
|
||||||
border-radius: 80px;
|
|
||||||
margin-top: -30px;
|
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape3 {
|
.login-subtitle {
|
||||||
position: relative;
|
font-size: 16px;
|
||||||
height: 150px;
|
color: var(--suma-dark-gray);
|
||||||
width: 150px;
|
margin-bottom: 30px;
|
||||||
background-color: #0074d9;
|
|
||||||
border-radius: 80px;
|
|
||||||
margin-top: -30px;
|
|
||||||
float: left;
|
|
||||||
margin-left: -31px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape4 {
|
.login-button {
|
||||||
position: relative;
|
display: flex;
|
||||||
height: 150px;
|
align-items: center;
|
||||||
width: 150px;
|
justify-content: center;
|
||||||
background-color: #0074d9;
|
background-color: white;
|
||||||
border-radius: 80px;
|
color: #444;
|
||||||
margin-top: -25px;
|
border: 1px solid #ddd;
|
||||||
float: left;
|
border-radius: 5px;
|
||||||
margin-left: -32px;
|
padding: 12px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape5 {
|
.login-button:hover {
|
||||||
position: relative;
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
height: 150px;
|
transform: translateY(-2px);
|
||||||
width: 150px;
|
|
||||||
background-color: #0074d9;
|
|
||||||
border-radius: 80px;
|
|
||||||
float: left;
|
|
||||||
margin-right: -48px;
|
|
||||||
margin-left: -32px;
|
|
||||||
margin-top: -30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape6 {
|
.login-button i {
|
||||||
position: relative;
|
margin-right: 10px;
|
||||||
height: 150px;
|
color: #4285F4;
|
||||||
width: 150px;
|
|
||||||
background-color: #0074d9;
|
|
||||||
border-radius: 80px;
|
|
||||||
float: left;
|
|
||||||
margin-right: -20px;
|
|
||||||
margin-top: -35px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape7 {
|
.login-footer {
|
||||||
position: relative;
|
margin-top: 30px;
|
||||||
height: 150px;
|
font-size: 14px;
|
||||||
width: 150px;
|
color: var(--suma-dark-gray);
|
||||||
background-color: #0074d9;
|
|
||||||
border-radius: 80px;
|
|
||||||
float: left;
|
|
||||||
margin-right: -20px;
|
|
||||||
margin-top: -57px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.float {
|
.login-footer a {
|
||||||
position: absolute;
|
color: var(--suma-red);
|
||||||
z-index: 2;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.login-footer a:hover {
|
||||||
margin-left: 145px;
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon-container {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--suma-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon-container i {
|
||||||
|
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<div id="login-row" class="row justify-content-center align-items-center">
|
<div class="login-container">
|
||||||
<div id="login-column" class="col-md-6">
|
<div class="login-icon">
|
||||||
<div class="box">
|
<i class="bi bi-play-btn-fill"></i>
|
||||||
<div class="shape1"></div>
|
</div>
|
||||||
<div class="shape2"></div>
|
<h1 class="login-title">Bem-vindo ao SumaTube</h1>
|
||||||
<div class="shape3"></div>
|
<p class="login-subtitle">Faça login para criar resumos inteligentes de vídeos do YouTube</p>
|
||||||
<div class="shape4"></div>
|
<div class="video-icon-container">
|
||||||
<div class="shape5"></div>
|
<i class="bi bi-file-earmark-text"></i>
|
||||||
<div class="shape6"></div>
|
<i class="bi bi-arrow-left-right mx-2"></i>
|
||||||
<div class="shape7"></div>
|
<i class="bi bi-youtube"></i>
|
||||||
<div class="float">
|
</div>
|
||||||
<br />
|
@using (Html.BeginForm("ExternalLoginGoogle", "Login", new { provider = "Google" }, FormMethod.Post, true, new { id = "googleLoginForm" }))
|
||||||
<br />
|
|
||||||
@using (Html.BeginForm("ExternalLogin", "Login", new { provider = "Microsoft" }, FormMethod.Post, true, new { id = "externalLoginForm", style = "margin-left: 150px" }))
|
|
||||||
{
|
{
|
||||||
@Html.AntiForgeryToken()
|
@Html.AntiForgeryToken()
|
||||||
<button type="submit" class="btn btn-success btn-block login">Login with Microsoft</button>
|
<button type="submit" class="login-button">
|
||||||
}
|
<i class="bi bi-google"></i>
|
||||||
<br />
|
Continuar/Entrar com Google
|
||||||
@using (Html.BeginForm("ExternalLoginGoogle", "Login", new { provider = "Google" }, FormMethod.Post, true, new { id = "externalLoginForm", style = "margin-left: 150px" }))
|
</button>
|
||||||
{
|
|
||||||
@Html.AntiForgeryToken()
|
|
||||||
<button type="submit" class="btn btn-success btn-block login">Login with Google</button>
|
|
||||||
}
|
}
|
||||||
</div>
|
<div class="login-footer">
|
||||||
</div>
|
Ao fazer login, você concorda com nossos <a href="/termos">Termos de Serviço</a> e <a href="/privacidade">Política de Privacidade</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
109
SumaTube/Views/Plans/Index - Copy.cshtml
Normal file
109
SumaTube/Views/Plans/Index - Copy.cshtml
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
@{
|
||||||
|
ViewBag.Title = "Planos";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row db-padding-btm db-attached">
|
||||||
|
<div class="col-4 col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
||||||
|
<div class="db-wrapper">
|
||||||
|
<div class="db-pricing-eleven db-bk-color-one">
|
||||||
|
<div class="price text-price-responsive">
|
||||||
|
<sup>R$</sup> 0,00
|
||||||
|
<small>para sempre!</small>
|
||||||
|
</div>
|
||||||
|
<div class="type text-type-responsive">
|
||||||
|
Básico
|
||||||
|
</div>
|
||||||
|
<ul class="text-responsive">
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>1 Bio/Links <span class="hide-in-cell">pessoal virtual</span></li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Exibir imagem </span>Whatsapp *</li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>Até 3 links </li>
|
||||||
|
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Sem página de</span> Produtos</li>
|
||||||
|
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Sem contador de </span>Visualizações</span></li>
|
||||||
|
</ul>
|
||||||
|
<div class="pricing-footer">
|
||||||
|
@if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
<div class="btn db-button-color-square btn-lg">Plano Inicial</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="btn db-button-color-square btn-lg">Comece com este!</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
||||||
|
<div class="db-wrapper">
|
||||||
|
<div class="db-pricing-eleven db-bk-color-two popular">
|
||||||
|
<div class="price text-price-responsive">
|
||||||
|
<sup>R$</sup>13
|
||||||
|
<small>por mês</small>
|
||||||
|
</div>
|
||||||
|
<div class="type text-type-responsive">
|
||||||
|
BIO
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>1 Bio/Links <span class="hide-in-cell">pessoal virtual</span></li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Exibir cartão </span>Whatsapp *</li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>100 links</li>
|
||||||
|
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Sem página de</span> Produtos</li>
|
||||||
|
@*
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>1 Link agendado **</li>
|
||||||
|
*@
|
||||||
|
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Contador de</span> Visualizações</li>
|
||||||
|
@*
|
||||||
|
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Registro de contatos</span> </li>
|
||||||
|
*@
|
||||||
|
</ul>
|
||||||
|
<div class="pricing-footer">
|
||||||
|
@if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
<a href="/Pay/?plan=bio" class="btn db-button-color-square btn-lg">Assinar</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a href="/Login" class="btn db-button-color-square btn-lg">Login / Registro</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
||||||
|
<div class="db-wrapper">
|
||||||
|
<div class="db-pricing-eleven db-bk-color-three">
|
||||||
|
<div class="price text-price-responsive">
|
||||||
|
<sup>R$</sup>27
|
||||||
|
<small>por mês</small>
|
||||||
|
</div>
|
||||||
|
<div class="type text-type-responsive">
|
||||||
|
CATÁLOGO
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>1 Bio/Cartão <span class="hide-in-cell">pessoal virtual</span></li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Exibir cartão </span>Whatsapp *</li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>Sem limite de links </li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>Links de Produtos</li>
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i>Contador<span class="hide-in-cell"> de visualizações</span> </li>
|
||||||
|
@*
|
||||||
|
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Registro de contatos</span> </li>
|
||||||
|
*@
|
||||||
|
</ul>
|
||||||
|
<div class="pricing-footer">
|
||||||
|
@if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn db-button-color-square btn-lg" onclick="waitingDialog.show('Custom message');window.location.href = '/Pay/?plan=catalogo';">Assinar</button>
|
||||||
|
|
||||||
|
@*
|
||||||
|
<a href="/Pay/?plan=catalogo" class="btn db-button-color-square btn-lg">Assinar</a>
|
||||||
|
*@
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a href="/Login" class="btn db-button-color-square btn-lg">Login / Registro</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
225
SumaTube/Views/Plans/Index - Copy.cshtml.css
Normal file
225
SumaTube/Views/Plans/Index - Copy.cshtml.css
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
/*=============================================================
|
||||||
|
|
||||||
|
Authour URL: www.designbootstrap.com
|
||||||
|
|
||||||
|
http://www.designbootstrap.com/
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
======================================================== */
|
||||||
|
|
||||||
|
/*============================================================
|
||||||
|
BACKGROUND COLORS
|
||||||
|
============================================================*/
|
||||||
|
.db-bk-color-one {
|
||||||
|
background-color: #D9EDD2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-bk-color-two {
|
||||||
|
background-color: #B9CEA0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-bk-color-three {
|
||||||
|
background-color: #D9EDD2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-bk-color-six {
|
||||||
|
background-color: #F59B24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-padding-btm {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-button-color-square {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(0, 0, 0, 0.50);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
-moz-border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-button-color-square:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(0, 0, 0, 0.50);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.db-pricing-eleven {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
margin-top: 50px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, .5);
|
||||||
|
border-radius: 7px 7px 7px 7px;
|
||||||
|
color: #101211;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven ul li {
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven ul li i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.db-pricing-eleven .price {
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
color: #353000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven .price small {
|
||||||
|
color: #63783F;
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven .type {
|
||||||
|
background-color: #63783F;
|
||||||
|
padding: 50px 20px;
|
||||||
|
font-weight: 900;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-price-responsive {
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 20px 10px 10px 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-type-responsive {
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-responsive {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-in-cell {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 544px) {
|
||||||
|
.db-pricing-eleven ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-price-responsive {
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 20px 10px 10px 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-type-responsive {
|
||||||
|
font-size: 30px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-responsive {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-in-cell {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medium devices (tablets, 768px and up) */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.text-price-responsive {
|
||||||
|
padding: 40px 20px 20px 20px;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-type-responsive {
|
||||||
|
font-size: 30px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-responsive {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large devices (desktops, 992px and up) */
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.text-price-responsive {
|
||||||
|
padding: 40px 20px 20px 20px;
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-type-responsive {
|
||||||
|
font-size: 30px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-responsive {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra large devices (large desktops, 1200px and up) */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.db-pricing-eleven ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.text-price-responsive {
|
||||||
|
padding: 40px 20px 20px 20px;
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-type-responsive {
|
||||||
|
color: white;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-responsive {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-in-cell {
|
||||||
|
display: inline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.db-pricing-eleven .pricing-footer {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-attached > .col-lg-4,
|
||||||
|
.db-attached > .col-lg-3,
|
||||||
|
.db-attached > .col-md-4,
|
||||||
|
.db-attached > .col-md-3,
|
||||||
|
.db-attached > .col-sm-4,
|
||||||
|
.db-attached > .col-sm-3 {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven.popular {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-pricing-eleven.popular .price {
|
||||||
|
padding-top: 80px;
|
||||||
|
}
|
||||||
@ -1,109 +1,394 @@
|
|||||||
@{
|
@{
|
||||||
ViewBag.Title = "Planos";
|
ViewData["Title"] = "Planos";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="row db-padding-btm db-attached">
|
@section Styles {
|
||||||
<div class="col-4 col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
<style type="text/css">
|
||||||
<div class="db-wrapper">
|
.plans-header {
|
||||||
<div class="db-pricing-eleven db-bk-color-one">
|
background-color: var(--suma-beige);
|
||||||
<div class="price text-price-responsive">
|
padding: 3rem 0;
|
||||||
<sup>R$</sup> 0,00
|
border-radius: 0 0 50% 50% / 20px;
|
||||||
<small>para sempre!</small>
|
margin-bottom: 3rem;
|
||||||
</div>
|
|
||||||
<div class="type text-type-responsive">
|
|
||||||
Básico
|
|
||||||
</div>
|
|
||||||
<ul class="text-responsive">
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>1 Bio/Links <span class="hide-in-cell">pessoal virtual</span></li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Exibir imagem </span>Whatsapp *</li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>Até 3 links </li>
|
|
||||||
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Sem página de</span> Produtos</li>
|
|
||||||
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Sem contador de </span>Visualizações</span></li>
|
|
||||||
</ul>
|
|
||||||
<div class="pricing-footer">
|
|
||||||
@if (User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
<div class="btn db-button-color-square btn-lg">Plano Inicial</div>
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="btn db-button-color-square btn-lg">Comece com este!</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
|
||||||
<div class="db-wrapper">
|
|
||||||
<div class="db-pricing-eleven db-bk-color-two popular">
|
|
||||||
<div class="price text-price-responsive">
|
|
||||||
<sup>R$</sup>13
|
|
||||||
<small>por mês</small>
|
|
||||||
</div>
|
|
||||||
<div class="type text-type-responsive">
|
|
||||||
BIO
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>1 Bio/Links <span class="hide-in-cell">pessoal virtual</span></li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Exibir cartão </span>Whatsapp *</li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>100 links</li>
|
|
||||||
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Sem página de</span> Produtos</li>
|
|
||||||
@*
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>1 Link agendado **</li>
|
|
||||||
*@
|
|
||||||
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Contador de</span> Visualizações</li>
|
|
||||||
@*
|
|
||||||
<li><i class="bi bi-square text-success"></i><span class="hide-in-cell">Registro de contatos</span> </li>
|
|
||||||
*@
|
|
||||||
</ul>
|
|
||||||
<div class="pricing-footer">
|
|
||||||
@if (User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
<a href="/Pay/?plan=bio" class="btn db-button-color-square btn-lg">Assinar</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a href="/Login" class="btn db-button-color-square btn-lg">Login / Registro</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 col-xs-4 col-sm-4 col-md-4 col-lg-4">
|
|
||||||
<div class="db-wrapper">
|
|
||||||
<div class="db-pricing-eleven db-bk-color-three">
|
|
||||||
<div class="price text-price-responsive">
|
|
||||||
<sup>R$</sup>27
|
|
||||||
<small>por mês</small>
|
|
||||||
</div>
|
|
||||||
<div class="type text-type-responsive">
|
|
||||||
CATÁLOGO
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>1 Bio/Cartão <span class="hide-in-cell">pessoal virtual</span></li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Exibir cartão </span>Whatsapp *</li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>Sem limite de links </li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>Links de Produtos</li>
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i>Contador<span class="hide-in-cell"> de visualizações</span> </li>
|
|
||||||
@*
|
|
||||||
<li><i class="bi bi-plus-square-fill text-success"></i><span class="hide-in-cell">Registro de contatos</span> </li>
|
|
||||||
*@
|
|
||||||
</ul>
|
|
||||||
<div class="pricing-footer">
|
|
||||||
@if (User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn db-button-color-square btn-lg" onclick="waitingDialog.show('Custom message');window.location.href = '/Pay/?plan=catalogo';">Assinar</button>
|
|
||||||
|
|
||||||
@*
|
.pricing-card {
|
||||||
<a href="/Pay/?plan=catalogo" class="btn db-button-color-square btn-lg">Assinar</a>
|
background-color: white;
|
||||||
*@
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
height: 100%;
|
||||||
|
border: 2px solid transparent;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
.pricing-card:hover {
|
||||||
<a href="/Login" class="btn db-button-color-square btn-lg">Login / Registro</a>
|
transform: translateY(-10px);
|
||||||
|
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing-card.popular {
|
||||||
|
border-color: var(--suma-red);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popular-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--suma-red);
|
||||||
|
color: white;
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-radius: 0 12px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--suma-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-price {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: var(--suma-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-period {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--suma-dark-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-feature {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-feature i {
|
||||||
|
color: var(--suma-red);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-feature.disabled {
|
||||||
|
color: var(--suma-dark-gray);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-action {
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-red {
|
||||||
|
color: var(--suma-red);
|
||||||
|
border-color: var(--suma-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-red:hover {
|
||||||
|
background-color: var(--suma-red);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-question {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--suma-red);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Header da página -->
|
||||||
|
<div class="plans-header">
|
||||||
|
<div class="container text-center">
|
||||||
|
<h1 class="display-4 mb-3">Planos de Assinatura</h1>
|
||||||
|
<p class="lead mb-4">Escolha o plano ideal para suas necessidades de resumo de vídeos</p>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-light active" id="monthlyBtn">Mensal</button>
|
||||||
|
<button type="button" class="btn btn-light" id="yearlyBtn">Anual (20% desconto)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cards de planos -->
|
||||||
|
<div class="container mb-5">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Plano Gratuito -->
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="pricing-card">
|
||||||
|
<div class="pricing-header">
|
||||||
|
<h3>Gratuito</h3>
|
||||||
|
<div class="pricing-price">R$ 0</div>
|
||||||
|
<div class="pricing-period">para sempre</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pricing-features">
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>3 resumos por mês</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Resumos básicos</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Vídeos de até 10 minutos</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature disabled">
|
||||||
|
<i class="bi bi-x-circle"></i>
|
||||||
|
<span>Download de resumos</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature disabled">
|
||||||
|
<i class="bi bi-x-circle"></i>
|
||||||
|
<span>Transcrição completa</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature disabled">
|
||||||
|
<i class="bi bi-x-circle"></i>
|
||||||
|
<span>Suporte prioritário</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-action">
|
||||||
|
<a href="#" class="btn btn-outline-red btn-lg btn-block">Começar grátis</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plano Premium -->
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="pricing-card popular">
|
||||||
|
<div class="popular-badge">Mais popular</div>
|
||||||
|
<div class="pricing-header">
|
||||||
|
<h3>Premium</h3>
|
||||||
|
<div class="pricing-price" id="premiumPrice">R$ 29,90</div>
|
||||||
|
<div class="pricing-period" id="premiumPeriod">por mês</div>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-features">
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Resumos ilimitados</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Resumos detalhados</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Vídeos de até 3 horas</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Download de resumos (PDF)</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Transcrição completa</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature disabled">
|
||||||
|
<i class="bi bi-x-circle"></i>
|
||||||
|
<span>Suporte prioritário</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-action">
|
||||||
|
<a href="#" class="btn btn-primary btn-lg btn-block">Assinar agora</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plano Pro -->
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="pricing-card">
|
||||||
|
<div class="pricing-header">
|
||||||
|
<h3>Profissional</h3>
|
||||||
|
<div class="pricing-price" id="proPrice">R$ 59,90</div>
|
||||||
|
<div class="pricing-period" id="proPeriod">por mês</div>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-features">
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Tudo do plano Premium</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Resumos para vídeos de qualquer duração</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Análise avançada de conteúdo</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Extração de pontos-chave personalizados</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>API para integração</span>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-feature">
|
||||||
|
<i class="bi bi-check-circle-fill"></i>
|
||||||
|
<span>Suporte prioritário 24/7</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-action">
|
||||||
|
<a href="#" class="btn btn-outline-red btn-lg btn-block">Experimente 7 dias grátis</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabela comparativa -->
|
||||||
|
<div class="mt-5 mb-5">
|
||||||
|
<h3 class="text-center mb-4">Comparação detalhada dos planos</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th>Recurso</th>
|
||||||
|
<th class="text-center">Gratuito</th>
|
||||||
|
<th class="text-center">Premium</th>
|
||||||
|
<th class="text-center">Profissional</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Resumos mensais</td>
|
||||||
|
<td class="text-center">3</td>
|
||||||
|
<td class="text-center">Ilimitados</td>
|
||||||
|
<td class="text-center">Ilimitados</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Duração máxima dos vídeos</td>
|
||||||
|
<td class="text-center">10 minutos</td>
|
||||||
|
<td class="text-center">3 horas</td>
|
||||||
|
<td class="text-center">Sem limite</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Transcrição completa</td>
|
||||||
|
<td class="text-center"><i class="bi bi-x text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="bi bi-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="bi bi-check text-success"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Download de resumos</td>
|
||||||
|
<td class="text-center"><i class="bi bi-x text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="bi bi-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="bi bi-check text-success"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Palavras-chave e tópicos</td>
|
||||||
|
<td class="text-center">Básico</td>
|
||||||
|
<td class="text-center">Avançado</td>
|
||||||
|
<td class="text-center">Personalizado</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Acesso a API</td>
|
||||||
|
<td class="text-center"><i class="bi bi-x text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="bi bi-x text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="bi bi-check text-success"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Suporte</td>
|
||||||
|
<td class="text-center">Email</td>
|
||||||
|
<td class="text-center">Email e chat</td>
|
||||||
|
<td class="text-center">Prioritário 24/7</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FAQs -->
|
||||||
|
<div class="container mb-5">
|
||||||
|
<h3 class="text-center mb-4">Perguntas Frequentes</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="faq-item">
|
||||||
|
<h5 class="faq-question">Como funciona o SumaTube?</h5>
|
||||||
|
<p>O SumaTube usa tecnologia avançada de IA para extrair e analisar as legendas dos vídeos do YouTube, criando resumos inteligentes que capturam os pontos principais do conteúdo.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faq-item">
|
||||||
|
<h5 class="faq-question">Posso cancelar minha assinatura a qualquer momento?</h5>
|
||||||
|
<p>Sim, você pode cancelar sua assinatura a qualquer momento. Não há contratos de longo prazo ou taxas de cancelamento.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faq-item">
|
||||||
|
<h5 class="faq-question">Como são processados os pagamentos?</h5>
|
||||||
|
<p>Processamos pagamentos via cartão de crédito, débito, PayPal e Pix. Todas as transações são seguras e criptografadas.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="faq-item">
|
||||||
|
<h5 class="faq-question">E se o vídeo não tiver legendas?</h5>
|
||||||
|
<p>O SumaTube pode gerar legendas automaticamente para vídeos que não as possuem, embora a precisão possa variar dependendo da qualidade do áudio.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faq-item">
|
||||||
|
<h5 class="faq-question">Quais idiomas são suportados?</h5>
|
||||||
|
<p>Atualmente suportamos resumos em português, inglês e espanhol. Estamos constantemente adicionando suporte para novos idiomas.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faq-item">
|
||||||
|
<h5 class="faq-question">Como posso entrar em contato com o suporte?</h5>
|
||||||
|
<p>Você pode entrar em contato conosco através do email suporte@sumatube.com ou pelo chat disponível no site para usuários Premium e Profissional.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Call to Action -->
|
||||||
|
<div class="text-center mt-5">
|
||||||
|
<h3 class="mb-3">Pronto para começar?</h3>
|
||||||
|
<p class="mb-4">Escolha o plano ideal para suas necessidades e comece a usar o SumaTube hoje mesmo.</p>
|
||||||
|
<a href="#" class="btn btn-primary btn-lg">Criar conta gratuita</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Toggle entre planos mensais e anuais
|
||||||
|
$('#monthlyBtn').click(function() {
|
||||||
|
$(this).addClass('active');
|
||||||
|
$('#yearlyBtn').removeClass('active');
|
||||||
|
|
||||||
|
// Atualizar preços para mensais
|
||||||
|
$('#premiumPrice').text('R$ 29,90');
|
||||||
|
$('#premiumPeriod').text('por mês');
|
||||||
|
$('#proPrice').text('R$ 59,90');
|
||||||
|
$('#proPeriod').text('por mês');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#yearlyBtn').click(function() {
|
||||||
|
$(this).addClass('active');
|
||||||
|
$('#monthlyBtn').removeClass('active');
|
||||||
|
|
||||||
|
// Atualizar preços para anuais com desconto
|
||||||
|
$('#premiumPrice').text('R$ 287,04');
|
||||||
|
$('#premiumPeriod').text('por ano (R$ 23,92/mês)');
|
||||||
|
$('#proPrice').text('R$ 575,04');
|
||||||
|
$('#proPeriod').text('por ano (R$ 47,92/mês)');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Animação aos cards
|
||||||
|
$('.pricing-card').hover(
|
||||||
|
function() {
|
||||||
|
$(this).addClass('shadow-lg');
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
$(this).removeClass('shadow-lg');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -1,118 +1,189 @@
|
|||||||
@using System.Security.Claims
|
@using System.Security.Claims
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="pt-br">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>@ViewData["Title"] - Blinks</title>
|
<title>@ViewData["Title"] - SumaTube</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="~/css/custom.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/SumaTube.styles.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/SumaTube.styles.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
@await RenderSectionAsync("Styles", required: false)
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
</head>
|
</head>
|
||||||
<body class="hide-body">
|
<body class="d-flex flex-column min-vh-100 hide-body">
|
||||||
<partial name="_Busy" />
|
<partial name="_Busy" />
|
||||||
<div id="wrapper">
|
|
||||||
|
<header>
|
||||||
<nav id="nav-bar" class="navbar navbar-expand-lg navbar-dark">
|
<nav id="nav-bar" class="navbar navbar-expand-lg navbar-dark">
|
||||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">SumaTube</a>
|
<div class="container">
|
||||||
|
<a class="navbar-brand d-flex align-items-center" asp-area="" asp-controller="Home" asp-action="Index">
|
||||||
|
<i class="bi bi-play-btn-fill mr-2"></i> SumaTube
|
||||||
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Campo de busca central -->
|
||||||
|
<div class="d-none d-md-block mx-auto">
|
||||||
|
<form class="form-inline my-2 my-lg-0">
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control search-input" type="search" placeholder="Buscar vídeos..." aria-label="Buscar">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-light" type="submit">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="navbar-collapse collapse" id="navbarNav">
|
<div class="navbar-collapse collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-white" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
|
<a class="nav-link text-white" asp-area="" asp-controller="Home" asp-action="Index">
|
||||||
|
<i class="bi bi-house-door"></i> Home
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@*
|
||||||
|
Exibir apenas são não estiver logado.
|
||||||
|
*@
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-white" asp-area="" asp-controller="Plans" asp-action="Index">
|
||||||
|
<i class="bi bi-star"></i> Planos
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-white" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
<a class="nav-link text-white" asp-area="" asp-controller="Video" asp-action="MySummary">
|
||||||
|
<i class="bi bi-file-text"></i> Meus resumos
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-white" asp-area="" asp-controller="Plans" asp-action="Index">Planos</a>
|
<a class="nav-link text-white" asp-area="" asp-controller="Video" asp-action="Extract">
|
||||||
|
<i class="bi bi-magic"></i> Extract
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<partial name="_Language" />
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@if (User != null && User.Identity != null && !User.Identity.IsAuthenticated)
|
@if (User != null && User.Identity != null && !User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<partial name="_Language" />
|
<li class="nav-item">
|
||||||
<li class="nav-item" style="margin-right: 10px">
|
<a class="nav-link text-white" asp-area="" asp-controller="Login" asp-action="Index">
|
||||||
<a class="nav-link text-white" asp-area="" asp-controller="Login" asp-action="Index"><i class="bi bi-person"></i> Login</a>
|
<i class="bi bi-box-arrow-in-right"></i> Login
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<partial name="_Language"/>
|
|
||||||
<li class="nav-item dropdown" style="margin-right: 10px">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<i class="bi bi-person"></i> @(User.FindFirst("FullName")!=null ? User.FindFirst("FullName").Value : "N/A")
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" asp-area="" asp-controller="Login" asp-action="Logout">Sair</a>
|
|
||||||
@*
|
@*
|
||||||
<a class="dropdown-item" href="#">Action</a>
|
Criar uma tela que permita alterar o plano.
|
||||||
<a class="dropdown-item" href="#">Another action</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#">Something else here</a>
|
|
||||||
*@
|
*@
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle text-white" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="bi bi-person-circle"></i> @(User.FindFirst("FullName") != null ? User.FindFirst("FullName").Value : "N/A")
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||||
|
<a class="dropdown-item" asp-controller="Account" asp-action="Profile">
|
||||||
|
<i class="bi bi-person"></i> Meu Perfil
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" asp-controller="Videos" asp-action="History">
|
||||||
|
<i class="bi bi-clock-history"></i> Histórico
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" asp-area="" asp-controller="Login" asp-action="Logout">
|
||||||
|
<i class="bi bi-box-arrow-right"></i> Sair
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@*
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link text-white" asp-area="" asp-controller="Login" asp-action="Logout">@User.FindFirst("FullName").Value
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
*@
|
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
|
||||||
<div class="container">
|
|
||||||
@RenderBody()
|
|
||||||
@*
|
|
||||||
<main role="main" class="pb-3">
|
|
||||||
</main>
|
|
||||||
*@
|
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<footer class="border-top footer text-muted">
|
<!-- Campo de busca para dispositivos móveis -->
|
||||||
|
<div class="container d-md-none mt-3 mb-3">
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="input-group w-100">
|
||||||
|
<input class="form-control" type="search" placeholder="Buscar vídeos..." aria-label="Buscar">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-light" type="submit">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Conteúdo principal -->
|
||||||
|
<main class="flex-grow-1">
|
||||||
|
<div class="container py-4">
|
||||||
|
@RenderBody()
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer mt-auto py-3">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
© 2024 - Blinks - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-4 mb-3 mb-md-0">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bi bi-play-btn-fill mr-2" style="color: var(--suma-red); font-size: 1.2rem;"></i>
|
||||||
|
<span class="font-weight-bold">SumaTube</span>
|
||||||
|
<span class="mx-2">|</span>
|
||||||
|
<small>Resumos inteligentes de vídeos</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3 mb-md-0 text-center">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<a href="#" class="text-muted mx-2"><i class="bi bi-facebook"></i></a>
|
||||||
|
<a href="#" class="text-muted mx-2"><i class="bi bi-twitter"></i></a>
|
||||||
|
<a href="#" class="text-muted mx-2"><i class="bi bi-instagram"></i></a>
|
||||||
|
<a href="#" class="text-muted mx-2"><i class="bi bi-youtube"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-md-right">
|
||||||
|
<div class="text-center text-md-right">
|
||||||
|
<small class="text-muted">© 2024 - Todos os direitos reservados</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
|
||||||
|
<!-- Scripts -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/wait.js" asp-append-version="true"></script>
|
<script src="~/js/wait.js" asp-append-version="true"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(function () {
|
$(function () {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.loading').hide();
|
$('.loading').hide();
|
||||||
//$('body').fadeIn(1000);
|
$('body').fadeIn(800);
|
||||||
$('body').slideDown('slow');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('a[href="#search"]').on('click', function (event) {
|
// Animação para links do menu
|
||||||
event.preventDefault();
|
$('.navbar-nav .nav-link').hover(
|
||||||
$('#search').addClass('open');
|
function() { $(this).addClass('pulse'); },
|
||||||
$('#search > form > input[type="search"]').focus();
|
function() { $(this).removeClass('pulse'); }
|
||||||
inactivateActiveOption();
|
);
|
||||||
searchActive();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#search, #search button.close').on('click keyup', function (event) {
|
// Ativar o item de menu atual
|
||||||
if (event.target == this || event.target.className == 'close' || event.keyCode == 27) {
|
$(document).ready(function () {
|
||||||
$(this).removeClass('open');
|
|
||||||
setActiveByLocation();
|
setActiveByLocation();
|
||||||
searchInactive();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Controle de carregamento de página
|
||||||
$(window).on('beforeunload', function () {
|
$(window).on('beforeunload', function () {
|
||||||
displayBusyIndicator();
|
displayBusyIndicator();
|
||||||
});
|
});
|
||||||
@ -121,20 +192,10 @@
|
|||||||
displayBusyIndicator();
|
displayBusyIndicator();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('#wrapper').fadeIn('slow');
|
|
||||||
$('a.nav-link').click(function () {
|
|
||||||
$('#wrapper').fadeOut('slow');
|
|
||||||
});
|
|
||||||
setActiveByLocation();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function setActiveByLocation() {
|
function setActiveByLocation() {
|
||||||
$('ul.navbar-nav').find('a[href="' + location.pathname + '"]')
|
$('ul.navbar-nav').find('a[href="' + location.pathname + '"]')
|
||||||
.closest('li')
|
.closest('li')
|
||||||
.addClass("active")
|
.addClass("active");
|
||||||
|
|
||||||
$('ul.navbar-nav').find('a[href="' + location.pathname + '"]')
|
$('ul.navbar-nav').find('a[href="' + location.pathname + '"]')
|
||||||
.closest('a')
|
.closest('a')
|
||||||
@ -142,37 +203,9 @@
|
|||||||
.addClass('text-black');
|
.addClass('text-black');
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchActive() {
|
|
||||||
$('#searchLi').addClass("active");
|
|
||||||
$('#searchLi > a')
|
|
||||||
.removeClass('text-white')
|
|
||||||
.addClass('text-black');
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchInactive() {
|
|
||||||
$('#searchLi').removeClass("active");
|
|
||||||
$('#searchLi > a')
|
|
||||||
.removeClass('text-black')
|
|
||||||
.addClass('text-white');
|
|
||||||
}
|
|
||||||
|
|
||||||
function inactivateActiveOption() {
|
|
||||||
$('ul.navbar-nav li.active')
|
|
||||||
.closest('li')
|
|
||||||
.removeClass("active")
|
|
||||||
|
|
||||||
$('ul.navbar-nav').find('a[href="' + location.pathname + '"]')
|
|
||||||
.closest('a')
|
|
||||||
.removeClass('text-black')
|
|
||||||
.addClass('text-white');
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayBusyIndicator() {
|
function displayBusyIndicator() {
|
||||||
//$('body').fadeOut(1000);
|
$('body').fadeOut(300);
|
||||||
$('body').slideUp('slow');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//$(".hide-body").fadeOut(2000);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
|||||||
@ -44,14 +44,6 @@ button.accept-policy {
|
|||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search {
|
#search {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Blinks</title>
|
<title>SumaTube</title>
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" integrity="sha384-gfdkjb5BdAXd+lj+gudLWI+BXq4IuLW5IT+brZEZsLFm++aCMlF1V92rMkPaX4PP" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" integrity="sha384-gfdkjb5BdAXd+lj+gudLWI+BXq4IuLW5IT+brZEZsLFm++aCMlF1V92rMkPaX4PP" crossorigin="anonymous">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@using Blinks
|
@using SumaTube
|
||||||
|
|
||||||
@*
|
@*
|
||||||
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||||
|
|||||||
273
SumaTube/Views/Video/Extract.cshtml
Normal file
273
SumaTube/Views/Video/Extract.cshtml
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Extrair Resumo";
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Styles {
|
||||||
|
<style type="text/css">
|
||||||
|
.extract-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extract-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-lg {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: 30px 0 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-extract {
|
||||||
|
border-radius: 0 30px 30px 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
background-color: var(--suma-beige);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon i {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--suma-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
background-color: var(--suma-red);
|
||||||
|
color: white;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
display: none;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container extract-container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="display-4 mb-3">Resumir Vídeo do YouTube</h1>
|
||||||
|
<p class="lead">Cole o link de qualquer vídeo do YouTube para obter um resumo inteligente</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="extract-card mb-5">
|
||||||
|
<form id="extractForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="youtubeUrl"><strong>URL do vídeo</strong></label>
|
||||||
|
<div class="input-group input-group-lg">
|
||||||
|
<input type="url" class="form-control form-control-lg" id="youtubeUrl" placeholder="https://www.youtube.com/watch?v=..." required>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg btn-extract" id="extractButton">
|
||||||
|
<i class="bi bi-magic mr-2"></i> Resumir
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Ex: https://www.youtube.com/watch?v=abcde12345</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-container" id="progressContainer">
|
||||||
|
<p class="text-center mb-2" id="processingStatus">Processando vídeo...</p>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-bar-striped progress-bar-animated bg-danger" id="progressBar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Como funciona -->
|
||||||
|
<div class="mb-5">
|
||||||
|
<h3 class="text-center mb-4">Como funciona</h3>
|
||||||
|
<div class="row">
|
||||||
|
<!-- Passo 1 -->
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="bi bi-link-45deg"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="text-center mb-3">Passo 1</h5>
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="step-number">1</div>
|
||||||
|
<div>
|
||||||
|
Cole o link do vídeo do YouTube que deseja resumir no campo acima.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Passo 2 -->
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="bi bi-cpu"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="text-center mb-3">Passo 2</h5>
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="step-number">2</div>
|
||||||
|
<div>
|
||||||
|
Nossa IA extrai as legendas e processa o conteúdo do vídeo automaticamente.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Passo 3 -->
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<i class="bi bi-file-earmark-text"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="text-center mb-3">Passo 3</h5>
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="step-number">3</div>
|
||||||
|
<div>
|
||||||
|
Receba um resumo completo com os principais pontos, timestamps e transcrição.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recursos -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 text-center mb-4">
|
||||||
|
<h3>Recursos incríveis do SumaTube</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recurso 1 -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="summary-section d-flex">
|
||||||
|
<div class="mr-4">
|
||||||
|
<i class="bi bi-clock" style="font-size: 2rem; color: var(--suma-red);"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5>Economize tempo</h5>
|
||||||
|
<p>Extraia o conteúdo mais importante de vídeos longos em questão de minutos.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recurso 2 -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="summary-section d-flex">
|
||||||
|
<div class="mr-4">
|
||||||
|
<i class="bi bi-translate" style="font-size: 2rem; color: var(--suma-red);"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5>Suporte a múltiplos idiomas</h5>
|
||||||
|
<p>Resumos disponíveis em português, inglês, espanhol e outros idiomas.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recurso 3 -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="summary-section d-flex">
|
||||||
|
<div class="mr-4">
|
||||||
|
<i class="bi bi-lightning-charge" style="font-size: 2rem; color: var(--suma-red);"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5>Processamento rápido</h5>
|
||||||
|
<p>Nossa tecnologia avançada processa os vídeos em segundos.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recurso 4 -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="summary-section d-flex">
|
||||||
|
<div class="mr-4">
|
||||||
|
<i class="bi bi-download" style="font-size: 2rem; color: var(--suma-red);"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5>Download de resumos</h5>
|
||||||
|
<p>Salve os resumos em PDF para referência futura e compartilhamento.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#extractForm').submit(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Validar URL
|
||||||
|
var youtubeUrl = $('#youtubeUrl').val();
|
||||||
|
if (!isValidYouTubeUrl(youtubeUrl)) {
|
||||||
|
alert('Por favor, insira uma URL válida do YouTube.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mostrar progresso
|
||||||
|
$('#extractButton').prop('disabled', true);
|
||||||
|
$('#progressContainer').show();
|
||||||
|
simulateProgress();
|
||||||
|
|
||||||
|
// Simulação de processamento - em um projeto real, aqui você faria a chamada AJAX para sua WebAPI
|
||||||
|
setTimeout(function() {
|
||||||
|
// Redirecionar para a página de resultado (em um caso real, você redirecionaria após receber a resposta da API)
|
||||||
|
window.location.href = '/Video/Summary?id=' + extractVideoId(youtubeUrl);
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function isValidYouTubeUrl(url) {
|
||||||
|
var regex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+/;
|
||||||
|
return regex.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVideoId(url) {
|
||||||
|
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
||||||
|
var match = url.match(regExp);
|
||||||
|
return (match && match[7].length == 11) ? match[7] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateProgress() {
|
||||||
|
var progress = 0;
|
||||||
|
var interval = setInterval(function() {
|
||||||
|
progress += 5;
|
||||||
|
$('#progressBar').css('width', progress + '%');
|
||||||
|
$('#progressBar').attr('aria-valuenow', progress);
|
||||||
|
|
||||||
|
// Atualizar mensagem de status
|
||||||
|
if (progress <= 20) {
|
||||||
|
$('#processingStatus').text('Extraindo informações do vídeo...');
|
||||||
|
} else if (progress <= 40) {
|
||||||
|
$('#processingStatus').text('Processando legendas...');
|
||||||
|
} else if (progress <= 60) {
|
||||||
|
$('#processingStatus').text('Identificando pontos principais...');
|
||||||
|
} else if (progress <= 80) {
|
||||||
|
$('#processingStatus').text('Gerando resumo...');
|
||||||
|
} else {
|
||||||
|
$('#processingStatus').text('Finalizando...');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress >= 100) {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
83
SumaTube/Views/Video/MySummary.cshtml
Normal file
83
SumaTube/Views/Video/MySummary.cshtml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
@using SumaTube.Domain.Entities.Videos
|
||||||
|
@model List<VideoSummary>
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Meus Resumos";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1>Meus Resumos</h1>
|
||||||
|
<a href="@Url.Action("Extract", "Video")" class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus-circle"></i> Novo Resumo
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model == null || !Model.Any())
|
||||||
|
{
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="bi bi-file-earmark-text" style="font-size: 3rem; color: var(--suma-red);"></i>
|
||||||
|
<h3 class="mt-3">Você ainda não possui resumos</h3>
|
||||||
|
<p class="text-muted">Clique em "Novo Resumo" para extrair o conteúdo de um vídeo do YouTube</p>
|
||||||
|
<a href="@Url.Action("Extract", "Video")" class="btn btn-primary mt-3">
|
||||||
|
<i class="bi bi-plus-circle"></i> Criar Meu Primeiro Resumo
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var summary in Model)
|
||||||
|
{
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="position-relative">
|
||||||
|
<img src="@summary.ThumbnailUrl" class="card-img-top" alt="@summary.Title">
|
||||||
|
@if (summary.Status == "PROCESSANDO")
|
||||||
|
{
|
||||||
|
<div class="position-absolute top-0 end-0 p-2 bg-warning text-dark rounded-bottom-left">
|
||||||
|
<i class="bi bi-hourglass-split"></i> Processando
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (summary.Status == "REALIZADO")
|
||||||
|
{
|
||||||
|
<div class="position-absolute top-0 end-0 p-2 bg-success text-white rounded-bottom-left">
|
||||||
|
<i class="bi bi-check-circle"></i> Concluído
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (summary.Status == "ERRO")
|
||||||
|
{
|
||||||
|
<div class="position-absolute top-0 end-0 p-2 bg-danger text-white rounded-bottom-left">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i> Erro
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-truncate" title="@summary.Title">@summary.Title</h5>
|
||||||
|
<p class="card-text text-muted small">
|
||||||
|
<i class="bi bi-calendar"></i> @summary.RequestDate.ToString("dd/MM/yyyy HH:mm")
|
||||||
|
<br>
|
||||||
|
<i class="bi bi-translate"></i> @summary.Language
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white border-top-0">
|
||||||
|
<a href="@Url.Action("Summary", "Video", new { id = summary.Id })" class="btn btn-sm btn-outline-primary w-100">
|
||||||
|
@if (summary.Status == "PROCESSANDO")
|
||||||
|
{
|
||||||
|
<span>Ver Progresso</span>
|
||||||
|
}
|
||||||
|
else if (summary.Status == "REALIZADO")
|
||||||
|
{
|
||||||
|
<span>Ver Resumo</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>Ver Detalhes</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
260
SumaTube/Views/Video/VideoSummary.cshtml
Normal file
260
SumaTube/Views/Video/VideoSummary.cshtml
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
@using SumaTube.Models;
|
||||||
|
|
||||||
|
@model VideoSummaryViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Resumo do Vídeo";
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Styles {
|
||||||
|
<style type="text/css">
|
||||||
|
.timestamp-link {
|
||||||
|
color: var(--suma-red);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-section {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-pill {
|
||||||
|
background-color: var(--suma-beige);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-tab-content {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-frame {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcription-text {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: var(--suma-light-gray);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Coluna do vídeo -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="video-frame mb-4">
|
||||||
|
<div class="embed-responsive embed-responsive-16by9">
|
||||||
|
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/@Model.VideoId" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="summary-section">
|
||||||
|
<h1>@Model.VideoTitle</h1>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<img src="@Model.ChannelThumbnail" alt="@Model.ChannelName" class="rounded-circle mr-2" style="width: 40px; height: 40px;">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">@Model.ChannelName</h6>
|
||||||
|
<small class="text-muted">@Model.PublishedDate • @Model.ViewCount visualizações</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-3 mb-3">
|
||||||
|
<div>
|
||||||
|
<span class="mr-3">
|
||||||
|
<i class="bi bi-hand-thumbs-up"></i> @Model.LikeCount
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<i class="bi bi-chat-left-text"></i> @Model.CommentCount
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-sm btn-outline-dark mr-2">
|
||||||
|
<i class="bi bi-share"></i> Compartilhar
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-dark">
|
||||||
|
<i class="bi bi-bookmark"></i> Salvar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs de navegação -->
|
||||||
|
<ul class="nav nav-tabs" id="summaryTabs" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" id="summary-tab" data-toggle="tab" href="#summary" role="tab" aria-controls="summary" aria-selected="true">
|
||||||
|
<i class="bi bi-journal-text"></i> Resumo
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="transcription-tab" data-toggle="tab" href="#transcription" role="tab" aria-controls="transcription" aria-selected="false">
|
||||||
|
<i class="bi bi-body-text"></i> Transcrição
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="keywords-tab" data-toggle="tab" href="#keywords" role="tab" aria-controls="keywords" aria-selected="false">
|
||||||
|
<i class="bi bi-tags"></i> Palavras-chave
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Conteúdo das tabs -->
|
||||||
|
<div class="tab-content summary-tab-content p-3 bg-white border border-top-0 rounded-bottom mb-4" id="summaryTabsContent">
|
||||||
|
<!-- Tab Resumo -->
|
||||||
|
<div class="tab-pane fade show active" id="summary" role="tabpanel" aria-labelledby="summary-tab">
|
||||||
|
<h4 class="mb-3">Principais pontos</h4>
|
||||||
|
|
||||||
|
@foreach (var point in Model.KeyPoints)
|
||||||
|
{
|
||||||
|
<div class="mb-3">
|
||||||
|
<p>
|
||||||
|
@* <a class="timestamp-link" data-time="@point.TimestampSeconds">@point.TimestampFormatted</a>
|
||||||
|
<strong>@point.Title</strong>
|
||||||
|
</p>
|
||||||
|
<p>@point.Description</p>
|
||||||
|
|
||||||
|
*@
|
||||||
|
<a class="timestamp-link" data-time="30">30s</a>
|
||||||
|
<strong>Titulo</strong>
|
||||||
|
</p>
|
||||||
|
<p>Description</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h4 class="mt-4 mb-3">Resumo geral</h4>
|
||||||
|
<p>@Model.SummaryText</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab Transcrição -->
|
||||||
|
<div class="tab-pane fade" id="transcription" role="tabpanel" aria-labelledby="transcription-tab">
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h4 class="mb-3">Transcrição completa</h4>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="bi bi-download"></i> Baixar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="transcription-text">
|
||||||
|
@foreach (var caption in Model.Captions)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
@*
|
||||||
|
<a class="timestamp-link" data-time="@caption.TimestampSeconds">
|
||||||
|
@caption.TimestampFormatted
|
||||||
|
</a>
|
||||||
|
@caption.Text
|
||||||
|
|
||||||
|
*@
|
||||||
|
<a class="timestamp-link" data-time="30s">
|
||||||
|
30s
|
||||||
|
</a>
|
||||||
|
caption.Text
|
||||||
|
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab Palavras-chave -->
|
||||||
|
<div class="tab-pane fade" id="keywords" role="tabpanel" aria-labelledby="keywords-tab">
|
||||||
|
<h4 class="mb-3">Tópicos principais</h4>
|
||||||
|
<div class="mb-4">
|
||||||
|
@foreach (var keyword in Model.Keywords)
|
||||||
|
{
|
||||||
|
<span class="tag-pill">@keyword</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mb-3">Temas relacionados</h4>
|
||||||
|
<div>
|
||||||
|
@foreach (var topic in Model.RelatedTopics)
|
||||||
|
{
|
||||||
|
<span class="tag-pill">@topic</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Coluna lateral -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<!-- Vídeos recomendados -->
|
||||||
|
<div class="summary-section">
|
||||||
|
<h5 class="mb-3">Vídeos relacionados</h5>
|
||||||
|
|
||||||
|
@foreach (var video in Model.RelatedVideos)
|
||||||
|
{
|
||||||
|
<div class="media mb-3">
|
||||||
|
@*
|
||||||
|
<img src="@video.ThumbnailUrl" class="mr-3" alt="@video.Title" style="width: 120px; border-radius: 4px;">
|
||||||
|
<div class="media-body">
|
||||||
|
<h6 class="mt-0">@video.Title</h6>
|
||||||
|
<small class="text-muted">@video.ChannelName</small><br>
|
||||||
|
<small class="text-muted">@video.ViewCount visualizações</small>
|
||||||
|
</div>
|
||||||
|
*@
|
||||||
|
<img src="#ThumbnailUrl" class="mr-3" alt="#Title" style="width: 120px; border-radius: 4px;">
|
||||||
|
<div class="media-body">
|
||||||
|
<h6 class="mt-0">Title</h6>
|
||||||
|
<small class="text-muted">video.ChannelName</small><br>
|
||||||
|
<small class="text-muted">x visualizações</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Call to action para planos premium -->
|
||||||
|
<div class="summary-section text-center" style="background-color: var(--suma-beige);">
|
||||||
|
<i class="bi bi-star-fill mb-3" style="font-size: 2rem; color: var(--suma-red);"></i>
|
||||||
|
<h5>Obtenha mais recursos com o plano Premium</h5>
|
||||||
|
<p class="small mb-3">Acesso ilimitado a resumos, download de textos e muito mais.</p>
|
||||||
|
<a href="#" class="btn btn-primary btn-block">Ver planos</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Função para clicar nos timestamps e pular para o momento do vídeo
|
||||||
|
$('.timestamp-link').click(function() {
|
||||||
|
var time = $(this).data('time');
|
||||||
|
var videoFrame = $('iframe')[0];
|
||||||
|
var videoUrl = videoFrame.src;
|
||||||
|
|
||||||
|
// Garantir que a URL tem os parâmetros necessários
|
||||||
|
if (videoUrl.indexOf('?') === -1) {
|
||||||
|
videoUrl += '?';
|
||||||
|
} else {
|
||||||
|
videoUrl += '&';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adicionar comando para pular para o tempo específico
|
||||||
|
videoUrl += 'start=' + time;
|
||||||
|
|
||||||
|
// Atualizar o iframe
|
||||||
|
videoFrame.src = videoUrl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
@using Blinks
|
@using SumaTube
|
||||||
@using Blinks.Models
|
@using SumaTube.Models
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|||||||
@ -1,8 +1,28 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Serilog": {
|
||||||
"LogLevel": {
|
"WriteTo": [
|
||||||
"Default": "Information",
|
{ "Name": "Console" },
|
||||||
"Microsoft.AspNetCore": "Warning"
|
{
|
||||||
|
"Name": "File",
|
||||||
|
"Args": {
|
||||||
|
"path": "logs/dev-app-.log",
|
||||||
|
"rollingInterval": "Day"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Seq",
|
||||||
|
"Args": {
|
||||||
|
"serverUrl": "http://192.168.0.76:5341",
|
||||||
|
"compact": true,
|
||||||
|
"batchPostingLimit": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Properties": {
|
||||||
|
"Environment": "Development",
|
||||||
|
"Workspace": "Dev",
|
||||||
|
"Application": "SumaTube"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
SumaTube/appsettings.Production.json
Normal file
33
SumaTube/appsettings.Production.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"Serilog": {
|
||||||
|
"MinimumLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Override": {
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"System": "Error" // Note que em produção aumentamos o nível de logs do sistema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WriteTo": [
|
||||||
|
{
|
||||||
|
"Name": "File",
|
||||||
|
"Args": {
|
||||||
|
"path": "logs/prod-app-.log",
|
||||||
|
"rollingInterval": "Day"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Seq",
|
||||||
|
"Args": {
|
||||||
|
"serverUrl": "http://logs-ingest.carneiro.ddnsfree.com",
|
||||||
|
"compact": true,
|
||||||
|
"batchPostingLimit": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Properties": {
|
||||||
|
"Environment": "Production",
|
||||||
|
"Workspace": "Prod",
|
||||||
|
"Application": "SumaTube"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,20 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Serilog": {
|
||||||
"LogLevel": {
|
"MinimumLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Override": {
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"System": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Enrich": [
|
||||||
|
"FromLogContext",
|
||||||
|
"WithMachineName",
|
||||||
|
"WithThreadId",
|
||||||
|
"WithEnvironmentUserName"
|
||||||
|
],
|
||||||
|
"Properties": {
|
||||||
|
"Application": "SumaTube"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
|
|||||||
255
SumaTube/wwwroot/css/custom.css
Normal file
255
SumaTube/wwwroot/css/custom.css
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/* Cores principais do SumaTube */
|
||||||
|
:root {
|
||||||
|
--suma-red: #cc0000;
|
||||||
|
--suma-light-red: #e60000;
|
||||||
|
--suma-dark-red: #990000;
|
||||||
|
--suma-beige: #f5f5dc;
|
||||||
|
--suma-light-gray: #f8f8f8;
|
||||||
|
--suma-gray: #e0e0e0;
|
||||||
|
--suma-dark-gray: #606060;
|
||||||
|
--suma-black: #212121;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilos Gerais */
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
font-family: 'Roboto', 'Segoe UI', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--suma-light-gray);
|
||||||
|
color: var(--suma-black);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-body {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Barra de Navegação */
|
||||||
|
#nav-bar {
|
||||||
|
background-color: var(--suma-red) !important;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item.active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link.text-black {
|
||||||
|
color: white !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conteúdo principal */
|
||||||
|
main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px 15px;
|
||||||
|
max-width: 1280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: var(--suma-beige);
|
||||||
|
color: var(--suma-dark-gray);
|
||||||
|
padding: 20px 0;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login container */
|
||||||
|
.login-container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 60px auto;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Seção de hero banner */
|
||||||
|
.hero-banner {
|
||||||
|
background: linear-gradient(135deg, var(--suma-red), var(--suma-dark-red));
|
||||||
|
color: white;
|
||||||
|
padding: 2.5rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-banner::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
right: -50%;
|
||||||
|
width: 60%;
|
||||||
|
height: 200%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: rotate(-15deg);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-banner h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-banner .lead {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botões */
|
||||||
|
.btn-primary, .btn-red {
|
||||||
|
background-color: var(--suma-red) !important;
|
||||||
|
border-color: var(--suma-dark-red) !important;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover, .btn-red:hover {
|
||||||
|
background-color: var(--suma-light-red) !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hero {
|
||||||
|
background-color: white;
|
||||||
|
color: var(--suma-red);
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hero:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
color: var(--suma-dark-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsividade */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-banner {
|
||||||
|
padding: 2rem 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-banner h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hero {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animações */
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Efeito de loading */
|
||||||
|
.loading {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
height: 2em;
|
||||||
|
width: 2em;
|
||||||
|
overflow: show;
|
||||||
|
margin: auto;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(245, 245, 220, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading:not(:required):after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
font-size: 10px;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
margin-top: -0.5em;
|
||||||
|
animation: spinner 1500ms infinite linear;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
box-shadow: rgba(204, 0, 0, 0.75) 1.5em 0 0 0, rgba(204, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(204, 0, 0, 0.75) 0 1.5em 0 0, rgba(204, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(204, 0, 0, 0.75) -1.5em 0 0 0, rgba(204, 0, 0, 0.75) -1.1em -1.1em 0 0, rgba(204, 0, 0, 0.75) 0 -1.5em 0 0, rgba(204, 0, 0, 0.75) 1.1em -1.1em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animação do spinner */
|
||||||
|
@keyframes spinner {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
SumaTube/wwwroot/img/video-summary-demo.png
Normal file
BIN
SumaTube/wwwroot/img/video-summary-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Loading…
Reference in New Issue
Block a user