talentro-commons 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- talentro_commons-0.1.1/PKG-INFO +33 -0
- talentro_commons-0.1.1/README.md +17 -0
- talentro_commons-0.1.1/pyproject.toml +26 -0
- talentro_commons-0.1.1/src/talentro/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/billing/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/billing/dataclasses.py +0 -0
- talentro_commons-0.1.1/src/talentro/billing/models.py +26 -0
- talentro_commons-0.1.1/src/talentro/campaigns/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/campaigns/dataclasses.py +22 -0
- talentro_commons-0.1.1/src/talentro/campaigns/models.py +80 -0
- talentro_commons-0.1.1/src/talentro/candidates/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/candidates/dataclasses.py +37 -0
- talentro_commons-0.1.1/src/talentro/constants.py +28 -0
- talentro_commons-0.1.1/src/talentro/event.py +35 -0
- talentro_commons-0.1.1/src/talentro/exceptions.py +45 -0
- talentro_commons-0.1.1/src/talentro/general/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/general/dataclasses.py +0 -0
- talentro_commons-0.1.1/src/talentro/general/models.py +64 -0
- talentro_commons-0.1.1/src/talentro/iam/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/iam/dataclasses.py +0 -0
- talentro_commons-0.1.1/src/talentro/iam/models.py +19 -0
- talentro_commons-0.1.1/src/talentro/iam/types.py +12 -0
- talentro_commons-0.1.1/src/talentro/integrations/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/integrations/dataclasses.py +34 -0
- talentro_commons-0.1.1/src/talentro/integrations/models.py +25 -0
- talentro_commons-0.1.1/src/talentro/util/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/util/enum.py +6 -0
- talentro_commons-0.1.1/src/talentro/vacancies/__init__.py +0 -0
- talentro_commons-0.1.1/src/talentro/vacancies/dataclasses.py +172 -0
- talentro_commons-0.1.1/src/talentro/vacancies/models.py +81 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: talentro-commons
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: This package contains all globally used code, services, models and data structures for Talentro
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Author: Emiel van Essen
|
|
7
|
+
Author-email: emiel@marksmen.nl
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: fastapi (>=0.116.0,<0.117.0)
|
|
13
|
+
Requires-Dist: sqlalchemy (>=2.0.38,<3.0.0)
|
|
14
|
+
Requires-Dist: sqlmodel (>=0.0.24,<0.0.25)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Talentro definitions
|
|
18
|
+
|
|
19
|
+
This package contains all models and data structures for Talentro
|
|
20
|
+
It is exclusively meant for the Talentro ecosystem.
|
|
21
|
+
|
|
22
|
+
## Initial run
|
|
23
|
+
|
|
24
|
+
- Run `poetry self update && poetry self add keyrings.google-artifactregistry-auth`
|
|
25
|
+
- Run `poetry config repositories.gcp https://europe-west4-python.pkg.dev/talentro-459113/talentro-python`
|
|
26
|
+
|
|
27
|
+
## How to create a new version
|
|
28
|
+
|
|
29
|
+
- Make changes in the code, like editing the models
|
|
30
|
+
- Bump the version number to desired version in `pyproject.toml` using the `major.minor.fix` format
|
|
31
|
+
- run `poetry publish --build --repository gcp`
|
|
32
|
+
|
|
33
|
+
Now a new version is uploaded to pypi and you can install it after a minute in the other projects.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Talentro definitions
|
|
2
|
+
|
|
3
|
+
This package contains all models and data structures for Talentro
|
|
4
|
+
It is exclusively meant for the Talentro ecosystem.
|
|
5
|
+
|
|
6
|
+
## Initial run
|
|
7
|
+
|
|
8
|
+
- Run `poetry self update && poetry self add keyrings.google-artifactregistry-auth`
|
|
9
|
+
- Run `poetry config repositories.gcp https://europe-west4-python.pkg.dev/talentro-459113/talentro-python`
|
|
10
|
+
|
|
11
|
+
## How to create a new version
|
|
12
|
+
|
|
13
|
+
- Make changes in the code, like editing the models
|
|
14
|
+
- Bump the version number to desired version in `pyproject.toml` using the `major.minor.fix` format
|
|
15
|
+
- run `poetry publish --build --repository gcp`
|
|
16
|
+
|
|
17
|
+
Now a new version is uploaded to pypi and you can install it after a minute in the other projects.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "talentro-commons"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "This package contains all globally used code, services, models and data structures for Talentro"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Emiel van Essen", email = "emiel@marksmen.nl"}
|
|
7
|
+
]
|
|
8
|
+
license = { text = "Proprietary" }
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"sqlalchemy (>=2.0.38,<3.0.0)",
|
|
13
|
+
"sqlmodel (>=0.0.24,<0.0.25)",
|
|
14
|
+
"fastapi (>=0.116.0,<0.117.0)"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
[tool.poetry]
|
|
19
|
+
packages = [
|
|
20
|
+
{include = "talentro", from = "src"}
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
26
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from sqlmodel import SQLModel, Field
|
|
7
|
+
|
|
8
|
+
from ..general.models import BillingOrganizationModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BillingEvent(SQLModel, table=True):
|
|
12
|
+
id: int = Field(
|
|
13
|
+
primary_key=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
organization: UUID = Field(index=True)
|
|
17
|
+
event_time: Optional[datetime] = Field(
|
|
18
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
19
|
+
nullable=False,
|
|
20
|
+
)
|
|
21
|
+
sku: str = Field(nullable=False)
|
|
22
|
+
count: int = Field(nullable=False, default=1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BillingProfile(BillingOrganizationModel, table=True):
|
|
26
|
+
stripe_customer_id: str = Field(nullable=False)
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from ..campaigns.models import ChannelType, CampaignGoal
|
|
6
|
+
from ..general.models import BaseModel
|
|
7
|
+
from ..integrations.dataclasses import Integration
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Campaign(BaseModel):
|
|
11
|
+
id: UUID
|
|
12
|
+
created_at: datetime
|
|
13
|
+
updated_at: Optional[datetime]
|
|
14
|
+
organization: UUID
|
|
15
|
+
name: str
|
|
16
|
+
external_id: Optional[str]
|
|
17
|
+
status: str
|
|
18
|
+
last_sync_date: datetime
|
|
19
|
+
ad_count: int
|
|
20
|
+
channel: Integration
|
|
21
|
+
channel_type: ChannelType
|
|
22
|
+
campaign_goal: Optional[CampaignGoal]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
import enum
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Column, JSON, Enum
|
|
9
|
+
from sqlmodel import Field
|
|
10
|
+
|
|
11
|
+
from ..general.models import CampaignsOrganizationModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ChannelType(StrEnum):
|
|
15
|
+
JOB_BOARD = 'job-board'
|
|
16
|
+
SOCIAL = 'social'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CampaignGoal(enum.Enum):
|
|
20
|
+
REACH = 'reach'
|
|
21
|
+
TRAFFIC = 'traffic'
|
|
22
|
+
CONVERSION = 'conversion'
|
|
23
|
+
LEADS = 'leads'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Campaign(CampaignsOrganizationModel, table=True):
|
|
27
|
+
name: str = Field(index=True)
|
|
28
|
+
external_id: Optional[str] = Field(index=True)
|
|
29
|
+
status: str = Field(index=True)
|
|
30
|
+
last_sync_date: Optional[datetime] = Field()
|
|
31
|
+
ad_count: int = Field(default=0)
|
|
32
|
+
channel_id: UUID = Field(index=True)
|
|
33
|
+
channel_type: ChannelType = Field(sa_column=Column(Enum(ChannelType)))
|
|
34
|
+
campaign_goal: Optional[CampaignGoal] = Field(sa_column=Column(Enum(CampaignGoal)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class VacancySelection(CampaignsOrganizationModel):
|
|
38
|
+
campaign_id: UUID = Field(foreign_key="campaign.id")
|
|
39
|
+
feed_id: UUID = Field(index=True)
|
|
40
|
+
selection_criteria: dict = Field(sa_column=Column(JSON))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AdSet(CampaignsOrganizationModel, table=True):
|
|
44
|
+
name: str = Field(index=True)
|
|
45
|
+
external_id: Optional[str] = Field(index=True)
|
|
46
|
+
campaign_id: UUID = Field(foreign_key="campaign.id")
|
|
47
|
+
platforms: list = Field(sa_column=Column(JSON))
|
|
48
|
+
ad_types: list = Field(sa_column=Column(JSON))
|
|
49
|
+
settings: dict = Field(sa_column=Column(JSON))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TargetLocation(CampaignsOrganizationModel, table=True):
|
|
53
|
+
ad_set: UUID = Field(foreign_key="adset.id")
|
|
54
|
+
address: str = Field(index=True)
|
|
55
|
+
distance: int = Field(index=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TargetAudience(CampaignsOrganizationModel, table=True):
|
|
59
|
+
ad_set: UUID = Field(foreign_key="adset.id")
|
|
60
|
+
age_min: int = Field(index=True, default=18)
|
|
61
|
+
age_max: int = Field(index=True, default=150)
|
|
62
|
+
interests: list = Field(sa_column=Column(JSON))
|
|
63
|
+
languages: list = Field(sa_column=Column(JSON))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Ad(CampaignsOrganizationModel, table=True):
|
|
67
|
+
name: str = Field(index=True)
|
|
68
|
+
external_id: Optional[str] = Field(index=True)
|
|
69
|
+
campaign_id: UUID = Field(foreign_key="campaign.id")
|
|
70
|
+
ad_set_id: Optional[UUID] = Field(foreign_key="adset.id")
|
|
71
|
+
vacancy_id: Optional[UUID] = Field()
|
|
72
|
+
lead_form: Optional[UUID] = Field(foreign_key="leadform.id")
|
|
73
|
+
primary_text: str = Field()
|
|
74
|
+
title: str = Field()
|
|
75
|
+
description: Optional[str] = Field()
|
|
76
|
+
conversion_goal: Optional[str] = Field()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LeadForm(CampaignsOrganizationModel, table=True):
|
|
80
|
+
title: str = Field()
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CandidateData(BaseModel):
|
|
9
|
+
email: str
|
|
10
|
+
first_name: str
|
|
11
|
+
last_name: str
|
|
12
|
+
phone_number: str
|
|
13
|
+
hashed_email: str
|
|
14
|
+
cv: str
|
|
15
|
+
motivation_letter: str
|
|
16
|
+
linked_in: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Candidate(CandidateData):
|
|
20
|
+
id: UUID
|
|
21
|
+
created_at: datetime
|
|
22
|
+
updated_at: Optional[datetime]
|
|
23
|
+
organization: UUID
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ApplicationData(BaseModel):
|
|
27
|
+
status: str
|
|
28
|
+
source: str
|
|
29
|
+
candidate_id: str
|
|
30
|
+
vacancy_id: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Application(ApplicationData):
|
|
34
|
+
id: UUID
|
|
35
|
+
created_at: datetime
|
|
36
|
+
updated_at: Optional[datetime]
|
|
37
|
+
organization: UUID
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ErrorCode(StrEnum):
|
|
5
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
|
6
|
+
NOT_AUTHENTICATED = "NOT_AUTHENTICATED"
|
|
7
|
+
NOT_FOUND = "NOT_FOUND"
|
|
8
|
+
BAD_REQUEST = "BAD_REQUEST"
|
|
9
|
+
ARGUMENT_ERROR = "ARGUMENT_ERROR"
|
|
10
|
+
NO_MEMBERSHIP = "NO_MEMBERSHIP"
|
|
11
|
+
PERMISSION_DENIED = "PERMISSION_DENIED"
|
|
12
|
+
ALREADY_EXISTS = "ALREADY_EXISTS"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DisplayMessage(StrEnum):
|
|
16
|
+
UNKNOWN_ERROR = "errors.unknownError"
|
|
17
|
+
NOT_AUTHENTICATED = "errors.notAuthenticated"
|
|
18
|
+
NOT_FOUND = "errors.notFound"
|
|
19
|
+
BAD_REQUEST = "errors.badRequest"
|
|
20
|
+
ARGUMENT_ERROR = "errors.argumentError"
|
|
21
|
+
PERMISSION_DENIED = "errors.noPermission"
|
|
22
|
+
NO_MEMBERSHIP = "errors.noMembership"
|
|
23
|
+
ALREADY_EXISTS = "errors.alreadyExists"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SKU(StrEnum):
|
|
27
|
+
SYNCED_VACANCY = 'synced_vacancy'
|
|
28
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Queue(StrEnum):
|
|
8
|
+
integrations = "integrations"
|
|
9
|
+
candidates = "candidates"
|
|
10
|
+
vacancies = "vacancies"
|
|
11
|
+
strategy = "strategy"
|
|
12
|
+
campaigns = "campaigns"
|
|
13
|
+
insights = "insights"
|
|
14
|
+
billing = "billing"
|
|
15
|
+
iam = "iam"
|
|
16
|
+
notifications = "notifications"
|
|
17
|
+
live_updates = "live-updates"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Event:
|
|
22
|
+
event_type: str
|
|
23
|
+
payload: dict
|
|
24
|
+
organization_id: str | None = None
|
|
25
|
+
created_on: datetime = None
|
|
26
|
+
|
|
27
|
+
def encode(self) -> bytes:
|
|
28
|
+
self.created_on: datetime = datetime.now()
|
|
29
|
+
return json.dumps(self.__dict__, default=str).encode()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Message:
|
|
34
|
+
event: Event
|
|
35
|
+
queue: Queue
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from http import HTTPStatus
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from fastapi import HTTPException
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class APIException(HTTPException):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
status_code: HTTPStatus,
|
|
11
|
+
error_code: str,
|
|
12
|
+
message: str,
|
|
13
|
+
display_message: str,
|
|
14
|
+
headers: Optional[dict[str, Any]] = None
|
|
15
|
+
) -> None:
|
|
16
|
+
detail = {
|
|
17
|
+
"error_code": error_code,
|
|
18
|
+
"message": message,
|
|
19
|
+
"display_message": display_message
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
super().__init__(
|
|
23
|
+
status_code=status_code.value,
|
|
24
|
+
detail=detail,
|
|
25
|
+
headers=headers
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
self.status_code: HTTPStatus = status_code
|
|
29
|
+
self.error_code: str = error_code
|
|
30
|
+
self.message: str = message
|
|
31
|
+
self.display_message: str = display_message
|
|
32
|
+
|
|
33
|
+
def __str__(self):
|
|
34
|
+
return (
|
|
35
|
+
f"[{self.status_code}] {self.error_code}: {self.message} "
|
|
36
|
+
f"(Display: {self.display_message})"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> dict[str, Any]:
|
|
40
|
+
return {
|
|
41
|
+
"status_code": self.status_code.value,
|
|
42
|
+
"error_code": self.error_code,
|
|
43
|
+
"message": self.message,
|
|
44
|
+
"display_message": self.display_message,
|
|
45
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from uuid import UUID, uuid4
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from sqlmodel import SQLModel, Field
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseModel(SQLModel):
|
|
10
|
+
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
|
11
|
+
created_at: Optional[datetime] = Field(
|
|
12
|
+
default_factory=lambda: datetime.now(timezone.utc)
|
|
13
|
+
)
|
|
14
|
+
updated_at: Optional[datetime] = Field(
|
|
15
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
16
|
+
sa_column_kwargs = {"onupdate": lambda: datetime.now(timezone.utc)}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# App specific models
|
|
21
|
+
## Integrations
|
|
22
|
+
class IntegrationsModel(BaseModel):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IntegrationsOrganizationModel(IntegrationsModel):
|
|
27
|
+
organization: UUID = Field(index=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Vacancies
|
|
31
|
+
class VacanciesModel(BaseModel):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VacanciesOrganizationModel(VacanciesModel):
|
|
36
|
+
organization: UUID = Field(index=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Campaigns
|
|
40
|
+
class CampaignsModel(BaseModel):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CampaignsOrganizationModel(CampaignsModel):
|
|
45
|
+
organization: UUID = Field(index=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Billing
|
|
50
|
+
class BillingModel(BaseModel):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BillingOrganizationModel(BillingModel):
|
|
55
|
+
organization: UUID = Field(index=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## IAM
|
|
59
|
+
class IAMModel(BaseModel):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class IAMOrganizationModel(BaseModel):
|
|
64
|
+
organization: UUID = Field(index=True)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from sqlmodel import Field
|
|
4
|
+
|
|
5
|
+
from ..general.models import IAMModel, IAMOrganizationModel
|
|
6
|
+
from sqlalchemy import Column, JSON
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CompositeRole(IAMModel, table=True):
|
|
10
|
+
name: str = Field(index=True)
|
|
11
|
+
roles: list = Field(sa_column=Column(JSON))
|
|
12
|
+
organization: UUID = Field(index=True, nullable=True)
|
|
13
|
+
manageable_roles: list = Field(sa_column=Column(JSON))
|
|
14
|
+
permission_level: int = Field(default=0)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MemberRoleConnection(IAMOrganizationModel, table=True):
|
|
18
|
+
composite_role_id: UUID = Field(foreign_key="compositerole.id")
|
|
19
|
+
member: UUID = Field(index=True, nullable=False)
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Integration(BaseModel):
|
|
9
|
+
id: UUID
|
|
10
|
+
created_at: datetime
|
|
11
|
+
updated_at: Optional[datetime]
|
|
12
|
+
organization: UUID
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
icon: str
|
|
16
|
+
type: str
|
|
17
|
+
tag: str
|
|
18
|
+
enabled: bool
|
|
19
|
+
description: str
|
|
20
|
+
code_reference: str
|
|
21
|
+
setup_config: dict
|
|
22
|
+
order: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Link(BaseModel):
|
|
26
|
+
id: UUID
|
|
27
|
+
created_at: datetime
|
|
28
|
+
updated_at: Optional[datetime]
|
|
29
|
+
organization: UUID
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
status: str
|
|
33
|
+
auth_config: dict
|
|
34
|
+
integration_id: UUID
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column, JSON
|
|
4
|
+
from sqlmodel import Field
|
|
5
|
+
|
|
6
|
+
from ..general.models import IntegrationsOrganizationModel, IntegrationsModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Integration(IntegrationsModel, table=True):
|
|
10
|
+
name: str = Field(index=True)
|
|
11
|
+
icon: str = Field(index=True)
|
|
12
|
+
type: str = Field(index=True)
|
|
13
|
+
tag: str = Field(index=True, nullable=True)
|
|
14
|
+
enabled: bool = Field(default=True, nullable=True)
|
|
15
|
+
description: str = Field(index=True, nullable=True)
|
|
16
|
+
code_reference: str = Field(index=True)
|
|
17
|
+
setup_config: dict = Field(sa_column=Column(JSON))
|
|
18
|
+
order: int = Field(default=0)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Link(IntegrationsOrganizationModel, table=True):
|
|
22
|
+
name: str = Field(index=True)
|
|
23
|
+
status: str = Field(index=True)
|
|
24
|
+
auth_config: dict = Field(sa_column=Column(JSON))
|
|
25
|
+
integration_id: UUID = Field(foreign_key="integration.id")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
from dataclasses import field
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from ..util.enum import to_enum
|
|
9
|
+
from ..vacancies.models import Feed, Vacancy as VacancyModel, SalaryFrequency, RemoteType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VacancyLocation(BaseModel):
|
|
13
|
+
zip_code: str | None = None
|
|
14
|
+
city: str | None = None
|
|
15
|
+
address: str | None = None
|
|
16
|
+
state: str | None = None
|
|
17
|
+
country: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Salary(BaseModel):
|
|
21
|
+
min: float | None = None
|
|
22
|
+
max: float | None = None
|
|
23
|
+
currency: str = "EUR"
|
|
24
|
+
frequency: SalaryFrequency = "month"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Hours(BaseModel):
|
|
28
|
+
min: int = 0
|
|
29
|
+
max: int = 40
|
|
30
|
+
fte: float = 1.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ContactDetails(BaseModel):
|
|
34
|
+
email: str | None = None
|
|
35
|
+
first_name: str | None = None
|
|
36
|
+
last_name: str | None = None
|
|
37
|
+
phone_number: str | None = None
|
|
38
|
+
role: str | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VacancyData(BaseModel):
|
|
42
|
+
|
|
43
|
+
# Required fields
|
|
44
|
+
reference_number: str
|
|
45
|
+
requisition_id: str
|
|
46
|
+
title: str
|
|
47
|
+
description: str
|
|
48
|
+
job_site_url: str
|
|
49
|
+
company_name: str
|
|
50
|
+
publish_date: datetime | None = None
|
|
51
|
+
category: List[str] = field(default_factory=list)
|
|
52
|
+
experience: List[str] = field(default_factory=list)
|
|
53
|
+
education: List[str] = field(default_factory=list)
|
|
54
|
+
|
|
55
|
+
# Connected data
|
|
56
|
+
hours: Hours = field(default_factory=Hours)
|
|
57
|
+
location: VacancyLocation = field(default_factory=VacancyLocation)
|
|
58
|
+
salary: Salary = field(default_factory=Salary)
|
|
59
|
+
recruiter: ContactDetails = field(default_factory=ContactDetails)
|
|
60
|
+
|
|
61
|
+
# Optional fields
|
|
62
|
+
status: str | None = None
|
|
63
|
+
parent_company_name: str | None = None
|
|
64
|
+
remote_type: RemoteType | None = None
|
|
65
|
+
expiration_date: datetime | None = None
|
|
66
|
+
last_updated_date: datetime | None = None
|
|
67
|
+
|
|
68
|
+
def to_model(self, feed: Feed):
|
|
69
|
+
return VacancyModel(
|
|
70
|
+
feed_id=feed.id,
|
|
71
|
+
organization=feed.organization,
|
|
72
|
+
reference_number=self.reference_number,
|
|
73
|
+
requisition_id=self.requisition_id,
|
|
74
|
+
title=self.title,
|
|
75
|
+
description=self.description,
|
|
76
|
+
status=self.status,
|
|
77
|
+
job_site_url=self.job_site_url,
|
|
78
|
+
company_name=self.company_name,
|
|
79
|
+
parent_company_name=self.parent_company_name,
|
|
80
|
+
remote_type=self.remote_type.value if self.remote_type else None,
|
|
81
|
+
publish_date=self.publish_date,
|
|
82
|
+
expiration_date=self.expiration_date,
|
|
83
|
+
last_updated_date=self.last_updated_date,
|
|
84
|
+
category=self.category,
|
|
85
|
+
experience=self.experience,
|
|
86
|
+
education=self.education,
|
|
87
|
+
hours_fte=self.hours.fte,
|
|
88
|
+
hours_min=self.hours.min,
|
|
89
|
+
hours_max=self.hours.max,
|
|
90
|
+
location_address=self.location.address,
|
|
91
|
+
location_zipcode=self.location.zip_code,
|
|
92
|
+
location_city=self.location.city,
|
|
93
|
+
location_state=self.location.state,
|
|
94
|
+
location_country=self.location.country,
|
|
95
|
+
salary_min=self.salary.min,
|
|
96
|
+
salary_max=self.salary.max,
|
|
97
|
+
salary_currency=self.salary.currency,
|
|
98
|
+
salary_frequency=self.salary.frequency.value if self.salary.frequency else SalaryFrequency.MONTH,
|
|
99
|
+
recruiter_first_name=self.recruiter.first_name,
|
|
100
|
+
recruiter_last_name=self.recruiter.last_name,
|
|
101
|
+
recruiter_phone_number=self.recruiter.phone_number,
|
|
102
|
+
recruiter_email=self.recruiter.email,
|
|
103
|
+
recruiter_role=self.recruiter.role,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
class Vacancy(VacancyData):
|
|
107
|
+
id: UUID
|
|
108
|
+
created_at: datetime
|
|
109
|
+
updated_at: Optional[datetime]
|
|
110
|
+
organization: UUID
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def from_model(cls: "Vacancy", model: VacancyModel) -> "Vacancy":
|
|
114
|
+
hours = Hours(
|
|
115
|
+
min=model.hours_min,
|
|
116
|
+
max=model.hours_max,
|
|
117
|
+
fte=model.hours_fte,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
location = VacancyLocation(
|
|
121
|
+
address=getattr(model, "location_address", None),
|
|
122
|
+
zip_code=getattr(model, "location_zipcode", None),
|
|
123
|
+
city=getattr(model, "location_city", None),
|
|
124
|
+
state=getattr(model, "location_state", None),
|
|
125
|
+
country=getattr(model, "location_country", None),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
salary = Salary(
|
|
129
|
+
min=getattr(model, "salary_min", None),
|
|
130
|
+
max=getattr(model, "salary_max", None),
|
|
131
|
+
currency=getattr(model, "salary_currency", "EUR") or "EUR",
|
|
132
|
+
frequency=to_enum(SalaryFrequency, getattr(model, "salary_frequency", SalaryFrequency.MONTH) or SalaryFrequency.MONTH),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
recruiter = ContactDetails(
|
|
136
|
+
first_name=getattr(model, "recruiter_first_name", None),
|
|
137
|
+
last_name=getattr(model, "recruiter_last_name", None),
|
|
138
|
+
phone_number=getattr(model, "recruiter_phone_number", None),
|
|
139
|
+
email=getattr(model, "recruiter_email", None),
|
|
140
|
+
role=getattr(model, "recruiter_role", None),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
remote_type = to_enum(RemoteType, getattr(model, "remote_type", None))
|
|
144
|
+
|
|
145
|
+
return cls(
|
|
146
|
+
organization=getattr(model, "organization", None),
|
|
147
|
+
id=model.id,
|
|
148
|
+
created_at=model.created_at,
|
|
149
|
+
updated_at=model.updated_at,
|
|
150
|
+
|
|
151
|
+
reference_number=model.reference_number,
|
|
152
|
+
requisition_id=model.requisition_id,
|
|
153
|
+
title=model.title,
|
|
154
|
+
description=model.description,
|
|
155
|
+
job_site_url=model.job_site_url,
|
|
156
|
+
company_name=model.company_name,
|
|
157
|
+
publish_date=model.publish_date,
|
|
158
|
+
category=list(model.category or []),
|
|
159
|
+
experience=list(model.experience or []),
|
|
160
|
+
education=list(model.education or []),
|
|
161
|
+
|
|
162
|
+
hours=hours,
|
|
163
|
+
location=location,
|
|
164
|
+
salary=salary,
|
|
165
|
+
recruiter=recruiter,
|
|
166
|
+
|
|
167
|
+
status=getattr(model, "status", None),
|
|
168
|
+
parent_company_name=getattr(model, "parent_company_name", None),
|
|
169
|
+
remote_type=remote_type,
|
|
170
|
+
expiration_date=getattr(model, "expiration_date", None),
|
|
171
|
+
last_updated_date=getattr(model, "last_updated_date", None),
|
|
172
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import Column, JSON, UniqueConstraint, Enum
|
|
8
|
+
from sqlmodel import Field
|
|
9
|
+
|
|
10
|
+
from ..general.models import VacanciesOrganizationModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SalaryFrequency(StrEnum):
|
|
14
|
+
MONTH = "month"
|
|
15
|
+
YEAR = "year"
|
|
16
|
+
WEEK = "week"
|
|
17
|
+
HOUR = "hour"
|
|
18
|
+
|
|
19
|
+
class RemoteType(StrEnum):
|
|
20
|
+
ONSITE = "onsite"
|
|
21
|
+
HYBRID = "hybrid"
|
|
22
|
+
REMOTE = "remote"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Feed(VacanciesOrganizationModel, table=True):
|
|
26
|
+
name: str = Field(index=True)
|
|
27
|
+
status: str = Field(index=True)
|
|
28
|
+
file_url: Optional[str] = Field()
|
|
29
|
+
ats_link_id: Optional[UUID] = Field()
|
|
30
|
+
last_sync_date: Optional[datetime] = Field()
|
|
31
|
+
synced_vacancy_count: int = Field(default=0)
|
|
32
|
+
mapping: dict = Field(sa_column=Column(JSON))
|
|
33
|
+
custom_fields: dict = Field(sa_column=Column(JSON))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Vacancy(VacanciesOrganizationModel, table=True):
|
|
37
|
+
feed_id: UUID = Field(foreign_key="feed.id")
|
|
38
|
+
reference_number: str = Field(index=True)
|
|
39
|
+
requisition_id: str = Field(index=True)
|
|
40
|
+
title: str = Field(index=True)
|
|
41
|
+
description: str = Field()
|
|
42
|
+
status: str = Field()
|
|
43
|
+
job_site_url: str = Field()
|
|
44
|
+
company_name: str = Field(index=True)
|
|
45
|
+
parent_company_name: Optional[str] = Field()
|
|
46
|
+
remote_type: Optional[RemoteType] = Field(sa_column=Column(Enum(RemoteType)))
|
|
47
|
+
publish_date: Optional[datetime] = Field()
|
|
48
|
+
expiration_date: Optional[datetime] = Field()
|
|
49
|
+
last_updated_date: Optional[datetime] = Field()
|
|
50
|
+
category: List[str] = Field(sa_column=Column(JSON))
|
|
51
|
+
experience: List[str] = Field(sa_column=Column(JSON))
|
|
52
|
+
education: List[str] = Field(sa_column=Column(JSON))
|
|
53
|
+
hours_fte: Optional[float] = Field(default=1.0)
|
|
54
|
+
hours_min: Optional[int] = Field(default=1)
|
|
55
|
+
hours_max: Optional[int] = Field(default=40)
|
|
56
|
+
location_address: Optional[str] = Field()
|
|
57
|
+
location_zipcode: Optional[str] = Field()
|
|
58
|
+
location_city: Optional[str] = Field()
|
|
59
|
+
location_state: Optional[str] = Field()
|
|
60
|
+
location_country: Optional[str] = Field()
|
|
61
|
+
salary_min: Optional[float] = Field()
|
|
62
|
+
salary_max: Optional[float] = Field()
|
|
63
|
+
salary_currency: str = Field(default="EUR")
|
|
64
|
+
salary_frequency: SalaryFrequency = Field(sa_column=Column(Enum(SalaryFrequency)))
|
|
65
|
+
recruiter_first_name: Optional[str] = Field()
|
|
66
|
+
recruiter_last_name: Optional[str] = Field()
|
|
67
|
+
recruiter_phone_number: Optional[str] = Field()
|
|
68
|
+
recruiter_email: Optional[str] = Field()
|
|
69
|
+
recruiter_role: Optional[str] = Field()
|
|
70
|
+
|
|
71
|
+
__table_args__ = (
|
|
72
|
+
UniqueConstraint("feed_id", "reference_number", name="uq_feed_reference"),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Modification(VacanciesOrganizationModel, table=True):
|
|
77
|
+
name: str = Field(index=True)
|
|
78
|
+
type: str = Field(index=True)
|
|
79
|
+
selector_configuration: dict = Field(sa_column=Column(JSON))
|
|
80
|
+
changes_configuration: dict = Field(sa_column=Column(JSON))
|
|
81
|
+
|