updated Service providers

This commit is contained in:
berkay 2025-04-05 22:54:05 +03:00
parent fa4df11323
commit 60507c87e0
21 changed files with 1134 additions and 183 deletions

View File

@ -0,0 +1,30 @@
ToDo List:
Redis Items:
Internal serves from Class inside API:
- [ ] Save Events activities as [
{"endpoint_code-UUID1": ["event-UUID1", "event-UUID2", "event-UUID3"]},
{"endpoint_code-UUID2": ["event-UUID4", "event-UUID5", "event-UUID6"]}
]
- [ ] Save Application activities as [
{"app1": ["appCode-UUID1", "appCode-UUID2", "appCode-UUID3"]},
{"app2": ["appCode-UUID4", "appCode-UUID5", "appCode-UUID6"]}
]
Serve Only from Redis:
- [] Endpoint: "authentication/page/valid"
send data: {"page": "app1"}
result: {"page": "appCode-UUID4"}
Client side Frontend:
On Redirect [send=> authentication/page/valid]
Retrieve available application UUIDs from API
Redirect to related page with UUID
{
"appCode-UUID1": App1.tsx
}

View File

@ -14,6 +14,7 @@ from ApiServices.AuthService.validations.request.authentication.login_post impor
RequestForgotPasswordPhone, RequestForgotPasswordPhone,
RequestForgotPasswordEmail, RequestForgotPasswordEmail,
RequestVerifyOTP, RequestVerifyOTP,
RequestApplication,
) )
from ApiServices.AuthService.events.auth.auth import AuthHandlers from ApiServices.AuthService.events.auth.auth import AuthHandlers
@ -367,7 +368,45 @@ def authentication_password_verify_otp(
"tz": tz or "GMT+3", "tz": tz or "GMT+3",
"token": token, "token": token,
} }
print('Token&OTP : ', data.otp, data.token) print("Token&OTP : ", data.otp, data.token)
if not domain or not language:
return JSONResponse(
content={"error": "EYS_0003"},
status_code=status.HTTP_406_NOT_ACCEPTABLE,
headers=headers,
)
return JSONResponse(
content={},
status_code=status.HTTP_202_ACCEPTED,
headers=headers,
)
@auth_route.post(
path="/page/valid",
summary="Verify if page is valid returns application avaliable",
description="Verify if page is valid returns application avaliable",
)
def authentication_page_valid(
request: Request,
data: RequestApplication,
language: str = Header(None, alias="language"),
domain: str = Header(None, alias="domain"),
tz: str = Header(None, alias="timezone"),
):
"""
Verify if page is valid returns application that can user reach
page: { url = /building/create}
result: { "application": "4c11f5ef-0bbd-41ac-925e-f79d9aac2b0e" }
"""
token = request.headers.get(api_config.ACCESS_TOKEN_TAG, None)
headers = {
"language": language or "",
"domain": domain or "",
"eys-ext": f"{str(uuid.uuid4())}",
"tz": tz or "GMT+3",
"token": token,
}
if not domain or not language: if not domain or not language:
return JSONResponse( return JSONResponse(
content={"error": "EYS_0003"}, content={"error": "EYS_0003"},

View File

@ -14,6 +14,10 @@ class RequestVerifyOTP(BaseModel):
otp: str otp: str
class RequestApplication(BaseModel):
page: str # /building/create
class RequestSelectOccupant(BaseModel): class RequestSelectOccupant(BaseModel):
company_uu_id: str company_uu_id: str

View File

@ -1,5 +1,7 @@
from fastapi import APIRouter, Request, Response import uuid
from fastapi import APIRouter, Request, Response, Header
from ApiServices.TemplateService.config import api_config
from ApiServices.TemplateService.events.template.event import template_event_cluster from ApiServices.TemplateService.events.template.event import template_event_cluster
test_template_route = APIRouter(prefix="/test", tags=["Test"]) test_template_route = APIRouter(prefix="/test", tags=["Test"])
@ -10,10 +12,25 @@ test_template_route = APIRouter(prefix="/test", tags=["Test"])
description="Test Template Route", description="Test Template Route",
operation_id="bb20c8c6-a289-4cab-9da7-34ca8a36c8e5", operation_id="bb20c8c6-a289-4cab-9da7-34ca8a36c8e5",
) )
def test_template(request: Request, response: Response): def test_template(
request: Request,
response: Response,
language: str = Header(None, alias="language"),
domain: str = Header(None, alias="domain"),
tz: str = Header(None, alias="timezone"),
):
""" """
Test Template Route Test Template Route
""" """
event_code = "bb20c8c6-a289-4cab-9da7-34ca8a36c8e5"
token = request.headers.get(api_config.ACCESS_TOKEN_TAG, None)
headers = {
"language": language or "",
"domain": domain or "",
"eys-ext": f"{str(uuid.uuid4())}",
"tz": tz or "GMT+3",
"token": token,
}
event_cluster_matched = template_event_cluster.match_event( event_cluster_matched = template_event_cluster.match_event(
event_keys=[ event_keys=[
"3f510dcf-9f84-4eb9-b919-f582f30adab1", "3f510dcf-9f84-4eb9-b919-f582f30adab1",

View File

@ -0,0 +1,210 @@
import enum
from typing import Optional, Union, Dict, Any, List
from pydantic import BaseModel
from Controllers.Redis.database import RedisActions
from ..config import api_config
class UserType(enum.Enum):
employee = 1
occupant = 2
class Credentials(BaseModel):
person_id: int
person_name: str
class ApplicationToken(BaseModel):
# Application Token Object -> is the main object for the user
user_type: int = UserType.occupant.value
credential_token: str = ""
user_uu_id: str
user_id: int
person_id: int
person_uu_id: str
request: Optional[dict] = None # Request Info of Client
expires_at: Optional[float] = None # Expiry timestamp
class OccupantToken(BaseModel):
# Selection of the occupant type for a build part is made by the user
living_space_id: int # Internal use
living_space_uu_id: str # Outer use
occupant_type_id: int
occupant_type_uu_id: str
occupant_type: str
build_id: int
build_uuid: str
build_part_id: int
build_part_uuid: str
responsible_company_id: Optional[int] = None
responsible_company_uuid: Optional[str] = None
responsible_employee_id: Optional[int] = None
responsible_employee_uuid: Optional[str] = None
reachable_event_codes: Optional[dict[str, list[str]]] = (
None # ID list of reachable modules
)
reachable_app_codes: Optional[dict[str, list[str]]] = (
None # ID list of reachable modules
)
class CompanyToken(BaseModel):
# Selection of the company for an employee is made by the user
company_id: int
company_uu_id: str
department_id: int # ID list of departments
department_uu_id: str # ID list of departments
duty_id: int
duty_uu_id: str
staff_id: int
staff_uu_id: str
employee_id: int
employee_uu_id: str
bulk_duties_id: int
reachable_event_codes: Optional[dict[str, list[str]]] = (
None # ID list of reachable modules
)
reachable_app_codes: Optional[dict[str, list[str]]] = (
None # ID list of reachable modules
)
class OccupantTokenObject(ApplicationToken):
# Occupant Token Object -> Requires selection of the occupant type for a specific build part
available_occupants: dict = None
selected_occupant: Optional[OccupantToken] = None # Selected Occupant Type
@property
def is_employee(self) -> bool:
return False
@property
def is_occupant(self) -> bool:
return True
class EmployeeTokenObject(ApplicationToken):
# Full hierarchy Employee[staff_id] -> Staff -> Duty -> Department -> Company
companies_id_list: List[int] # List of company objects
companies_uu_id_list: List[str] # List of company objects
duty_id_list: List[int] # List of duty objects
duty_uu_id_list: List[str] # List of duty objects
selected_company: Optional[CompanyToken] = None # Selected Company Object
@property
def is_employee(self) -> bool:
return True
@property
def is_occupant(self) -> bool:
return False
TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject]
class TokenProvider:
AUTH_KEY: str = api_config.ACCESS_TOKEN_TAG
AUTH_TOKEN: str = "AUTH_TOKEN"
@classmethod
def process_redis_object(cls, redis_object: Dict[str, Any]) -> TokenDictType:
"""
Process Redis object and return appropriate token object.
"""
if not redis_object.get("selected_company"):
redis_object["selected_company"] = None
if not redis_object.get("selected_occupant"):
redis_object["selected_occupant"] = None
if redis_object.get("user_type") == UserType.employee.value:
return EmployeeTokenObject(**redis_object)
elif redis_object.get("user_type") == UserType.occupant.value:
return OccupantTokenObject(**redis_object)
raise ValueError("Invalid user type")
@classmethod
def get_dict_from_redis(
cls, token: Optional[str] = None, user_uu_id: Optional[str] = None
) -> Union[TokenDictType, List[TokenDictType]]:
"""
Retrieve token object from Redis using token and user_uu_id
"""
token_to_use, user_uu_id_to_use = token or "*", user_uu_id or "*"
list_of_token_dict, auth_key_list = [], [
cls.AUTH_TOKEN,
token_to_use,
user_uu_id_to_use,
]
if token:
result = RedisActions.get_json(list_keys=auth_key_list, limit=1)
if first_record := result.first:
return cls.process_redis_object(first_record)
elif user_uu_id:
result = RedisActions.get_json(list_keys=auth_key_list)
if all_records := result.all:
for all_record in all_records:
list_of_token_dict.append(cls.process_redis_object(all_record))
return list_of_token_dict
raise ValueError(
"Token not found in Redis. Please check the token or user_uu_id."
)
@classmethod
def retrieve_application_codes(
cls, page_url: str, tokens: Union[TokenDictType, List[TokenDictType]]
):
if isinstance(tokens, TokenDictType):
if application_codes := tokens.reachable_app_codes.get(page_url, None):
return application_codes
elif isinstance(tokens, list):
retrieved_event_apps = []
for token in tokens:
if application_codes := token.reachable_app_codes.get(page_url, None):
retrieved_event_apps.append(application_codes)
return retrieved_event_apps
raise ValueError("Invalid token type or no application codes found.")
@classmethod
def retrieve_event_codes(
cls, endpoint_code: str, tokens: Union[TokenDictType, List[TokenDictType]]
):
if isinstance(tokens, TokenDictType):
if event_codes := tokens.reachable_event_codes.get(endpoint_code, None):
return event_codes
elif isinstance(tokens, List):
retrieved_event_codes = []
for token in tokens:
if isinstance(token, TokenDictType):
if event_codes := token.reachable_event_codes.get(
endpoint_code, None
):
retrieved_event_codes.append(event_codes)
return retrieved_event_codes
raise ValueError("Invalid token type or no event codes found.")

View File

@ -8,9 +8,15 @@
"name": "client-frontend", "name": "client-frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1",
"flatpickr": "^4.6.13",
"next": "15.2.4", "next": "15.2.4",
"next-crypto": "^1.0.8",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0",
"react-hook-form": "^7.55.0",
"sonner": "^2.0.3",
"zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@ -42,6 +48,17 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
"integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
"dependencies": {
"@standard-schema/utils": "^0.3.0"
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
}
},
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5", "version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
@ -509,6 +526,11 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@ -876,6 +898,11 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/flatpickr": {
"version": "4.6.13",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
"integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw=="
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -1195,6 +1222,11 @@
} }
} }
}, },
"node_modules/next-crypto": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/next-crypto/-/next-crypto-1.0.8.tgz",
"integrity": "sha512-6VcrH+xFuuCRGCdDMjFFibhJ97c4s+J/6SEV73RUYJhh38MDW4WXNZNTWIMZBq0B29LOIfAQ0XA37xGUZZCCjA=="
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -1274,6 +1306,21 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.55.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz",
"integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.26.0", "version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@ -1339,6 +1386,15 @@
"is-arrayish": "^0.3.1" "is-arrayish": "^0.3.1"
} }
}, },
"node_modules/sonner": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -1415,6 +1471,14 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true "dev": true
},
"node_modules/zod": {
"version": "3.24.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View File

@ -9,16 +9,22 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1",
"flatpickr": "^4.6.13",
"next": "15.2.4",
"next-crypto": "^1.0.8",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"next": "15.2.4" "react-hook-form": "^7.55.0",
"sonner": "^2.0.3",
"zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@tailwindcss/postcss": "^4", "tailwindcss": "^4",
"tailwindcss": "^4" "typescript": "^5"
} }
} }

