Commit 02897568 authored by liang.tang's avatar liang.tang
Browse files

magic-api

parents
Pipeline #222 failed with stages
in 0 seconds
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1647853515880" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1190" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M914.5 653.5c-5.5 0-11 1.1-16 3.3l-0.2 0.1h-0.2L510.2 822.2 122.2 657h-0.2l-0.2-0.1c-5-2.1-10.3-3.3-16-3.3-23.1 0-41.8 19.3-41.8 43.1 0 18 10.7 33.3 25.8 39.8l403.9 172.1 0.4 0.1c10.2 4.4 21.8 4.4 32 0l0.2-0.1c0.1 0 0.1-0.1 0.2-0.1l403.9-172.1c15.1-6.5 25.8-21.8 25.8-39.8 0.1-23.8-18.6-43.1-41.7-43.1z m0-186.5c-7.9-0.2-16 3.2-16 3.2L510.2 635.6 121.8 470.2s-10.3-3.2-16-3.2C82.7 467 64 486.2 64 510c0 17.9 10.7 33.3 25.8 39.7l403.9 172c0.1 0 0.1 0.1 0.2 0.1l0.1 0.1c5 2.1 10.3 3.3 16 3.3 5.7 0 11.1-1.2 16-3.3l0.2-0.1c0.1 0 0.1 0 0.2-0.1l403.9-172c15.1-6.4 25.8-21.8 25.9-39.7 0.1-23.8-18.6-43-41.7-43zM89.8 363.2l403.9 172.1c0.1 0 0.1 0 0.2 0.1l0.1 0.1c5 2.1 10.3 3.2 16 3.2 5.5 0 10.9-1.1 16-3.2l0.2-0.1 0.2-0.1 403.9-172c15.1-6.5 25.8-21.8 25.9-39.7 0-18-10.7-33.3-25.8-39.8L526.5 111.6c-0.1 0-0.1 0-0.2-0.1l-0.2-0.1c-10.2-4.4-21.8-4.4-32 0l-0.1 0.1L89.8 283.7C74.7 290.1 64 305.5 64 323.5c0 17.9 10.7 33.2 25.8 39.7z" p-id="1191" fill="#000000"></path></svg>
\ No newline at end of file
import MagicComponent from './service/magic-component.js'
import localZhCN from './i18n/zh-cn.js'
import localEn from './i18n/en.js'
import MagicComponentInfo from './components/magic-component-info.vue'
import 'vite-plugin-svg-icons/register'
export default (opt) => {
const i18n = opt.i18n
// 添加i18n 国际化信息
i18n.add('zh-cn', localZhCN)
i18n.add('en', localEn)
return {
// 左侧资源
resource: [{
// 资源类型,和后端存储结构一致
type: 'component',
// 展示图标
icon: '#magic-component-component', // #开头表示图标在插件中
// 展示名称
title: 'component.name',
// 运行服务
service: MagicComponent(opt.bus, opt.constants, i18n.format, opt.Message, opt.request),
}],
// 底部工具条
toolbars: [{
// 当打开的资源类型为 task 时显示
type: 'component',
// 工具条展示的标题
title: 'component.title',
// 展示图标
icon: 'parameter',
// 对应的组件
component: MagicComponentInfo,
}]
}
}
export default function (bus, constants, $i, Message, request) {
return {
// svg text
getIcon: item => ['Vue', '#41B883'],
// 任务名称
name: $i('component.name'),
// 脚本语言
language: 'html',
// 默认脚本
defaultScript: `<template>
</template>
<script setup>
</script>
<style scoped>
</style>`,
// 是否允许执行测试
runnable: false,
// 是否需要填写路径
requirePath: true,
// 合并
merge: item => item
}
}
import vue from '@vitejs/plugin-vue'
import viteSvgIcons from 'vite-plugin-svg-icons'
import path from 'path'
import pkg from './package.json'
export default {
base: './',
build: {
minify: false,
cssCodeSplit: true, // 将组件的 style 打包到 js 文件中
outDir: 'dist',
lib: {
target: 'esnext',
formats: ['iife'],
entry: path.resolve(__dirname, 'src/index.js'),
name: 'MagicComponent',
fileName: (format) => `magic-component.${pkg.version}.${format}.js`
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external: ['vue'],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue'
}
}
}
},
plugins: [
vue(),
viteSvgIcons({
iconDirs: [path.resolve(process.cwd(), 'src/icons')],
symbolId: 'magic-component-[name]'
}),
]
}
package org.ssssssss.magicapi.component.model;
import org.ssssssss.magicapi.core.model.MagicEntity;
import org.ssssssss.magicapi.core.model.PathMagicEntity;
import java.util.Objects;
public class ComponentInfo extends PathMagicEntity {
/**
* 组件描述
*/
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public ComponentInfo copy() {
ComponentInfo info = new ComponentInfo();
super.copyTo(info);
info.setDescription(this.description);
return info;
}
@Override
public MagicEntity simple() {
ComponentInfo info = new ComponentInfo();
super.simple(info);
return info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ComponentInfo componentInfo = (ComponentInfo) o;
return Objects.equals(id, componentInfo.id) &&
Objects.equals(path, componentInfo.path) &&
Objects.equals(script, componentInfo.script) &&
Objects.equals(name, componentInfo.name) &&
Objects.equals(description, componentInfo.description);
}
@Override
public int hashCode() {
return Objects.hash(id, path, script, name, groupId, description);
}
}
package org.ssssssss.magicapi.component.service;
import org.ssssssss.magicapi.component.model.ComponentInfo;
import org.ssssssss.magicapi.core.exception.InvalidArgumentException;
import org.ssssssss.magicapi.core.model.JsonCode;
import org.ssssssss.magicapi.core.service.AbstractPathMagicResourceStorage;
import java.util.UUID;
public class ComponentInfoMagicResourceStorage extends AbstractPathMagicResourceStorage<ComponentInfo> {
@Override
public String folder() {
return "component";
}
@Override
public Class<ComponentInfo> magicClass() {
return ComponentInfo.class;
}
@Override
public void validate(ComponentInfo entity) {
}
@Override
public String buildMappingKey(ComponentInfo info) {
return buildMappingKey(info, magicResourceService.getGroupPath(info.getGroupId()));
}
}
package org.ssssssss.magicapi.component.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.ssssssss.magicapi.component.model.ComponentInfo;
import org.ssssssss.magicapi.core.event.FileEvent;
import org.ssssssss.magicapi.core.event.GroupEvent;
import org.ssssssss.magicapi.core.service.AbstractMagicDynamicRegistry;
import org.ssssssss.magicapi.core.service.MagicResourceStorage;
public class ComponentMagicDynamicRegistry extends AbstractMagicDynamicRegistry<ComponentInfo> {
public ComponentMagicDynamicRegistry(MagicResourceStorage<ComponentInfo> magicResourceStorage) {
super(magicResourceStorage);
}
@EventListener(condition = "#event.type == 'component'")
public void onFileEvent(FileEvent event) {
processEvent(event);
}
@EventListener(condition = "#event.type == 'component'")
public void onGroupEvent(GroupEvent event) {
processEvent(event);
}
}
package org.ssssssss.magicapi.component.starter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.ssssssss.magicapi.component.service.ComponentInfoMagicResourceStorage;
import org.ssssssss.magicapi.component.service.ComponentMagicDynamicRegistry;
import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
import org.ssssssss.magicapi.core.model.Plugin;
@Configuration
public class MagicAPIComponentConfiguration implements MagicPluginConfiguration {
@Bean
@ConditionalOnMissingBean
public ComponentInfoMagicResourceStorage componentInfoMagicResourceStorage() {
return new ComponentInfoMagicResourceStorage();
}
@Bean
@ConditionalOnMissingBean
public ComponentMagicDynamicRegistry componentMagicDynamicRegistry(ComponentInfoMagicResourceStorage componentInfoMagicResourceStorage) {
return new ComponentMagicDynamicRegistry(componentInfoMagicResourceStorage);
}
@Override
public Plugin plugin() {
return new Plugin("组件", "MagicComponent", "magic-component.1.0.0.iife.js");
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.component.starter.MagicAPIComponentConfiguration
org.ssssssss.magicapi.component.starter.MagicAPIComponentConfiguration
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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.ssssssss</groupId>
<artifactId>magic-api-plugins</artifactId>
<version>2.1.1</version>
</parent>
<artifactId>magic-api-plugin-elasticsearch</artifactId>
<packaging>jar</packaging>
<name>magic-api-plugin-elasticsearch</name>
<description>magic-api-plugin-elasticsearch</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
</project>
package org.ssssssss.magicapi.elasticsearch;
import org.elasticsearch.client.RestClient;
import java.io.IOException;
import java.util.Map;
public class ElasticSearchConnection extends ElasticSearchRest {
public ElasticSearchConnection(RestClient restClient, String endpoint) {
super(restClient);
super.endpoint(endpoint);
}
public ElasticSearchConnection parameter(String key, String value) {
if (value != null) {
parameters.put(key, value);
}
return this;
}
public ElasticSearchConnection parameters(Map<String, String> params) {
if (params != null) {
parameters.putAll(params);
}
return this;
}
public Object put(Object data) throws IOException {
return processResponse(json(data).doPut());
}
public Object delete() throws IOException {
return processResponse(doDelete());
}
public Object delete(Object data) throws IOException {
return processResponse(json(data).doDelete());
}
public Object post(Object data) throws IOException {
return processResponse(json(data).doPost());
}
public Object get() throws IOException {
return processResponse(doGet());
}
}
package org.ssssssss.magicapi.elasticsearch;
import org.elasticsearch.client.RestClient;
import org.ssssssss.magicapi.utils.JsonUtils;
import org.ssssssss.script.annotation.Comment;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ElasticSearchIndex {
private final RestClient restClient;
private final String name;
private final String type;
public ElasticSearchIndex(RestClient restClient, String name, String type) {
this.restClient = restClient;
this.name = name;
this.type = type;
}
@Comment("根据`_id`保存,当存在时更新,不存在时插入")
public Object save(@Comment(value = "_id", name = "_id")String _id, @Comment(value = "保存对象", name = "data")Object data) throws IOException {
return connect("/%s/%s/%s", this.name, this.type, _id).post(data);
}
@Comment("不指定`_id`插入")
public Object insert(@Comment(value = "插入对象", name = "data")Object data) throws IOException {
return connect("/%s/%s", this.name, this.type).post(data);
}
@Comment("指定`_id`插入,当`_id`存在时不会更新")
public Object insert(@Comment(value = "_id", name = "_id")String _id, @Comment(value = "插入对象", name = "data")Object data) throws IOException {
return connect("/%s/%s/%s/_create", this.name, this.type, _id).post(data);
}
@Comment("根据`id`删除")
public Object delete(@Comment(value = "id", name = "id")String id) throws IOException {
return connect("/%s/%s/%s", this.name, this.type, id).delete();
}
@Comment("批量保存,当包含`id`时,则使用该列值匹配保存")
public Object bulkSave(@Comment(value = "保存内容", name = "list") List<Map<String, Object>> list) throws IOException {
StringBuilder builder = new StringBuilder();
list.forEach(item -> {
Object id = item.get("id");
if(id != null){
builder.append(String.format("{ \"index\":{ \"_id\": \"%s\" } }\r\n", id));
} else {
builder.append("{ \"index\":{} }\r\n");
}
builder.append(JsonUtils.toJsonStringWithoutPretty(item));
builder.append("\r\n");
});
return connect("/%s/%s/_bulk", this.name, this.type).post(builder.toString());
}
@Comment("根据`_id`修改")
public Object update(@Comment(value = "_id", name = "_id")String _id, @Comment(value = "修改项", name = "data")Object data) throws IOException {
return connect("/%s/%s/%s", this.name, this.type, _id).post(Collections.singletonMap("doc", data));
}
@Comment("搜索")
public Object search(@Comment(value = "搜索`DSL`语句", name = "dsl")Map<String, Object> dsl) throws IOException {
return connect("/%s/_search", this.name).post(dsl);
}
private ElasticSearchConnection connect(String format, Object... args) {
return new ElasticSearchConnection(this.restClient, String.format(format, args));
}
}
package org.ssssssss.magicapi.elasticsearch;
import org.elasticsearch.client.RestClient;
import org.ssssssss.magicapi.core.annotation.MagicModule;
import org.ssssssss.script.annotation.Comment;
@MagicModule("elasticsearch")
public class ElasticSearchModule {
private static final String DOC = "_doc";
private final RestClient restClient;
public ElasticSearchModule(RestClient restClient) {
this.restClient = restClient;
}
@Comment(value = "ElasticSearch REST API")
public ElasticSearchConnection rest(String url){
return new ElasticSearchConnection(this.restClient, url);
}
public ElasticSearchIndex index(String indexName){
return new ElasticSearchIndex(this.restClient, indexName, DOC);
}
}
package org.ssssssss.magicapi.elasticsearch;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.ssssssss.magicapi.utils.JsonUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ElasticSearchRest {
private final RestClient restClient;
private String method;
private String endpoint = "/";
private HttpEntity entity;
protected final Map<String, String> parameters = new HashMap<>();
public ElasticSearchRest(RestClient restClient) {
this.restClient = restClient;
}
ElasticSearchRest endpoint(String endpoint){
this.endpoint = endpoint;
return this;
}
Response doGet() throws IOException {
this.method = "GET";
return execute();
}
Response doPost() throws IOException {
this.method = "POST";
return execute();
}
Response doDelete() throws IOException {
this.method = "DELETE";
return execute();
}
Response doPut() throws IOException {
this.method = "PUT";
return execute();
}
ElasticSearchRest json(Object data){
if(data == null){
return this;
}
String json = null;
if(data instanceof CharSequence){
json = data.toString();
} else {
json = JsonUtils.toJsonString(data);
}
if(json != null){
this.entity = new NStringEntity(json, ContentType.APPLICATION_JSON);
}
return this;
}
private Response execute() throws IOException {
Request request = new Request(method, this.endpoint);
request.addParameters(parameters);
request.setEntity(entity);
return this.restClient.performRequest(request);
}
Object processResponse(Response response) throws IOException {
int code = response.getStatusLine().getStatusCode();
if (code >= 200 && code < 300) { // 2xx
HttpEntity entity = response.getEntity();
String resp = EntityUtils.toString(entity, StandardCharsets.UTF_8);
ContentType contentType = ContentType.get(entity);
if (Objects.equals(ContentType.APPLICATION_JSON.getMimeType(), contentType.getMimeType())) {
return JsonUtils.readValue(resp, Object.class);
}
}
return response;
}
}
package org.ssssssss.magicapi.elasticsearch;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
import org.ssssssss.magicapi.core.model.Plugin;
@Configuration
public class MagicElasticSearchConfiguration implements MagicPluginConfiguration {
@Override
public Plugin plugin() {
return new Plugin("ElasticSearch");
}
@Bean
@ConditionalOnBean(RestHighLevelClient.class)
public ElasticSearchModule elasticSearchModule(RestHighLevelClient restHighLevelClient){
return new ElasticSearchModule(restHighLevelClient.getLowLevelClient());
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.elasticsearch.MagicElasticSearchConfiguration
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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.ssssssss</groupId>
<artifactId>magic-api-plugins</artifactId>
<version>2.1.1</version>
</parent>
<artifactId>magic-api-plugin-git</artifactId>
<packaging>jar</packaging>
<name>magic-api-plugin-git</name>
<description>magic-api-plugin-git</description>
<properties>
<jgit.version>5.13.0.202109080827-r</jgit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>${jgit.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
<version>${jgit.version}</version>
</dependency>
</dependencies>
</project>
package org.ssssssss.magicapi.git;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.*;
import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ssssssss.magicapi.core.exception.MagicAPIException;
import java.io.File;
import java.io.IOException;
/**
* git仓库
*
* @author soriee
* @date 2022/2/20 22:48
*/
public class GitRepo {
private static final Logger logger = LoggerFactory.getLogger(GitRepo.class);
/**
* 文件路径地址
*/
private String rootPath;
private String gitFilePath;
private MagicGitProperties properties;
private Git git;
public GitRepo(String rootPath, MagicGitProperties properties) {
this.rootPath = rootPath;
this.gitFilePath = rootPath + File.separator + ".git";
this.properties = properties;
}
private void valid() {
File repoDir = new File(rootPath);
File gitFile = new File(gitFilePath);
// 如果文件夹不存在 则创建文件夹
if (!repoDir.exists()) {
repoDir.mkdirs();
}
if (!gitFile.exists() && repoDir.list().length > 0) {
throw new MagicAPIException("初次项目启动时,请保持文件夹为空。");
}
}
/**
* 设置ssh秘钥或者账号密码或者使用OAuth2进行认证
*
* @param transportCommand
* @return
* @author soriee
* @date 2022/2/28 20:06
*/
private void setSshOrCredentials(TransportCommand transportCommand) {
if (this.getProperties().getPrivateKey() != null) {
// ssh
final SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
@Override
protected void configure(OpenSshConfig.Host host, Session session) {
}
@Override
protected JSch createDefaultJSch(FS fs) throws JSchException {
JSch defaultJSch = super.createDefaultJSch(fs);
defaultJSch.addIdentity(GitRepo.this.getProperties().getPrivateKey());
return defaultJSch;
}
};
transportCommand.setTransportConfigCallback(new TransportConfigCallback() {
@Override
public void configure(Transport transport) {
SshTransport sshTransport = (SshTransport) transport;
sshTransport.setSshSessionFactory(sshSessionFactory);
}
});
} else if (StringUtils.isNotBlank(properties.getUsername())
&& StringUtils.isNotBlank(properties.getPassword())) {
// 账号密码
transportCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
properties.getUsername(),
properties.getPassword()));
} else if (StringUtils.isNotBlank(properties.getUrl()) && properties.getUrl().contains("oauth2:")) {
//采取OAuth2进行认证 面向gitlab api场景 Heaven96 @ 2022年11月30日13:27:38 开始
final String url = properties.getUrl();
int start = url.indexOf("oauth2:") + 7;
int end = url.indexOf("@");
transportCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider("oauth2",url.substring(start,end)));
//采取OAuth2进行认证 面向gitlab api场景 Heaven96 @ 2022年11月30日13:27:38 结束
}
}
/**
* 项目设置仓库
*
* @return
* @author soriee
* @date 2022/2/24 20:43
*/
public void setupRepo() throws IOException, GitAPIException {
this.valid();
File gitFile = new File(gitFilePath);
try {
if (gitFile.exists()) {
// 项目存在,则打开为仓库, 并且强制更新一次
FileRepositoryBuilder builder = new FileRepositoryBuilder();
Repository repository = builder.create(gitFile);
git = new Git(repository);
// 更新两次,避免删除文件未更新
this.update(false);
this.update(true);
} else {
CloneCommand cloneCommand = Git.cloneRepository()
.setURI(properties.getUrl())
.setDirectory(new File(rootPath))
.setBranch(properties.getBranch());
this.setSshOrCredentials(cloneCommand);
git = cloneCommand.call();
}
} catch (IOException | GitAPIException e) {
logger.error("初始化git仓库失败", e);
throw e;
}
}
/**
* 更新
* 1.git add .
* 2.git commit -m "同步数据"
* 3.git pull
* 4.git push
* @param update
* @return
* @author soriee
* @date 2022/2/20 22:54
*/
public boolean update(boolean update) {
try {
git.add().setUpdate(update).addFilepattern(".").call();
git.commit().setMessage("同步数据").call();
PullCommand pull = git.pull();
this.setSshOrCredentials(pull);
PullResult pullResult = pull.call();
if (!pullResult.isSuccessful()) {
throw new MagicAPIException("git更新失败, 请重试或尝试手动更新");
}
PushCommand pushCommand = git.push();
this.setSshOrCredentials(pushCommand);
pushCommand.call();
} catch (GitAPIException e) {
logger.error("git更新失败", e);
throw new MagicAPIException("git更新失败, 请重试或尝试手动更新");
}
return true;
}
public MagicGitProperties getProperties() {
return properties;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment