Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a13d8c29 | |||
| 04102c75a4 | |||
| 3860b44dc5 | |||
| 94631c18d0 | |||
| c394c0c563 | |||
| 886d8e73d3 | |||
| 43f5d5ef60 | |||
| a0620ea391 | |||
| 8902feada7 | |||
| 13ce96e07e |
@@ -0,0 +1,21 @@
|
|||||||
|
name: Validate Flux manifests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
kustomize-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install kustomize
|
||||||
|
run: |
|
||||||
|
curl -sL "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
|
||||||
|
sudo mv kustomize /usr/local/bin/
|
||||||
|
|
||||||
|
- name: kustomize build flux/
|
||||||
|
run: kustomize build flux/ > /dev/null
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Company Status
|
# Company Status
|
||||||
|
|
||||||
_Last updated: 2026-05-27 (agent cycle — evening triage)_
|
_Last updated: 2026-05-27 (agent cycle — night triage)_
|
||||||
|
|
||||||
## APIs
|
## APIs
|
||||||
| API | Spec | Code | Deployed | Listed on RapidAPI | Paying Users | MRR |
|
| API | Spec | Code | Deployed | Listed on RapidAPI | Paying Users | MRR |
|
||||||
@@ -21,7 +21,13 @@ Legend: [x]=done, [~]=in-progress, [ ]=not started
|
|||||||
- **API service manifests:** `flux/zip-enrichment/`, `flux/holidays/`, `flux/air-quality/` scaffolded (PR #48, closes #46)
|
- **API service manifests:** `flux/zip-enrichment/`, `flux/holidays/`, `flux/air-quality/` scaffolded (PR #48, closes #46)
|
||||||
- **Image automation:** `flux/image-automation/` — ImageRepository + ImagePolicy + ImageUpdateAutomation for all three APIs
|
- **Image automation:** `flux/image-automation/` — ImageRepository + ImagePolicy + ImageUpdateAutomation for all three APIs
|
||||||
|
|
||||||
## Completed This Cycle (2026-05-27 — Evening Triage)
|
## Completed This Cycle (2026-05-27 — Night Triage)
|
||||||
|
- **#67** — New issue triaged: grafana.leeworks.dev missing from DNS checklist. Confirmed `docs/operator-runbook.md` Phase 4 DNS table already lists 7 subdomains including grafana (added by #62). No code change needed; gated on operator DNS setup. Comment posted.
|
||||||
|
- **#66** — New issue triaged: Flux reconciliation validation checklist. Blocked on operator completing Phase 0 (#47, #2, #3, #4). `kustomize build flux/` = PASS confirmed. Comment posted.
|
||||||
|
- **No open PRs** — nothing to merge or review.
|
||||||
|
- **`kustomize build flux/` = PASS** — no regressions.
|
||||||
|
|
||||||
|
## Completed Previous Cycle (2026-05-27 — Evening Triage)
|
||||||
- **Triage pass** — all 15 open agent-ready issues reviewed; status comments posted on each.
|
- **Triage pass** — all 15 open agent-ready issues reviewed; status comments posted on each.
|
||||||
- **No open PRs** — nothing to merge or review.
|
- **No open PRs** — nothing to merge or review.
|
||||||
- **No new feature work** — all issues remain blocked on operator actions or cross-repo prerequisites.
|
- **No new feature work** — all issues remain blocked on operator actions or cross-repo prerequisites.
|
||||||
|
|||||||
+4
-2
@@ -102,6 +102,7 @@ dig aqi.leeworks.dev +short
|
|||||||
dig docs.leeworks.dev +short
|
dig docs.leeworks.dev +short
|
||||||
dig status.leeworks.dev +short
|
dig status.leeworks.dev +short
|
||||||
dig registry.leeworks.dev +short
|
dig registry.leeworks.dev +short
|
||||||
|
dig grafana.leeworks.dev +short
|
||||||
|
|
||||||
# Check TLS certificates (once services are deployed)
|
# Check TLS certificates (once services are deployed)
|
||||||
curl -v https://zip.leeworks.dev/health 2>&1 | grep -E "SSL|certificate|issuer"
|
curl -v https://zip.leeworks.dev/health 2>&1 | grep -E "SSL|certificate|issuer"
|
||||||
@@ -125,7 +126,7 @@ The following actions require human operator access to the DNS provider:
|
|||||||
|
|
||||||
1. Log into the DNS provider managing `leeworks.dev`
|
1. Log into the DNS provider managing `leeworks.dev`
|
||||||
2. Find the cluster ingress IP: `kubectl get svc -n ingress-nginx ingress-nginx-controller`
|
2. Find the cluster ingress IP: `kubectl get svc -n ingress-nginx ingress-nginx-controller`
|
||||||
3. Create/update the 6 A records listed in the table above
|
3. Create/update the 7 A records listed in the table above
|
||||||
4. Verify propagation: `dig +trace zip.leeworks.dev`
|
4. Verify propagation: `dig +trace zip.leeworks.dev`
|
||||||
|
|
||||||
DNS propagation typically takes 5–60 minutes.
|
DNS propagation typically takes 5–60 minutes.
|
||||||
@@ -141,4 +142,5 @@ DNS propagation typically takes 5–60 minutes.
|
|||||||
- [ ] `docs.leeworks.dev` → DNS record created
|
- [ ] `docs.leeworks.dev` → DNS record created
|
||||||
- [ ] `status.leeworks.dev` → DNS record created
|
- [ ] `status.leeworks.dev` → DNS record created
|
||||||
- [ ] `registry.leeworks.dev` → DNS record created
|
- [ ] `registry.leeworks.dev` → DNS record created
|
||||||
- [ ] TLS certificates issued and valid for all 6 subdomains
|
- [ ] `grafana.leeworks.dev` → DNS record created
|
||||||
|
- [ ] TLS certificates issued and valid for all 7 subdomains
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ kubectl create secret generic gitea-image-automation-token \
|
|||||||
|
|
||||||
## Phase 4 — DNS for API services (issue #33)
|
## Phase 4 — DNS for API services (issue #33)
|
||||||
|
|
||||||
Add DNS A records for all six leeworks.dev subdomains (all point to the same
|
Add DNS A records for all seven leeworks.dev subdomains (all point to the same
|
||||||
cluster ingress IP):
|
cluster ingress IP):
|
||||||
|
|
||||||
| Hostname | Target |
|
| Hostname | Target |
|
||||||
@@ -199,6 +199,16 @@ cluster ingress IP):
|
|||||||
| `docs.leeworks.dev` | `<cluster ingress IP>` |
|
| `docs.leeworks.dev` | `<cluster ingress IP>` |
|
||||||
| `grafana.leeworks.dev` | `<cluster ingress IP>` |
|
| `grafana.leeworks.dev` | `<cluster ingress IP>` |
|
||||||
| `status.leeworks.dev` | `<cluster ingress IP>` |
|
| `status.leeworks.dev` | `<cluster ingress IP>` |
|
||||||
|
| `registry.leeworks.dev` | `<cluster ingress IP>` |
|
||||||
|
|
||||||
|
Verify DNS propagation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for host in zip holidays aqi docs grafana status registry; do
|
||||||
|
echo -n "${host}.leeworks.dev: "
|
||||||
|
dig ${host}.leeworks.dev +short
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
cert-manager will obtain Let's Encrypt certificates automatically once DNS
|
cert-manager will obtain Let's Encrypt certificates automatically once DNS
|
||||||
propagates (typically minutes, up to 48 h).
|
propagates (typically minutes, up to 48 h).
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Follow this list top-to-bottom; each step unblocks the next.
|
|||||||
- [ ] 7. Add api-company Flux source + kustomization to 0xWheatyz/Talos — unblocks all GitOps reconciliation
|
- [ ] 7. Add api-company Flux source + kustomization to 0xWheatyz/Talos — unblocks all GitOps reconciliation
|
||||||
- [ ] 8. `gitea-registry` (zip-enrichment, holidays, air-quality, docs-site) — imagePullSecret for pods pulling from `registry.leeworks.dev`
|
- [ ] 8. `gitea-registry` (zip-enrichment, holidays, air-quality, docs-site) — imagePullSecret for pods pulling from `registry.leeworks.dev`
|
||||||
- [ ] 9. `gitea-image-automation-token` (flux-system) — write-scoped token for Flux ImageUpdateAutomation to push image-tag commits
|
- [ ] 9. `gitea-image-automation-token` (flux-system) — write-scoped token for Flux ImageUpdateAutomation to push image-tag commits
|
||||||
|
- [ ] 10. `rapidapi-proxy-secret` (zip-enrichment, holidays, air-quality) — RapidAPI Proxy Secret for server-side request validation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -155,6 +156,44 @@ Unblocks: Issue #2 (Flux reconciliation of all `flux/` manifests in this repo).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. `rapidapi-proxy-secret` — RapidAPI Proxy Secret (per API namespace)
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| Name | `rapidapi-proxy-secret` |
|
||||||
|
| Namespaces | `zip-enrichment`, `holidays`, `air-quality` |
|
||||||
|
| Purpose | Every API service validates the `X-RapidAPI-Proxy-Secret` header on every route. Requests without a valid secret return HTTP 403. |
|
||||||
|
| Source | RapidAPI dashboard → API Settings → Security → **Proxy Secret** (generated after each API listing is created) |
|
||||||
|
| Unblocks | Phase 3 server middleware; API services will start but reject all traffic without this secret |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for NS in zip-enrichment holidays air-quality; do
|
||||||
|
kubectl create secret generic rapidapi-proxy-secret \
|
||||||
|
--namespace=$NS \
|
||||||
|
--from-literal=X-RapidAPI-Proxy-Secret=<value-from-rapidapi-dashboard>
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source:** RapidAPI dashboard → select your API → Settings → Security → Proxy Secret
|
||||||
|
|
||||||
|
> **Note:** Placeholder `ExternalSecret` manifests are committed at
|
||||||
|
> `flux/zip-enrichment/externalsecret.yaml`, `flux/holidays/externalsecret.yaml`,
|
||||||
|
> and `flux/air-quality/externalsecret.yaml`. These will auto-sync this secret
|
||||||
|
> from the configured backend once the External Secrets Operator (ESO) is
|
||||||
|
> deployed (see issue #61). Until then, create manually using the commands above.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
```bash
|
||||||
|
for NS in zip-enrichment holidays air-quality; do
|
||||||
|
echo -n "$NS: "
|
||||||
|
kubectl get secret rapidapi-proxy-secret -n $NS -o jsonpath='{.data.X-RapidAPI-Proxy-Secret}' | base64 -d | wc -c
|
||||||
|
echo " chars"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Dependency Order
|
## Dependency Order
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -166,6 +205,7 @@ Unblocks: Issue #2 (Flux reconciliation of all `flux/` manifests in this repo).
|
|||||||
5 + 6 (GITEA_TOKEN + registry packages) → CI pushes images → API services deploy
|
5 + 6 (GITEA_TOKEN + registry packages) → CI pushes images → API services deploy
|
||||||
8 (gitea-registry) → pods can pull images from registry.leeworks.dev → services start
|
8 (gitea-registry) → pods can pull images from registry.leeworks.dev → services start
|
||||||
9 (gitea-image-automation-token) → Flux ImageUpdateAutomation pushes tag-update commits
|
9 (gitea-image-automation-token) → Flux ImageUpdateAutomation pushes tag-update commits
|
||||||
|
10 (rapidapi-proxy-secret × 3) → API server middleware validates RapidAPI requests → revenue enabled
|
||||||
```
|
```
|
||||||
|
|
||||||
Once all nine items are complete, the full stack (runner, registry, Prometheus, Grafana, Gatus, docs-site, three API services) reconciles automatically via FluxCD with no further manual steps.
|
Once all nine items are complete, the full stack (runner, registry, Prometheus, Grafana, Gatus, docs-site, three API services) reconciles automatically via FluxCD with no further manual steps.
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
# Placeholder: inject the RapidAPI Proxy Secret here once ESO is deployed.
|
apiVersion: external-secrets.io/v1beta1
|
||||||
# Replace with a real ExternalSecret once leeworks-agents/api-company#2 and
|
kind: ExternalSecret
|
||||||
# the external-secrets operator are running in the cluster.
|
metadata:
|
||||||
#
|
name: rapidapi-proxy-secret
|
||||||
# Example (uncomment and fill in secretStore name):
|
namespace: air-quality
|
||||||
#
|
spec:
|
||||||
# apiVersion: external-secrets.io/v1beta1
|
refreshInterval: 1h
|
||||||
# kind: ExternalSecret
|
secretStoreRef:
|
||||||
# metadata:
|
name: kubernetes-provider
|
||||||
# name: rapidapi-proxy-secret
|
kind: ClusterSecretStore
|
||||||
# namespace: air-quality
|
target:
|
||||||
# spec:
|
name: rapidapi-proxy-secret
|
||||||
# refreshInterval: 1h
|
creationPolicy: Owner
|
||||||
# secretStoreRef:
|
data:
|
||||||
# name: <your-secret-store>
|
- secretKey: X-RapidAPI-Proxy-Secret
|
||||||
# kind: ClusterSecretStore
|
remoteRef:
|
||||||
# target:
|
key: rapidapi-air-quality-source
|
||||||
# name: rapidapi-proxy-secret
|
property: X-RapidAPI-Proxy-Secret
|
||||||
# creationPolicy: Owner
|
|
||||||
# data:
|
|
||||||
# - secretKey: X-RapidAPI-Proxy-Secret
|
|
||||||
# remoteRef:
|
|
||||||
# key: rapidapi/air-quality
|
|
||||||
# property: proxy-secret
|
|
||||||
#
|
|
||||||
# Until then, create manually:
|
|
||||||
# kubectl create secret generic rapidapi-proxy-secret \
|
|
||||||
# --from-literal=X-RapidAPI-Proxy-Secret=<value> \
|
|
||||||
# -n air-quality
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# ClusterSecretStore using the Kubernetes provider.
|
||||||
|
# Reads secrets from the cluster itself — no external vault required.
|
||||||
|
# The service account below must have get/list access to secrets in
|
||||||
|
# the namespaces where ExternalSecrets are created.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: eso-kubernetes-provider
|
||||||
|
namespace: external-secrets
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: eso-kubernetes-provider-reader
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: eso-kubernetes-provider-reader
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: eso-kubernetes-provider-reader
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: eso-kubernetes-provider
|
||||||
|
namespace: external-secrets
|
||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
metadata:
|
||||||
|
name: kubernetes-provider
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
kubernetes:
|
||||||
|
remoteNamespace: external-secrets
|
||||||
|
server:
|
||||||
|
caProvider:
|
||||||
|
type: ConfigMap
|
||||||
|
name: kube-root-ca.crt
|
||||||
|
namespace: external-secrets
|
||||||
|
key: ca.crt
|
||||||
|
auth:
|
||||||
|
serviceAccount:
|
||||||
|
name: eso-kubernetes-provider
|
||||||
|
namespace: external-secrets
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: external-secrets
|
||||||
|
namespace: external-secrets
|
||||||
|
spec:
|
||||||
|
interval: 15m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: external-secrets
|
||||||
|
version: ">=0.9.0 <1.0.0"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: external-secrets
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 60m
|
||||||
|
install:
|
||||||
|
crds: CreateReplace
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
crds: CreateReplace
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
installCRDs: true
|
||||||
|
webhook:
|
||||||
|
port: 9443
|
||||||
|
certController:
|
||||||
|
requeueInterval: 5m
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: HelmRepository
|
||||||
|
metadata:
|
||||||
|
name: external-secrets
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 60m
|
||||||
|
url: https://charts.external-secrets.io
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- helmrepository.yaml
|
||||||
|
- helmrelease.yaml
|
||||||
|
- clustersecretstore.yaml
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: external-secrets
|
||||||
@@ -1,29 +1,18 @@
|
|||||||
# Placeholder: inject the RapidAPI Proxy Secret here once ESO is deployed.
|
apiVersion: external-secrets.io/v1beta1
|
||||||
# Replace with a real ExternalSecret once leeworks-agents/api-company#2 and
|
kind: ExternalSecret
|
||||||
# the external-secrets operator are running in the cluster.
|
metadata:
|
||||||
#
|
name: rapidapi-proxy-secret
|
||||||
# Example (uncomment and fill in secretStore name):
|
namespace: holidays
|
||||||
#
|
spec:
|
||||||
# apiVersion: external-secrets.io/v1beta1
|
refreshInterval: 1h
|
||||||
# kind: ExternalSecret
|
secretStoreRef:
|
||||||
# metadata:
|
name: kubernetes-provider
|
||||||
# name: rapidapi-proxy-secret
|
kind: ClusterSecretStore
|
||||||
# namespace: holidays
|
target:
|
||||||
# spec:
|
name: rapidapi-proxy-secret
|
||||||
# refreshInterval: 1h
|
creationPolicy: Owner
|
||||||
# secretStoreRef:
|
data:
|
||||||
# name: <your-secret-store>
|
- secretKey: X-RapidAPI-Proxy-Secret
|
||||||
# kind: ClusterSecretStore
|
remoteRef:
|
||||||
# target:
|
key: rapidapi-holidays-source
|
||||||
# name: rapidapi-proxy-secret
|
property: X-RapidAPI-Proxy-Secret
|
||||||
# creationPolicy: Owner
|
|
||||||
# data:
|
|
||||||
# - secretKey: X-RapidAPI-Proxy-Secret
|
|
||||||
# remoteRef:
|
|
||||||
# key: rapidapi/holidays
|
|
||||||
# property: proxy-secret
|
|
||||||
#
|
|
||||||
# Until then, create manually:
|
|
||||||
# kubectl create secret generic rapidapi-proxy-secret \
|
|
||||||
# --from-literal=X-RapidAPI-Proxy-Secret=<value> \
|
|
||||||
# -n holidays
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
resources:
|
resources:
|
||||||
|
- external-secrets
|
||||||
- gitea-runner
|
- gitea-runner
|
||||||
- monitoring
|
- monitoring
|
||||||
- docs-site
|
- docs-site
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
# Placeholder: inject the RapidAPI Proxy Secret here once ESO is deployed.
|
apiVersion: external-secrets.io/v1beta1
|
||||||
# Replace with a real ExternalSecret once leeworks-agents/api-company#2 and
|
kind: ExternalSecret
|
||||||
# the external-secrets operator are running in the cluster.
|
metadata:
|
||||||
#
|
name: rapidapi-proxy-secret
|
||||||
# Example (uncomment and fill in secretStore name):
|
namespace: zip-enrichment
|
||||||
#
|
spec:
|
||||||
# apiVersion: external-secrets.io/v1beta1
|
refreshInterval: 1h
|
||||||
# kind: ExternalSecret
|
secretStoreRef:
|
||||||
# metadata:
|
name: kubernetes-provider
|
||||||
# name: rapidapi-proxy-secret
|
kind: ClusterSecretStore
|
||||||
# namespace: zip-enrichment
|
target:
|
||||||
# spec:
|
name: rapidapi-proxy-secret
|
||||||
# refreshInterval: 1h
|
creationPolicy: Owner
|
||||||
# secretStoreRef:
|
data:
|
||||||
# name: <your-secret-store>
|
- secretKey: X-RapidAPI-Proxy-Secret
|
||||||
# kind: ClusterSecretStore
|
remoteRef:
|
||||||
# target:
|
key: rapidapi-zip-enrichment-source
|
||||||
# name: rapidapi-proxy-secret
|
property: X-RapidAPI-Proxy-Secret
|
||||||
# creationPolicy: Owner
|
|
||||||
# data:
|
|
||||||
# - secretKey: X-RapidAPI-Proxy-Secret
|
|
||||||
# remoteRef:
|
|
||||||
# key: rapidapi/zip-enrichment
|
|
||||||
# property: proxy-secret
|
|
||||||
#
|
|
||||||
# Until then, create manually:
|
|
||||||
# kubectl create secret generic rapidapi-proxy-secret \
|
|
||||||
# --from-literal=X-RapidAPI-Proxy-Secret=<value> \
|
|
||||||
# -n zip-enrichment
|
|
||||||
|
|||||||
Reference in New Issue
Block a user