| 218 | } |
| 219 | |
| 220 | func Workspaces(ctx context.Context, db database.Store, query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, []codersdk.ValidationError) { |
| 221 | filter := database.GetWorkspacesParams{ |
| 222 | AgentInactiveDisconnectTimeoutSeconds: int64(agentInactiveDisconnectTimeout.Seconds()), |
| 223 | |
| 224 | // #nosec G115 - Safe conversion for pagination offset which is expected to be within int32 range |
| 225 | Offset: int32(page.Offset), |
| 226 | // #nosec G115 - Safe conversion for pagination limit which is expected to be within int32 range |
| 227 | Limit: int32(page.Limit), |
| 228 | } |
| 229 | |
| 230 | if query == "" { |
| 231 | return filter, nil |
| 232 | } |
| 233 | |
| 234 | // Always lowercase for all searches. |
| 235 | query = strings.ToLower(query) |
| 236 | values, errors := searchTerms(query, func(term string, values url.Values) error { |
| 237 | // It is a workspace name, and maybe includes an owner |
| 238 | parts := splitQueryParameterByDelimiter(term, '/', false) |
| 239 | switch len(parts) { |
| 240 | case 1: |
| 241 | values.Add("name", parts[0]) |
| 242 | case 2: |
| 243 | values.Add("owner", parts[0]) |
| 244 | values.Add("name", parts[1]) |
| 245 | default: |
| 246 | return xerrors.Errorf("Query element %q can only contain 1 '/'", term) |
| 247 | } |
| 248 | return nil |
| 249 | }) |
| 250 | if len(errors) > 0 { |
| 251 | return filter, errors |
| 252 | } |
| 253 | |
| 254 | parser := httpapi.NewQueryParamParser() |
| 255 | filter.WorkspaceIds = parser.UUIDs(values, []uuid.UUID{}, "id") |
| 256 | filter.OwnerUsername = parser.String(values, "", "owner") |
| 257 | filter.TemplateName = parser.String(values, "", "template") |
| 258 | filter.Name = parser.String(values, "", "name") |
| 259 | filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus])) |
| 260 | filter.HasAgentStatuses = parser.Strings(values, []string{}, "has-agent") |
| 261 | filter.Dormant = parser.Boolean(values, false, "dormant") |
| 262 | filter.LastUsedAfter = parser.Time3339Nano(values, time.Time{}, "last_used_after") |
| 263 | filter.LastUsedBefore = parser.Time3339Nano(values, time.Time{}, "last_used_before") |
| 264 | filter.UsingActive = sql.NullBool{ |
| 265 | // Invert the value of the query parameter to get the correct value. |
| 266 | // UsingActive returns if the workspace is on the latest template active version. |
| 267 | Bool: !parser.Boolean(values, true, "outdated"), |
| 268 | // Only include this search term if it was provided. Otherwise default to omitting it |
| 269 | // which will return all workspaces. |
| 270 | Valid: values.Has("outdated"), |
| 271 | } |
| 272 | filter.HasAITask = parser.NullableBoolean(values, sql.NullBool{}, "has-ai-task") |
| 273 | filter.HasExternalAgent = parser.NullableBoolean(values, sql.NullBool{}, "has_external_agent") |
| 274 | filter.OrganizationID = parseOrganization(ctx, db, parser, values, "organization") |
| 275 | filter.Shared = parser.NullableBoolean(values, sql.NullBool{}, "shared") |
| 276 | // TODO: support "me" by passing in the actorID |
| 277 | filter.SharedWithUserID = parseUser(ctx, db, parser, values, "shared_with_user", uuid.Nil) |