Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 10 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,23 @@
# User service
## user-service
- **Purpose:** User registration/login and JWT issuance.
- **Base path:** `/api/users`

## Create users database + user on mysql server
### Create database
```
kubectl -n cloudshopt exec -it cloudshopt-mysql-0 -- bash
```

```
# mysql -u root -prootpass

CREATE DATABASE cloudshopt_users CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'users'@'%' IDENTIFIED BY 'userspass';
CREATE USER 'users'@'%' IDENTIFIED BY 'CHANGE_ME_PASSWORD';
GRANT ALL PRIVILEGES ON cloudshopt_users.* TO 'users'@'%';
FLUSH PRIVILEGES;
```

Ustvari še bazo za *dev* okolje
```
CREATE DATABASE cloudshopt_users_dev CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'users_dev'@'%' IDENTIFIED BY 'userspass';
GRANT ALL PRIVILEGES ON cloudshopt_users_dev.* TO 'users_dev'@'%';
FLUSH PRIVILEGES;
```

## Crete external secrets for prod and dev
prod:
```
kubectl -n cloudshopt create secret generic user-service-secrets \
--from-literal=DB_PASSWORD="userspass" \
--from-literal=REDIS_PASSWORD="redispass" \
--dry-run=client -o yaml | kubectl apply -f -
```

dev:
```
kubectl -n cloudshopt-dev create secret generic user-service-secrets \
--from-literal=DB_PASSWORD="userspass" \
--from-literal=REDIS_PASSWORD="redispass" \
--dry-run=client -o yaml | kubectl apply -f -
```

check for secrets:
```
kubectl get secret -n cloudshopt user-service-secrets
kubectl get secret -n cloudshopt-dev user-service-secrets
```
### Migrations

## Install user-service for prod and dev
prod:
```
helm upgrade --install user-service ./helm/user-service \
-n cloudshopt \
-f helm/user-service/values.yaml
```

dev:
```
helm upgrade --install user-service-dev ./helm/user-service \
-n cloudshopt-dev \
-f helm/user-service/values-dev.yaml
```



## Migrations

run migrations:
```
kubectl exec -n cloudshopt-dev -it deploy/user-service-dev -c app -- sh

kubectl exec -n cloudshopt -it deploy/user-service -c app -- sh
# php artisan migrate
```

244 changes: 244 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
openapi: 3.0.3
info:
title: CloudShopt User Service API
version: 1.0.0
tags:
- name: Health
- name: Diagnostics
- name: Auth

paths:
/healthz:
get:
tags: [Health]
summary: Health check
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/StatusOkBool"

/info:
get:
tags: [Diagnostics]
summary: Service info (sha, time)
responses:
"200":
description: Info
content:
application/json:
schema:
$ref: "#/components/schemas/InfoResponse"

/database:
get:
tags: [Diagnostics]
summary: Database connectivity check
responses:
"200":
description: DB OK
content:
application/json:
schema:
$ref: "#/components/schemas/DatabaseOkResponse"
"500":
description: DB connection failed
content:
application/json:
schema:
$ref: "#/components/schemas/DatabaseFailResponse"

/auth/register:
post:
tags: [Auth]
summary: Register user
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RegisterRequest"
responses:
"201":
description: Registered
content:
application/json:
schema:
$ref: "#/components/schemas/AuthResponse"
"422":
$ref: "#/components/responses/ValidationError"

/auth/login:
post:
tags: [Auth]
summary: Login user
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LoginRequest"
responses:
"200":
description: Logged in
content:
application/json:
schema:
$ref: "#/components/schemas/AuthResponse"
"422":
$ref: "#/components/responses/ValidationError"

/me:
get:
tags: [Auth]
summary: Get current user (JWT)
security:
- bearerAuth: []
responses:
"200":
description: User
content:
application/json:
schema:
$ref: "#/components/schemas/MeResponse"
"401":
$ref: "#/components/responses/Unauthorized"

components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT

responses:
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorMessage"
examples:
missingToken:
value: { message: "Missing Bearer token" }
invalid:
value: { message: "Invalid or expired token" }

ValidationError:
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ValidationError"

schemas:
ErrorMessage:
type: object
required: [message]
properties:
message: { type: string }

ValidationError:
type: object
required: [message, errors]
properties:
message: { type: string }
errors:
type: object
additionalProperties:
type: array
items: { type: string }

StatusOkBool:
type: object
required: [ok]
properties:
ok:
type: boolean
example: true

InfoResponse:
type: object
required: [ok, service, sha, time]
properties:
ok: { type: boolean, example: true }
service: { type: string, example: user-service }
sha:
type: string
nullable: true
example: 0.0.1
time:
type: string
format: date-time
example: "2026-01-29T10:15:30.123Z"

DatabaseOkResponse:
type: object
required: [ok, db, time]
properties:
ok: { type: boolean, example: true }
db:
type: object
required: [connection, database, ping_ms]
properties:
connection: { type: string, example: mysql }
database: { type: string, example: cloudshopt_users_dev }
ping_ms: { type: number, format: float, example: 1.25 }
time:
type: string
format: date-time
example: "2026-01-29T10:15:30.123Z"

DatabaseFailResponse:
type: object
required: [ok, error, message]
properties:
ok: { type: boolean, example: false }
error: { type: string, example: DB connection failed }
message:
type: string
nullable: true
example: "SQLSTATE[HY000] ..."

User:
type: object
required: [id, name, email]
properties:
id: { type: integer, example: 2 }
name: { type: string, example: Timotej }
email: { type: string, format: email, example: timotej@test.com }

RegisterRequest:
type: object
required: [name, email, password, password_confirmation]
properties:
name: { type: string }
email: { type: string, format: email }
password: { type: string, format: password }
password_confirmation: { type: string, format: password }

LoginRequest:
type: object
required: [email, password]
properties:
email: { type: string, format: email }
password: { type: string, format: password }

AuthResponse:
type: object
required: [token, token_type, expires_in, user]
properties:
token: { type: string }
token_type: { type: string, example: Bearer }
expires_in: { type: integer, example: 3600 }
user:
$ref: "#/components/schemas/User"

MeResponse:
type: object
required: [user]
properties:
user:
$ref: "#/components/schemas/User"
22 changes: 22 additions & 0 deletions resources/views/swagger.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>API Docs</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>

<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>
window.onload = () => {
SwaggerUIBundle({
url: "/api/users/openapi.yaml",
dom_id: "#swagger-ui",
});
};
</script>
</body>
</html>
17 changes: 16 additions & 1 deletion routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;

Route::get('/openapi.yaml', function () {
$path = base_path('docs/openapi.yaml');

abort_unless(file_exists($path), 404, 'openapi.yaml not found');

return response()->file($path, [
'Content-Type' => 'application/yaml; charset=utf-8',
'Cache-Control' => 'no-store',
]);
});

Route::get('/docs', function () {
return response()->view('swagger');
});

Route::get('/info', function () {
return response()->json([
'ok' => true,
Expand Down Expand Up @@ -42,4 +57,4 @@
Route::post('/login', [AuthController::class, 'login']);
});

Route::middleware('jwt')->get('/me', [AuthController::class, 'me']);
Route::middleware('jwt')->get('/me', [AuthController::class, 'me']);