Skip to content

Commit 19b90df

Browse files
authored
Add get coaching session by id (#168)
* Add a new docs/release-notes/ directory where each release's notes are stored in addition to being part of a GitHub release. * Implements a GET endpoint for retrieving a coaching session by it's specific id * Run cargo fmt * Address all of the failing clippy lint errors so the GitHub Actions lint stage passes. * Implement a UserCanAccessCoachingSession Check and use it when retrieving a coaching session by id * Run cargo fmt
1 parent ce3da4c commit 19b90df

33 files changed

+412
-162
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# 🚀 Refactor Platform v1.0.0-beta1
2+
3+
**First Public Beta Release**
4+
5+
We're excited to announce the first public beta release of the Refactor Coaching & Mentorship Platform! This Rust-based backend provides a comprehensive web API for coaching and mentoring software engineers, designed for professional coaches, informal mentors, and engineering leaders.
6+
7+
## 🎯 What's New
8+
9+
### 🏗️ **Core Platform Architecture**
10+
11+
- **Layered Architecture**: Clean separation between web, domain, entity API, and data layers
12+
- **Domain-Driven Design**: Business logic centralized in the domain layer with clear entity relationships
13+
- **Repository Pattern**: Abstracted database operations through the entity API layer
14+
- **RESTful API**: Comprehensive HTTP API with OpenAPI documentation
15+
16+
### 👥 **User Management & Authentication**
17+
18+
- **Session-based Authentication**: Secure cookie-based user sessions with `axum-login`
19+
- **Role-based Access Control**: Admin and user roles with granular permissions
20+
- **User Profiles**: Complete user management with profile updates and password changes
21+
- **Organization Membership**: Users can belong to multiple organizations
22+
23+
### 🏢 **Organization Management**
24+
25+
- **Multi-tenant Architecture**: Support for multiple coaching organizations
26+
- **Organization Administration**: Create, update, and manage coaching organizations
27+
- **User Assignment**: Add and remove users from organizations
28+
- **Hierarchical Permissions**: Organization-scoped access controls
29+
30+
### 🤝 **Coaching Relationship Management**
31+
32+
- **Coach-Coachee Relationships**: Formal relationship tracking between coaches and coachees
33+
- **Relationship Lifecycle**: Create, manage, and archive coaching relationships
34+
- **Cross-organizational Support**: Relationships can span multiple organizations
35+
36+
### 📝 **Coaching Session Management**
37+
38+
- **Session Tracking**: Schedule and document coaching sessions
39+
- **Session Notes**: Rich text note-taking with TipTap integration for collaborative editing
40+
- **Session History**: Complete timeline of coaching interactions
41+
42+
### 🎯 **Goal & Action Management**
43+
44+
- **Overarching Goals**: Long-term objectives for coaching relationships
45+
- **Action Items**: Specific, trackable commitments and next steps
46+
- **Status Tracking**: Monitor progress with customizable status workflows (Not Started, In Progress, Completed, Won't Do)
47+
- **Due Date Management**: Time-bound action items with deadline tracking
48+
49+
### 📋 **Agreements & Documentation**
50+
51+
- **Coaching Agreements**: Formal agreements and contracts management
52+
- **Document Lifecycle**: Create, update, and manage coaching documentation
53+
- **Structured Data**: Consistent data models across all coaching artifacts
54+
55+
### 🔗 **External Integrations**
56+
57+
- **TipTap Cloud**: Real-time collaborative document editing
58+
- **JWT Token Generation**: Secure token-based authentication for external services
59+
- **API Versioning**: Built-in support for API evolution and backward compatibility
60+
61+
## 🛠️ **Technical Highlights**
62+
63+
### 🦀 **Modern Rust Stack**
64+
65+
- **Axum Web Framework**: High-performance async web server
66+
- **SeaORM**: Type-safe database operations with PostgreSQL
67+
- **Tokio Runtime**: Efficient async/await concurrency
68+
- **UUID-based IDs**: Globally unique identifiers for all entities
69+
70+
### 📊 **Database & Migrations**
71+
72+
- **PostgreSQL Backend**: Robust relational database with full ACID compliance
73+
- **Schema Migrations**: Versioned database schema with rollback support
74+
- **Connection Pooling**: Efficient database connection management
75+
- **SSL Support**: Secure database connections for production environments
76+
77+
### 🚀 **Production-Ready Deployment**
78+
79+
- **Docker Containerization**: Multi-stage builds for optimized container images
80+
- **Docker Compose**: Complete local development and production deployment setup
81+
- **Health Checks**: Built-in health monitoring and service dependencies
82+
- **Environment Configuration**: Comprehensive environment variable management
83+
84+
### 🔒 **Security Features**
85+
86+
- **HTTPS/TLS**: Secure communication with SSL certificate management
87+
- **CORS Configuration**: Configurable cross-origin resource sharing
88+
- **Input Validation**: Comprehensive request validation and sanitization
89+
- **Error Handling**: Structured error responses with proper HTTP status codes
90+
91+
### 📚 **API Documentation**
92+
93+
- **OpenAPI/Swagger**: Auto-generated API documentation with RapiDoc
94+
- **Type Safety**: Full TypeScript-compatible API schemas
95+
- **Interactive Documentation**: Built-in API explorer and testing interface
96+
97+
### 🔧 **Development Experience**
98+
99+
- **Hot Reload**: Fast development iteration with cargo watch
100+
- **Database Seeding**: Test data generation for development
101+
- **Comprehensive Logging**: Structured logging with configurable levels
102+
- **Debug Tools**: Built-in debugging and diagnostic endpoints
103+
104+
## 🚀 **Deployment & Infrastructure**
105+
106+
### ☁️ **Cloud-Ready Architecture**
107+
108+
- **DigitalOcean Integration**: Automated deployment to DigitalOcean infrastructure
109+
- **Tailscale Networking**: Secure private networking for deployments
110+
- **GitHub Actions**: Automated CI/CD with comprehensive testing
111+
- **Multi-Environment Support**: Separate staging and production environments
112+
113+
### 🐳 **Container Orchestration**
114+
115+
- **Multi-Service Setup**: Coordinated deployment of backend, frontend, and database
116+
- **Nginx Reverse Proxy**: Load balancing and SSL termination
117+
- **Database Migrations**: Automated schema migrations on deployment
118+
- **Environment Variables**: Comprehensive configuration management
119+
120+
## 📖 **Documentation**
121+
122+
This release includes comprehensive documentation:
123+
124+
- **Architecture Diagrams**: Visual system overview and component relationships
125+
- **API Documentation**: Complete endpoint reference with examples
126+
- **Deployment Guides**: Step-by-step deployment instructions
127+
- **Development Setup**: Local development environment configuration
128+
- **Database Schema**: Entity-relationship diagrams and migration guides
129+
130+
## 🧪 **What's Beta About This Release**
131+
132+
This beta release is feature-complete for core coaching workflows but may have:
133+
134+
- Minor API changes before v1.0.0 stable
135+
- Additional configuration options and fine-tuning
136+
- Performance optimizations based on real-world usage
137+
- Extended test coverage and edge case handling
138+
139+
## 🔮 **What's Next**
140+
141+
Looking ahead to v1.0.0 stable:
142+
143+
- **Enhanced Reporting**: Analytics and progress tracking dashboards
144+
- **Notification System**: Email and in-app notifications for important events
145+
- **Advanced Permissions**: Fine-grained access controls and custom roles
146+
- **API Rate Limiting**: Production-scale request throttling
147+
- **Audit Logging**: Comprehensive activity tracking for compliance
148+
149+
## 🚀 **Getting Started**
150+
151+
### Prerequisites
152+
153+
- Docker & Docker Compose
154+
- PostgreSQL (local or remote)
155+
- Rust 1.70+ (for local development)
156+
157+
### Quick Start
158+
159+
```bash
160+
git clone https://github.com/refactor-group/refactor-platform-rs.git
161+
cd refactor-platform-rs
162+
docker-compose --env-file .env.local up --build
163+
```
164+
165+
Visit `http://localhost:4000/rapidoc` for interactive API documentation.
166+
167+
### Configuration
168+
169+
All configuration is managed through environment variables. See our [Environment Variables Guide](docs/runbooks/adding_new_environment_variables_backend.md) for complete configuration options.
170+
171+
## 🤝 **Contributing**
172+
173+
We welcome contributions! Please see our contributing guidelines and check out our [good first issues](https://github.com/refactor-group/refactor-platform-rs/labels/good%20first%20issue).
174+
175+
## 📞 **Support**
176+
177+
- **Documentation**: [docs/](docs/)
178+
- **Issues**: [GitHub Issues](https://github.com/refactor-group/refactor-platform-rs/issues)
179+
- **Discussions**: [GitHub Discussions](https://github.com/refactor-group/refactor-platform-rs/discussions)
180+
181+
---
182+
183+
**Full Changelog**: https://github.com/refactor-group/refactor-platform-rs/commits/1.0.0-beta1
184+
185+
_This beta release represents months of development creating a robust, scalable platform for coaching and mentoring software engineers. We're excited to get feedback from the community as we work toward our stable 1.0.0 release!_

domain/src/coaching_session.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct SessionDate(NaiveDateTime);
1919
impl SessionDate {
2020
fn new(date: NaiveDateTime) -> Result<Self, Error> {
2121
let truncated = date.duration_trunc(TimeDelta::minutes(1)).map_err(|err| {
22-
warn!("Failed to truncate date_time: {:?}", err);
22+
warn!("Failed to truncate date_time: {err:?}");
2323
Error {
2424
source: Some(Box::new(err)),
2525
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other(
@@ -48,10 +48,7 @@ pub async fn create(
4848
coaching_session_model.date = SessionDate::new(coaching_session_model.date)?.into_inner();
4949

5050
let document_name = generate_document_name(&organization.slug, &coaching_relationship.slug);
51-
info!(
52-
"Attempting to create Tiptap document with name: {}",
53-
document_name
54-
);
51+
info!("Attempting to create Tiptap document with name: {document_name}");
5552
coaching_session_model.collab_document_name = Some(document_name.clone());
5653

5754
let tiptap = TiptapDocument::new(config).await?;

domain/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub enum ExternalErrorKind {
5454

5555
impl fmt::Display for Error {
5656
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57-
write!(f, "Domain Error: {:?}", self)
57+
write!(f, "Domain Error: {self:?}")
5858
}
5959
}
6060

domain/src/gateway/tiptap.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async fn build_auth_headers(config: &Config) -> Result<reqwest::header::HeaderMa
2424
})?;
2525
let mut headers = reqwest::header::HeaderMap::new();
2626
let mut auth_value = reqwest::header::HeaderValue::from_str(&auth_key).map_err(|err| {
27-
warn!("Failed to create auth header value: {:?}", err);
27+
warn!("Failed to create auth header value: {err:?}");
2828
Error {
2929
source: Some(Box::new(err)),
3030
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other(
@@ -64,7 +64,7 @@ impl TiptapDocument {
6464
.send()
6565
.await
6666
.map_err(|e| {
67-
warn!("Failed to send request: {:?}", e);
67+
warn!("Failed to send request: {e:?}");
6868
Error {
6969
source: Some(Box::new(e)),
7070
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
@@ -75,7 +75,7 @@ impl TiptapDocument {
7575
Ok(())
7676
} else {
7777
let error_text = response.text().await.unwrap_or_default();
78-
warn!("Failed to create Tiptap document: {}", error_text);
78+
warn!("Failed to create Tiptap document: {error_text}");
7979
Err(Error {
8080
source: None,
8181
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
@@ -86,7 +86,7 @@ impl TiptapDocument {
8686
pub async fn delete(&self, document_name: &str) -> Result<(), Error> {
8787
let url = self.format_url(document_name);
8888
let response = self.client.delete(url).send().await.map_err(|e| {
89-
warn!("Failed to send request: {:?}", e);
89+
warn!("Failed to send request: {e:?}");
9090
Error {
9191
source: Some(Box::new(e)),
9292
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
@@ -97,10 +97,7 @@ impl TiptapDocument {
9797
if status.is_success() || status.as_u16() == 404 {
9898
Ok(())
9999
} else {
100-
warn!(
101-
"Failed to delete Tiptap document: {}, with status: {}",
102-
document_name, status
103-
);
100+
warn!("Failed to delete Tiptap document: {document_name}, with status: {status}");
104101
Err(Error {
105102
source: None,
106103
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),

domain/src/jwt/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ pub async fn generate_collab_token(
4848

4949
let collab_document_name = coaching_session.collab_document_name.ok_or_else(|| {
5050
warn!(
51-
"Failed to get collab document name from coaching session with ID: {}",
52-
coaching_session_id
51+
"Failed to get collab document name from coaching session with ID: {coaching_session_id}"
5352
);
5453
Error {
5554
source: None,

entity_api/src/action.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub async fn create(
1414
action_model: Model,
1515
user_id: Id,
1616
) -> Result<Model, Error> {
17-
debug!("New Action Model to be inserted: {:?}", action_model);
17+
debug!("New Action Model to be inserted: {action_model:?}");
1818

1919
let now = chrono::Utc::now();
2020

@@ -38,7 +38,7 @@ pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Mod
3838

3939
match result {
4040
Some(action) => {
41-
debug!("Existing Action model to be Updated: {:?}", action);
41+
debug!("Existing Action model to be Updated: {action:?}");
4242

4343
let active_model: ActiveModel = ActiveModel {
4444
id: Unchanged(action.id),
@@ -55,7 +55,7 @@ pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Mod
5555
Ok(active_model.update(db).await?.try_into_model()?)
5656
}
5757
None => {
58-
error!("Action with id {} not found", id);
58+
error!("Action with id {id} not found");
5959

6060
Err(Error {
6161
source: None,
@@ -74,7 +74,7 @@ pub async fn update_status(
7474

7575
match result {
7676
Some(action) => {
77-
debug!("Existing Action model to be Updated: {:?}", action);
77+
debug!("Existing Action model to be Updated: {action:?}");
7878

7979
let active_model: ActiveModel = ActiveModel {
8080
id: Unchanged(action.id),
@@ -91,7 +91,7 @@ pub async fn update_status(
9191
Ok(active_model.update(db).await?.try_into_model()?)
9292
}
9393
None => {
94-
error!("Action with id {} not found", id);
94+
error!("Action with id {id} not found");
9595

9696
Err(Error {
9797
source: None,

entity_api/src/agreement.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub async fn create(
1414
agreement_model: Model,
1515
user_id: Id,
1616
) -> Result<Model, Error> {
17-
debug!("New Agreement Model to be inserted: {:?}", agreement_model);
17+
debug!("New Agreement Model to be inserted: {agreement_model:?}");
1818

1919
let now = chrono::Utc::now();
2020

@@ -35,7 +35,7 @@ pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Mod
3535

3636
match result {
3737
Some(agreement) => {
38-
debug!("Existing Agreement model to be Updated: {:?}", agreement);
38+
debug!("Existing Agreement model to be Updated: {agreement:?}");
3939

4040
let active_model: ActiveModel = ActiveModel {
4141
id: Unchanged(agreement.id),
@@ -49,7 +49,7 @@ pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Mod
4949
Ok(active_model.update(db).await?.try_into_model()?)
5050
}
5151
None => {
52-
debug!("Agreement with id {} not found", id);
52+
debug!("Agreement with id {id} not found");
5353

5454
Err(Error {
5555
source: None,

entity_api/src/coaching_relationship.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ pub async fn create(
2222
organization_id: Id,
2323
coaching_relationship_model: Model,
2424
) -> Result<CoachingRelationshipWithUserNames, Error> {
25-
debug!(
26-
"New Coaching Relationship Model to be inserted: {:?}",
27-
coaching_relationship_model
28-
);
25+
debug!("New Coaching Relationship Model to be inserted: {coaching_relationship_model:?}");
2926

3027
let coach = user::find_by_id(db, coaching_relationship_model.coach_id).await?;
3128
let coachee = user::find_by_id(db, coaching_relationship_model.coachee_id).await?;

entity_api/src/coaching_session.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ pub async fn create(
1111
db: &DatabaseConnection,
1212
coaching_session_model: Model,
1313
) -> Result<Model, Error> {
14-
debug!(
15-
"New Coaching Session Model to be inserted: {:?}",
16-
coaching_session_model
17-
);
14+
debug!("New Coaching Session Model to be inserted: {coaching_session_model:?}");
1815

1916
let now = chrono::Utc::now();
2017

entity_api/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub enum EntityApiErrorKind {
3838

3939
impl fmt::Display for Error {
4040
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41-
write!(f, "Entity API Error: {:?}", self)
41+
write!(f, "Entity API Error: {self:?}")
4242
}
4343
}
4444

0 commit comments

Comments
 (0)