Adding the Spring java API for creating Widget.
Migrating the code to Spring Java instead of Golang
This commit is contained in:
parent
7ea78bf64c
commit
e4f59ff941
70
app/server/server/pom.xml
Normal file
70
app/server/server/pom.xml
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.1.3.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.mobtools</groupId>
|
||||||
|
<artifactId>server</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>server</name>
|
||||||
|
<description>This is the API server for the Mobtools project</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>11</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.mobtools.server;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.mobtools.server.configurations;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaAuditing
|
||||||
|
public class CommonConfig {
|
||||||
|
|
||||||
|
private String ELASTIC_THREAD_POOL_NAME = "mobtools-elastic-pool";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Scheduler scheduler() {
|
||||||
|
return Schedulers.newElastic(ELASTIC_THREAD_POOL_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.mobtools.server.configurations;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||||
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebFluxSecurity
|
||||||
|
@EnableReactiveMethodSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
// private final DaoAuthenticationManager reactiveAuthenticationManager;
|
||||||
|
|
||||||
|
// private final SecurityContextRepository securityContextRepository;
|
||||||
|
|
||||||
|
// @Autowired
|
||||||
|
// public SecurityConfig(DaoAuthenticationManager reactiveAuthenticationManager,
|
||||||
|
// SecurityContextRepository securityContextRepository) {
|
||||||
|
// this.reactiveAuthenticationManager = reactiveAuthenticationManager;
|
||||||
|
// this.securityContextRepository = securityContextRepository;
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||||
|
return http
|
||||||
|
.csrf().disable()
|
||||||
|
.formLogin().disable()
|
||||||
|
.httpBasic().disable()
|
||||||
|
// .authenticationManager(reactiveAuthenticationManager)
|
||||||
|
// .securityContextRepository(securityContextRepository)
|
||||||
|
.authorizeExchange()
|
||||||
|
.anyExchange().permitAll()
|
||||||
|
.and()
|
||||||
|
.logout().disable()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.mobtools.server.constants;
|
||||||
|
|
||||||
|
public interface Url {
|
||||||
|
String BASE_URL = "/api";
|
||||||
|
String VERSION = "/v1";
|
||||||
|
String WIDGET_URL = BASE_URL + VERSION + "/widgets";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.mobtools.server.controllers;
|
||||||
|
|
||||||
|
public abstract class BaseController {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.mobtools.server.controllers;
|
||||||
|
|
||||||
|
import com.mobtools.server.constants.Url;
|
||||||
|
import com.mobtools.server.domains.Widget;
|
||||||
|
import com.mobtools.server.dtos.ResponseDto;
|
||||||
|
import com.mobtools.server.services.WidgetService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Url.WIDGET_URL)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WidgetController extends BaseController {
|
||||||
|
|
||||||
|
private final WidgetService widgetService;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
|
public Mono<ResponseDto<Widget>> create(@Valid @RequestBody Widget widget) {
|
||||||
|
return widgetService.create(widget)
|
||||||
|
.map(createdWidget -> new ResponseDto<>(HttpStatus.CREATED.value(), createdWidget, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("")
|
||||||
|
public Flux<ResponseDto<Widget>> getAllWidgets() {
|
||||||
|
return widgetService.get()
|
||||||
|
.map(user -> new ResponseDto<>(HttpStatus.OK.value(), user, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{name}")
|
||||||
|
public Mono<ResponseDto<Widget>> getByName(@PathVariable String id) {
|
||||||
|
return widgetService.getByName(id)
|
||||||
|
.map(user -> new ResponseDto<>(HttpStatus.OK.value(), user, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Mono<ResponseDto<Widget>> getCureFitUser(@PathVariable Long id) {
|
||||||
|
return widgetService.update(id)
|
||||||
|
.map(cfUser -> new ResponseDto<>(HttpStatus.OK.value(), cfUser, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.mobtools.server.domains;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
@MappedSuperclass
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public abstract class BaseDomain implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 7459916000501322517L;
|
||||||
|
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
@CreatedDate
|
||||||
|
protected Date createdAt;
|
||||||
|
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
@Column(nullable = false)
|
||||||
|
@LastModifiedDate
|
||||||
|
protected Date updatedAt;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@CreatedBy
|
||||||
|
protected String createdBy;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@LastModifiedBy
|
||||||
|
protected String modifiedBy;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
protected Boolean deleted = false;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.mobtools.server.domains;
|
||||||
|
|
||||||
|
public enum PricingPlan {
|
||||||
|
FREE, STARTUP, BUSINESS, ENTERPRISE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.mobtools.server.domains;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
@SequenceGenerator(initialValue = 1, name = "tenant_gen", sequenceName = "tenant_gen")
|
||||||
|
public class Tenant extends BaseDomain {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tenant_gen")
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String domain;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String website;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.mobtools.server.domains;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
// Specially adding the table name here because the keyword "User" is reserved in Postgres
|
||||||
|
@Table(name = "users")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
@SequenceGenerator(initialValue = 1, name = "user_gen", sequenceName = "user_gen")
|
||||||
|
public class User extends BaseDomain {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(nullable = false)
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_gen")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String email;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.mobtools.server.domains;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
@SequenceGenerator(initialValue = 1, name = "widget_gen", sequenceName = "widget_gen")
|
||||||
|
public class Widget extends BaseDomain {
|
||||||
|
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "widget_gen")
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private WidgetType type;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private PricingPlan pricingPlan;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.mobtools.server.domains;
|
||||||
|
|
||||||
|
public enum WidgetType {
|
||||||
|
DB, DISPLAY
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.mobtools.server.dtos;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ResponseDto<T> implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 8965011907233699993L;
|
||||||
|
|
||||||
|
private int status;
|
||||||
|
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.mobtools.server.repositories;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.repository.NoRepositoryBean;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@NoRepositoryBean
|
||||||
|
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.mobtools.server.repositories;
|
||||||
|
|
||||||
|
import com.mobtools.server.domains.Widget;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface WidgetRepository extends BaseRepository<Widget, Long> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.mobtools.server.services;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class BaseService {
|
||||||
|
|
||||||
|
final Scheduler scheduler;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.mobtools.server.services;
|
||||||
|
|
||||||
|
import com.mobtools.server.domains.Widget;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface WidgetService {
|
||||||
|
|
||||||
|
Mono<Widget> getByName(String id);
|
||||||
|
|
||||||
|
Flux<Widget> get();
|
||||||
|
|
||||||
|
Mono<Widget> create(Widget widget);
|
||||||
|
|
||||||
|
Mono<Widget> update(Long id);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.mobtools.server.services;
|
||||||
|
|
||||||
|
import com.mobtools.server.domains.Widget;
|
||||||
|
import com.mobtools.server.repositories.WidgetRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class WidgetServiceImpl extends BaseService implements WidgetService {
|
||||||
|
|
||||||
|
private WidgetRepository widgetRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WidgetServiceImpl(Scheduler scheduler, WidgetRepository widgetRepository) {
|
||||||
|
super(scheduler);
|
||||||
|
this.widgetRepository = widgetRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Widget> getByName(String id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<Widget> get() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Widget> create(Widget widget) {
|
||||||
|
return Mono.fromCallable(
|
||||||
|
() -> widgetRepository.save(widget)
|
||||||
|
).subscribeOn(this.scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Widget> update(Long id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/server/server/src/main/resources/application.properties
Normal file
16
app/server/server/src/main/resources/application.properties
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# JDBC Properties
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/mobtoolsnew?useSSL=false
|
||||||
|
spring.datasource.username=postgres
|
||||||
|
spring.datasource.password=root
|
||||||
|
spring.datasource.hikari.pool-name=Hikari-Eval-Pool
|
||||||
|
spring.datasource.hikari.maximum-pool-size=16
|
||||||
|
|
||||||
|
# JPA Properties
|
||||||
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||||
|
spring.jpa.show-sql=true
|
||||||
|
|
||||||
|
# Jackson Properties
|
||||||
|
spring.jackson.default-property-inclusion=non_null
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.mobtools.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest
|
||||||
|
public class ServerApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package url
|
||||||
const ComponentURL = "/components"
|
const ComponentURL = "/components"
|
||||||
const QueryURL = "/query"
|
const QueryURL = "/query"
|
||||||
const LoginURL = "/login"
|
const LoginURL = "/login"
|
||||||
|
const AccountURL = "/accounts"
|
||||||
const AuthURL = "/auth/{provider}"
|
const AuthURL = "/auth/{provider}"
|
||||||
const AuthCallbackURL = "/auth/{provider}/callback"
|
const AuthCallbackURL = "/auth/{provider}/callback"
|
||||||
const LogoutURL = "/logout/{provider}"
|
const LogoutURL = "/logout/{provider}"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user