View File

@ -0,0 +1,144 @@
"use server";
// import { retrieveAccessToken } from "@/apicalls/cookies/token";
const defaultHeaders = {
accept: "application/json",
language: "tr",
domain: "evyos.com.tr",
tz: "GMT+3",
"Content-type": "application/json",
};
const DefaultResponse = {
error: "Hata tipi belirtilmedi",
status: "500",
data: {},
};
const cacheList = ["no-cache", "no-store", "force-cache", "only-if-cached"];
const prepareResponse = (response: any, statusCode: number) => {
try {
return {
status: statusCode,
data: response || {},
};
} catch (error) {
console.error("Error preparing response:", error);
return {
...DefaultResponse,
error: "Response parsing error",
};
}
};
const fetchData = async (
endpoint: string,
payload: any,
method: string = "POST",
cache: boolean = false
) => {
try {
const headers = {
...defaultHeaders,
};
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
};
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(payload);
}
const response = await fetch(endpoint, fetchOptions);
const responseJson = await response.json();
console.log("Fetching:", endpoint, fetchOptions);
console.log("Response:", responseJson);
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error("Fetch error:", error);
return {
...DefaultResponse,
error: error instanceof Error ? error.message : "Network error",
};
}
};
const updateDataWithToken = async (
endpoint: string,
uuid: string,
payload: any,
method: string = "POST",
cache: boolean = false
) => {
// const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
// "evyos-session-key": accessToken,
};
try {
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
};
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(payload.payload);
}
const response = await fetch(`${endpoint}/${uuid}`, fetchOptions);
const responseJson = await response.json();
console.log("Fetching:", `${endpoint}/${uuid}`, fetchOptions);
console.log("Response:", responseJson);
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error("Update error:", error);
return {
...DefaultResponse,
error: error instanceof Error ? error.message : "Network error",
};
}
};
const fetchDataWithToken = async (
endpoint: string,
payload: any,
method: string = "POST",
cache: boolean = false
) => {
// const accessToken = (await retrieveAccessToken()) || "";
const headers = {
...defaultHeaders,
// "evyos-session-key": accessToken,
};
try {
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
};
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(payload);
}
const response = await fetch(endpoint, fetchOptions);
const responseJson = await response.json();
console.log("Fetching:", endpoint, fetchOptions);
console.log("Response:", responseJson);
return prepareResponse(responseJson, response.status);
} catch (error) {
console.error("Fetch with token error:", error);
return {
...DefaultResponse,
error: error instanceof Error ? error.message : "Network error",
};
}
};
export { fetchData, fetchDataWithToken, updateDataWithToken };

