Skip to content

Commit fd7c918

Browse files
committed
open workspace from a hash url
1 parent e7d6ea9 commit fd7c918

File tree

9 files changed

+229
-42
lines changed

9 files changed

+229
-42
lines changed

Diff for: lapdev-api/src/router.rs

+8
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ fn v1_api_routes(additional_router: Option<Router<CoreState>>) -> Router<CoreSta
162162
"/organizations/:org_id/workspaces/:workspace_name/stop",
163163
post(workspace::stop_workspace),
164164
)
165+
.route(
166+
"/organizations/:org_id/workspaces/:workspace_name/ports",
167+
get(workspace::workspace_ports),
168+
)
169+
.route(
170+
"/organizations/:org_id/workspaces/:workspace_name/ports/:port",
171+
put(workspace::update_workspace_port),
172+
)
165173
.route("/account/ssh_keys", post(account::create_ssh_key))
166174
.route("/account/ssh_keys", get(account::all_ssh_keys))
167175
.route("/account/ssh_keys/:key_id", delete(account::delete_ssh_key))

Diff for: lapdev-api/src/workspace.rs

+97-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ use axum::{
77
Json,
88
};
99
use axum_extra::{headers::Cookie, TypedHeader};
10+
use chrono::Utc;
1011
use hyper::StatusCode;
11-
use lapdev_common::{NewWorkspace, WorkspaceInfo, WorkspaceService, WorkspaceStatus};
12+
use lapdev_common::{
13+
AuditAction, AuditResourceKind, NewWorkspace, UpdateWorkspacePort, WorkspaceInfo,
14+
WorkspacePort, WorkspaceService, WorkspaceStatus,
15+
};
1216
use lapdev_db::entities;
1317
use lapdev_rpc::error::ApiError;
14-
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
18+
use sea_orm::{
19+
ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait,
20+
};
1521
use tracing::error;
1622
use uuid::Uuid;
1723

@@ -257,3 +263,92 @@ pub async fn stop_workspace(
257263
.await?;
258264
Ok(StatusCode::NO_CONTENT.into_response())
259265
}
266+
267+
pub async fn workspace_ports(
268+
TypedHeader(cookie): TypedHeader<Cookie>,
269+
Path((org_id, workspace_name)): Path<(Uuid, String)>,
270+
State(state): State<CoreState>,
271+
) -> Result<Response, ApiError> {
272+
let user = state.authenticate(&cookie).await?;
273+
state
274+
.db
275+
.get_organization_member(user.id, org_id)
276+
.await
277+
.map_err(|_| ApiError::Unauthorized)?;
278+
let ws = state
279+
.db
280+
.get_workspace_by_name(&workspace_name)
281+
.await
282+
.map_err(|_| ApiError::InvalidRequest("workspace name doesn't exist".to_string()))?;
283+
if ws.user_id != user.id {
284+
return Err(ApiError::Unauthorized);
285+
}
286+
let ports = state.db.get_workspace_ports(ws.id).await?;
287+
Ok(Json(
288+
ports
289+
.into_iter()
290+
.map(|p| WorkspacePort {
291+
port: p.port as u16,
292+
shared: p.shared,
293+
})
294+
.collect::<Vec<_>>(),
295+
)
296+
.into_response())
297+
}
298+
299+
pub async fn update_workspace_port(
300+
TypedHeader(cookie): TypedHeader<Cookie>,
301+
Path((org_id, workspace_name, port)): Path<(Uuid, String, u16)>,
302+
State(state): State<CoreState>,
303+
info: RequestInfo,
304+
Json(update_workspace_port): Json<UpdateWorkspacePort>,
305+
) -> Result<Response, ApiError> {
306+
let user = state.authenticate(&cookie).await?;
307+
state
308+
.db
309+
.get_organization_member(user.id, org_id)
310+
.await
311+
.map_err(|_| ApiError::Unauthorized)?;
312+
let ws = state
313+
.db
314+
.get_workspace_by_name(&workspace_name)
315+
.await
316+
.map_err(|_| ApiError::InvalidRequest("workspace name doesn't exist".to_string()))?;
317+
if ws.user_id != user.id {
318+
return Err(ApiError::Unauthorized);
319+
}
320+
let port = state
321+
.db
322+
.get_workspace_port(ws.id, port)
323+
.await?
324+
.ok_or_else(|| ApiError::InvalidRequest(format!("port {port} not found")))?;
325+
326+
let now = Utc::now();
327+
let txn = state.db.conn.begin().await?;
328+
state
329+
.conductor
330+
.enterprise
331+
.insert_audit_log(
332+
&txn,
333+
now.into(),
334+
ws.user_id,
335+
ws.organization_id,
336+
AuditResourceKind::Workspace.to_string(),
337+
ws.id,
338+
format!("{} port {}", ws.name, port.port),
339+
AuditAction::WorkspaceUpdate.to_string(),
340+
info.ip.clone(),
341+
info.user_agent.clone(),
342+
)
343+
.await?;
344+
entities::workspace_port::ActiveModel {
345+
id: ActiveValue::Set(port.id),
346+
shared: ActiveValue::Set(update_workspace_port.shared),
347+
..Default::default()
348+
}
349+
.update(&txn)
350+
.await?;
351+
txn.commit().await?;
352+
353+
Ok(StatusCode::NO_CONTENT.into_response())
354+
}

