Compare commits
5 Commits
a39fcc69a4
...
ca7f6ad212
Author | SHA1 | Date | |
---|---|---|---|
ca7f6ad212 | |||
7112874a06 | |||
a38657ddf1 | |||
6ba1189c6c | |||
d8bdb048c4 |
@@ -15,6 +15,8 @@ require (
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
|
@@ -10,6 +10,7 @@ cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0
|
||||
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
|
||||
cloud.google.com/go/translate v1.12.3 h1:XJ7LipYJi80BCgVk2lx1fwc7DIYM6oV2qx1G4IAGQ5w=
|
||||
cloud.google.com/go/translate v1.12.3/go.mod h1:qINOVpgmgBnY4YTFHdfVO4nLrSBlpvlIyosqpGEgyEg=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -17,12 +18,19 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
|
||||
github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
|
@@ -2,17 +2,49 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
translate "cloud.google.com/go/translate/apiv3"
|
||||
"cloud.google.com/go/translate/apiv3/translatepb"
|
||||
)
|
||||
|
||||
const project_id string = "india-translate-testing-452100"
|
||||
|
||||
type translationStruct struct {
|
||||
En string `db:"english" json:"en"`
|
||||
Hi string `db:"hindi" json:"hi"`
|
||||
Bn string `db:"bengali" json:"bn"`
|
||||
Mr string `db:"marathi" json:"mr"`
|
||||
Ta string `db:"tamil" json:"ta"`
|
||||
Te string `db:"telugu" json:"te"`
|
||||
Ml string `db:"malayalam" json:"ml"`
|
||||
Kn string `db:"kannada" json:"kn"`
|
||||
Gu string `db:"gujarati" json:"gu"`
|
||||
Or string `db:"oriya" json:"or"`
|
||||
Ur string `db:"urdu" json:"ur"`
|
||||
Lus string `db:"mizo" json:"lus"`
|
||||
As string `db:"assamese" json:"as"`
|
||||
Pa string `db:"punjabi" json:"pa"`
|
||||
Mai string `db:"maithili" json:"mai"`
|
||||
Mwr string `db:"marwari" json:"mwr"`
|
||||
Sat string `db:"santali" json:"sat"`
|
||||
Ne string `db:"nepali" json:"ne"`
|
||||
Gom string `db:"konkani" json:"gom"`
|
||||
Tcy string `db:"tulu" json:"tcy"`
|
||||
Bho string `db:"bhojpuri" json:"bho"`
|
||||
Doi string `db:"dogri" json:"doi"`
|
||||
Mni_mtei string `db:"manipuri" json:"mni_mtei"`
|
||||
Sd string `db:"sindhi" json:"sd"`
|
||||
Awa string `db:"awadhi" json:"awa"`
|
||||
}
|
||||
|
||||
var lang_codes []string = []string{
|
||||
"hi", // Hindi
|
||||
"bn", // Bengali
|
||||
@@ -40,6 +72,39 @@ var lang_codes []string = []string{
|
||||
"awa", // Awadhi
|
||||
}
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
/*
|
||||
Returns the cached translation from the database, for the given english text. The first parameter
|
||||
|
||||
indicates whether or not the translation exists.
|
||||
*/
|
||||
func getCachedTranslation(data string) (bool, translationStruct) {
|
||||
prepared, err := db.Preparex("SELECT * from TRANSLATIONS WHERE english = ? COLLATE NOCASE")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
translations := translationStruct{}
|
||||
err = prepared.Get(&translations, data)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, translations
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
return true, translations
|
||||
}
|
||||
}
|
||||
|
||||
func addToDatabase(translation translationStruct) {
|
||||
_, err := db.NamedExec(`INSERT INTO translations VALUES (:english, :hindi, :bengali, :marathi, :tamil, :telugu, :kannada, :malayalam, :oriya, :gujarati, :marwari, :urdu, :mizo, :assamese, :punjabi, :maithili, :santali, :nepali, :konkani, :tulu, :bhojpuri, :dogri, :manipuri, :sindhi, :awadhi)`, &translation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func translateText(text string, targetLang string) (result string, err error) {
|
||||
return translateTextHelper(project_id, "en-US", targetLang, text)
|
||||
}
|
||||
@@ -80,22 +145,39 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
queries := r.URL.Query()
|
||||
toTranslate := queries["query"][0]
|
||||
|
||||
langToTranslation := make(map[string]string)
|
||||
for _, lang_code := range lang_codes {
|
||||
translation, err := translateText(toTranslate, lang_code)
|
||||
if ok, translation := getCachedTranslation(toTranslate); ok {
|
||||
translationJson, _ := json.Marshal(translation)
|
||||
fmt.Fprintf(w, "%v", string(translationJson))
|
||||
} else {
|
||||
langToTranslation := make(map[string]string)
|
||||
for _, lang_code := range lang_codes {
|
||||
translation, err := translateText(toTranslate, lang_code)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
langToTranslation[lang_code] = translation
|
||||
}
|
||||
langToTranslationJson, _ := json.Marshal(langToTranslation)
|
||||
translation := translationStruct{}
|
||||
err := json.Unmarshal(langToTranslationJson, &translation)
|
||||
translation.En = toTranslate // langToTranslation doesn't contain the english value
|
||||
addToDatabase(translation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
langToTranslation[lang_code] = translation
|
||||
fmt.Fprintf(w, "%v", string(langToTranslationJson))
|
||||
}
|
||||
|
||||
langToTranslationJson, _ := json.Marshal(langToTranslation)
|
||||
fmt.Fprintf(w, "%v", string(langToTranslationJson))
|
||||
|
||||
// fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Starting server...")
|
||||
var err error
|
||||
db, err = sqlx.Connect("sqlite3", "translations.db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
http.HandleFunc("/", handler)
|
||||
log.Fatal(http.ListenAndServe(":9090", nil))
|
||||
}
|
||||
|
BIN
golangServer/translations.db
Normal file
BIN
golangServer/translations.db
Normal file
Binary file not shown.
125
index.js
125
index.js
@@ -1,57 +1,32 @@
|
||||
const svg = d3.select("svg")
|
||||
|
||||
const tamilColor = "#75d795" // Tamil
|
||||
const malayalamColor = "#ff7c7c" // Malayalam
|
||||
const kannadaColor = "#ffe77c" // Kannada
|
||||
const teluguColor = "#7c9dff" // Telugu
|
||||
const marathiColor = "#e0ff7c" // Marathi
|
||||
const konkaniColor = "#9b7cff" // Konkani
|
||||
const hindiColor = "#d17cff" // Hindi
|
||||
const gujaratiColor = "#7cffee" // Gujarati
|
||||
const marwariColor = "#7bc4c9" // Marwari
|
||||
const oriyaColor = "#9bcc9f" // Oriya
|
||||
const bengaliColor = "#bf9a77" // Bengali
|
||||
const punjabiColor = "#e84a35" // Punjabi
|
||||
const mizoColor = "#a6a4de" // Mizo
|
||||
const assameseColor = "#c9535b" // Assamese
|
||||
const bhojpuriColor = "#b3b876" // Bhojpuri
|
||||
const manipuriColor = "#c9afad" // Manipuri
|
||||
const dogriColor = "#9595e6" // Dogri (near Kashmir)
|
||||
const nepaliColor = "#71998e" // Nepali
|
||||
const urduColor = "#3fa179" // Urdu
|
||||
const tuluColor = "#dedc52" // Tulu
|
||||
const maithaliColor = "#4472a6" // Maithali
|
||||
const santaliColor = "#96bf60" // Santhali
|
||||
const sindhiColor = "#e89931" // Sindhi
|
||||
const awadhiColor = "#847fb5" // Awadhi
|
||||
|
||||
const defaultColor = "#555555"
|
||||
|
||||
const languages = {
|
||||
tamil: {name: "Tamil", color: tamilColor, code: "ta", districts: []},
|
||||
malayalam: {name: "Malayalam", color: malayalamColor, code: "ml", districts: []},
|
||||
kannada: {name: "Kannada", color: kannadaColor, code: "kn", districts: []},
|
||||
telugu: {name: "Telugu", color: teluguColor, code: "te", districts: []},
|
||||
marathi: {name: "Marathi", color: marathiColor, code: "mr", districts: []},
|
||||
konkani: {name: "Konkani", color: konkaniColor, code: "gom", districts: []},
|
||||
hindi: {name: "Hindi", color: hindiColor, code: "hi", districts: []},
|
||||
gujarati: {name: "Gujarati", color: gujaratiColor, code: "gu", districts: []},
|
||||
marwari: {name: "Marwari", color: marwariColor, code: "mwr", districts: []},
|
||||
oriya: {name: "Oriya", color: oriyaColor, code: "or", districts: []},
|
||||
bengali: {name: "Bengali", color: bengaliColor, code: "bn", districts: []},
|
||||
punjabi: {name: "Punjabi", color: punjabiColor, code: "pa", districts: []},
|
||||
mizo: {name: "Mizo", color: mizoColor, code: "lus", districts: []},
|
||||
assamese: {name: "Assamese", color: assameseColor, code: "as", districts: []},
|
||||
bhojpuri: {name: "Bhojpuri", color: bhojpuriColor, code: "bho", districts: []},
|
||||
manipuri: {name: "Manipuri", color: manipuriColor, code: "mni-Mtei", districts: []},
|
||||
dogri: {name: "Dogri", color: dogriColor, code: "doi", districts: []},
|
||||
nepali: {name: "Nepali", color: nepaliColor, code: "ne", districts: []},
|
||||
urdu: {name: "Urdu", color: urduColor, code: "ur", districts: []},
|
||||
tulu: {name: "Tulu", color: tuluColor, code: "tcy", districts: []},
|
||||
maithali: {name: "Maithali", color: maithaliColor, code: "mai", districts: []},
|
||||
santali: {name: "Santali", color: santaliColor, code: "sat", districts: []},
|
||||
sindhi: {name: "Sindhi", color: sindhiColor, code: "sd", districts: []},
|
||||
awadhi: {name: "Awadhi", color: awadhiColor, code: "awa", districts: []},
|
||||
tamil: {name: "Tamil", color: "#75d795", code: "ta", districts: []},
|
||||
malayalam: {name: "Malayalam", color: "#ff7c7c", code: "ml", districts: []},
|
||||
kannada: {name: "Kannada", color: "#ffe77c", code: "kn", districts: []},
|
||||
telugu: {name: "Telugu", color: "#7c9dff", code: "te", districts: []},
|
||||
marathi: {name: "Marathi", color: "#e0ff7c", code: "mr", districts: []},
|
||||
konkani: {name: "Konkani", color: "#9b7cff", code: "gom", districts: []},
|
||||
hindi: {name: "Hindi", color: "#d17cff", code: "hi", districts: []},
|
||||
gujarati: {name: "Gujarati", color: "#7cffee", code: "gu", districts: []},
|
||||
marwari: {name: "Marwari", color: "#7bc4c9", code: "mwr", districts: []},
|
||||
oriya: {name: "Oriya", color: "#9bcc9f", code: "or", districts: []},
|
||||
bengali: {name: "Bengali", color: "#bf9a77", code: "bn", districts: []},
|
||||
punjabi: {name: "Punjabi", color: "#e84a35", code: "pa", districts: []},
|
||||
mizo: {name: "Mizo", color: "#a6a4de", code: "lus", districts: []},
|
||||
assamese: {name: "Assamese", color: "#c9535b", code: "as", districts: []},
|
||||
bhojpuri: {name: "Bhojpuri", color: "#b3b876", code: "bho", districts: []},
|
||||
manipuri: {name: "Manipuri", color: "#c9afad", code: "mni-Mtei", districts: []},
|
||||
dogri: {name: "Dogri", color: "#9595e6", code: "doi", districts: []},
|
||||
nepali: {name: "Nepali", color: "#71998e", code: "ne", districts: []},
|
||||
urdu: {name: "Urdu", color: "#3fa179", code: "ur", districts: []},
|
||||
tulu: {name: "Tulu", color: "#dedc52", code: "tcy", districts: []},
|
||||
maithali: {name: "Maithali", color: "#4472a6", code: "mai", districts: []},
|
||||
santali: {name: "Santali", color: "#96bf60", code: "sat", districts: []},
|
||||
sindhi: {name: "Sindhi", color: "#e89931", code: "sd", districts: []},
|
||||
awadhi: {name: "Awadhi", color: "#847fb5", code: "awa", districts: []},
|
||||
};
|
||||
|
||||
// Credit: https://www.artcraftblend.com/blogs/colors/shades-of-pastel
|
||||
@@ -89,7 +64,6 @@ const state2lang = {
|
||||
"Lakshadweep": languages["malayalam"],
|
||||
"Delhi": languages["hindi"],
|
||||
"Chandigarh": languages["hindi"]
|
||||
|
||||
}
|
||||
|
||||
const district2lang = { // Should override state colors
|
||||
@@ -143,7 +117,7 @@ const district2lang = { // Should override state colors
|
||||
"Hapur": languages["urdu"],
|
||||
|
||||
"Kutch": languages["sindhi"],
|
||||
|
||||
|
||||
"Godda": languages["santali"],
|
||||
"Deoghar": languages["santali"],
|
||||
"Dumka": languages["santali"],
|
||||
@@ -166,14 +140,12 @@ const district2lang = { // Should override state colors
|
||||
"Rae Bareli": languages["awadhi"],
|
||||
"Amethi": languages["awadhi"],
|
||||
"Bahraich": languages["awadhi"],
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Functions for calculating and dealing with language boundaries
|
||||
function reverseCoordArrays(coords) {
|
||||
if (!Array.isArray(coords)) {
|
||||
return coords;
|
||||
return coords;
|
||||
}
|
||||
if (coords.every(item => Array.isArray(item) && item.length == 2 && item.every(val => Number.isFinite(val)))) {
|
||||
if (!turf.booleanClockwise(coords)) {
|
||||
@@ -186,14 +158,13 @@ function reverseCoordArrays(coords) {
|
||||
return coords.map(reverseCoordArrays);
|
||||
}
|
||||
|
||||
|
||||
function getOuterBoundaryPolygon(features) {
|
||||
// Check if we have features to process
|
||||
if (!features || features.length === 0) {
|
||||
console.warn("No features to process for boundary");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Handle single feature case
|
||||
if (features.length === 1) {
|
||||
return features[0];
|
||||
@@ -205,6 +176,15 @@ function getOuterBoundaryPolygon(features) {
|
||||
return combined;
|
||||
}
|
||||
|
||||
function district2langFunc(d) {
|
||||
if (district2lang.hasOwnProperty(d.properties.district)) {
|
||||
return district2lang[d.properties.district];
|
||||
} else if (state2lang.hasOwnProperty(d.properties.st_nm)) {
|
||||
return state2lang[d.properties.st_nm];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function stateOrDistrictOrLanguage(d) {
|
||||
if (typeof d.properties.district !== 'undefined') {
|
||||
@@ -233,24 +213,18 @@ function drawMap(world) {
|
||||
.attr("d", path)
|
||||
.attr("class", d => stateOrDistrictOrLanguage(d))
|
||||
.attr("fill", function(d) {
|
||||
if (stateOrDistrictOrLanguage(d) === "district") {
|
||||
if (district2lang.hasOwnProperty(d.properties.district)) {
|
||||
return district2lang[d.properties.district].color;
|
||||
} else if (state2lang.hasOwnProperty(d.properties.st_nm)) {
|
||||
return state2lang[d.properties.st_nm].color;
|
||||
} else {
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
if (stateOrDistrictOrLanguage(d) === "district") {
|
||||
const districtLang = district2langFunc(d)
|
||||
if (typeof districtLang !== 'undefined') {
|
||||
return districtLang.color
|
||||
} else {
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
})
|
||||
.each(function(d) {
|
||||
if (stateOrDistrictOrLanguage(d) === "district") {
|
||||
let districtLang;
|
||||
if (district2lang.hasOwnProperty(d.properties.district)) {
|
||||
districtLang = district2lang[d.properties.district];
|
||||
} else if (state2lang.hasOwnProperty(d.properties.st_nm)) {
|
||||
districtLang = state2lang[d.properties.st_nm];
|
||||
}
|
||||
const districtLang = district2langFunc(d);
|
||||
if (typeof districtLang !== 'undefined') {
|
||||
districtLang.districts.push(d)
|
||||
}
|
||||
@@ -277,7 +251,7 @@ function drawMap(world) {
|
||||
if (stateOrDistrictOrLanguage(d) == "language") {
|
||||
return d.properties.lang_name;
|
||||
} else {
|
||||
d3.select(this).remove() // Only add text if the element is a language
|
||||
d3.select(this).remove() // Only add this attribute if the element is a language
|
||||
}
|
||||
});
|
||||
|
||||
@@ -285,7 +259,7 @@ function drawMap(world) {
|
||||
|
||||
const coordinates = [77.69916967457782,23.389970772934166];
|
||||
const [xCoord, yCoord] = projection(coordinates);
|
||||
|
||||
|
||||
svg.append("text")
|
||||
.attr("x", xCoord)
|
||||
.attr("y", yCoord)
|
||||
@@ -299,7 +273,7 @@ function drawMap(world) {
|
||||
// "type": "FeatureCollection",
|
||||
// "features": lang.districts
|
||||
// };
|
||||
//
|
||||
//
|
||||
// let outerBound = getOuterBoundaryPolygon(geojson.features)
|
||||
// outerBound["id"] = "lang" + lang.name
|
||||
// outerBound.properties["lang_name"]= lang.name
|
||||
@@ -318,4 +292,3 @@ function drawMap(world) {
|
||||
}
|
||||
|
||||
d3.json("india_with_districts_with_languages.json").then(drawMap)
|
||||
|
||||
|
Reference in New Issue
Block a user