View File

@ -0,0 +1,75 @@
const formatServiceUrl = (url: string) => {
if (!url) return "";
return url.startsWith("http") ? url : `http://${url}`;
};
export const baseUrlAuth = formatServiceUrl(
process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
);
// export const baseUrlValidation = formatServiceUrl(
// process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "validationservice:8888"
// );
// export const baseUrlEvent = formatServiceUrl(
// process.env.NEXT_PUBLIC_EVENT_SERVICE_URL || "eventservice:8888"
// );
export const tokenSecret = process.env.TOKENSECRET_90 || "";
export const cookieObject: any = {
httpOnly: true,
path: "/",
sameSite: "none",
secure: true,
maxAge: 3600,
priority: "high",
};
interface FilterListInterface {
page?: number | null | undefined;
size?: number | null | undefined;
orderField?: string | null | undefined;
orderType?: string | null | undefined;
includeJoins?: any[] | null | undefined;
query?: any | null | undefined;
}
class FilterList {
page: number;
size: number;
orderField: string;
orderType: string;
includeJoins: any[];
query: any;
constructor({
page = 1,
size = 5,
orderField = "id",
orderType = "asc",
includeJoins = [],
query = {},
}: FilterListInterface = {}) {
this.page = page ?? 1;
this.size = size ?? 5;
this.orderField = orderField ?? "uu_id";
this.orderType = orderType ?? "asc";
this.orderType = this.orderType.startsWith("a") ? "asc" : "desc";
this.includeJoins = includeJoins ?? [];
this.query = query ?? {};
}
filter() {
return {
page: this.page,
size: this.size,
orderField: this.orderField,
orderType: this.orderType,
includeJoins: this.includeJoins,
query: this.query,
};
}
}
const defaultFilterList = new FilterList({});
export { FilterList, defaultFilterList };
export type { FilterListInterface };

