es-user 0.1.0__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.
- es_user-0.1.0/LICENSE +21 -0
- es_user-0.1.0/PKG-INFO +18 -0
- es_user-0.1.0/README.md +1 -0
- es_user-0.1.0/es_user/.DS_Store +0 -0
- es_user-0.1.0/es_user/__init__.py +0 -0
- es_user-0.1.0/es_user/contact/__init__.py +0 -0
- es_user-0.1.0/es_user/contact/model.py +28 -0
- es_user-0.1.0/es_user/contact/schema.py +27 -0
- es_user-0.1.0/es_user/contact/service.py +37 -0
- es_user-0.1.0/es_user/db/__init__.py +0 -0
- es_user-0.1.0/es_user/db/pg.py +171 -0
- es_user-0.1.0/es_user/db/session.py +17 -0
- es_user-0.1.0/es_user/user/__init__.py +0 -0
- es_user-0.1.0/es_user/user/model.py +54 -0
- es_user-0.1.0/es_user/user/schema.py +69 -0
- es_user-0.1.0/es_user/user/service.py +334 -0
- es_user-0.1.0/es_user/util/CONSTANTS.py +18 -0
- es_user-0.1.0/es_user/util/__init__.py +0 -0
- es_user-0.1.0/es_user/util/enums.py +30 -0
- es_user-0.1.0/es_user/util/service.py +23 -0
- es_user-0.1.0/pyproject.toml +15 -0
es_user-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Babafemi Adigun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
es_user-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: es-user
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: user management for ESSL apps
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Femi Adigun
|
|
7
|
+
Author-email: femi.adigun@myeverlasting.net
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Internal User Management package for ESSL apps
|
|
18
|
+
|
es_user-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Internal User Management package for ESSL apps
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from sqlalchemy import Column, DateTime
|
|
13
|
+
from sqlalchemy.sql import func
|
|
14
|
+
from sqlalchemy.sql.sqltypes import Integer,String
|
|
15
|
+
from es_user.db.session import Base
|
|
16
|
+
class Lead(Base):
|
|
17
|
+
__tablename__ = "leads"
|
|
18
|
+
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
|
19
|
+
created_at = Column(DateTime(), server_default=func.now())
|
|
20
|
+
first_name = Column(String)
|
|
21
|
+
last_name = Column(String)
|
|
22
|
+
email = Column(String, nullable=False)
|
|
23
|
+
phone_number = Column(String)
|
|
24
|
+
country = Column(String)
|
|
25
|
+
subject = Column(String)
|
|
26
|
+
message = Column(String)
|
|
27
|
+
created_at = Column(DateTime(), server_default=func.now())
|
|
28
|
+
updated_at = Column(DateTime(), server_default=func.now(), onupdate=func.now())
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from datetime import datetime,date
|
|
13
|
+
from pydantic import BaseModel, EmailStr
|
|
14
|
+
|
|
15
|
+
class LeadBase(BaseModel):
|
|
16
|
+
first_name: str
|
|
17
|
+
last_name: str
|
|
18
|
+
email: EmailStr
|
|
19
|
+
phone_number: str | None = None
|
|
20
|
+
subject: str | None = None
|
|
21
|
+
message: str | None = None
|
|
22
|
+
created_at: datetime = datetime.now()
|
|
23
|
+
class LeadDto(LeadBase):
|
|
24
|
+
id: int
|
|
25
|
+
class LeadFiter(BaseModel):
|
|
26
|
+
start_date: date
|
|
27
|
+
end_date: date
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
import logging
|
|
13
|
+
from sqlalchemy.orm import Session
|
|
14
|
+
from es_user.contact.schema import LeadDto,LeadBase
|
|
15
|
+
from es_user.contact.model import Lead
|
|
16
|
+
from es_user.db import pg
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
logging.basicConfig(level=logging.INFO)
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
def make_contact_form(lead: LeadBase,db: Session) -> LeadDto:
|
|
22
|
+
"""Submits a contact form.
|
|
23
|
+
Args:
|
|
24
|
+
lead (LeadDto): Lead form content.
|
|
25
|
+
db (Session): Database session.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
new_lead = pg.add_model(Lead,db, **lead.model_dump())
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise e
|
|
31
|
+
return LeadDto(**new_lead.__dict__)
|
|
32
|
+
def all_leads(db: Session) -> list[LeadDto]:
|
|
33
|
+
leads = db.query(Lead).all()
|
|
34
|
+
return [LeadDto(**lead.__dict__) for lead in leads]
|
|
35
|
+
def leads_by_created(start_date: datetime, end_date: datetime, db: Session) -> list[LeadDto]:
|
|
36
|
+
leads = db.query(Lead).filter(Lead.created_at >= start_date, Lead.created_at <= end_date).all()
|
|
37
|
+
return [LeadDto(**lead.__dict__) for lead in leads]
|
|
File without changes
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from typing import Type,Any
|
|
13
|
+
from sqlalchemy.exc import IntegrityError,SQLAlchemyError
|
|
14
|
+
from sqlalchemy.orm import Session
|
|
15
|
+
import logging
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
def add_model(model_class: Type, db: Session, **kwargs):
|
|
18
|
+
"""
|
|
19
|
+
Persist a SQLAlchemy model to the database.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
- model_class (Type): The SQLAlchemy model class to be persisted.
|
|
23
|
+
- db (Session): The database session to use for persistence.
|
|
24
|
+
- **kwargs: Keyword arguments to pass to the model constructor.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
- bool: True if the model is persisted successfully, False otherwise.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
- IntegrityError: If the model cannot be persisted due to an integrity error.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
new_model = model_class(**kwargs)
|
|
34
|
+
db.add(new_model)
|
|
35
|
+
db.commit()
|
|
36
|
+
db.refresh(new_model)
|
|
37
|
+
return new_model
|
|
38
|
+
except IntegrityError as e:
|
|
39
|
+
db.rollback()
|
|
40
|
+
error_msg = f"Integrity Error when adding {model_class.__name__}: {str(e)}"
|
|
41
|
+
logger.error(error_msg)
|
|
42
|
+
raise ValueError(error_msg) from e
|
|
43
|
+
except SQLAlchemyError as e:
|
|
44
|
+
db.rollback()
|
|
45
|
+
error_msg = f"Database Error when adding {model_class.__name__}: {str(e)}"
|
|
46
|
+
logger.error(error_msg)
|
|
47
|
+
raise ValueError(error_msg) from e
|
|
48
|
+
except Exception as e:
|
|
49
|
+
db.rollback()
|
|
50
|
+
error_msg = f"Unexpected error when adding {model_class.__name__}: {str(e)}"
|
|
51
|
+
logger.error(error_msg)
|
|
52
|
+
raise ValueError(error_msg) from e
|
|
53
|
+
def update_model(model_class: Type, db: Session, model_id: int, **kwargs) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Update a SQLAlchemy model in the database.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
- model_class (Type): The SQLAlchemy model class to be updated.
|
|
59
|
+
- db (Session): The database session to use for updating.
|
|
60
|
+
- id (int): The ID of the model instance to be updated.
|
|
61
|
+
- **kwargs: Keyword arguments with the attributes to be updated and their new values.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
- model: The updatd model.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
- Exception: If an error occurs during the update process.
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
model = db.query(model_class).get(model_id)
|
|
71
|
+
if not model:
|
|
72
|
+
raise ValueError(f"{model_class.__name__} with id {model_id} not found.")
|
|
73
|
+
|
|
74
|
+
for key, value in kwargs.items():
|
|
75
|
+
if hasattr(model, key) and key != 'id':
|
|
76
|
+
setattr(model, key, value)
|
|
77
|
+
|
|
78
|
+
db.commit()
|
|
79
|
+
db.refresh(model)
|
|
80
|
+
return model
|
|
81
|
+
|
|
82
|
+
except SQLAlchemyError as e:
|
|
83
|
+
logger.error(f"Error updating {model_class.__name__}: {e}")
|
|
84
|
+
db.rollback()
|
|
85
|
+
raise ValueError(f"Database error: {str(e)}")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Unexpected error: {e}")
|
|
88
|
+
db.rollback()
|
|
89
|
+
raise ValueError(f"Unexpected error: {str(e)}")
|
|
90
|
+
|
|
91
|
+
def get_model(model_class: Type, db: Session, id: int) -> object:
|
|
92
|
+
"""
|
|
93
|
+
Retrieve a SQLAlchemy model instance from the database by ID.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
- model_class (Type): The SQLAlchemy model class to be retrieved.
|
|
97
|
+
- db (Session): The database session to use for retrieval.
|
|
98
|
+
- id (int): The ID of the model instance to be retrieved.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
- object: The retrieved model instance, or None if not found.
|
|
102
|
+
"""
|
|
103
|
+
return db.query(model_class).filter_by(id=id).first()
|
|
104
|
+
def get_model_by_field(model_class: Type, db: Session, field: str, value: Any) -> object:
|
|
105
|
+
"""
|
|
106
|
+
Retrieve a SQLAlchemy model instance from the database by a specified field and value.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
- model_class (Type): The SQLAlchemy model class to be retrieved.
|
|
110
|
+
- db (Session): The database session to use for retrieval.
|
|
111
|
+
- field (str): The field name to filter by.
|
|
112
|
+
- value (Any): The value of the field to filter by.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
- object: The retrieved model instance, or None if not found.
|
|
116
|
+
"""
|
|
117
|
+
return db.query(model_class).filter(getattr(model_class, field) == value).first()
|
|
118
|
+
def get_models_by_field(model_class: Type, db: Session, field: str, value: str) -> list[object]:
|
|
119
|
+
"""
|
|
120
|
+
Retrieve a SQLAlchemy model instance from the database by a specific field.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
- model_class (Type): The SQLAlchemy model class to be retrieved.
|
|
124
|
+
- db (Session): The database session to use for retrieval.
|
|
125
|
+
- field (str): The field to search for.
|
|
126
|
+
- value (str): The value to search for.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
- List[object]: A list of all instances of the specified model class that match the search criteria.
|
|
130
|
+
"""
|
|
131
|
+
return db.query(model_class).filter(getattr(model_class, field) == value).all()
|
|
132
|
+
|
|
133
|
+
def get_all_models(model_class: Type, db: Session) -> list:
|
|
134
|
+
"""
|
|
135
|
+
Retrieve all instances of a SQLAlchemy model from the database.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
- model_class (Type): The SQLAlchemy model class to be retrieved.
|
|
139
|
+
- db (Session): The database session to use for retrieval.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
- list: A list of all instances of the specified model class.
|
|
143
|
+
"""
|
|
144
|
+
return db.query(model_class).all()
|
|
145
|
+
|
|
146
|
+
def delete_model(model_class: Type, db: Session, id: int) -> bool:
|
|
147
|
+
"""
|
|
148
|
+
Delete a SQLAlchemy model instance from the database by ID.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
- model_class (Type): The SQLAlchemy model class to be deleted.
|
|
152
|
+
- db (Session): The database session to use for deletion.
|
|
153
|
+
- id (int): The ID of the model instance to be deleted.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
- bool: True if the model is deleted successfully, False otherwise.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
- Exception: If an error occurs during the deletion process.
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
model = db.query(model_class).get(id)
|
|
163
|
+
if not model:
|
|
164
|
+
raise ValueError(f"{model_class.__name__} with id {id} not found.")
|
|
165
|
+
db.delete(model)
|
|
166
|
+
db.commit()
|
|
167
|
+
return True
|
|
168
|
+
except Exception as e:
|
|
169
|
+
db.rollback()
|
|
170
|
+
logger.error(f"Error: {e}")
|
|
171
|
+
raise ValueError(str(e)) from e
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from es_user.util.CONSTANTS import SCHEMA
|
|
13
|
+
from sqlalchemy import MetaData
|
|
14
|
+
from sqlalchemy.orm import declarative_base
|
|
15
|
+
|
|
16
|
+
metadata = MetaData(schema=SCHEMA)
|
|
17
|
+
Base = declarative_base(metadata=metadata)
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from sqlalchemy import Column, DateTime, ForeignKey, Enum
|
|
13
|
+
from sqlalchemy.sql import func
|
|
14
|
+
from sqlalchemy.sql.sqltypes import Integer,String,Boolean
|
|
15
|
+
from sqlalchemy.dialects.postgresql import ARRAY
|
|
16
|
+
from sqlalchemy.orm import relationship
|
|
17
|
+
from es_user.db.session import Base
|
|
18
|
+
from es_user.util.enums import AccessRoleEnum,StatusEnum
|
|
19
|
+
from es_user.util.CONSTANTS import SCHEMA
|
|
20
|
+
class Address(Base):
|
|
21
|
+
__tablename__ = "addresses"
|
|
22
|
+
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
|
23
|
+
street = Column(String)
|
|
24
|
+
city = Column(String)
|
|
25
|
+
state = Column(String)
|
|
26
|
+
zip_code = Column(String)
|
|
27
|
+
email = Column(String)
|
|
28
|
+
phone_number = Column(String)
|
|
29
|
+
country = Column(String)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Appuser(Base):
|
|
33
|
+
__tablename__ = "appusers"
|
|
34
|
+
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
|
35
|
+
first_name = Column(String)
|
|
36
|
+
last_name = Column(String)
|
|
37
|
+
country = Column(String)
|
|
38
|
+
email = Column(String, nullable=False, unique=True)
|
|
39
|
+
password = Column(String)
|
|
40
|
+
token = Column(String)
|
|
41
|
+
dp = Column(String)
|
|
42
|
+
is_active = Column(Boolean)
|
|
43
|
+
roles = Column(ARRAY(Enum(AccessRoleEnum, schema=SCHEMA)))
|
|
44
|
+
status = Column(Enum(StatusEnum, schema=SCHEMA))
|
|
45
|
+
gender = Column(String)
|
|
46
|
+
socialmedia = Column(String)
|
|
47
|
+
created_at = Column(DateTime(), server_default=func.now())
|
|
48
|
+
updated_at = Column(DateTime(), server_default=func.now(), onupdate=func.now())
|
|
49
|
+
address_id = Column(
|
|
50
|
+
Integer, ForeignKey("addresses.id"), nullable=True
|
|
51
|
+
)
|
|
52
|
+
address = relationship(
|
|
53
|
+
"Address", uselist=False, backref="user"
|
|
54
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pydantic import BaseModel, EmailStr, field_validator
|
|
14
|
+
from es_user.util.enums import AccessRoleEnum, StatusEnum
|
|
15
|
+
from typing import Literal
|
|
16
|
+
gender_type = Literal['MALE', 'FEMALE', 'OTHER']
|
|
17
|
+
class AddressBase(BaseModel):
|
|
18
|
+
street: str
|
|
19
|
+
city: str
|
|
20
|
+
state: str
|
|
21
|
+
zip_code: str | None = None
|
|
22
|
+
email: EmailStr | None = None
|
|
23
|
+
phone_number: str | None = None
|
|
24
|
+
country: str
|
|
25
|
+
class AddressDto(AddressBase):
|
|
26
|
+
id: int | None = None
|
|
27
|
+
class UserEmail(BaseModel):
|
|
28
|
+
email: EmailStr
|
|
29
|
+
class UserReset(UserEmail):
|
|
30
|
+
token: str
|
|
31
|
+
password: str
|
|
32
|
+
new_password: str | None = None
|
|
33
|
+
class UserPhoto(BaseModel):
|
|
34
|
+
file_path: str
|
|
35
|
+
user_id: int
|
|
36
|
+
class UserBase(BaseModel):
|
|
37
|
+
first_name: str
|
|
38
|
+
last_name: str
|
|
39
|
+
is_active: bool = False
|
|
40
|
+
roles: list[AccessRoleEnum]
|
|
41
|
+
socialmedia: str | None = None # comma separated string
|
|
42
|
+
gender: gender_type
|
|
43
|
+
token: str | None = None
|
|
44
|
+
email: EmailStr
|
|
45
|
+
status: StatusEnum = StatusEnum.NEW
|
|
46
|
+
|
|
47
|
+
class UserResponse(UserBase):
|
|
48
|
+
id: int
|
|
49
|
+
dp: str | None = None
|
|
50
|
+
created_at: datetime = datetime.now()
|
|
51
|
+
|
|
52
|
+
@field_validator('roles')
|
|
53
|
+
def validate_roles(cls, roles):
|
|
54
|
+
if AccessRoleEnum.GUEST in roles and AccessRoleEnum.ADMIN in roles:
|
|
55
|
+
raise ValueError("User cannot have roles of Guest and Admin at the same time")
|
|
56
|
+
return roles
|
|
57
|
+
|
|
58
|
+
class AppuserDto(UserBase):
|
|
59
|
+
password: str
|
|
60
|
+
address: AddressBase | None = None
|
|
61
|
+
|
|
62
|
+
class AppuserUpdate(UserBase):
|
|
63
|
+
id: int
|
|
64
|
+
address: AddressDto | None = None
|
|
65
|
+
@field_validator('roles')
|
|
66
|
+
def validate_roles(cls, roles):
|
|
67
|
+
if AccessRoleEnum.GUEST in roles and AccessRoleEnum.ADMIN in roles:
|
|
68
|
+
raise ValueError("User cannot have roles of Guest and Admin at the same time")
|
|
69
|
+
return roles
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from sqlalchemy import desc
|
|
13
|
+
from sqlalchemy.orm import Session
|
|
14
|
+
from sqlalchemy.exc import SQLAlchemyError,IntegrityError
|
|
15
|
+
from pydantic import EmailStr
|
|
16
|
+
from es_user.user.model import Appuser, Address
|
|
17
|
+
from es_user.user.schema import AppuserDto, UserResponse, AppuserUpdate, UserReset, AddressDto
|
|
18
|
+
from es_user.util.service import encrypt_pass, verify_password
|
|
19
|
+
from es_user.util.enums import AccessRoleEnum, StatusEnum
|
|
20
|
+
from es_user.db import pg
|
|
21
|
+
import logging
|
|
22
|
+
import uuid
|
|
23
|
+
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
def make_admin(db: Session, password: str, email: str) -> Appuser | None:
|
|
28
|
+
"""Create admin user if it does not exist.
|
|
29
|
+
Args:
|
|
30
|
+
db (Session): Database session.
|
|
31
|
+
password (str): Admin password.
|
|
32
|
+
email (str): Admin email.
|
|
33
|
+
"""
|
|
34
|
+
user = db.query(Appuser).filter(Appuser.email == "office@horacelearning.com").first()
|
|
35
|
+
if user:
|
|
36
|
+
logger.info("Horace Admin user already exists")
|
|
37
|
+
return
|
|
38
|
+
new_admin = AppuserDto(
|
|
39
|
+
first_name="Horace",
|
|
40
|
+
last_name="Admin",
|
|
41
|
+
email=email,
|
|
42
|
+
token="Horace@123",
|
|
43
|
+
password=encrypt_pass(password),
|
|
44
|
+
status=StatusEnum.NEW,
|
|
45
|
+
is_active=True,
|
|
46
|
+
gender="MALE",
|
|
47
|
+
roles=[AccessRoleEnum.ADMIN],
|
|
48
|
+
)
|
|
49
|
+
pg.add_model(Appuser, db, **new_admin.__dict__)
|
|
50
|
+
async def add_user(user: AppuserDto, db: Session) -> UserResponse:
|
|
51
|
+
try:
|
|
52
|
+
new_user = None
|
|
53
|
+
if user.address:
|
|
54
|
+
addr = pg.add_model(Address, db, **user.address.__dict__)
|
|
55
|
+
user.address = addr
|
|
56
|
+
new_user = pg.add_model(Appuser, db, **user.__dict__)
|
|
57
|
+
|
|
58
|
+
if new_user:
|
|
59
|
+
return UserResponse(**new_user.__dict__)
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("Failed to add new user.")
|
|
62
|
+
except IntegrityError as e:
|
|
63
|
+
if "duplicate key value violates unique constraint" in str(e.orig):
|
|
64
|
+
logger.error(f"Error adding user: {e}")
|
|
65
|
+
raise ValueError(f"Email already exists. {user.email}")
|
|
66
|
+
else:
|
|
67
|
+
logger.error(f"Error adding user: {e}")
|
|
68
|
+
raise ValueError("An error occurred.")
|
|
69
|
+
except SQLAlchemyError as e:
|
|
70
|
+
logger.error(f"Error adding user: {e}")
|
|
71
|
+
raise ValueError(
|
|
72
|
+
"Database error.")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Unexpected error: {e}")
|
|
75
|
+
raise ValueError(f"Unexpected error.{e}")
|
|
76
|
+
|
|
77
|
+
# def bulk_upload_users(contents: bytes, file_type: str, db: Session) -> int:
|
|
78
|
+
# """
|
|
79
|
+
# Uploads user data from a CSV or JSON file.
|
|
80
|
+
|
|
81
|
+
# Args:
|
|
82
|
+
# file (IO): The file object of the CSV or JSON file.
|
|
83
|
+
# file_type (str): "csv" or "json".
|
|
84
|
+
# db (Session): The database session object.
|
|
85
|
+
|
|
86
|
+
# Returns:
|
|
87
|
+
# int: The number of users successfully uploaded.
|
|
88
|
+
# """
|
|
89
|
+
# uploaded_count = 0
|
|
90
|
+
|
|
91
|
+
# try:
|
|
92
|
+
# if file_type == "csv":
|
|
93
|
+
# data = read_csv(io.BytesIO(contents), converters={"roles": lambda x: x.split(",")})
|
|
94
|
+
# #
|
|
95
|
+
# # records = data.to_dict(orient='records')
|
|
96
|
+
# elif file_type == "json":
|
|
97
|
+
# data = read_json(io.BytesIO(contents))
|
|
98
|
+
# # records = data.to_dict(orient='records')
|
|
99
|
+
# else:
|
|
100
|
+
# raise ValueError("Invalid file type. Supported types: csv, json")
|
|
101
|
+
# data["password"] = data["password"].apply(encrypt_pass)
|
|
102
|
+
# data["status"] = StatusEnum.NEW
|
|
103
|
+
# records = data.to_dict(orient='records')
|
|
104
|
+
# user_dtos = [AppuserDto(**row) for row in records]
|
|
105
|
+
|
|
106
|
+
# db.bulk_insert_mappings(Appuser, [user_dto.__dict__ for user_dto in user_dtos])
|
|
107
|
+
# db.commit()
|
|
108
|
+
# uploaded_count = len(records)
|
|
109
|
+
|
|
110
|
+
# except ValueError as ve:
|
|
111
|
+
# raise ve
|
|
112
|
+
# except (csv.Error, json.JSONDecodeError) as file_error:
|
|
113
|
+
# raise Exception(f"Error reading the file: {file_error}")
|
|
114
|
+
# except Exception as e:
|
|
115
|
+
# db.rollback()
|
|
116
|
+
# raise Exception(f"Error processing user data: {e}")
|
|
117
|
+
|
|
118
|
+
# return uploaded_count
|
|
119
|
+
|
|
120
|
+
def get_user_by_id(usr: int, db: Session) -> UserResponse:
|
|
121
|
+
try:
|
|
122
|
+
user = pg.get_model(Appuser, db, usr)
|
|
123
|
+
if not user:
|
|
124
|
+
raise ValueError(f"No user with id {usr}")
|
|
125
|
+
return UserResponse(**user.__dict__)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(f"Get user by id failed {str(e)}")
|
|
128
|
+
raise ValueError(f"Error getting User {usr}")
|
|
129
|
+
def activate_user(email: str, db: Session) -> bool:
|
|
130
|
+
try:
|
|
131
|
+
user = db.query(Appuser).filter(Appuser.email == email).first()
|
|
132
|
+
if not user:
|
|
133
|
+
raise ValueError(f"No user with email {email}")
|
|
134
|
+
user.is_active = not user.is_active
|
|
135
|
+
db.commit()
|
|
136
|
+
return True
|
|
137
|
+
except Exception as e:
|
|
138
|
+
db.rollback()
|
|
139
|
+
logger.error(f"Error activating user {email}: {str(e)}")
|
|
140
|
+
raise ValueError(f"Error activating user {email}: {str(e)}")
|
|
141
|
+
def update_user(user_update: AppuserUpdate, db: Session) -> UserResponse:
|
|
142
|
+
try:
|
|
143
|
+
existing_user = db.query(Appuser).get(user_update.id)
|
|
144
|
+
if not existing_user:
|
|
145
|
+
raise ValueError(f"User not found. {user_update.id}")
|
|
146
|
+
|
|
147
|
+
# Update the address if provided
|
|
148
|
+
if user_update.address:
|
|
149
|
+
if existing_user.address_id:
|
|
150
|
+
addr = db.query(Address).get(existing_user.address_id)
|
|
151
|
+
for key, value in user_update.address.__dict__.items():
|
|
152
|
+
setattr(addr, key, value)
|
|
153
|
+
addr.id = existing_user.address_id
|
|
154
|
+
db.commit()
|
|
155
|
+
db.refresh(addr)
|
|
156
|
+
else:
|
|
157
|
+
addr = Address(**user_update.address.__dict__)
|
|
158
|
+
db.add(addr)
|
|
159
|
+
db.commit()
|
|
160
|
+
db.refresh(addr)
|
|
161
|
+
existing_user.address_id = addr.id
|
|
162
|
+
|
|
163
|
+
# Update the user details
|
|
164
|
+
for key, value in user_update.__dict__.items():
|
|
165
|
+
if key != 'address' and key != 'id':
|
|
166
|
+
setattr(existing_user, key, value)
|
|
167
|
+
db.commit()
|
|
168
|
+
db.refresh(existing_user)
|
|
169
|
+
|
|
170
|
+
logger.info(f"User {existing_user.email} updated successfully")
|
|
171
|
+
return UserResponse(**existing_user.__dict__)
|
|
172
|
+
|
|
173
|
+
except IntegrityError as e:
|
|
174
|
+
db.rollback()
|
|
175
|
+
if "duplicate key value violates unique constraint" in str(e.orig):
|
|
176
|
+
logger.error(f"Error updating user: {e}")
|
|
177
|
+
raise ValueError(f"Email already exists: {user_update.email}")
|
|
178
|
+
else:
|
|
179
|
+
logger.error(f"Integrity error updating user: {e}")
|
|
180
|
+
raise ValueError("An error occurred during data integrity validation.")
|
|
181
|
+
except SQLAlchemyError as e:
|
|
182
|
+
db.rollback()
|
|
183
|
+
logger.error(f"Database error updating user: {e}")
|
|
184
|
+
raise ValueError("Database error.")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error(f"Unexpected error: {e}")
|
|
187
|
+
raise ValueError(f"Unexpected error: {e}")
|
|
188
|
+
|
|
189
|
+
def update_photo(user_id: int, photo: str, db: Session) -> bool:
|
|
190
|
+
try:
|
|
191
|
+
user = db.query(Appuser).get(user_id)
|
|
192
|
+
user.dp = photo
|
|
193
|
+
db.commit()
|
|
194
|
+
return True
|
|
195
|
+
except Exception as e:
|
|
196
|
+
raise e
|
|
197
|
+
|
|
198
|
+
def get_userby_username(email: str,db:Session) -> Appuser:
|
|
199
|
+
user = db.query(Appuser).filter(Appuser.email == email).first()
|
|
200
|
+
return user
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def all_users(db: Session) -> list[UserResponse]:
|
|
204
|
+
try:
|
|
205
|
+
users = pg.get_all_models(Appuser, db)
|
|
206
|
+
users_resp = [
|
|
207
|
+
UserResponse(
|
|
208
|
+
**user.__dict__
|
|
209
|
+
)
|
|
210
|
+
for user in users
|
|
211
|
+
]
|
|
212
|
+
return users_resp
|
|
213
|
+
except SQLAlchemyError as e:
|
|
214
|
+
logger.error(f"Database error retrieving all users: {e}")
|
|
215
|
+
raise ValueError("Database error.")
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.error(f"Unexpected error retrieving all users: {e}")
|
|
218
|
+
raise ValueError("Unexpected error.")
|
|
219
|
+
def delete_user(db: Session, id: int) -> bool:
|
|
220
|
+
try:
|
|
221
|
+
user = db.query(Appuser).get(id)
|
|
222
|
+
if user is None:
|
|
223
|
+
raise ValueError(f"User with id {id} not found.")
|
|
224
|
+
db.delete(user)
|
|
225
|
+
db.commit()
|
|
226
|
+
return True
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise ValueError(f"An error occurred. {e}")
|
|
229
|
+
def set_delete_user(db: Session, id: int) -> bool:
|
|
230
|
+
try:
|
|
231
|
+
user = db.query(Appuser).get(id)
|
|
232
|
+
if user is None:
|
|
233
|
+
raise ValueError(f"User with id {id} not found.")
|
|
234
|
+
|
|
235
|
+
user.status = StatusEnum.DELETED
|
|
236
|
+
db.commit()
|
|
237
|
+
return True
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
db.rollback() # Rollback in case of an error
|
|
241
|
+
logger.error(f"Error deleting user with id {id}: {e}")
|
|
242
|
+
raise ValueError(f"An error occurred deleting user with id {id}.")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def users_by_role(db: Session, role: AccessRoleEnum) -> list[UserResponse]:
|
|
246
|
+
"""
|
|
247
|
+
Retrieve users based on their role.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
db (Session): Database session.
|
|
251
|
+
role (str): Role to filter users.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
List[UserResponse]: List of users matching the specified role.
|
|
255
|
+
"""
|
|
256
|
+
users = db.query(Appuser).filter(Appuser.roles.contains([role])).order_by(desc(Appuser.id)).all()
|
|
257
|
+
users_resp = [
|
|
258
|
+
UserResponse(
|
|
259
|
+
id=user.id,
|
|
260
|
+
timestamp=user.timestamp,
|
|
261
|
+
first_name=user.first_name,
|
|
262
|
+
last_name=user.last_name,
|
|
263
|
+
email=user.email,
|
|
264
|
+
password=user.password,
|
|
265
|
+
is_active=user.is_active,
|
|
266
|
+
roles=user.roles,
|
|
267
|
+
gender=user.gender,
|
|
268
|
+
socialmedia=user.socialmedia
|
|
269
|
+
)
|
|
270
|
+
for user in users
|
|
271
|
+
]
|
|
272
|
+
return users_resp
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def is_exist(email: EmailStr, db: Session) -> int:
|
|
276
|
+
user = db.query(Appuser).filter(Appuser.email == email).first()
|
|
277
|
+
return user.id if user else 0
|
|
278
|
+
|
|
279
|
+
async def rest_password_token(email: str, db: Session) -> Appuser | None:
|
|
280
|
+
try:
|
|
281
|
+
user = db.query(Appuser).filter(Appuser.email == email).first()
|
|
282
|
+
if not user:
|
|
283
|
+
raise ValueError(f"No user with email {email}")
|
|
284
|
+
token = str(uuid.uuid4())[:8]
|
|
285
|
+
user.token = token
|
|
286
|
+
db.commit()
|
|
287
|
+
# mailer=MailerDto(
|
|
288
|
+
# recipients=[email],
|
|
289
|
+
# subject="Password Reset",
|
|
290
|
+
# message=f"Your password reset token is {token}",
|
|
291
|
+
# create_at=datetime.now(),
|
|
292
|
+
# is_html=False
|
|
293
|
+
# )
|
|
294
|
+
# asyncio.create_task(send_email_sendgrid(mailer))
|
|
295
|
+
# logger.info("Password reset token %s sent to %s", token, email)
|
|
296
|
+
return user
|
|
297
|
+
except Exception as e:
|
|
298
|
+
db.rollback()
|
|
299
|
+
logger.error(f"Error resetting password for {email}: {str(e)}")
|
|
300
|
+
def reset_password(reset: UserReset, db: Session) -> bool:
|
|
301
|
+
try:
|
|
302
|
+
email = reset.email
|
|
303
|
+
user = db.query(Appuser).filter(
|
|
304
|
+
(Appuser.email == email) & (Appuser.token == reset.token)
|
|
305
|
+
).first()
|
|
306
|
+
logger.info("compare %s and ",user.token, reset.token)
|
|
307
|
+
if not user:
|
|
308
|
+
raise ValueError(f"No user with email {email} or token {reset.token}")
|
|
309
|
+
user.password = encrypt_pass(reset.password)
|
|
310
|
+
db.commit()
|
|
311
|
+
return True
|
|
312
|
+
except Exception as e:
|
|
313
|
+
db.rollback()
|
|
314
|
+
logger.error(f"Error resetting password for: {str(e)}")
|
|
315
|
+
return False
|
|
316
|
+
def manual_change_password(user: UserReset, db: Session) -> bool:
|
|
317
|
+
try:
|
|
318
|
+
exist_user = db.query(Appuser).filter(Appuser.email == user.email).first()
|
|
319
|
+
if not exist_user:
|
|
320
|
+
raise ValueError(f"No user with email {user.email}")
|
|
321
|
+
if not verify_password(user.password, exist_user.password):
|
|
322
|
+
raise ValueError("Incorrect username or password") from None
|
|
323
|
+
if user.new_password is None:
|
|
324
|
+
raise ValueError("New password cannot be None")
|
|
325
|
+
exist_user.password = encrypt_pass(user.new_password)
|
|
326
|
+
db.commit()
|
|
327
|
+
return True
|
|
328
|
+
except Exception as e:
|
|
329
|
+
db.rollback()
|
|
330
|
+
logger.error(f"Error resetting password for {user.email}: {str(e)}")
|
|
331
|
+
return False
|
|
332
|
+
def create_address(address: AddressDto, db: Session) -> bool:
|
|
333
|
+
address = pg.add_model(Address,db, **address.model_dump())
|
|
334
|
+
return address
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
load_dotenv()
|
|
16
|
+
SCHEMA = os.getenv("db_schema")
|
|
17
|
+
APP = os.getenv("app_name")
|
|
18
|
+
LOGO=os.getenv("logo")
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
import enum
|
|
13
|
+
class AccessRoleEnum(enum.Enum):
|
|
14
|
+
ADMIN = "ADMIN"
|
|
15
|
+
USER = "USER"
|
|
16
|
+
GUEST = "GUEST"
|
|
17
|
+
ACCOUNT = "ACCOUNT"
|
|
18
|
+
REFEREE = "REFEREE"
|
|
19
|
+
STAFF = "STAFF"
|
|
20
|
+
class StatusEnum(enum.Enum):
|
|
21
|
+
NEW = "NEW"
|
|
22
|
+
ENROLLED = "ENROLLED"
|
|
23
|
+
ADMITTED = "ADMITTED"
|
|
24
|
+
DEBTOR = "DEBTOR"
|
|
25
|
+
DELETED = "DELETED"
|
|
26
|
+
SUSPENDED = "SUSPENDED"
|
|
27
|
+
EXPELLED = "EXPELLED"
|
|
28
|
+
PENDING = "PENDING"
|
|
29
|
+
COMPLETED = "COMPLETED"
|
|
30
|
+
FAILED = "FAILED"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Copyright 2024 Everlasting Systems and Solutions LLC (www.myeverlasting.net).
|
|
2
|
+
All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
No part of this software or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of the author, unless otherwise indicated for stand-alone materials.
|
|
5
|
+
|
|
6
|
+
For permission requests, write to the publisher at the email address below:
|
|
7
|
+
office@myeverlasting.net
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
import bcrypt
|
|
13
|
+
|
|
14
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Verify a plain password against the hashed version.
|
|
17
|
+
"""
|
|
18
|
+
# Ensure both plain_password and hashed_password are bytes
|
|
19
|
+
return bcrypt.checkpw(plain_password.encode(), hashed_password.encode())
|
|
20
|
+
def encrypt_pass(plain_password: str) -> str:
|
|
21
|
+
"""Hash a password for storing."""
|
|
22
|
+
salt = bcrypt.gensalt()
|
|
23
|
+
return bcrypt.hashpw(plain_password.encode(), salt).decode()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "es-user"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "user management for ESSL apps"
|
|
5
|
+
authors = ["Femi Adigun <femi.adigun@myeverlasting.net>"]
|
|
6
|
+
license = "mit"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.9"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["poetry-core"]
|
|
15
|
+
build-backend = "poetry.core.masonry.api"
|