Support running Halo with PostgreSQL (#2472)

#### What type of PR is this?

/kind feature
/area core
/milestone 2.0

#### What this PR does / why we need it:

Add PostgreSQL script to support running Halo with PostgreSQL database. BTW, there was a weird issue (emty posts even if there are some posts created) while listing Post, and I fixed it in this PR.

#### Which issue(s) this PR fixes:

Partial Fixes https://github.com/halo-dev/halo/issues/2464

#### Special notes for reviewers

Steps to test:

1. Start up PostgreSQL. e.g.:

    ```yaml
    version: '3.1'
    
    services:
    
      db:
        image: postgres
        restart: always
        environment:
          POSTGRES_PASSWORD: openpostgresql
        ports:
          - 5432:5432
    
      adminer:
        image: adminer
        restart: always
        ports:
          - 8080:8080
    ```
    
    ```bash
    docker-compose -f postgresql.yaml up
    ```

2. Start Halo with `postgresql` profile. e.g.:

    ```bash
    ./gradlew bootRun --args="--spring.profiles.active=postgresql"
    ```

3. Validate the functionality of Halo

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/2474/head
John Niang 2022-09-27 10:24:15 +08:00 committed by GitHub
parent 264a4330c3
commit 1fc37673f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 40 deletions

View File

@ -128,59 +128,70 @@ public class PostServiceImpl implements PostService {
private Mono<ListedPost> getListedPost(Post post) { private Mono<ListedPost> getListedPost(Post post) {
Assert.notNull(post, "The post must not be null."); Assert.notNull(post, "The post must not be null.");
ListedPost listedPost = new ListedPost(); return Mono.just(post)
listedPost.setPost(post); .map(p -> {
return Mono.zip(listTags(post.getSpec().getTags()), ListedPost listedPost = new ListedPost();
listCategories(post.getSpec().getCategories()), listedPost.setPost(p);
listContributors(post.getStatusOrDefault().getContributors())
)
.map(tuple -> {
List<Tag> tags = tuple.getT1();
List<Category> categories = tuple.getT2();
List<Contributor> contributors = tuple.getT3();
listedPost.setTags(tags);
listedPost.setCategories(categories);
listedPost.setContributors(contributors);
return listedPost; return listedPost;
}); })
.flatMap(lp -> setTags(post.getSpec().getTags(), lp))
.flatMap(lp -> setCategories(post.getSpec().getCategories(), lp))
.flatMap(lp -> setContributors(post.getStatus().getContributors(), lp));
} }
private Mono<List<Tag>> listTags(List<String> tagNames) { private Mono<ListedPost> setTags(List<String> tagNames, ListedPost post) {
return listTags(tagNames)
.collectSortedList()
.doOnNext(post::setTags)
.map(tags -> post)
.switchIfEmpty(Mono.defer(() -> Mono.just(post)));
}
private Mono<ListedPost> setCategories(List<String> categoryNames, ListedPost post) {
return listCategories(categoryNames)
.collectSortedList()
.doOnNext(post::setCategories)
.map(categories -> post)
.switchIfEmpty(Mono.defer(() -> Mono.just(post)));
}
private Mono<ListedPost> setContributors(List<String> contributorNames, ListedPost post) {
return listContributors(contributorNames)
.collectSortedList()
.doOnNext(post::setContributors)
.map(contributors -> post)
.switchIfEmpty(Mono.defer(() -> Mono.just(post)));
}
private Flux<Tag> listTags(List<String> tagNames) {
if (tagNames == null) { if (tagNames == null) {
return Mono.empty(); return Flux.empty();
} }
return Flux.fromStream(tagNames.stream() return Flux.fromIterable(tagNames)
.map(tagName -> client.fetch(Tag.class, tagName))) .flatMap(tagName -> client.fetch(Tag.class, tagName));
.flatMap(Function.identity())
.collectList();
} }
private Mono<List<Category>> listCategories(List<String> categoryNames) { private Flux<Category> listCategories(List<String> categoryNames) {
if (categoryNames == null) { if (categoryNames == null) {
return Mono.empty(); return Flux.empty();
} }
return Flux.fromStream(categoryNames.stream() return Flux.fromIterable(categoryNames)
.map(categoryName -> client.fetch(Category.class, categoryName))) .flatMap(categoryName -> client.fetch(Category.class, categoryName));
.flatMap(Function.identity())
.collectList();
} }
private Mono<List<Contributor>> listContributors(List<String> usernames) { private Flux<Contributor> listContributors(List<String> usernames) {
if (usernames == null) { if (usernames == null) {
return Mono.empty(); return Flux.empty();
} }
return Flux.fromIterable(usernames) return Flux.fromIterable(usernames)
.map(username -> client.fetch(User.class, username) .flatMap(username -> client.fetch(User.class, username))
.map(user -> { .map(user -> {
Contributor contributor = new Contributor(); Contributor contributor = new Contributor();
contributor.setName(username); contributor.setName(user.getMetadata().getName());
contributor.setDisplayName(user.getSpec().getDisplayName()); contributor.setDisplayName(user.getSpec().getDisplayName());
contributor.setAvatar(user.getSpec().getAvatar()); contributor.setAvatar(user.getSpec().getAvatar());
return contributor; return contributor;
}) });
)
.flatMap(Function.identity())
.collectList();
} }
@Override @Override

View File

@ -122,7 +122,8 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
String headSnapshot = post.getSpec().getHeadSnapshot(); String headSnapshot = post.getSpec().getHeadSnapshot();
contentService.listSnapshots(Snapshot.SubjectRef.of(Post.KIND, name)) contentService.listSnapshots(Snapshot.SubjectRef.of(Post.KIND, name))
.collectList() .collectList()
.subscribe(snapshots -> { .blockOptional()
.ifPresent(snapshots -> {
List<String> contributors = snapshots.stream() List<String> contributors = snapshots.stream()
.map(snapshot -> { .map(snapshot -> {
Set<String> usernames = snapshot.getSpec().getContributors(); Set<String> usernames = snapshot.getSpec().getContributors();
@ -143,6 +144,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
.ifPresent(snapshot -> { .ifPresent(snapshot -> {
status.setInProgress(!isPublished(snapshot)); status.setInProgress(!isPublished(snapshot));
}); });
}); });
// handle cancel publish,has released version and published is false and not handled // handle cancel publish,has released version and published is false and not handled

View File

@ -0,0 +1,9 @@
spring:
r2dbc:
url: r2dbc:pool:postgresql://localhost:5432/halo
username: postgres
password: openpostgresql
sql:
init:
mode: always
platform: postgresql

View File

@ -0,0 +1,7 @@
create table if not exists extensions
(
name varchar(255) not null,
data bytea,
version bigint,
primary key (name)
);