View File

@ -0,0 +1,172 @@
"use server";
import NextCrypto from "next-crypto";
import { fetchData, fetchDataWithToken } from "../api-fetcher";
import { baseUrlAuth, cookieObject, tokenSecret } from "../basics";
import { cookies } from "next/headers";
// import { setAvailableEvents } from "../events/available";
const loginEndpoint = `${baseUrlAuth}/authentication/login`;
const loginSelectEndpoint = `${baseUrlAuth}/authentication/select`;
console.log("loginEndpoint", loginEndpoint);
console.log("loginSelectEndpoint", loginSelectEndpoint);
interface LoginViaAccessKeys {
accessKey: string;
password: string;
rememberMe: boolean;
}
interface LoginSelectEmployee {
company_uu_id: string;
}
interface LoginSelectOccupant {
selectedBuilding: any;
build_part_uu_id: string;
occupant_uu_id: string;
}
async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
try {
const cookieStore = await cookies();
const nextCrypto = new NextCrypto(tokenSecret);
const response = await fetchData(
loginEndpoint,
{
access_key: payload.accessKey,
password: payload.password,
remember_me: payload.rememberMe,
},
"POST",
false
);
console.log("response", response);
if (response.status === 200 || response.status === 202) {
const loginRespone = response?.data;
const accessToken = await nextCrypto.encrypt(loginRespone.access_token);
const accessObject = await nextCrypto.encrypt(
JSON.stringify(loginRespone.selection_list)
);
const userProfile = await nextCrypto.encrypt(
JSON.stringify(loginRespone.user)
);
cookieStore.set({
name: "accessToken",
value: accessToken,
...cookieObject,
});
cookieStore.set({
name: "accessObject",
value: accessObject,
...cookieObject,
});
cookieStore.set({
name: "userProfile",
value: JSON.stringify(userProfile),
...cookieObject,
});
try {
return {
completed: true,
message: "Login successful",
status: 200,
data: loginRespone,
};
} catch (error) {
console.error("JSON parse error:", error);
return {
completed: false,
message: "Login NOT successful",
status: 401,
data: "{}",
};
}
}
return {
completed: false,
// error: response.error || "Login failed",
// message: response.message || "Authentication failed",
status: response.status || 500,
};
} catch (error) {
console.error("Login error:", error);
return {
completed: false,
// error: error instanceof Error ? error.message : "Login error",
// message: "An error occurred during login",
status: 500,
};
}
}
async function loginSelectEmployee(payload: LoginSelectEmployee) {
const cookieStore = await cookies();
const nextCrypto = new NextCrypto(tokenSecret);
const selectResponse: any = await fetchDataWithToken(
loginSelectEndpoint,
{
company_uu_id: payload.company_uu_id,
},
"POST",
false
);
if (selectResponse.status === 200) {
const usersSelection = await nextCrypto.encrypt(
JSON.stringify({
company_uu_id: payload.company_uu_id,
user_type: "employee",
})
);
cookieStore.set({
name: "userSelection",
value: usersSelection,
...cookieObject,
});
}
return selectResponse;
}
async function loginSelectOccupant(payload: LoginSelectOccupant) {
const selectedBuilding = payload.selectedBuilding;
const cookieStore = await cookies();
const nextCrypto = new NextCrypto(tokenSecret);
const selectResponse: any = await fetchDataWithToken(
loginSelectEndpoint,
{
build_part_uu_id: payload.build_part_uu_id,
occupant_uu_id: payload.occupant_uu_id,
},
"POST",
false
);
if (selectResponse.status === 200) {
const usersSelection = await nextCrypto.encrypt(
JSON.stringify({
company_uu_id: {
build_part_uu_id: payload.build_part_uu_id,
occupant_uu_id: payload.occupant_uu_id,
build_id: selectedBuilding,
},
user_type: "occupant",
})
);
cookieStore.set({
name: "userSelection",
value: usersSelection,
...cookieObject,
});
// await setAvailableEvents();
}
return selectResponse;
}
export { loginViaAccessKeys, loginSelectEmployee, loginSelectOccupant };

