mirror of
https://github.com/QingdaoU/JudgeServer.git
synced 2024-12-28 04:51:44 +00:00
add JudgeServer client for go
This commit is contained in:
parent
f9af52145c
commit
2da659edcb
19
client/go/README.md
Normal file
19
client/go/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# JudgeServer client for Golang
|
||||
|
||||
# Installation
|
||||
|
||||
Install:
|
||||
|
||||
```shell
|
||||
go get -d github.com/QingdaoU/JudgeServer/client/go
|
||||
```
|
||||
|
||||
Import:
|
||||
|
||||
```go
|
||||
import "github.com/QingdaoU/JudgeServer/client/go"
|
||||
```
|
||||
|
||||
# Examples
|
||||
|
||||
[examples](https://github.com/QingdaoU/JudgeServer/tree/master/client/go/examples)
|
120
client/go/client.go
Normal file
120
client/go/client.go
Normal file
@ -0,0 +1,120 @@
|
||||
package judge
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Resp struct {
|
||||
RespData interface{} `json:"data"`
|
||||
RespErr string `json:"err"`
|
||||
err error `json:"-"`
|
||||
}
|
||||
|
||||
func (resp *Resp) Data() interface{} {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
return resp.RespData
|
||||
}
|
||||
|
||||
func (resp *Resp) Err() error {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
return resp.err
|
||||
}
|
||||
|
||||
func parseResp(body []byte) (*Resp, error) {
|
||||
resp := &Resp{}
|
||||
err := json.Unmarshal(body, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 有错误的响应了
|
||||
if resp.RespErr != "" {
|
||||
resp.err = fmt.Errorf("err: %s, data: %s", resp.RespErr, resp.RespData)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
opts *options
|
||||
sha256Token string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func (c *Client) request(method, path string, body io.Reader) (resp *Resp, err error) {
|
||||
req, err := http.NewRequest("POST", c.opts.EndpointURL+"/"+path, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Judge-Server-Token", c.sha256Token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
httpResp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b, err := ioutil.ReadAll(httpResp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
httpResp.Body.Close()
|
||||
return parseResp(b)
|
||||
}
|
||||
|
||||
func (c *Client) post(path string, body io.Reader) (resp *Resp, err error) {
|
||||
return c.request("POST", path, body)
|
||||
}
|
||||
|
||||
// Ping Judge server
|
||||
func (c *Client) Ping() (resp *Resp, err error) {
|
||||
resp, err = c.post("ping", nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) CompileSpj(src, spjVersion string, spjCompileConfig *CompileConfig) (resp *Resp, err error) {
|
||||
data := map[string]interface{}{
|
||||
"src": src,
|
||||
"spj_version": spjVersion,
|
||||
"spj_compile_config": spjCompileConfig,
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err = c.post("compile_spj", bytes.NewReader(b))
|
||||
return
|
||||
}
|
||||
|
||||
func New(endpointURL, token string, timeout time.Duration) *Client {
|
||||
return NewClient(
|
||||
WithEndpointURL(endpointURL),
|
||||
WithToken(token),
|
||||
WithTimeout(timeout),
|
||||
)
|
||||
}
|
||||
|
||||
func NewClient(options ...Option) *Client {
|
||||
opts := DefaultOptions
|
||||
for _, o := range options {
|
||||
o(opts)
|
||||
}
|
||||
sha256Token := sha256.Sum256([]byte(opts.Token))
|
||||
return &Client{
|
||||
opts: opts,
|
||||
sha256Token: hex.EncodeToString(sha256Token[:]),
|
||||
httpClient: &http.Client{
|
||||
Timeout: opts.Timeout,
|
||||
},
|
||||
}
|
||||
}
|
56
client/go/client_test.go
Normal file
56
client/go/client_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package judge
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var client *Client
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// 创建一个client。 这句代码等价于 New("http://127.0.0.1:12358", "YOUR_TOKEN_HERE", 0)
|
||||
client = NewClient(
|
||||
WithEndpointURL("http://127.0.0.1:12358"),
|
||||
WithToken("YOUR_TOKEN_HERE"),
|
||||
WithTimeout(0),
|
||||
)
|
||||
m.Run()
|
||||
}
|
||||
|
||||
func TestClient_Ping(t *testing.T) {
|
||||
resp, err := client.Ping()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error. error: %+v", err)
|
||||
return
|
||||
}
|
||||
if resp.Err() != nil {
|
||||
t.Errorf("unexpected error. error: %+v", resp.Err())
|
||||
return
|
||||
}
|
||||
|
||||
if resp.RespData == nil {
|
||||
t.Error("resp.RespData unexpected nil")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CompileSpj(t *testing.T) {
|
||||
cSpjSrc := `
|
||||
#include <stdio.h>
|
||||
int main(){
|
||||
return 1;
|
||||
}
|
||||
`
|
||||
resp, err := client.CompileSpj(cSpjSrc, "2", CLangSPJCompile)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error. error: %+v", err)
|
||||
return
|
||||
}
|
||||
if resp.Err() != nil {
|
||||
t.Errorf("unexpected error. error: %+v", resp.Err())
|
||||
return
|
||||
}
|
||||
if resp.RespData == nil {
|
||||
t.Error("resp.RespData unexpected nil")
|
||||
return
|
||||
}
|
||||
}
|
138
client/go/examples/example.go
Normal file
138
client/go/examples/example.go
Normal file
@ -0,0 +1,138 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/QingdaoU/JudgeServer/client/go"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
cSrc = `
|
||||
#include <stdio.h>
|
||||
int main(){
|
||||
int a, b;
|
||||
scanf("%d%d", &a, &b);
|
||||
printf("%d\n", a+b);
|
||||
return 0;
|
||||
}
|
||||
`
|
||||
cSPJSrc = `
|
||||
#include <stdio.h>
|
||||
int main(){
|
||||
return 1;
|
||||
}
|
||||
`
|
||||
cppSrc = `
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
int a,b;
|
||||
cin >> a >> b;
|
||||
cout << a+b << endl;
|
||||
return 0;
|
||||
}
|
||||
`
|
||||
javaSrc = `
|
||||
import java.util.Scanner;
|
||||
public class Main{
|
||||
public static void main(String[] args){
|
||||
Scanner in=new Scanner(System.in);
|
||||
int a=in.nextInt();
|
||||
int b=in.nextInt();
|
||||
System.out.println(a + b);
|
||||
}
|
||||
}
|
||||
`
|
||||
py2Src = `
|
||||
s = raw_input()
|
||||
s1 = s.split(" ")
|
||||
print int(s1[0]) + int(s1[1])
|
||||
`
|
||||
py3Src = `
|
||||
s = input()
|
||||
s1 = s.split(" ")
|
||||
print(int(s1[0]) + int(s1[1]))
|
||||
`
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建一个client。 这句代码等价于 judge.New("http://127.0.0.1:12358", "YOUR_TOKEN_HERE", 0)
|
||||
client := judge.NewClient(
|
||||
judge.WithEndpointURL("http://127.0.0.1:12358"),
|
||||
judge.WithToken("YOUR_TOKEN_HERE"),
|
||||
judge.WithTimeout(0),
|
||||
)
|
||||
|
||||
fmt.Println("ping:")
|
||||
resp, err := client.Ping()
|
||||
if err != nil {
|
||||
// 这个 err 是发生在 client 这边的错误。 例如json编码失败
|
||||
fmt.Printf("ping client error. error is: %v.\n", err)
|
||||
} else if resp.Err() != nil {
|
||||
// 这个 resp.Err() 是 JudgeServer 响应的错误。 例如token错误 TokenVerificationFailed
|
||||
fmt.Printf("ping server error. error is: %v.\n", resp.Err().Error())
|
||||
} else {
|
||||
fmt.Println(resp.Data())
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("cpp_judge")
|
||||
resp, err = client.JudgeWithRequest(&judge.JudgeRequest{
|
||||
Src: cppSrc,
|
||||
LanguageConfig: judge.CPPLangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
TestCaseId: "normal",
|
||||
})
|
||||
fmt.Println(resp, err)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("java_judge")
|
||||
resp, err = client.JudgeWithRequest(&judge.JudgeRequest{
|
||||
Src: javaSrc,
|
||||
LanguageConfig: judge.JavaLangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 256 * 1024 * 1024,
|
||||
TestCaseId: "normal",
|
||||
})
|
||||
fmt.Println(resp, err)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("c_spj_judge")
|
||||
resp, err = client.JudgeWithRequest(&judge.JudgeRequest{
|
||||
Src: cSrc,
|
||||
LanguageConfig: judge.CLangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
TestCaseId: "spj",
|
||||
SPJVersion: "3",
|
||||
SPJConfig: judge.CLangSPJConfig,
|
||||
SPJCompileConfig: judge.CLangSPJCompile,
|
||||
SPJSrc: cSPJSrc,
|
||||
})
|
||||
fmt.Println(resp, err)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("py2_judge")
|
||||
resp, err = client.JudgeWithRequest(&judge.JudgeRequest{
|
||||
Src: py2Src,
|
||||
LanguageConfig: judge.PY2LangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
TestCaseId: "normal",
|
||||
})
|
||||
fmt.Println(resp, err)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("py3_judge")
|
||||
resp, err = client.JudgeWithRequest(&judge.JudgeRequest{
|
||||
Src: py3Src,
|
||||
LanguageConfig: judge.PY3LangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
TestCaseId: "normal",
|
||||
})
|
||||
fmt.Println(resp, err)
|
||||
}
|
46
client/go/judge.go
Normal file
46
client/go/judge.go
Normal file
@ -0,0 +1,46 @@
|
||||
package judge
|
||||
|
||||
import (
|
||||
"time"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type JudgeRequest struct {
|
||||
Src string `json:"src"`
|
||||
LanguageConfig *LangConfig `json:"language_config"`
|
||||
MaxCpuTime int64 `json:"max_cpu_time"`
|
||||
MaxMemory int64 `json:"max_memory"`
|
||||
TestCaseId string `json:"test_case_id"`
|
||||
SPJVersion string `json:"spj_version"`
|
||||
SPJConfig *SPJConfig `json:"spj_config"`
|
||||
SPJCompileConfig *CompileConfig `json:"spj_compile_config"`
|
||||
SPJSrc string `json:"spj_src"`
|
||||
Output bool `json:"output"`
|
||||
}
|
||||
|
||||
// 这个方法为了模仿 php 和 python client 不推荐使用
|
||||
func (c *Client) Judge(src string, languageConfig *LangConfig, maxCpuTime time.Duration, maxMemory int64, testCaseId,
|
||||
spjVersion string, spjConfig *SPJConfig, spjCompileConfig *CompileConfig, spjSrc string, output bool) (resp *Resp, err error) {
|
||||
return c.JudgeWithRequest(&JudgeRequest{
|
||||
Src: src,
|
||||
LanguageConfig: languageConfig,
|
||||
MaxCpuTime: int64(maxCpuTime),
|
||||
MaxMemory: maxMemory,
|
||||
TestCaseId: testCaseId,
|
||||
SPJVersion: spjVersion,
|
||||
SPJConfig: spjConfig,
|
||||
SPJCompileConfig: spjCompileConfig,
|
||||
SPJSrc: spjSrc,
|
||||
Output: output,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) JudgeWithRequest(req *JudgeRequest) (resp *Resp, err error) {
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err = c.post("judge", bytes.NewReader(b))
|
||||
return
|
||||
}
|
76
client/go/judge_test.go
Normal file
76
client/go/judge_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package judge
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClient_JudgeWithRequest(t *testing.T) {
|
||||
javaSrc := `
|
||||
import java.util.Scanner;
|
||||
public class Main{
|
||||
public static void main(String[] args){
|
||||
Scanner in=new Scanner(System.in);
|
||||
int a=in.nextInt();
|
||||
int b=in.nextInt();
|
||||
System.out.println(a + b);
|
||||
}
|
||||
}
|
||||
`
|
||||
cSrc := `
|
||||
#include <stdio.h>
|
||||
int main(){
|
||||
int a, b;
|
||||
scanf("%d%d", &a, &b);
|
||||
printf("%d\n", a+b);
|
||||
return 0;
|
||||
}
|
||||
`
|
||||
cSPJSrc := `
|
||||
#include <stdio.h>
|
||||
int main(){
|
||||
return 1;
|
||||
}
|
||||
`
|
||||
var tests = []struct {
|
||||
JudgeRequest *JudgeRequest
|
||||
}{
|
||||
{
|
||||
JudgeRequest: &JudgeRequest{
|
||||
Src: javaSrc,
|
||||
LanguageConfig: JavaLangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 256 * 1024 * 1024,
|
||||
TestCaseId: "normal",
|
||||
},
|
||||
},
|
||||
{
|
||||
JudgeRequest: &JudgeRequest{
|
||||
Src: cSrc,
|
||||
LanguageConfig: CLangConfig,
|
||||
MaxCpuTime: 1000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
TestCaseId: "spj",
|
||||
SPJVersion: "3",
|
||||
SPJConfig: CLangSPJConfig,
|
||||
SPJCompileConfig: CLangSPJCompile,
|
||||
SPJSrc: cSPJSrc,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
resp, err := client.JudgeWithRequest(test.JudgeRequest)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error. error: %+v", err)
|
||||
return
|
||||
}
|
||||
if resp.Err() != nil {
|
||||
t.Errorf("unexpected error. error: %+v", resp.Err())
|
||||
return
|
||||
}
|
||||
if resp.RespData == nil {
|
||||
t.Error("resp.RespData unexpected nil")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
126
client/go/languages.go
Normal file
126
client/go/languages.go
Normal file
@ -0,0 +1,126 @@
|
||||
package judge
|
||||
|
||||
type CompileConfig struct {
|
||||
SrcName string `json:"src_name"`
|
||||
ExeName string `json:"exe_name"`
|
||||
MaxCpuTime int64 `json:"max_cpu_time"`
|
||||
MaxRealTime int64 `json:"max_real_time"`
|
||||
MaxMemory int64 `json:"max_memory"`
|
||||
CompileCommand string `json:"compile_command"`
|
||||
}
|
||||
|
||||
var DefaultEnv = []string{"LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"}
|
||||
|
||||
type RunConfig struct {
|
||||
Command string `json:"command"`
|
||||
SeccompRule string `json:"seccomp_rule"`
|
||||
Env []string `json:"env"`
|
||||
MemoryLimitCheckOnly int `json:"memory_limit_check_only"`
|
||||
}
|
||||
|
||||
type LangConfig struct {
|
||||
CompileConfig CompileConfig `json:"compile"`
|
||||
RunConfig RunConfig `json:"run"`
|
||||
}
|
||||
|
||||
type SPJConfig struct {
|
||||
ExeName string `json:"exe_name"`
|
||||
Command string `json:"command"`
|
||||
SeccompRule string `json:"seccomp_rule"`
|
||||
}
|
||||
|
||||
var CLangConfig = &LangConfig{
|
||||
CompileConfig: CompileConfig{
|
||||
SrcName: "main.c",
|
||||
ExeName: "main",
|
||||
MaxCpuTime: 3000,
|
||||
MaxRealTime: 5000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
CompileCommand: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {src_path} -lm -o {exe_path}",
|
||||
},
|
||||
RunConfig: RunConfig{
|
||||
Command: "{exe_path}",
|
||||
SeccompRule: "c_cpp",
|
||||
Env: DefaultEnv,
|
||||
},
|
||||
}
|
||||
|
||||
var CLangSPJCompile = &CompileConfig{
|
||||
SrcName: "spj-{spj_version}.c",
|
||||
ExeName: "spj-{spj_version}",
|
||||
MaxCpuTime: 3000,
|
||||
MaxRealTime: 5000,
|
||||
MaxMemory: 1024 * 1024 * 1024,
|
||||
CompileCommand: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {src_path} -lm -o {exe_path}",
|
||||
}
|
||||
|
||||
var CLangSPJConfig = &SPJConfig{
|
||||
ExeName: "spj-{spj_version}",
|
||||
Command: "{exe_path} {in_file_path} {user_out_file_path}",
|
||||
SeccompRule: "c_cpp",
|
||||
}
|
||||
|
||||
var CPPLangConfig = &LangConfig{
|
||||
CompileConfig: CompileConfig{
|
||||
SrcName: "main.cpp",
|
||||
ExeName: "main",
|
||||
MaxCpuTime: 3000,
|
||||
MaxRealTime: 5000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
CompileCommand: "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++11 {src_path} -lm -o {exe_path}",
|
||||
},
|
||||
RunConfig: RunConfig{
|
||||
Command: "{exe_path}",
|
||||
SeccompRule: "c_cpp",
|
||||
Env: DefaultEnv,
|
||||
},
|
||||
}
|
||||
|
||||
var JavaLangConfig = &LangConfig{
|
||||
CompileConfig: CompileConfig{
|
||||
SrcName: "Main.java",
|
||||
ExeName: "Main",
|
||||
MaxCpuTime: 3000,
|
||||
MaxRealTime: 5000,
|
||||
MaxMemory: -1,
|
||||
CompileCommand: "/usr/bin/javac {src_path} -d {exe_dir} -encoding UTF8",
|
||||
},
|
||||
RunConfig: RunConfig{
|
||||
Command: "/usr/bin/java -cp {exe_dir} -XX:MaxRAM={max_memory}k -Djava.security.manager -Dfile.encoding=UTF-8 -Djava.security.policy==/etc/java_policy -Djava.awt.headless=true Main",
|
||||
SeccompRule: "",
|
||||
Env: DefaultEnv,
|
||||
MemoryLimitCheckOnly: 1,
|
||||
},
|
||||
}
|
||||
|
||||
var PY2LangConfig = &LangConfig{
|
||||
CompileConfig: CompileConfig{
|
||||
SrcName: "solution.py",
|
||||
ExeName: "solution.pyc",
|
||||
MaxCpuTime: 3000,
|
||||
MaxRealTime: 5000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
CompileCommand: "/usr/bin/python -m py_compile {src_path}",
|
||||
},
|
||||
RunConfig: RunConfig{
|
||||
Command: "/usr/bin/python {exe_path}",
|
||||
SeccompRule: "general",
|
||||
Env: DefaultEnv,
|
||||
},
|
||||
}
|
||||
|
||||
var PY3LangConfig = &LangConfig{
|
||||
CompileConfig: CompileConfig{
|
||||
SrcName: "solution.py",
|
||||
ExeName: "__pycache__/solution.cpython-35.pyc",
|
||||
MaxCpuTime: 3000,
|
||||
MaxRealTime: 5000,
|
||||
MaxMemory: 128 * 1024 * 1024,
|
||||
CompileCommand: "/usr/bin/python3 -m py_compile {src_path}",
|
||||
},
|
||||
RunConfig: RunConfig{
|
||||
Command: "/usr/bin/python3 {exe_path}",
|
||||
SeccompRule: "general",
|
||||
Env: append(DefaultEnv, "PYTHONIOENCODING=UTF-8"),
|
||||
},
|
||||
}
|
38
client/go/options.go
Normal file
38
client/go/options.go
Normal file
@ -0,0 +1,38 @@
|
||||
package judge
|
||||
|
||||
import "time"
|
||||
|
||||
type options struct {
|
||||
// scheme://host:port .
|
||||
EndpointURL string
|
||||
Token string
|
||||
// 请求超时时间
|
||||
// 如果 Timeout 为 0,那么意味着不会超时
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
var DefaultOptions = &options{
|
||||
EndpointURL: "http://127.0.0.1:12358",
|
||||
Token: "YOUR_TOKEN_HERE",
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
func WithEndpointURL(u string) Option {
|
||||
return func(o *options) {
|
||||
o.EndpointURL = u
|
||||
}
|
||||
}
|
||||
|
||||
func WithToken(token string) Option {
|
||||
return func(o *options) {
|
||||
o.Token = token
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) Option {
|
||||
return func(o *options) {
|
||||
o.Timeout = timeout
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user