package org.rakam.ui;
import com.google.auto.service.AutoService;
import com.google.common.base.Optional;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Binder;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.OptionalBinder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import org.flywaydb.core.Flyway;
import org.rakam.analysis.CustomParameter;
import org.rakam.analysis.JDBCPoolDataSource;
import org.rakam.config.EncryptionConfig;
import org.rakam.config.JDBCConfig;
import org.rakam.plugin.RakamModule;
import org.rakam.plugin.stream.EventStreamConfig;
import org.rakam.plugin.user.UserPluginConfig;
import org.rakam.report.EmailClientConfig;
import org.rakam.report.eventexplorer.EventExplorerConfig;
import org.rakam.report.realtime.RealTimeConfig;
import org.rakam.server.http.HttpRequestHandler;
import org.rakam.server.http.HttpService;
import org.rakam.ui.UIEvents.ProjectCreatedEvent;
import org.rakam.ui.UIPermissionParameterProvider.Project;
import org.rakam.ui.customreport.CustomPageHttpService;
import org.rakam.ui.customreport.CustomReport;
import org.rakam.ui.customreport.CustomReportHttpService;
import org.rakam.ui.customreport.CustomReportMetadata;
import org.rakam.ui.customreport.JDBCCustomReportMetadata;
import org.rakam.ui.page.CustomPageDatabase;
import org.rakam.ui.page.FileBackedCustomPageDatabase;
import org.rakam.ui.page.JDBCCustomPageDatabase;
import org.rakam.ui.report.Report;
import org.rakam.ui.report.ReportHttpService;
import org.rakam.ui.report.UIRecipeHttpService;
import org.rakam.ui.user.UserSubscriptionHttpService;
import org.rakam.ui.user.WebUserHttpService;
import org.rakam.util.ConditionalModule;
import org.rakam.util.NotFoundHandler;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.util.IntegerMapper;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import static io.airlift.configuration.ConfigBinder.configBinder;
@ConditionalModule(config = "ui.enable", value = "true")
@AutoService(RakamModule.class)
public class RakamUIModule
extends RakamModule
{
@Override
protected void setup(Binder binder)
{
configBinder(binder).bindConfig(EmailClientConfig.class);
configBinder(binder).bindConfig(EncryptionConfig.class);
configBinder(binder).bindConfig(UserPluginConfig.class);
configBinder(binder).bindConfig(RealTimeConfig.class);
configBinder(binder).bindConfig(EventStreamConfig.class);
configBinder(binder).bindConfig(EventExplorerConfig.class);
configBinder(binder).bindConfig(UserPluginConfig.class);
OptionalBinder.newOptionalBinder(binder, AuthService.class);
RakamUIConfig rakamUIConfig = buildConfigObject(RakamUIConfig.class);
OptionalBinder.newOptionalBinder(binder, DashboardService.class);
OptionalBinder.newOptionalBinder(binder, ReportMetadata.class);
OptionalBinder.newOptionalBinder(binder, CustomPageDatabase.class);
OptionalBinder.newOptionalBinder(binder, CustomReportMetadata.class);
if (rakamUIConfig.getCustomPageBackend() != null) {
switch (rakamUIConfig.getCustomPageBackend()) {
case FILE:
binder.bind(FileBackedCustomPageDatabase.class).in(Scopes.SINGLETON);
break;
case JDBC:
binder.bind(JDBCCustomPageDatabase.class).in(Scopes.SINGLETON);
break;
}
}
Multibinder<CustomParameter> customParameters = Multibinder.newSetBinder(binder, CustomParameter.class);
customParameters.addBinding().toProvider(UIPermissionParameterProvider.class);
binder.bind(JDBCPoolDataSource.class)
.annotatedWith(Names.named("ui.metadata.jdbc"))
.toInstance(JDBCPoolDataSource.getOrCreateDataSource(buildConfigObject(JDBCConfig.class, "ui.metadata.jdbc")));
binder.bind(FlywayExecutor.class).asEagerSingleton();
binder.bind(ProjectDeleteEventListener.class).asEagerSingleton();
binder.bind(ReportMetadata.class).to(JDBCReportMetadata.class).in(Scopes.SINGLETON);
binder.bind(CustomReportMetadata.class).to(JDBCCustomReportMetadata.class).in(Scopes.SINGLETON);
binder.bind(DefaultDashboardCreator.class).asEagerSingleton();
Multibinder<HttpService> httpServices = Multibinder.newSetBinder(binder, HttpService.class);
httpServices.addBinding().to(WebUserHttpService.class);
httpServices.addBinding().to(ReportHttpService.class);
httpServices.addBinding().to(DashboardService.class);
httpServices.addBinding().to(CustomReportHttpService.class);
httpServices.addBinding().to(CustomPageHttpService.class);
httpServices.addBinding().to(ProxyWebService.class);
httpServices.addBinding().to(WebHookUIHttpService.class);
httpServices.addBinding().to(ScheduledTaskUIHttpService.class);
httpServices.addBinding().to(CustomEventMapperUIHttpService.class);
httpServices.addBinding().to(ClusterService.class);
httpServices.addBinding().to(UIRecipeHttpService.class);
if(rakamUIConfig.getScreenCaptureService() != null) {
httpServices.addBinding().to(ScheduledEmailService.class);
}
if (rakamUIConfig.getStripeKey() != null) {
httpServices.addBinding().to(UserSubscriptionHttpService.class);
}
httpServices.addBinding().to(RakamUIWebService.class);
binder.bind(HttpRequestHandler.class)
.annotatedWith(NotFoundHandler.class)
.toProvider(WebsiteRequestHandler.class);
}
@Override
public String name()
{
return "Web Interface for Rakam APIs";
}
@Override
public String description()
{
return "Can be used as a BI tool and a tool that allows you to create your customized analytics service frontend.";
}
public enum CustomPageBackend
{
FILE, JDBC
}
public static class DefaultDashboardCreator
{
private final DashboardService service;
private final DBI dbi;
@Inject
public DefaultDashboardCreator(DashboardService service, @javax.inject.Named("ui.metadata.jdbc") JDBCPoolDataSource dataSource)
{
this.service = service;
this.dbi = new DBI(dataSource);
}
@Subscribe
public void onCreateProject(ProjectCreatedEvent event)
{
Integer id;
try (Handle handle = dbi.open()) {
id = handle.createQuery("select user_id from web_user_project where id = :id")
.bind("id", event.project).map(IntegerMapper.FIRST).first();
}
Project project = new Project(event.project, id);
DashboardService.Dashboard dashboard = service.create(project, "My dashboard", true, null);
service.setDefault(project, dashboard.id);
}
}
public static class ProjectDeleteEventListener
{
private final DashboardService dashboardService;
private final CustomPageDatabase customPageDatabase;
private final ReportMetadata reportMetadata;
private final CustomReportMetadata customReportMetadata;
@Inject
public ProjectDeleteEventListener(DashboardService dashboardService,
Optional<CustomPageDatabase> customPageDatabase,
ReportMetadata reportMetadata,
CustomReportMetadata customReportMetadata)
{
this.reportMetadata = reportMetadata;
this.customReportMetadata = customReportMetadata;
this.customPageDatabase = customPageDatabase.orNull();
this.dashboardService = dashboardService;
}
@Subscribe
public void onDeleteProject(UIEvents.ProjectDeletedEvent event)
{
for (DashboardService.Dashboard dashboard : dashboardService.list(new Project(0, event.project)).dashboards) {
dashboardService.delete(new Project(event.project, 0), dashboard.id);
}
if (customPageDatabase != null) {
for (CustomPageDatabase.Page page : customPageDatabase.list(event.project)) {
customPageDatabase.delete(event.project, page.slug);
}
}
for (Report report : reportMetadata.getReports(null, event.project)) {
reportMetadata.delete(null, event.project, report.slug);
}
for (Map.Entry<String, List<CustomReport>> types : customReportMetadata.list(event.project).entrySet()) {
for (CustomReport customReport : types.getValue()) {
customReportMetadata.delete(types.getKey(), event.project, customReport.name);
}
}
}
}
public static class FlywayExecutor
{
@Inject
public FlywayExecutor(@Named("ui.metadata.jdbc") JDBCConfig config)
{
Flyway flyway = new Flyway();
flyway.setBaselineOnMigrate(true);
flyway.setLocations("db/migration/ui");
flyway.setTable("schema_version_ui");
flyway.setDataSource(config.getUrl(), config.getUsername(), config.getPassword());
// try {
flyway.migrate();
// }
// catch (FlywayException e) {
// flyway.repair();
// }
}
}
public static class WebsiteRequestHandler
implements Provider<HttpRequestHandler>
{
private final RakamUIWebService service;
@Inject
public WebsiteRequestHandler(RakamUIConfig config)
{
service = new RakamUIWebService(config);
}
@Override
public HttpRequestHandler get()
{
return service::main;
}
}
}