View File

@ -0,0 +1,16 @@
import React from "react";
import Login from "@/components/auth/login";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "WAG Login",
description: "Login to WAG system",
};
export default function LoginPage() {
return (
<div className="min-h-screen bg-gray-100">
<Login />
</div>
);
}

View File

@ -0,0 +1,7 @@
import React from "react";
function SelectPage() {
return <div>SelectPage</div>;
}
export default SelectPage;

View File

@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Suspense } from "react";
export const metadata: Metadata = {
title: "WAG Frontend",
description: "WAG Frontend Application",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<div className="min-h-screen flex">
<div className="w-1/3 bg-purple-600 p-8 text-white">
<h1 className="text-4xl font-bold mb-4">Welcome to evyos</h1>
<p className="text-lg">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
</p>
</div>
<div className="w-2/3">
<Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
</div>
</div>
</body>
</html>
);
}

View File

@ -1,17 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Create Next App",
description: "Generated by create next app", description: "Generated by create next app",
@ -24,11 +13,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body <body>{children}</body>
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html> </html>
); );
} }

View File

@ -1,103 +1,23 @@
import Image from "next/image"; export default async function Home() {
const result = await fetch("http://auth_service:8001/authentication/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
language: "tr",
domain: "evyos.com.tr",
tz: "GMT+3",
},
body: JSON.stringify({
access_key: "karatay.berkay.sup@evyos.com.tr",
password: "string",
remember_me: true,
}),
});
const data = await result.json();
export default function Home() {
return ( return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start"> {JSON.stringify({ data: data?.data })}
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div> </div>
); );
} }

