This is an automated email from the ASF dual-hosted git repository.

lzljs3620320 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-paimon-webui.git


The following commit(s) were added to refs/heads/main by this push:
     new a0c588c  Add paimon web api module and fix some issues (#7)
a0c588c is described below

commit a0c588c464c07c7fd0a4c39e347b218242f4a611
Author: s7monk <[email protected]>
AuthorDate: Mon Jul 24 13:57:03 2023 +0800

    Add paimon web api module and fix some issues (#7)
---
 paimon-web-api/pom.xml                             | 110 +++++++++++
 .../paimon/web/api/catalog/CatalogCreator.java     |  46 +++++
 .../paimon/web/api/common/CatalogProperties.java   |  34 +---
 .../paimon/web/api/database/DatabaseManager.java   |  40 ++--
 .../paimon/web/api/table/ColumnMetadata.java       |  50 ++---
 .../apache/paimon/web/api/table/TableManager.java  | 103 ++++++++++
 .../apache/paimon/web/api/table/TableMetadata.java | 185 +++++++++++++++++
 .../paimon/web/api/table/TableOptionExtractor.java |  70 +++++++
 .../paimon/web/api/catalog/CatalogCreatorTest.java |  39 ++--
 .../web/api/database/DatabaseManagerTest.java      |  94 +++++++++
 .../paimon/web/api/table/TableManagerTest.java     | 220 +++++++++++++++++++++
 .../apache/paimon/web/common}/enums/Status.java    |   2 +-
 .../apache/paimon/web/common/result/R.java         |   9 +-
 paimon-web-server/pom.xml                          |  14 +-
 .../web/server/controller/UserController.java      |   4 +-
 .../src/main/resources/log4j2-spring.xml           |  18 ++
 pom.xml                                            |  13 +-
 17 files changed, 937 insertions(+), 114 deletions(-)

diff --git a/paimon-web-api/pom.xml b/paimon-web-api/pom.xml
new file mode 100644
index 0000000..6d6886d
--- /dev/null
+++ b/paimon-web-api/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<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.apache.paimon</groupId>
+        <artifactId>paimon-webui</artifactId>
+        <version>0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>paimon-web-api</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <paimon.version>0.5-SNAPSHOT</paimon.version>
+        <hadoop.version>2.8.5</hadoop.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.paimon</groupId>
+            <artifactId>paimon-bundle</artifactId>
+            <version>${paimon.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.paimon</groupId>
+            <artifactId>paimon-web-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.hadoop</groupId>
+            <artifactId>hadoop-common</artifactId>
+            <version>${hadoop.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.avro</groupId>
+                    <artifactId>avro</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>jdk.tools</groupId>
+                    <artifactId>jdk.tools</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.hadoop</groupId>
+            <artifactId>hadoop-hdfs-client</artifactId>
+            <version>${hadoop.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.avro</groupId>
+                    <artifactId>avro</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git 
a/paimon-web-api/src/main/java/org/apache/paimon/web/api/catalog/CatalogCreator.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/catalog/CatalogCreator.java
new file mode 100644
index 0000000..22514e6
--- /dev/null
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/catalog/CatalogCreator.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.api.catalog;
+
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.catalog.CatalogContext;
+import org.apache.paimon.catalog.CatalogFactory;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.web.api.common.CatalogProperties;
+
+/** paimon catalog creator. */
+public class CatalogCreator {
+
+    public static Catalog createFilesystemCatalog(String path) {
+        CatalogContext context = CatalogContext.create(new Path(path));
+        return CatalogFactory.createCatalog(context);
+    }
+
+    public static Catalog createHiveCatalog(
+            String warehouse, String metastore, String uri, String 
hiveConfDir) {
+        Options options = new Options();
+        options.set(CatalogProperties.WAREHOUSE_LOCATION, warehouse);
+        options.set(CatalogProperties.METASTORE_TYPE, metastore);
+        options.set(CatalogProperties.URI, uri);
+        options.set(CatalogProperties.HIVE_CONF_DIR, hiveConfDir);
+        CatalogContext context = CatalogContext.create(options);
+        return CatalogFactory.createCatalog(context);
+    }
+}
diff --git 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/common/CatalogProperties.java
similarity index 52%
copy from 
paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
copy to 
paimon-web-api/src/main/java/org/apache/paimon/web/api/common/CatalogProperties.java
index 0406ba1..c8930fd 100644
--- 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/common/CatalogProperties.java
@@ -16,36 +16,16 @@
  * limitations under the License.
  */
 
-package com.apache.paimon.web.common.result.enums;
+package org.apache.paimon.web.api.common;
 
-/**
- * Status enum.
- *
- * <p><b>NOTE:</b> This enumeration is used to define status codes and 
internationalization messages
- * for response data. <br>
- * This is mainly responsible for the internationalization information 
returned by the interface
- */
-public enum Status {
-    /** response data msg */
-    SUCCESS(200, "Successfully"),
-    FAILED(400, "Failed"),
-
-    USER_NOT_EXIST(10001, "User Not Exist"),
-    ;
+/** paimon catalog properties. */
+public class CatalogProperties {
 
-    private final int code;
-    private final String msg;
+    public static final String METASTORE_TYPE = "metastore";
 
-    Status(int code, String msg) {
-        this.code = code;
-        this.msg = msg;
-    }
+    public static final String WAREHOUSE_LOCATION = "warehouse";
 
-    public int getCode() {
-        return this.code;
-    }
+    public static final String URI = "uri";
 
-    public String getMsg() {
-        return this.msg;
-    }
+    public static final String HIVE_CONF_DIR = "hive-conf-dir";
 }
diff --git 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/database/DatabaseManager.java
similarity index 51%
copy from 
paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
copy to 
paimon-web-api/src/main/java/org/apache/paimon/web/api/database/DatabaseManager.java
index 0406ba1..c0272bf 100644
--- 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/database/DatabaseManager.java
@@ -16,36 +16,30 @@
  * limitations under the License.
  */
 
-package com.apache.paimon.web.common.result.enums;
+package org.apache.paimon.web.api.database;
 
-/**
- * Status enum.
- *
- * <p><b>NOTE:</b> This enumeration is used to define status codes and 
internationalization messages
- * for response data. <br>
- * This is mainly responsible for the internationalization information 
returned by the interface
- */
-public enum Status {
-    /** response data msg */
-    SUCCESS(200, "Successfully"),
-    FAILED(400, "Failed"),
+import org.apache.paimon.catalog.Catalog;
 
-    USER_NOT_EXIST(10001, "User Not Exist"),
-    ;
+import java.util.List;
 
-    private final int code;
-    private final String msg;
+/** paimon database manager. */
+public class DatabaseManager {
+
+    public static void createDatabase(Catalog catalog, String name)
+            throws Catalog.DatabaseAlreadyExistException {
+        catalog.createDatabase(name, false);
+    }
 
-    Status(int code, String msg) {
-        this.code = code;
-        this.msg = msg;
+    public static boolean databaseExists(Catalog catalog, String name) {
+        return catalog.databaseExists(name);
     }
 
-    public int getCode() {
-        return this.code;
+    public static List<String> listDatabase(Catalog catalog) {
+        return catalog.listDatabases();
     }
 
-    public String getMsg() {
-        return this.msg;
+    public static void dropDatabase(Catalog catalog, String name)
+            throws Catalog.DatabaseNotEmptyException, 
Catalog.DatabaseNotExistException {
+        catalog.dropDatabase(name, false, true);
     }
 }
diff --git 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/ColumnMetadata.java
similarity index 53%
copy from 
paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
copy to 
paimon-web-api/src/main/java/org/apache/paimon/web/api/table/ColumnMetadata.java
index 0406ba1..163f50d 100644
--- 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/ColumnMetadata.java
@@ -16,36 +16,40 @@
  * limitations under the License.
  */
 
-package com.apache.paimon.web.common.result.enums;
+package org.apache.paimon.web.api.table;
 
-/**
- * Status enum.
- *
- * <p><b>NOTE:</b> This enumeration is used to define status codes and 
internationalization messages
- * for response data. <br>
- * This is mainly responsible for the internationalization information 
returned by the interface
- */
-public enum Status {
-    /** response data msg */
-    SUCCESS(200, "Successfully"),
-    FAILED(400, "Failed"),
+import org.apache.paimon.types.DataType;
+
+import javax.annotation.Nullable;
+
+/** table metadata. */
+public class ColumnMetadata {
+
+    private final String name;
 
-    USER_NOT_EXIST(10001, "User Not Exist"),
-    ;
+    private final DataType type;
 
-    private final int code;
-    private final String msg;
+    private final @Nullable String description;
+
+    public ColumnMetadata(String name, DataType type) {
+        this(name, type, null);
+    }
+
+    public ColumnMetadata(String name, DataType type, String description) {
+        this.name = name;
+        this.type = type;
+        this.description = description;
+    }
 
-    Status(int code, String msg) {
-        this.code = code;
-        this.msg = msg;
+    public String name() {
+        return this.name;
     }
 
-    public int getCode() {
-        return this.code;
+    public DataType type() {
+        return this.type;
     }
 
-    public String getMsg() {
-        return this.msg;
+    public String description() {
+        return this.description;
     }
 }
diff --git 
a/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableManager.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableManager.java
new file mode 100644
index 0000000..83188f5
--- /dev/null
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableManager.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.api.table;
+
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.catalog.Identifier;
+import org.apache.paimon.schema.Schema;
+import org.apache.paimon.table.Table;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** paimon table manager. */
+public class TableManager {
+
+    public static void createTable(
+            Catalog catalog, String dbName, String tableName, TableMetadata 
tableMetadata)
+            throws Catalog.TableAlreadyExistException, 
Catalog.DatabaseNotExistException {
+        Schema.Builder schemaBuilder =
+                Schema.newBuilder()
+                        .partitionKeys(
+                                tableMetadata.primaryKeys() == null
+                                        ? ImmutableList.of()
+                                        : 
ImmutableList.copyOf(tableMetadata.primaryKeys()))
+                        .partitionKeys(
+                                tableMetadata.partitionKeys() == null
+                                        ? ImmutableList.of()
+                                        : 
ImmutableList.copyOf(tableMetadata.partitionKeys()))
+                        .comment(tableMetadata.comment() == null ? "" : 
tableMetadata.comment())
+                        .options(handleOptions(tableMetadata.options()));
+
+        for (ColumnMetadata column : tableMetadata.columns()) {
+            schemaBuilder.column(column.name(), column.type(), 
column.description());
+        }
+
+        Schema schema = schemaBuilder.build();
+
+        Identifier identifier = Identifier.create(dbName, tableName);
+
+        catalog.createTable(identifier, schema, false);
+    }
+
+    public static boolean tableExists(Catalog catalog, String dbName, String 
tableName) {
+        Identifier identifier = Identifier.create(dbName, tableName);
+        return catalog.tableExists(identifier);
+    }
+
+    public static Table GetTable(Catalog catalog, String dbName, String 
tableName)
+            throws Catalog.TableNotExistException {
+        Identifier identifier = Identifier.create(dbName, tableName);
+        return catalog.getTable(identifier);
+    }
+
+    public static List<String> listTables(Catalog catalog, String dbName)
+            throws Catalog.DatabaseNotExistException {
+        return catalog.listTables(dbName);
+    }
+
+    public static void dropTable(Catalog catalog, String dbName, String 
tableName)
+            throws Catalog.TableNotExistException {
+        Identifier identifier = Identifier.create(dbName, tableName);
+        catalog.dropTable(identifier, false);
+    }
+
+    public static void renameTable(Catalog catalog, String dbName, String 
fromTable, String toTable)
+            throws Catalog.TableAlreadyExistException, 
Catalog.TableNotExistException {
+        Identifier fromTableIdentifier = Identifier.create(dbName, fromTable);
+        Identifier toTableIdentifier = Identifier.create(dbName, toTable);
+        catalog.renameTable(fromTableIdentifier, toTableIdentifier, false);
+    }
+
+    private static Map<String, String> handleOptions(Map<String, String> 
options) {
+        List<String> keys = TableOptionExtractor.keys();
+        Map<String, String> filteredOptions = new HashMap<>();
+
+        for (String key : options.keySet()) {
+            if (keys.contains(key)) {
+                filteredOptions.put(key, options.get(key));
+            }
+        }
+
+        return filteredOptions;
+    }
+}
diff --git 
a/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableMetadata.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableMetadata.java
new file mode 100644
index 0000000..67ec73e
--- /dev/null
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableMetadata.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.api.table;
+
+import com.google.common.base.Preconditions;
+
+import javax.annotation.Nullable;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/** table metadata. */
+public class TableMetadata {
+
+    private final List<ColumnMetadata> columns;
+
+    private final List<String> partitionKeys;
+
+    private final List<String> primaryKeys;
+
+    private final Map<String, String> options;
+
+    private final String comment;
+
+    public TableMetadata(
+            List<ColumnMetadata> columns,
+            List<String> partitionKeys,
+            List<String> primaryKeys,
+            Map<String, String> options,
+            String comment) {
+        this.columns = normalizeFields(columns, primaryKeys, partitionKeys);
+        this.partitionKeys = partitionKeys;
+        this.primaryKeys = primaryKeys;
+        this.options = new HashMap<>(options);
+        this.comment = comment;
+    }
+
+    public List<ColumnMetadata> columns() {
+        return columns;
+    }
+
+    public List<String> partitionKeys() {
+        return partitionKeys;
+    }
+
+    public List<String> primaryKeys() {
+        return primaryKeys;
+    }
+
+    public Map<String, String> options() {
+        return options;
+    }
+
+    public String comment() {
+        return comment;
+    }
+
+    private static List<ColumnMetadata> normalizeFields(
+            List<ColumnMetadata> columns, List<String> primaryKeys, 
List<String> partitionKeys) {
+        List<String> fieldNames =
+                
columns.stream().map(ColumnMetadata::name).collect(Collectors.toList());
+
+        Set<String> duplicateColumns = duplicate(fieldNames);
+        Preconditions.checkState(
+                duplicateColumns.isEmpty(),
+                "Table column %s must not contain duplicate fields. Found: %s",
+                fieldNames,
+                duplicateColumns);
+
+        Set<String> allFields = new HashSet<>(fieldNames);
+
+        duplicateColumns = duplicate(partitionKeys);
+        Preconditions.checkState(
+                duplicateColumns.isEmpty(),
+                "Partition key constraint %s must not contain duplicate 
columns. Found: %s",
+                partitionKeys,
+                duplicateColumns);
+        Preconditions.checkState(
+                allFields.containsAll(partitionKeys),
+                "Table column %s should include all partition fields %s",
+                fieldNames,
+                partitionKeys);
+
+        if (primaryKeys.isEmpty()) {
+            return columns;
+        }
+        duplicateColumns = duplicate(primaryKeys);
+        Preconditions.checkState(
+                duplicateColumns.isEmpty(),
+                "Primary key constraint %s must not contain duplicate columns. 
Found: %s",
+                primaryKeys,
+                duplicateColumns);
+        Preconditions.checkState(
+                allFields.containsAll(primaryKeys),
+                "Table column %s should include all primary key constraint %s",
+                fieldNames,
+                primaryKeys);
+        Set<String> pkSet = new HashSet<>(primaryKeys);
+        Preconditions.checkState(
+                pkSet.containsAll(partitionKeys),
+                "Primary key constraint %s should include all partition fields 
%s",
+                primaryKeys,
+                partitionKeys);
+
+        // primary key should not nullable
+        List<ColumnMetadata> newFields = new ArrayList<>();
+        for (ColumnMetadata field : columns) {
+            if (pkSet.contains(field.name()) && field.type().isNullable()) {
+                newFields.add(
+                        new ColumnMetadata(
+                                field.name(), field.type().copy(false), 
field.description()));
+            } else {
+                newFields.add(field);
+            }
+        }
+        return newFields;
+    }
+
+    private static Set<String> duplicate(List<String> names) {
+        return names.stream()
+                .filter(name -> Collections.frequency(names, name) > 1)
+                .collect(Collectors.toSet());
+    }
+
+    public static TableMetadata.Builder builder() {
+        return new Builder();
+    }
+
+    public static final class Builder {
+        private List<ColumnMetadata> columns = new ArrayList<>();
+
+        private List<String> partitionKeys = new ArrayList<>();
+
+        private List<String> primaryKeys = new ArrayList<>();
+
+        private Map<String, String> options = new HashMap<>();
+
+        @Nullable private String comment;
+
+        public Builder columns(List<ColumnMetadata> columns) {
+            this.columns = new ArrayList<>(columns);
+            return this;
+        }
+
+        public Builder partitionKeys(List<String> partitionKeys) {
+            this.partitionKeys = new ArrayList<>(partitionKeys);
+            return this;
+        }
+
+        public Builder primaryKeys(List<String> primaryKeys) {
+            this.partitionKeys = new ArrayList<>(primaryKeys);
+            return this;
+        }
+
+        public Builder options(Map<String, String> options) {
+            this.options.putAll(options);
+            return this;
+        }
+
+        public Builder comment(String comment) {
+            this.comment = comment;
+            return this;
+        }
+
+        public TableMetadata build() {
+            return new TableMetadata(columns, partitionKeys, primaryKeys, 
options, comment);
+        }
+    }
+}
diff --git 
a/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableOptionExtractor.java
 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableOptionExtractor.java
new file mode 100644
index 0000000..0f19e41
--- /dev/null
+++ 
b/paimon-web-api/src/main/java/org/apache/paimon/web/api/table/TableOptionExtractor.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.api.table;
+
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.options.ConfigOption;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/** table option extractor. */
+public class TableOptionExtractor {
+
+    public static List<String> keys() {
+        List<OptionWithMetaInfo> optionWithMetaInfos = 
extractConfigOptions(CoreOptions.class);
+        List<String> keys = new ArrayList<>();
+        for (OptionWithMetaInfo optionWithMetaInfo : optionWithMetaInfos) {
+            keys.add(optionWithMetaInfo.option.key());
+        }
+        return keys;
+    }
+
+    public static List<OptionWithMetaInfo> extractConfigOptions(Class<?> 
clazz) {
+        try {
+            List<OptionWithMetaInfo> configOptions = new ArrayList<>(8);
+            Field[] fields = clazz.getFields();
+            for (Field field : fields) {
+                if (isConfigOption(field)) {
+                    configOptions.add(
+                            new OptionWithMetaInfo((ConfigOption<?>) 
field.get(null), field));
+                }
+            }
+            return configOptions;
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Failed to extract config options from class " + clazz + 
'.', e);
+        }
+    }
+
+    private static boolean isConfigOption(Field field) {
+        return field.getType().equals(ConfigOption.class);
+    }
+
+    static class OptionWithMetaInfo {
+        final ConfigOption<?> option;
+        final Field field;
+
+        public OptionWithMetaInfo(ConfigOption<?> option, Field field) {
+            this.option = option;
+            this.field = field;
+        }
+    }
+}
diff --git 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
 
b/paimon-web-api/src/test/java/org/apache/paimon/web/api/catalog/CatalogCreatorTest.java
similarity index 53%
copy from 
paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
copy to 
paimon-web-api/src/test/java/org/apache/paimon/web/api/catalog/CatalogCreatorTest.java
index 0406ba1..a472ade 100644
--- 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
+++ 
b/paimon-web-api/src/test/java/org/apache/paimon/web/api/catalog/CatalogCreatorTest.java
@@ -16,36 +16,25 @@
  * limitations under the License.
  */
 
-package com.apache.paimon.web.common.result.enums;
+package org.apache.paimon.web.api.catalog;
 
-/**
- * Status enum.
- *
- * <p><b>NOTE:</b> This enumeration is used to define status codes and 
internationalization messages
- * for response data. <br>
- * This is mainly responsible for the internationalization information 
returned by the interface
- */
-public enum Status {
-    /** response data msg */
-    SUCCESS(200, "Successfully"),
-    FAILED(400, "Failed"),
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.catalog.FileSystemCatalog;
 
-    USER_NOT_EXIST(10001, "User Not Exist"),
-    ;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
-    private final int code;
-    private final String msg;
+import static org.assertj.core.api.Assertions.assertThat;
 
-    Status(int code, String msg) {
-        this.code = code;
-        this.msg = msg;
-    }
+/** The test class of catalog creator in {@link CatalogCreator}. */
+public class CatalogCreatorTest {
 
-    public int getCode() {
-        return this.code;
-    }
+    @TempDir java.nio.file.Path tempFile;
 
-    public String getMsg() {
-        return this.msg;
+    @Test
+    public void testCreateFileSystemCatalog() {
+        String warehouse = tempFile.toUri().toString();
+        Catalog catalog = CatalogCreator.createFilesystemCatalog(warehouse);
+        assertThat(catalog).isInstanceOf(FileSystemCatalog.class);
     }
 }
diff --git 
a/paimon-web-api/src/test/java/org/apache/paimon/web/api/database/DatabaseManagerTest.java
 
b/paimon-web-api/src/test/java/org/apache/paimon/web/api/database/DatabaseManagerTest.java
new file mode 100644
index 0000000..25b71a2
--- /dev/null
+++ 
b/paimon-web-api/src/test/java/org/apache/paimon/web/api/database/DatabaseManagerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.api.database;
+
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.web.api.catalog.CatalogCreator;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/** The test class of database manager in {@link DatabaseManager}. */
+public class DatabaseManagerTest {
+
+    @TempDir java.nio.file.Path tempFile;
+    Catalog catalog;
+
+    @BeforeEach
+    public void setUp() {
+        String warehouse = tempFile.toUri().toString();
+        catalog = CatalogCreator.createFilesystemCatalog(warehouse);
+    }
+
+    @AfterEach
+    public void tearDown() throws Exception {
+        if (catalog != null) {
+            catalog.close();
+        }
+    }
+
+    @Test
+    public void testDatabaseExists() throws 
Catalog.DatabaseAlreadyExistException {
+        DatabaseManager.createDatabase(catalog, "db_01");
+        boolean exists = DatabaseManager.databaseExists(catalog, "db_01");
+        assertThat(exists).isTrue();
+    }
+
+    @Test
+    public void testCreateDatabase() throws 
Catalog.DatabaseAlreadyExistException {
+        DatabaseManager.createDatabase(catalog, "db_01");
+        boolean exists = catalog.databaseExists("db_01");
+        assertThat(exists).isTrue();
+
+        //  Create database throws DatabaseAlreadyExistException when database 
already exists
+        assertThatExceptionOfType(Catalog.DatabaseAlreadyExistException.class)
+                .isThrownBy(() -> catalog.createDatabase("db_01", false))
+                .withMessage("Database db_01 already exists.");
+    }
+
+    @Test
+    public void testDropDatabase() throws Exception {
+        DatabaseManager.createDatabase(catalog, "db_01");
+        DatabaseManager.dropDatabase(catalog, "db_01");
+        boolean exists = catalog.databaseExists("db_01");
+        assertThat(exists).isFalse();
+
+        // Drop database throws DatabaseNotExistException when database does 
not exist
+        assertThatExceptionOfType(Catalog.DatabaseNotExistException.class)
+                .isThrownBy(() -> DatabaseManager.dropDatabase(catalog, 
"db_04"))
+                .withMessage("Database db_04 does not exist.");
+    }
+
+    @Test
+    public void testListDatabase() throws 
Catalog.DatabaseAlreadyExistException {
+        DatabaseManager.createDatabase(catalog, "db_01");
+        DatabaseManager.createDatabase(catalog, "db_02");
+        DatabaseManager.createDatabase(catalog, "db_03");
+
+        List<String> dbs = DatabaseManager.listDatabase(catalog);
+        assertThat(dbs).contains("db_01", "db_02", "db_03");
+    }
+}
diff --git 
a/paimon-web-api/src/test/java/org/apache/paimon/web/api/table/TableManagerTest.java
 
b/paimon-web-api/src/test/java/org/apache/paimon/web/api/table/TableManagerTest.java
new file mode 100644
index 0000000..8e4db37
--- /dev/null
+++ 
b/paimon-web-api/src/test/java/org/apache/paimon/web/api/table/TableManagerTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.web.api.table;
+
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.table.Table;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.web.api.catalog.CatalogCreator;
+import org.apache.paimon.web.api.database.DatabaseManager;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/** The test class of table manager in {@link TableManager}. */
+public class TableManagerTest {
+
+    @TempDir java.nio.file.Path tempFile;
+    Catalog catalog;
+    private static final String DB_NAME = "test_db";
+    private static final String TABLE_NAME = "test_table";
+    private static final String DEFAULT_TABLE = "default_table";
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        String warehouse = tempFile.toUri().toString();
+        catalog = CatalogCreator.createFilesystemCatalog(warehouse);
+        DatabaseManager.createDatabase(catalog, DB_NAME);
+        TableManager.createTable(
+                catalog,
+                DB_NAME,
+                DEFAULT_TABLE,
+                new TableMetadata(
+                        Lists.newArrayList(new ColumnMetadata("id", 
DataTypes.STRING(), "")),
+                        ImmutableList.of(),
+                        ImmutableList.of(),
+                        ImmutableMap.of(),
+                        ""));
+    }
+
+    @AfterEach
+    public void tearDown() throws Exception {
+        if (catalog != null) {
+            catalog.close();
+        }
+    }
+
+    @Test
+    public void testTableExists() {
+        boolean exists = TableManager.tableExists(catalog, DB_NAME, 
TABLE_NAME);
+        assertThat(exists).isFalse();
+        assertThat(TableManager.tableExists(catalog, DB_NAME, 
DEFAULT_TABLE)).isTrue();
+    }
+
+    @Test
+    public void testCreateTable()
+            throws Catalog.TableAlreadyExistException, 
Catalog.DatabaseNotExistException {
+        ArrayList<ColumnMetadata> columns =
+                Lists.newArrayList(
+                        new ColumnMetadata("id", DataTypes.BIGINT()),
+                        new ColumnMetadata("name", DataTypes.STRING()),
+                        new ColumnMetadata("age", DataTypes.INT()),
+                        new ColumnMetadata("birth", DataTypes.DATE()));
+
+        ArrayList<String> primaryKeys = Lists.newArrayList("id", "birth");
+
+        ArrayList<String> partitionKeys = Lists.newArrayList("birth");
+
+        Map<String, String> options =
+                ImmutableMap.of(
+                        "bucket", "4",
+                        "changelog-producer", "input");
+
+        TableMetadata tableMetadata =
+                TableMetadata.builder()
+                        .columns(columns)
+                        .partitionKeys(partitionKeys)
+                        .primaryKeys(primaryKeys)
+                        .options(options)
+                        .build();
+
+        TableManager.createTable(catalog, DB_NAME, TABLE_NAME, tableMetadata);
+        boolean exists = TableManager.tableExists(catalog, DB_NAME, 
TABLE_NAME);
+        assertThat(exists).isTrue();
+
+        // Create table throws Exception when table is system table
+        assertThatExceptionOfType(IllegalArgumentException.class)
+                .isThrownBy(
+                        () ->
+                                TableManager.createTable(
+                                        catalog, DB_NAME, "$system_table", 
tableMetadata))
+                .withMessage(
+                        "Cannot 'createTable' for system table 
'Identifier{database='test_db', table='$system_table'}', please use data 
table.");
+
+        // Create table throws DatabaseNotExistException when database does 
not exist
+        assertThatExceptionOfType(Catalog.DatabaseNotExistException.class)
+                .isThrownBy(
+                        () -> TableManager.createTable(catalog, "db_01", 
TABLE_NAME, tableMetadata))
+                .withMessage("Database db_01 does not exist.");
+
+        // Create table throws TableAlreadyExistException when table already 
exists and
+        // ignoreIfExists is false
+        assertThatExceptionOfType(Catalog.TableAlreadyExistException.class)
+                .isThrownBy(
+                        () -> TableManager.createTable(catalog, DB_NAME, 
TABLE_NAME, tableMetadata))
+                .withMessage("Table test_db.test_table already exists.");
+    }
+
+    @Test
+    public void testGetTable() throws Catalog.TableNotExistException {
+        Table table = TableManager.GetTable(catalog, DB_NAME, DEFAULT_TABLE);
+        assertThat(table).isInstanceOf(Table.class);
+
+        // Get table throws TableNotExistException when table does not exist
+        assertThatExceptionOfType(Catalog.TableNotExistException.class)
+                .isThrownBy(() -> TableManager.GetTable(catalog, DB_NAME, 
TABLE_NAME))
+                .withMessage("Table test_db.test_table does not exist.");
+    }
+
+    @Test
+    public void testListTables() throws Catalog.DatabaseNotExistException {
+        List<String> tables = TableManager.listTables(catalog, DB_NAME);
+        assertThat(tables).contains("default_table");
+
+        // List tables throws DatabaseNotExistException when database does not 
exist
+        assertThatExceptionOfType(Catalog.DatabaseNotExistException.class)
+                .isThrownBy(() -> TableManager.listTables(catalog, "db_01"))
+                .withMessage("Database db_01 does not exist.");
+    }
+
+    @Test
+    public void testDropTable() throws Exception {
+        TableManager.createTable(
+                catalog,
+                DB_NAME,
+                "tb_01",
+                new TableMetadata(
+                        Lists.newArrayList(new ColumnMetadata("id", 
DataTypes.STRING(), "")),
+                        ImmutableList.of(),
+                        ImmutableList.of(),
+                        ImmutableMap.of(),
+                        ""));
+        assertThat(TableManager.tableExists(catalog, DB_NAME, 
"tb_01")).isTrue();
+
+        TableManager.dropTable(catalog, DB_NAME, "tb_01");
+        assertThat(TableManager.tableExists(catalog, DB_NAME, 
"tb_01")).isFalse();
+
+        // Drop table throws TableNotExistException when table does not exist
+        assertThatExceptionOfType(Catalog.TableNotExistException.class)
+                .isThrownBy(() -> TableManager.dropTable(catalog, DB_NAME, 
"tb_02"))
+                .withMessage("Table test_db.tb_02 does not exist.");
+    }
+
+    @Test
+    public void testRenameTable() throws Exception {
+        TableManager.createTable(
+                catalog,
+                DB_NAME,
+                "tb_01",
+                new TableMetadata(
+                        Lists.newArrayList(new ColumnMetadata("id", 
DataTypes.STRING(), "")),
+                        ImmutableList.of(),
+                        ImmutableList.of(),
+                        ImmutableMap.of(),
+                        ""));
+
+        TableManager.renameTable(catalog, DB_NAME, "tb_01", "tb_02");
+        assertThat(TableManager.tableExists(catalog, DB_NAME, 
"tb_01")).isFalse();
+        assertThat(TableManager.tableExists(catalog, DB_NAME, 
"tb_02")).isTrue();
+
+        // Rename table throws TableNotExistException when fromTable does not 
exist
+        assertThatExceptionOfType(Catalog.TableNotExistException.class)
+                .isThrownBy(() -> TableManager.renameTable(catalog, DB_NAME, 
"tb_01", "table_04"))
+                .withMessage("Table test_db.tb_01 does not exist.");
+
+        // Rename table throws TableAlreadyExistException when toTable already 
exists
+        TableManager.createTable(
+                catalog,
+                DB_NAME,
+                "tb_03",
+                new TableMetadata(
+                        Lists.newArrayList(new ColumnMetadata("id", 
DataTypes.STRING(), "")),
+                        ImmutableList.of(),
+                        ImmutableList.of(),
+                        ImmutableMap.of(),
+                        ""));
+
+        assertThatExceptionOfType(Catalog.TableAlreadyExistException.class)
+                .isThrownBy(
+                        () -> TableManager.renameTable(catalog, DB_NAME, 
"tb_03", "default_table"))
+                .withMessage("Table test_db.default_table already exists.");
+    }
+}
diff --git 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
 
b/paimon-web-common/src/main/java/org/apache/paimon/web/common/enums/Status.java
similarity index 96%
rename from 
paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
rename to 
paimon-web-common/src/main/java/org/apache/paimon/web/common/enums/Status.java
index 0406ba1..cb957d5 100644
--- 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/enums/Status.java
+++ 
b/paimon-web-common/src/main/java/org/apache/paimon/web/common/enums/Status.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package com.apache.paimon.web.common.result.enums;
+package org.apache.paimon.web.common.enums;
 
 /**
  * Status enum.
diff --git 
a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/R.java 
b/paimon-web-common/src/main/java/org/apache/paimon/web/common/result/R.java
similarity index 94%
rename from 
paimon-web-common/src/main/java/com/apache/paimon/web/common/result/R.java
rename to 
paimon-web-common/src/main/java/org/apache/paimon/web/common/result/R.java
index d4378d6..fa2be30 100644
--- a/paimon-web-common/src/main/java/com/apache/paimon/web/common/result/R.java
+++ b/paimon-web-common/src/main/java/org/apache/paimon/web/common/result/R.java
@@ -16,14 +16,13 @@
  * limitations under the License.
  */
 
-package com.apache.paimon.web.common.result;
+package org.apache.paimon.web.common.result;
+
+import org.apache.paimon.web.common.enums.Status;
 
-import com.apache.paimon.web.common.result.enums.Status;
 import lombok.Data;
 
-/**
- * result
- */
+/** result */
 @Data
 public class R<T> {
     /** result code */
diff --git a/paimon-web-server/pom.xml b/paimon-web-server/pom.xml
index 87f6d8a..bbbac21 100644
--- a/paimon-web-server/pom.xml
+++ b/paimon-web-server/pom.xml
@@ -34,14 +34,11 @@ under the License.
         Paimon Web Server is a server for the [Apache 
Paimon](https://paimon.apache.org/) project.
     </description>
 
-    <properties>
-    </properties>
     <dependencies>
-
         <dependency>
             <groupId>org.apache.paimon</groupId>
             <artifactId>paimon-web-common</artifactId>
-            <version>${paimon.version}</version>
+            <version>${project.version}</version>
         </dependency>
 
         <dependency>
@@ -66,6 +63,7 @@ under the License.
                 </exclusion>
             </exclusions>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
@@ -76,6 +74,7 @@ under the License.
                 </exclusion>
             </exclusions>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-log4j2</artifactId>
@@ -98,11 +97,13 @@ under the License.
             <artifactId>junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-test</artifactId>
             <scope>test</scope>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-autoconfigure</artifactId>
@@ -139,6 +140,7 @@ under the License.
                 </includes>
             </resource>
         </resources>
+
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
@@ -159,6 +161,7 @@ under the License.
                     </execution>
                 </executions>
             </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-assembly-plugin</artifactId>
@@ -179,6 +182,7 @@ under the License.
                     </execution>
                 </executions>
             </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-resources-plugin</artifactId>
@@ -202,6 +206,7 @@ under the License.
                     </execution>
                 </executions>
             </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
@@ -209,7 +214,6 @@ under the License.
                 <configuration>
                     <classesDirectory>target/classes/</classesDirectory>
                     <excludes>
-                        <!--注意这玩意默认从编译结果目录开始算目录结构-->
                         <exclude>/application*.yml</exclude>
                         <exclude>/mybatis*.xml</exclude>
                         <exclude>/DinkyFlinkDockerfile</exclude>
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/UserController.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/UserController.java
index 182563a..737c3e8 100644
--- 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/UserController.java
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/UserController.java
@@ -18,10 +18,10 @@
 
 package org.apache.paimon.web.server.controller;
 
+import org.apache.paimon.web.common.result.R;
 import org.apache.paimon.web.server.data.model.User;
 import org.apache.paimon.web.server.service.UserService;
 
-import com.apache.paimon.web.common.result.R;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import static com.apache.paimon.web.common.result.enums.Status.USER_NOT_EXIST;
+import static org.apache.paimon.web.common.enums.Status.USER_NOT_EXIST;
 
 /** User api controller. */
 @Slf4j
diff --git a/paimon-web-server/src/main/resources/log4j2-spring.xml 
b/paimon-web-server/src/main/resources/log4j2-spring.xml
index e3bc44a..dbf95f7 100644
--- a/paimon-web-server/src/main/resources/log4j2-spring.xml
+++ b/paimon-web-server/src/main/resources/log4j2-spring.xml
@@ -1,4 +1,22 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
 <configuration status="OFF" monitorInterval="600">
   <Properties>
     <property name="LOG_PATH">./logs/</property>
diff --git a/pom.xml b/pom.xml
index 4b14536..5f34879 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,6 +27,7 @@ under the License.
         <module>paimon-web-server</module>
         <module>paimon-web-common</module>
         <module>paimon-web-gateway</module>
+        <module>paimon-web-api</module>
     </modules>
 
     <parent>
@@ -61,7 +62,7 @@ under the License.
     </scm>
 
     <properties>
-        <paimon.version>0.1-SNAPSHOT</paimon.version>
+        <project.version>0.1-SNAPSHOT</project.version>
         <java.version>8</java.version>
         
<maven-dependency-plugin.version>3.5.0</maven-dependency-plugin.version>
         <mysql-connector-java.version>8.0.28</mysql-connector-java.version>
@@ -80,6 +81,7 @@ under the License.
         <maven.compiler.target>8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <junit5.version>5.8.1</junit5.version>
+        <assertj.version>3.23.1</assertj.version>
         <slf4j.version>1.7.25</slf4j.version>
         <log4j.version>2.17.1</log4j.version>
         <guava.version>31.1-jre</guava.version>
@@ -125,6 +127,13 @@ under the License.
                 <version>${junit5.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.assertj</groupId>
+                <artifactId>assertj-core</artifactId>
+                <version>${assertj.version}</version>
+                <scope>test</scope>
+            </dependency>
+
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava</artifactId>
@@ -167,7 +176,6 @@ under the License.
                 <version>${mysql-connector-java.version}</version>
             </dependency>
 
-
             <dependency>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>
@@ -185,7 +193,6 @@ under the License.
 
     <build>
         <plugins>
-
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>

Reply via email to