Diff for: lapdev-common/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ pub struct NewWorkspace {
121121
pub source: RepoSource,
122122
pub branch: Option<String>,
123123
pub machine_type_id: Uuid,
124+
pub from_hash: bool,
124125
}
125126

126127
#[derive(Serialize, Deserialize, Debug)]
@@ -315,6 +316,17 @@ pub struct WorkspaceInfo {
315316
pub hostname: String,
316317
}
317318

319+
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
320+
pub struct WorkspacePort {
321+
pub port: u16,
322+
pub shared: bool,
323+
}
324+
325+
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
326+
pub struct UpdateWorkspacePort {
327+
pub shared: bool,
328+
}
329+
318330
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
319331
pub struct WorkspaceService {
320332
pub name: String,
@@ -419,6 +431,7 @@ pub enum AuditAction {
419431
WorkspaceDelete,
420432
WorkspaceStart,
421433
WorkspaceStop,
434+
WorkspaceUpdate,
422435
ProjectCreate,
423436
ProjectDelete,
424437
ProjectUpdateEnv,

Diff for: lapdev-common/src/utils.rs

+15
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,18 @@ pub fn sha256(input: &str) -> String {
1414
let hash = Sha256::digest(input.as_bytes());
1515
base16ct::lower::encode_string(&hash)
1616
}
17+
18+
pub fn format_repo_url(repo: &str) -> String {
19+
let repo = repo.trim().to_lowercase();
20+
let repo = if !repo.starts_with("http://")
21+
&& !repo.starts_with("https://")
22+
&& !repo.starts_with("ssh://")
23+
{
24+
format!("https://{repo}")
25+
} else {
26+
repo.to_string()
27+
};
28+
repo.strip_suffix('/')
29+
.map(|r| r.to_string())
30+
.unwrap_or(repo)
31+
}

Diff for: lapdev-conductor/src/server.rs

+17-19
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use chrono::Utc;
55
use data_encoding::BASE64_MIME;
66
use futures::{channel::mpsc::UnboundedReceiver, stream::AbortHandle, SinkExt, StreamExt};
77
use git2::{Cred, RemoteCallbacks};
8+
use lapdev_common::{utils, PrebuildReplicaStatus, WorkspaceHostStatus};
89
use lapdev_common::{
910
utils::rand_string, AuditAction, AuditResourceKind, AuthProvider, BuildTarget, CpuCore,
1011
CreateWorkspaceRequest, DeleteWorkspaceRequest, GitBranch, NewProject, NewProjectResponse,
1112
NewWorkspace, NewWorkspaceResponse, PrebuildInfo, PrebuildStatus, PrebuildUpdateEvent,
1213
ProjectRequest, RepoBuildInfo, RepoBuildOutput, RepoSource, StartWorkspaceRequest,
1314
StopWorkspaceRequest, UsageResourceKind, WorkspaceStatus, WorkspaceUpdateEvent,
1415
};
15-
use lapdev_common::{PrebuildReplicaStatus, WorkspaceHostStatus};
1616
use lapdev_db::{api::DbApi, entities};
1717
use lapdev_enterprise::enterprise::Enterprise;
1818
use lapdev_rpc::{
@@ -520,21 +520,6 @@ impl Conductor {
520520
}
521521
}
522522

523-
fn format_repo_url(&self, repo: &str) -> String {
524-
let repo = repo.trim().to_lowercase();
525-
let repo = if !repo.starts_with("http://")
526-
&& !repo.starts_with("https://")
527-
&& !repo.starts_with("ssh://")
528-
{
529-
format!("https://{repo}")
530-
} else {
531-
repo.to_string()
532-
};
533-
repo.strip_suffix('/')
534-
.map(|r| r.to_string())
535-
.unwrap_or(repo)
536-
}
537-
538523
async fn get_raw_repo_details(
539524
&self,
540525
repo_url: &str,
@@ -582,7 +567,7 @@ impl Conductor {
582567
};
583568
tracing::warn!("can't open repo {local_repo_url}: {err}");
584569
ApiError::RepositoryInvalid(
585-
"Repository URL invalid or we don't have access to it. If it's a private repo, you can try to update the permission in User Settings -> Git Providers.".to_string(),
570+
format!("Repository {local_repo_url} is invalid or we don't have access to it. If it's a private repo, you can try to update the permission in User Settings -> Git Providers."),
586571
)
587572
})?
588573
};
@@ -662,7 +647,7 @@ impl Conductor {
662647
ip: Option<String>,
663648
user_agent: Option<String>,
664649
) -> Result<NewProjectResponse, ApiError> {
665-
let repo = self.format_repo_url(&project.repo);
650+
let repo = utils::format_repo_url(&project.repo);
666651

667652
let oauth = self.find_match_oauth_for_repo(&user, &repo).await?;
668653

@@ -1483,7 +1468,7 @@ impl Conductor {
14831468
) -> Result<RepoDetails, ApiError> {
14841469
let project = match source {
14851470
RepoSource::Url(repo) => {
1486-
let repo = self.format_repo_url(repo);
1471+
let repo = utils::format_repo_url(repo);
14871472
let project = self.db.get_project_by_repo(org_id, &repo).await?;
14881473
if let Some(project) = project {
14891474
project
@@ -1948,6 +1933,19 @@ impl Conductor {
19481933
workspace.branch.as_deref(),
19491934
)
19501935
.await?;
1936+
if workspace.from_hash {
1937+
// if the workspace was created from hash
1938+
// we check if there's existing workspace
1939+
if let Ok(Some(workspace)) = self
1940+
.db
1941+
.get_workspace_by_url(org.id, user.id, &repo.url)
1942+
.await
1943+
{
1944+
return Ok(NewWorkspaceResponse {
1945+
name: workspace.name,
1946+
});
1947+
}
1948+
}
19511949

19521950
let machine_type = self
19531951
.db

Diff for: lapdev-dashboard/src/account.rs

+3-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use leptos::{
88
component, create_action, create_local_resource, expect_context, use_context, view, window,
99
IntoView, Resource, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalWith,
1010
};
11-
use leptos_router::{use_location, use_params_map};
11+
use leptos_router::use_params_map;
1212

1313
use crate::{cluster::OauthSettings, modal::ErrorResponse};
1414

@@ -18,17 +18,9 @@ pub async fn get_login() -> Result<MeUser> {
1818
}
1919

2020
async fn now_login(provider: AuthProvider) -> Result<()> {
21-
let location = use_location();
22-
let next = format!("{}{}", location.pathname.get_untracked(), {
23-
let search = location.search.get_untracked();
24-
if search.is_empty() {
25-
"".to_string()
26-
} else {
27-
format!("?{search}")
28-
}
29-
});
30-
let next = urlencoding::encode(&next).to_string();
3121
let location = window().window().location();
22+
let next = location.href().unwrap_or_default();
23+
let next = urlencoding::encode(&next).to_string();
3224
let resp = Request::get("/api/private/session")
3325
.query([
3426
("provider", &provider.to_string()),

Diff for: lapdev-dashboard/src/git_provider.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,9 @@ async fn connect_oauth(
2424
provider: AuthProvider,
2525
update_read_repo: Option<bool>,
2626
) -> Result<(), ErrorResponse> {
27-
let location = use_location();
28-
let next = format!(
29-
"{}{}",
30-
location.pathname.get_untracked(),
31-
location.search.get_untracked()
32-
);
3327
let location = window().window().location();
28+
let next = location.href().unwrap_or_default();
29+
let next = urlencoding::encode(&next).to_string();
3430
let url = if update_read_repo.is_some() {
3531
"/api/v1/account/git_providers/update_scope"
3632
} else {

0 commit comments

Comments
 (0)