View File

@ -0,0 +1,147 @@
"use client";
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { loginViaAccessKeys } from "@/apicalls/login/login";
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(5, "Password must be at least 5 characters"),
});
type LoginFormData = z.infer<typeof loginSchema>;
function Login() {
// Open transition for form login
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [jsonText, setJsonText] = useState<string | null>(null);
const Router = useRouter();
const {
register,
formState: { errors },
handleSubmit,
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginFormData) => {
try {
startTransition(() => {
try {
loginViaAccessKeys({
accessKey: data.email,
password: data.password,
rememberMe: false,
})
.then((result: any) => {
const dataResponse = result?.data;
if (dataResponse?.access_token) {
setJsonText(JSON.stringify(dataResponse));
setTimeout(() => {
Router.push("/auth/select");
}, 2000);
}
return dataResponse;
})
.catch(() => {});
} catch (error) {}
});
} catch (error) {
console.error("Login error:", error);
setError("An error occurred during login");
}
};
return (
<>
<div className="flex h-full min-h-[inherit] flex-col items-center justify-center gap-4">
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
<h2 className="mb-6 text-center text-2xl font-bold text-gray-900">
Login
</h2>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email
</label>
<input
{...register("email")}
type="email"
id="email"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">
{errors.email.message}
</p>
)}
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Password
</label>
<input
{...register("password")}
type="password"
id="password"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500"
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">
{errors.password.message}
</p>
)}
</div>
{error && (
<div className="rounded-md bg-red-50 p-4">
<p className="text-sm text-red-700">{error}</p>
</div>
)}
<button
type="submit"
disabled={isPending}
className="w-full rounded-md bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50"
>
{isPending ? "Logging in..." : "Login"}
</button>
</form>
</div>
{jsonText && (
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
<h2 className="mb-4 text-center text-xl font-bold text-gray-900">
Response Data
</h2>
<div className="space-y-2">
{Object.entries(JSON.parse(jsonText)).map(([key, value]) => (
<div key={key} className="flex items-start gap-2">
<strong className="text-gray-700">{key}:</strong>
<span className="text-gray-600">
{typeof value === "object"
? JSON.stringify(value)
: value?.toString() || "N/A"}
</span>
</div>
))}
</div>
</div>
)}
</div>
</>
);
}
export default Login;

View File

