Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Altinn.Studio.Designer.Exceptions.CustomTemplate;
using Altinn.Studio.Designer.Helpers;
using Altinn.Studio.Designer.Hubs.Sync;
using Altinn.Studio.Designer.Infrastructure.ApiKeyAuth;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Models.Dto;
using Altinn.Studio.Designer.RepositoryClient.Model;
Expand Down Expand Up @@ -74,6 +75,7 @@ IBranchService branchService
/// All parameters create the search parameters
/// </remarks>
/// <returns>List of filtered repositories that user has access to.</returns>
[AllowApiKey]
[HttpGet]
[Route("search")]
public async Task<SearchResults> Search(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Reflection;
using Altinn.Studio.Designer.Infrastructure.ApiKeyAuth;
using Xunit;
using RepositoryControllerType = Altinn.Studio.Designer.Controllers.RepositoryController;

namespace Designer.Tests.Controllers.RepositoryController;

public class SearchAuthMetadataTests
{
[Fact]
public void Search_AllowsApiKeyAuthentication()
{
MethodInfo method = typeof(RepositoryControllerType).GetMethod(nameof(RepositoryControllerType.Search));

Assert.NotNull(method);
Assert.NotNull(method.GetCustomAttribute<AllowApiKeyAttribute>());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public async Task CopyRepository_TargetExistsLocally_InitialCloneMoved()
);
int actualCloneCount = Directory
.GetDirectories(developerClonePath)
.Count(d => Path.GetFileName(d) == targetRepositoryName);
.Count(d => Path.GetFileName(d).Equals(targetRepositoryName, StringComparison.Ordinal));
Assert.Equal(1, actualCloneCount);
}
finally
Expand Down
4 changes: 4 additions & 0 deletions src/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Section ordering: Added, Changed, Fixed, Removed, Security, Deprecated.

## [Unreleased]

### Added

- Add `apps search` for discovering app repositories in Altinn Studio.

## [0.1.0-preview.8] - 2026-05-13

### Changed
Expand Down
1 change: 1 addition & 0 deletions src/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ studioctl auth login --env dev --with-token < token.txt
## Core commands

- `studioctl auth login`: login with Ansattporten or `--with-token` for `prod`, `dev`, `staging`, or `local`
- `studioctl apps search`: search app repositories in Altinn Studio
- `studioctl app clone`: clone `org/repo` from the selected Altinn Studio environment
- `studioctl app run`: run app locally
- `studioctl env up`: start localtest
Expand Down
51 changes: 51 additions & 0 deletions src/cli/internal/cmd/app/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ type CloneResult struct {
AbsPath string
}

// SearchRequest contains repository search inputs.
type SearchRequest struct {
Env string
Query string
Sort string
Order string
Page int
Limit int
}

// SearchResult contains repositories found by app search.
type SearchResult struct {
Repositories []studio.Repository
TotalCount int
TotalPages int
}

// ResolveHost resolves the configured host for an environment.
func (s *Service) ResolveHost(env string) (string, error) {
creds, err := auth.LoadCredentials(s.cfg.Home)
Expand Down Expand Up @@ -114,3 +131,37 @@ func (s *Service) Clone(ctx context.Context, req CloneRequest) (CloneResult, err
AbsPath: absPath,
}, nil
}

// Search searches app repositories visible in the selected environment.
func (s *Service) Search(ctx context.Context, req SearchRequest) (SearchResult, error) {
creds, err := auth.LoadCredentials(s.cfg.Home)
if err != nil {
return SearchResult{}, fmt.Errorf("load credentials: %w", err)
}

envCreds, err := creds.Get(req.Env)
if err != nil {
if errors.Is(err, auth.ErrNotLoggedIn) {
return SearchResult{}, fmt.Errorf("%w: %s", ErrNotLoggedIn, req.Env)
}
return SearchResult{}, fmt.Errorf("get credentials for %s: %w", req.Env, err)
}

client := studio.NewClientForEnv(req.Env, s.cfg.Home, envCreds, s.cfg.Version)
result, err := client.SearchApps(ctx, studio.SearchAppsRequest{
Query: req.Query,
Sort: req.Sort,
Order: req.Order,
Page: req.Page,
Limit: req.Limit,
})
if err != nil {
return SearchResult{}, fmt.Errorf("search repos: %w", err)
}

return SearchResult{
Repositories: result.Data,
TotalCount: result.TotalCount,
TotalPages: result.TotalPages,
}, nil
}
Loading
Loading