@ -1,5 +1,4 @@
services: services:
mongo_service: mongo_service:
container_name: mongo_service container_name: mongo_service
image: "bitnami/mongodb:latest" image: "bitnami/mongodb:latest"
@ -59,70 +58,66 @@ services:
- wag-services - wag-services
ports: ports:
- "3000:3000" - "3000:3000"
volumes: # volumes:
- .WebServices/client-frontend:WebServices/client-frontend # - client-frontend:/WebServices/client-frontend
- WebServices/client-frontend/node_modules
- WebServices/client-frontend/.next
environment: environment:
- NODE_ENV=development - NODE_ENV=development
management_frontend: # management_frontend:
container_name: management_frontend # container_name: management_frontend
build: # build:
context: . # context: .
dockerfile: WebServices/management-frontend/Dockerfile # dockerfile: WebServices/management-frontend/Dockerfile
networks: # networks:
- wag-services # - wag-services
ports: # ports:
- "3001:3000" # Using different host port to avoid conflicts # - "3001:3000" # Using different host port to avoid conflicts
volumes: # # volumes:
- .WebServices/management-frontend:WebServices/management-frontend # # - management-frontend:/WebServices/management-frontend
- WebServices/management-frontend/node_modules # environment:
- WebServices/management-frontend/.next # - NODE_ENV=development
environment:
- NODE_ENV=development
# initializer_service: # initializer_service:
# container_name: initializer_service # container_name: initializer_service
# build: # build:
# context: . # context: .
# dockerfile: ApiServices/InitialService/Dockerfile # dockerfile: ApiServices/InitialService/Dockerfile
# networks: # networks:
# - wag-services # - wag-services
# env_file: # env_file:
# - api_env.env # - api_env.env
# depends_on: # depends_on:
# - postgres-service # - postgres-service
# - mongo_service # - mongo_service
# - redis_service # - redis_service
# template_service: # template_service:
# container_name: template_service # container_name: template_service
# build: # build:
# context: . # context: .
# dockerfile: ApiServices/TemplateService/Dockerfile # dockerfile: ApiServices/TemplateService/Dockerfile
# networks: # networks:
# - wag-services # - wag-services
# env_file: # env_file:
# - api_env.env # - api_env.env
# environment: # environment:
# - API_PATH=app:app # - API_PATH=app:app
# - API_HOST=0.0.0.0 # - API_HOST=0.0.0.0
# - API_PORT=8000 # - API_PORT=8000
# - API_LOG_LEVEL=info # - API_LOG_LEVEL=info
# - API_RELOAD=1 # - API_RELOAD=1
# - API_ACCESS_TOKEN_TAG=1 # - API_ACCESS_TOKEN_TAG=1
# - API_APP_NAME=evyos-template-api-gateway # - API_APP_NAME=evyos-template-api-gateway
# - API_TITLE=WAG API Template Api Gateway # - API_TITLE=WAG API Template Api Gateway
# - API_FORGOT_LINK=https://template_service/forgot-password # - API_FORGOT_LINK=https://template_service/forgot-password
# - API_DESCRIPTION=This api is serves as web template api gateway only to evyos web services. # - API_DESCRIPTION=This api is serves as web template api gateway only to evyos web services.
# - API_APP_URL=https://template_service # - API_APP_URL=https://template_service
# ports: # ports:
# - "8000:8000" # - "8000:8000"
# depends_on: # depends_on:
# - postgres-service # - postgres-service
# - mongo_service # - mongo_service
# - redis_service # - redis_service
auth_service: auth_service:
container_name: auth_service container_name: auth_service
@ -161,10 +156,11 @@ services:
# networks: # networks:
# - wag-services # - wag-services
networks: networks:
wag-services: wag-services:
volumes: volumes:
postgres-data: postgres-data:
mongodb-data: mongodb-data:
client-frontend:
management-frontend:

75
package-lock.json generated Normal file
View File

@ -0,0 +1,75 @@
{
"name": "prod-wag-backend-automate-services",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"flatpickr": "^4.6.13",
"next-crypto": "^1.0.8",
"react-hook-form": "^7.55.0",
"sonner": "^2.0.3",
"zod": "^3.24.2"
}
},
"node_modules/@hookform/resolvers": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
"integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
"dependencies": {
"@standard-schema/utils": "^0.3.0"
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
}
},
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
},
"node_modules/flatpickr": {
"version": "4.6.13",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
"integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw=="
},
"node_modules/next-crypto": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/next-crypto/-/next-crypto-1.0.8.tgz",
"integrity": "sha512-6VcrH+xFuuCRGCdDMjFFibhJ97c4s+J/6SEV73RUYJhh38MDW4WXNZNTWIMZBq0B29LOIfAQ0XA37xGUZZCCjA=="
},
"node_modules/react-hook-form": {
"version": "7.55.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz",
"integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/sonner": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/zod": {
"version": "3.24.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"flatpickr": "^4.6.13",
"next-crypto": "^1.0.8",
"react-hook-form": "^7.55.0",
"sonner": "^2.0.3",
"zod": "^3.24.2"
}
}