mirror of
https://github.com/coder/coder.git
synced 2026-10-31 19:36:41 +08:00
feat: add chat model config RBAC resource (#27957)
Add the `chat_model_config` RBAC resource, actions, and API key scopes. This layer includes the scope migration and generated API types. Model list queries apply token scopes, SQL authorization filters, and row ACLs. Write and default-management handlers keep deployment configuration authorization until the next API layer replaces it. Depends on #27956 _This pull request description was generated by Coder Agents._
This commit is contained in:
@@ -816,6 +816,7 @@ var (
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceAIProvider.Type: {policy.ActionRead},
|
||||
rbac.ResourceChat.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceChatModelConfig.Type: {policy.ActionRead},
|
||||
rbac.ResourceWorkspace.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
rbac.ResourceDeploymentConfig.Type: {policy.ActionRead},
|
||||
rbac.ResourceMCPServerConfig.Type: {policy.ActionRead},
|
||||
@@ -2221,6 +2222,8 @@ func (q *querier) DeleteChatDebugDataByChatID(ctx context.Context, arg database.
|
||||
}
|
||||
|
||||
func (q *querier) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) (uuid.UUID, error) {
|
||||
// Interim write gate: see InsertChatModelConfig.
|
||||
// TODO(mafredri): swap to ResourceChatModelConfig object delete after CODAGT-709 M3 (org-scoping cutover)
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
@@ -3509,6 +3512,8 @@ func (q *querier) GetChatMessagesForPromptByChatID(ctx context.Context, chatID u
|
||||
}
|
||||
|
||||
func (q *querier) GetChatModelConfigByID(ctx context.Context, id uuid.UUID) (database.ChatModelConfig, error) {
|
||||
// Interim read gate: see InsertChatModelConfig.
|
||||
// TODO(mafredri): swap to ResourceChatModelConfig object read after CODAGT-709 M3 (org-scoping cutover)
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return database.ChatModelConfig{}, err
|
||||
}
|
||||
@@ -3516,10 +3521,11 @@ func (q *querier) GetChatModelConfigByID(ctx context.Context, id uuid.UUID) (dat
|
||||
}
|
||||
|
||||
func (q *querier) GetChatModelConfigs(ctx context.Context) ([]database.ChatModelConfig, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return nil, err
|
||||
prep, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceChatModelConfig.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("(dev error) prepare sql filter: %w", err)
|
||||
}
|
||||
return q.db.GetChatModelConfigs(ctx)
|
||||
return q.db.GetAuthorizedChatModelConfigs(ctx, prep)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatModelConfigsForTelemetry(ctx context.Context) ([]database.GetChatModelConfigsForTelemetryRow, error) {
|
||||
@@ -3755,12 +3761,8 @@ func (q *querier) GetDatabaseNow(ctx context.Context) (time.Time, error) {
|
||||
}
|
||||
|
||||
func (q *querier) GetDefaultChatModelConfig(ctx context.Context, organizationID uuid.UUID) (database.ChatModelConfig, error) {
|
||||
// Reading the default model config is needed for chat creation.
|
||||
// TODO(CODAGT-161): scope this check when org context is available.
|
||||
// This function has no org context to scope the check, and
|
||||
// ResourceDeploymentConfig is too restrictive (admin-only).
|
||||
// The handler layer gates chat creation via ActionCreate on
|
||||
// the org-scoped ResourceChat.
|
||||
// Chat creation authorizes the organization-scoped chat before this lookup.
|
||||
// TODO(mafredri): remove after CODAGT-709 M3.
|
||||
if _, ok := ActorFromContext(ctx); !ok {
|
||||
return database.ChatModelConfig{}, ErrNoActor
|
||||
}
|
||||
@@ -3800,22 +3802,25 @@ func (q *querier) GetEligibleProvisionerDaemonsByProvisionerJobIDs(ctx context.C
|
||||
}
|
||||
|
||||
func (q *querier) GetEnabledChatModelConfigByID(ctx context.Context, id uuid.UUID) (database.ChatModelConfig, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
// TODO(mafredri): authorize the fetched object after CODAGT-709 M3
|
||||
// (org-scoping cutover).
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChatModelConfig); err != nil {
|
||||
return database.ChatModelConfig{}, err
|
||||
}
|
||||
return q.db.GetEnabledChatModelConfigByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetEnabledChatModelConfigs(ctx context.Context) ([]database.GetEnabledChatModelConfigsRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
// TODO(mafredri): replace this deployment-wide query after CODAGT-709 M3
|
||||
// (org-scoping cutover).
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChatModelConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetEnabledChatModelConfigs(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetEnabledChatModelConfigsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.GetEnabledChatModelConfigsByOrganizationRow, error) {
|
||||
// TODO(CODAGT-161): authorize against an organization-scoped chat model config resource.
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChatModelConfig.InOrg(organizationID)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetEnabledChatModelConfigsByOrganization(ctx, organizationID)
|
||||
@@ -6111,6 +6116,9 @@ func (q *querier) InsertChatMessages(ctx context.Context, arg database.InsertCha
|
||||
}
|
||||
|
||||
func (q *querier) InsertChatModelConfig(ctx context.Context, arg database.InsertChatModelConfigParams) (database.ChatModelConfig, error) {
|
||||
// The routes create configs in the default organization. Organization
|
||||
// administrators must not manage configs that other organizations can read.
|
||||
// TODO(mafredri): swap to ResourceChatModelConfig create-in-org after CODAGT-709 M3 (org-scoping cutover)
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return database.ChatModelConfig{}, err
|
||||
}
|
||||
@@ -7314,7 +7322,9 @@ func (q *querier) UnpinChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
}
|
||||
|
||||
func (q *querier) UnsetDefaultChatModelConfigs(ctx context.Context, organizationID uuid.UUID) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
// Interim write gate: see InsertChatModelConfig.
|
||||
// TODO(mafredri): swap to ResourceChatModelConfig update-in-org after CODAGT-709 M3 (org-scoping cutover)
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UnsetDefaultChatModelConfigs(ctx, organizationID)
|
||||
@@ -7493,12 +7503,30 @@ func (q *querier) UpdateChatMCPServerIDs(ctx context.Context, arg database.Updat
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatModelConfig(ctx context.Context, arg database.UpdateChatModelConfigParams) (database.ChatModelConfig, error) {
|
||||
// Interim write gate: see InsertChatModelConfig.
|
||||
// TODO(mafredri): swap to ResourceChatModelConfig object update after CODAGT-709 M3 (org-scoping cutover)
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return database.ChatModelConfig{}, err
|
||||
}
|
||||
return q.db.UpdateChatModelConfig(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatModelConfigACLByID(ctx context.Context, arg database.UpdateChatModelConfigACLByIDParams) (database.ChatModelConfig, error) {
|
||||
config, err := q.db.GetChatModelConfigByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
return database.ChatModelConfig{}, err
|
||||
}
|
||||
object := rbac.ResourceChatModelConfig.
|
||||
WithID(config.ID).
|
||||
InOrg(config.OrganizationID).
|
||||
WithACLUserList(config.UserACL.RBACACL()).
|
||||
WithGroupACL(config.GroupACL.RBACACL())
|
||||
if err := q.authorizeContext(ctx, policy.ActionShare, object); err != nil {
|
||||
return database.ChatModelConfig{}, err
|
||||
}
|
||||
return q.db.UpdateChatModelConfigACLByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatPinOrder(ctx context.Context, arg database.UpdateChatPinOrderParams) error {
|
||||
chat, err := q.db.GetChatByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
@@ -9450,6 +9478,20 @@ func (q *querier) GetAuthorizedChatsByChatFileID(ctx context.Context, fileID uui
|
||||
return q.db.GetAuthorizedChatsByChatFileID(ctx, fileID, prepared)
|
||||
}
|
||||
|
||||
func (q *querier) GetAuthorizedChatModelConfigs(ctx context.Context, prepared rbac.PreparedAuthorized) ([]database.ChatModelConfig, error) {
|
||||
return q.db.GetAuthorizedChatModelConfigs(ctx, prepared)
|
||||
}
|
||||
|
||||
// GetDefaultChatModelConfigCandidates returns all non-deleted configs.
|
||||
// A filtered result can leave an organization without a default.
|
||||
// TODO(mafredri): remove after CODAGT-709 M3.
|
||||
func (q *querier) GetDefaultChatModelConfigCandidates(ctx context.Context) ([]database.ChatModelConfig, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetDefaultChatModelConfigCandidates(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetAuthorizedMCPServerConfigs(ctx context.Context, organizationID uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.MCPServerConfig, error) {
|
||||
return q.db.GetAuthorizedMCPServerConfigs(ctx, organizationID, prepared)
|
||||
}
|
||||
|
||||
@@ -276,6 +276,105 @@ func TestUpdateChatACLByIDGuards(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateChatModelConfigACLByIDAuthorization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
organizationID := uuid.New()
|
||||
otherOrganizationID := uuid.New()
|
||||
readerID := uuid.New()
|
||||
config := database.ChatModelConfig{
|
||||
ID: uuid.New(),
|
||||
OrganizationID: organizationID,
|
||||
UserACL: database.ChatACL{
|
||||
readerID.String(): {Permissions: []policy.Action{policy.ActionRead}},
|
||||
},
|
||||
GroupACL: database.ChatACL{},
|
||||
}
|
||||
arg := database.UpdateChatModelConfigACLByIDParams{
|
||||
ID: config.ID,
|
||||
GroupACL: database.ChatACL{},
|
||||
UserACL: database.ChatACL{},
|
||||
UpdatedBy: uuid.NullUUID{UUID: uuid.New(), Valid: true},
|
||||
}
|
||||
updated := config
|
||||
updated.GroupACL = arg.GroupACL
|
||||
updated.UserACL = arg.UserACL
|
||||
updated.UpdatedBy = arg.UpdatedBy
|
||||
|
||||
orgAdminSubject := func(id uuid.UUID, orgID uuid.UUID) rbac.Subject {
|
||||
return rbac.Subject{
|
||||
ID: id.String(),
|
||||
Roles: rbac.Roles(must(rbac.RoleIdentifiers{rbac.ScopedRoleOrgAdmin(orgID)}.Expand())),
|
||||
Groups: []string{},
|
||||
Scope: rbac.ExpandableScope(rbac.ScopeAll),
|
||||
}
|
||||
}
|
||||
readerSubject := rbac.Subject{
|
||||
ID: readerID.String(),
|
||||
Roles: rbac.Roles(must(rbac.RoleIdentifiers{rbac.RoleMember()}.Expand())),
|
||||
Groups: []string{},
|
||||
Scope: rbac.ExpandableScope(rbac.ScopeAll),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
subject rbac.Subject
|
||||
fetchError error
|
||||
allowed bool
|
||||
}{
|
||||
{
|
||||
name: "Allowed",
|
||||
subject: orgAdminSubject(uuid.New(), organizationID),
|
||||
allowed: true,
|
||||
},
|
||||
{
|
||||
name: "DeniedReadOnlyACL",
|
||||
subject: readerSubject,
|
||||
},
|
||||
{
|
||||
name: "HiddenRow",
|
||||
subject: orgAdminSubject(uuid.New(), organizationID),
|
||||
fetchError: sql.ErrNoRows,
|
||||
},
|
||||
{
|
||||
name: "CrossOrganization",
|
||||
subject: orgAdminSubject(uuid.New(), otherOrganizationID),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
db := dbmock.NewMockStore(ctrl)
|
||||
db.EXPECT().Wrappers().Return([]string{}).AnyTimes()
|
||||
db.EXPECT().GetChatModelConfigByID(gomock.Any(), config.ID).Return(config, testCase.fetchError)
|
||||
if testCase.allowed {
|
||||
db.EXPECT().UpdateChatModelConfigACLByID(gomock.Any(), arg).Return(updated, nil)
|
||||
}
|
||||
|
||||
q := dbauthz.New(db, rbac.NewAuthorizer(prometheus.NewRegistry()), slogtest.Make(t, &slogtest.Options{
|
||||
IgnoreErrors: true,
|
||||
}), coderdtest.AccessControlStorePointer())
|
||||
got, err := q.UpdateChatModelConfigACLByID(dbauthz.As(t.Context(), testCase.subject), arg)
|
||||
|
||||
switch {
|
||||
case testCase.allowed:
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, updated, got)
|
||||
case testCase.fetchError != nil:
|
||||
require.ErrorIs(t, err, testCase.fetchError)
|
||||
require.Equal(t, database.ChatModelConfig{}, got)
|
||||
default:
|
||||
require.Error(t, err)
|
||||
require.True(t, dbauthz.IsNotAuthorizedError(err), "expected authorization failure, got: %v", err)
|
||||
require.Equal(t, database.ChatModelConfig{}, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDBAuthzRecursive is a simple test to search for infinite recursion
|
||||
// bugs. It isn't perfect, and only catches a subset of the possible bugs
|
||||
// as only the first db call will be made. But it is better than nothing.
|
||||
@@ -1083,10 +1182,20 @@ func (s *MethodTestSuite) TestChats() {
|
||||
dbm.EXPECT().GetDefaultChatModelConfig(gomock.Any(), config.OrganizationID).Return(config, nil).AnyTimes()
|
||||
check.Args(config.OrganizationID).Asserts().Returns(config)
|
||||
}))
|
||||
s.Run("GetChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
s.Run("GetChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().GetAuthorizedChatModelConfigs(gomock.Any(), gomock.Any()).Return([]database.ChatModelConfig{}, nil).AnyTimes()
|
||||
// No asserts here because SQLFilter.
|
||||
check.Args().Asserts()
|
||||
}))
|
||||
s.Run("GetAuthorizedChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().GetAuthorizedChatModelConfigs(gomock.Any(), gomock.Any()).Return([]database.ChatModelConfig{}, nil).AnyTimes()
|
||||
// No asserts here because callers provide the SQL filter.
|
||||
check.Args(emptyPreparedAuthorized{}).Asserts()
|
||||
}))
|
||||
s.Run("GetDefaultChatModelConfigCandidates", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
configA := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
|
||||
configB := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
|
||||
dbm.EXPECT().GetChatModelConfigs(gomock.Any()).Return([]database.ChatModelConfig{configA, configB}, nil).AnyTimes()
|
||||
dbm.EXPECT().GetDefaultChatModelConfigCandidates(gomock.Any()).Return([]database.ChatModelConfig{configA, configB}, nil).AnyTimes()
|
||||
check.Args().Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.ChatModelConfig{configA, configB})
|
||||
}))
|
||||
|
||||
@@ -1194,13 +1303,13 @@ func (s *MethodTestSuite) TestChats() {
|
||||
s.Run("GetEnabledChatModelConfigByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
config := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
|
||||
dbm.EXPECT().GetEnabledChatModelConfigByID(gomock.Any(), config.ID).Return(config, nil).AnyTimes()
|
||||
check.Args(config.ID).Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns(config)
|
||||
check.Args(config.ID).Asserts(rbac.ResourceChatModelConfig, policy.ActionRead).Returns(config)
|
||||
}))
|
||||
s.Run("GetEnabledChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
rowA := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsRow{})
|
||||
rowB := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsRow{})
|
||||
dbm.EXPECT().GetEnabledChatModelConfigs(gomock.Any()).Return([]database.GetEnabledChatModelConfigsRow{rowA, rowB}, nil).AnyTimes()
|
||||
check.Args().Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsRow{rowA, rowB})
|
||||
check.Args().Asserts(rbac.ResourceChatModelConfig, policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsRow{rowA, rowB})
|
||||
}))
|
||||
|
||||
s.Run("GetEnabledChatModelConfigsByOrganization", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
@@ -1208,7 +1317,7 @@ func (s *MethodTestSuite) TestChats() {
|
||||
rowA := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsByOrganizationRow{})
|
||||
rowB := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsByOrganizationRow{})
|
||||
dbm.EXPECT().GetEnabledChatModelConfigsByOrganization(gomock.Any(), orgID).Return([]database.GetEnabledChatModelConfigsByOrganizationRow{rowA, rowB}, nil).AnyTimes()
|
||||
check.Args(orgID).Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsByOrganizationRow{rowA, rowB})
|
||||
check.Args(orgID).Asserts(rbac.ResourceChatModelConfig.InOrg(orgID), policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsByOrganizationRow{rowA, rowB})
|
||||
}))
|
||||
|
||||
s.Run("GetStaleChats", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
@@ -1489,6 +1598,28 @@ func (s *MethodTestSuite) TestChats() {
|
||||
check.Args(arg).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate).Returns(config)
|
||||
}))
|
||||
|
||||
s.Run("UpdateChatModelConfigACLByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
config := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
|
||||
arg := database.UpdateChatModelConfigACLByIDParams{
|
||||
ID: config.ID,
|
||||
GroupACL: database.ChatACL{},
|
||||
UserACL: database.ChatACL{},
|
||||
UpdatedBy: uuid.NullUUID{UUID: uuid.New(), Valid: true},
|
||||
}
|
||||
updated := config
|
||||
updated.GroupACL = arg.GroupACL
|
||||
updated.UserACL = arg.UserACL
|
||||
updated.UpdatedBy = arg.UpdatedBy
|
||||
dbm.EXPECT().GetChatModelConfigByID(gomock.Any(), config.ID).Return(config, nil).AnyTimes()
|
||||
dbm.EXPECT().UpdateChatModelConfigACLByID(gomock.Any(), arg).Return(updated, nil).AnyTimes()
|
||||
object := rbac.ResourceChatModelConfig.
|
||||
WithID(config.ID).
|
||||
InOrg(config.OrganizationID).
|
||||
WithACLUserList(config.UserACL.RBACACL()).
|
||||
WithGroupACL(config.GroupACL.RBACACL())
|
||||
check.Args(arg).Asserts(object, policy.ActionShare).Returns(updated)
|
||||
}))
|
||||
|
||||
s.Run("UpdateChatPinOrder", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
arg := database.UpdateChatPinOrderParams{
|
||||
@@ -1537,7 +1668,7 @@ func (s *MethodTestSuite) TestChats() {
|
||||
s.Run("UnsetDefaultChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
orgID := uuid.New()
|
||||
dbm.EXPECT().UnsetDefaultChatModelConfigs(gomock.Any(), orgID).Return(nil).AnyTimes()
|
||||
check.Args(orgID).Asserts(rbac.ResourceSystem, policy.ActionUpdate)
|
||||
check.Args(orgID).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("UpsertChatDiffStatus", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
|
||||
+24
@@ -5320,6 +5320,14 @@ func (m queryMetricsStore) UpdateChatModelConfig(ctx context.Context, arg databa
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatModelConfigACLByID(ctx context.Context, arg database.UpdateChatModelConfigACLByIDParams) (database.ChatModelConfig, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateChatModelConfigACLByID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateChatModelConfigACLByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatModelConfigACLByID").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatPinOrder(ctx context.Context, arg database.UpdateChatPinOrderParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateChatPinOrder(ctx, arg)
|
||||
@@ -6832,6 +6840,22 @@ func (m queryMetricsStore) GetAuthorizedChatsByChatFileID(ctx context.Context, f
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAuthorizedChatModelConfigs(ctx context.Context, prepared rbac.PreparedAuthorized) ([]database.ChatModelConfig, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAuthorizedChatModelConfigs(ctx, prepared)
|
||||
m.queryLatencies.WithLabelValues("GetAuthorizedChatModelConfigs").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetAuthorizedChatModelConfigs").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetDefaultChatModelConfigCandidates(ctx context.Context) ([]database.ChatModelConfig, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetDefaultChatModelConfigCandidates(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetDefaultChatModelConfigCandidates").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetDefaultChatModelConfigCandidates").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAuthorizedMCPServerConfigs(ctx context.Context, organizationID uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.MCPServerConfig, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAuthorizedMCPServerConfigs(ctx, organizationID, prepared)
|
||||
|
||||
Generated
+45
@@ -2475,6 +2475,21 @@ func (mr *MockStoreMockRecorder) GetAuthorizedAuditLogsOffset(ctx, arg, prepared
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedAuditLogsOffset", reflect.TypeOf((*MockStore)(nil).GetAuthorizedAuditLogsOffset), ctx, arg, prepared)
|
||||
}
|
||||
|
||||
// GetAuthorizedChatModelConfigs mocks base method.
|
||||
func (m *MockStore) GetAuthorizedChatModelConfigs(ctx context.Context, prepared rbac.PreparedAuthorized) ([]database.ChatModelConfig, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAuthorizedChatModelConfigs", ctx, prepared)
|
||||
ret0, _ := ret[0].([]database.ChatModelConfig)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAuthorizedChatModelConfigs indicates an expected call of GetAuthorizedChatModelConfigs.
|
||||
func (mr *MockStoreMockRecorder) GetAuthorizedChatModelConfigs(ctx, prepared any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedChatModelConfigs", reflect.TypeOf((*MockStore)(nil).GetAuthorizedChatModelConfigs), ctx, prepared)
|
||||
}
|
||||
|
||||
// GetAuthorizedChats mocks base method.
|
||||
func (m *MockStore) GetAuthorizedChats(ctx context.Context, arg database.GetChatsParams, prepared rbac.PreparedAuthorized) ([]database.GetChatsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -3630,6 +3645,21 @@ func (mr *MockStoreMockRecorder) GetDefaultChatModelConfig(ctx, organizationID a
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultChatModelConfig", reflect.TypeOf((*MockStore)(nil).GetDefaultChatModelConfig), ctx, organizationID)
|
||||
}
|
||||
|
||||
// GetDefaultChatModelConfigCandidates mocks base method.
|
||||
func (m *MockStore) GetDefaultChatModelConfigCandidates(ctx context.Context) ([]database.ChatModelConfig, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDefaultChatModelConfigCandidates", ctx)
|
||||
ret0, _ := ret[0].([]database.ChatModelConfig)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetDefaultChatModelConfigCandidates indicates an expected call of GetDefaultChatModelConfigCandidates.
|
||||
func (mr *MockStoreMockRecorder) GetDefaultChatModelConfigCandidates(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultChatModelConfigCandidates", reflect.TypeOf((*MockStore)(nil).GetDefaultChatModelConfigCandidates), ctx)
|
||||
}
|
||||
|
||||
// GetDefaultOrganization mocks base method.
|
||||
func (m *MockStore) GetDefaultOrganization(ctx context.Context) (database.Organization, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -10073,6 +10103,21 @@ func (mr *MockStoreMockRecorder) UpdateChatModelConfig(ctx, arg any) *gomock.Cal
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatModelConfig", reflect.TypeOf((*MockStore)(nil).UpdateChatModelConfig), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatModelConfigACLByID mocks base method.
|
||||
func (m *MockStore) UpdateChatModelConfigACLByID(ctx context.Context, arg database.UpdateChatModelConfigACLByIDParams) (database.ChatModelConfig, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateChatModelConfigACLByID", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatModelConfig)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateChatModelConfigACLByID indicates an expected call of UpdateChatModelConfigACLByID.
|
||||
func (mr *MockStoreMockRecorder) UpdateChatModelConfigACLByID(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatModelConfigACLByID", reflect.TypeOf((*MockStore)(nil).UpdateChatModelConfigACLByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatPinOrder mocks base method.
|
||||
func (m *MockStore) UpdateChatPinOrder(ctx context.Context, arg database.UpdateChatPinOrderParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+9
-2
@@ -284,7 +284,13 @@ CREATE TYPE api_key_scope AS ENUM (
|
||||
'mcp_server_config:read',
|
||||
'mcp_server_config:update',
|
||||
'mcp_server_config:delete',
|
||||
'mcp_server_config:share'
|
||||
'mcp_server_config:share',
|
||||
'chat_model_config:*',
|
||||
'chat_model_config:create',
|
||||
'chat_model_config:read',
|
||||
'chat_model_config:update',
|
||||
'chat_model_config:delete',
|
||||
'chat_model_config:share'
|
||||
);
|
||||
|
||||
CREATE TYPE app_sharing_level AS ENUM (
|
||||
@@ -611,7 +617,8 @@ CREATE TYPE resource_type AS ENUM (
|
||||
'user_ai_budget_override',
|
||||
'oauth2_provider_settings',
|
||||
'chat_instruction_settings',
|
||||
'mcp_server_config'
|
||||
'mcp_server_config',
|
||||
'chat_model_config'
|
||||
);
|
||||
|
||||
CREATE TYPE shareable_workspace_owners AS ENUM (
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Enum additions to api_key_scope are intentionally not reverted because
|
||||
-- Postgres cannot drop enum values safely.
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_model_config:*';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_model_config:create';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_model_config:read';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_model_config:update';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_model_config:delete';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_model_config:share';
|
||||
@@ -0,0 +1 @@
|
||||
-- No-op, enum values can't be dropped.
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TYPE resource_type
|
||||
ADD VALUE IF NOT EXISTS 'chat_model_config';
|
||||
@@ -53,9 +53,76 @@ type customQuerier interface {
|
||||
connectionLogQuerier
|
||||
aibridgeQuerier
|
||||
chatQuerier
|
||||
chatModelConfigQuerier
|
||||
mcpServerConfigQuerier
|
||||
}
|
||||
|
||||
type chatModelConfigQuerier interface {
|
||||
GetAuthorizedChatModelConfigs(ctx context.Context, prepared rbac.PreparedAuthorized) ([]ChatModelConfig, error)
|
||||
// GetDefaultChatModelConfigCandidates returns every non-deleted config in
|
||||
// every organization without an authorization filter. The caller selects
|
||||
// one organization's promotion candidate from this deployment-wide list.
|
||||
GetDefaultChatModelConfigCandidates(ctx context.Context) ([]ChatModelConfig, error)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetDefaultChatModelConfigCandidates(ctx context.Context) ([]ChatModelConfig, error) {
|
||||
return q.GetChatModelConfigs(ctx)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetAuthorizedChatModelConfigs(ctx context.Context, prepared rbac.PreparedAuthorized) ([]ChatModelConfig, error) {
|
||||
authorizedFilter, err := prepared.CompileToSQL(ctx, rbac.ConfigChatModelConfigs())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("compile authorized filter: %w", err)
|
||||
}
|
||||
|
||||
filtered, err := insertAuthorizedFilter(getChatModelConfigs, fmt.Sprintf(" AND %s", authorizedFilter))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("insert authorized filter: %w", err)
|
||||
}
|
||||
|
||||
// The name comment is for metric tracking
|
||||
query := fmt.Sprintf("-- name: GetAuthorizedChatModelConfigs :many\n%s", filtered)
|
||||
rows, err := q.db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ChatModelConfig
|
||||
for rows.Next() {
|
||||
var i ChatModelConfig
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Model,
|
||||
&i.DisplayName,
|
||||
&i.CreatedBy,
|
||||
&i.UpdatedBy,
|
||||
&i.Enabled,
|
||||
&i.IsDefault,
|
||||
&i.Deleted,
|
||||
&i.DeletedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ContextLimit,
|
||||
&i.CompressionThreshold,
|
||||
&i.Options,
|
||||
&i.AIProviderID,
|
||||
&i.OrganizationID,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
type templateQuerier interface {
|
||||
GetAuthorizedTemplates(ctx context.Context, arg GetTemplatesWithFilterParams, prepared rbac.PreparedAuthorized) ([]Template, error)
|
||||
GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error)
|
||||
|
||||
Generated
+23
-2
@@ -529,6 +529,12 @@ const (
|
||||
ApiKeyScopeMcpServerConfigUpdate APIKeyScope = "mcp_server_config:update"
|
||||
ApiKeyScopeMcpServerConfigDelete APIKeyScope = "mcp_server_config:delete"
|
||||
ApiKeyScopeMcpServerConfigShare APIKeyScope = "mcp_server_config:share"
|
||||
ApiKeyScopeChatModelConfig APIKeyScope = "chat_model_config:*"
|
||||
ApiKeyScopeChatModelConfigCreate APIKeyScope = "chat_model_config:create"
|
||||
ApiKeyScopeChatModelConfigRead APIKeyScope = "chat_model_config:read"
|
||||
ApiKeyScopeChatModelConfigUpdate APIKeyScope = "chat_model_config:update"
|
||||
ApiKeyScopeChatModelConfigDelete APIKeyScope = "chat_model_config:delete"
|
||||
ApiKeyScopeChatModelConfigShare APIKeyScope = "chat_model_config:share"
|
||||
)
|
||||
|
||||
func (e *APIKeyScope) Scan(src interface{}) error {
|
||||
@@ -809,7 +815,13 @@ func (e APIKeyScope) Valid() bool {
|
||||
ApiKeyScopeMcpServerConfigRead,
|
||||
ApiKeyScopeMcpServerConfigUpdate,
|
||||
ApiKeyScopeMcpServerConfigDelete,
|
||||
ApiKeyScopeMcpServerConfigShare:
|
||||
ApiKeyScopeMcpServerConfigShare,
|
||||
ApiKeyScopeChatModelConfig,
|
||||
ApiKeyScopeChatModelConfigCreate,
|
||||
ApiKeyScopeChatModelConfigRead,
|
||||
ApiKeyScopeChatModelConfigUpdate,
|
||||
ApiKeyScopeChatModelConfigDelete,
|
||||
ApiKeyScopeChatModelConfigShare:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -1059,6 +1071,12 @@ func AllAPIKeyScopeValues() []APIKeyScope {
|
||||
ApiKeyScopeMcpServerConfigUpdate,
|
||||
ApiKeyScopeMcpServerConfigDelete,
|
||||
ApiKeyScopeMcpServerConfigShare,
|
||||
ApiKeyScopeChatModelConfig,
|
||||
ApiKeyScopeChatModelConfigCreate,
|
||||
ApiKeyScopeChatModelConfigRead,
|
||||
ApiKeyScopeChatModelConfigUpdate,
|
||||
ApiKeyScopeChatModelConfigDelete,
|
||||
ApiKeyScopeChatModelConfigShare,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3613,6 +3631,7 @@ const (
|
||||
ResourceTypeOauth2ProviderSettings ResourceType = "oauth2_provider_settings"
|
||||
ResourceTypeChatInstructionSettings ResourceType = "chat_instruction_settings"
|
||||
ResourceTypeMCPServerConfig ResourceType = "mcp_server_config"
|
||||
ResourceTypeChatModelConfig ResourceType = "chat_model_config"
|
||||
)
|
||||
|
||||
func (e *ResourceType) Scan(src interface{}) error {
|
||||
@@ -3689,7 +3708,8 @@ func (e ResourceType) Valid() bool {
|
||||
ResourceTypeUserAIBudgetOverride,
|
||||
ResourceTypeOauth2ProviderSettings,
|
||||
ResourceTypeChatInstructionSettings,
|
||||
ResourceTypeMCPServerConfig:
|
||||
ResourceTypeMCPServerConfig,
|
||||
ResourceTypeChatModelConfig:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -3735,6 +3755,7 @@ func AllResourceTypeValues() []ResourceType {
|
||||
ResourceTypeOauth2ProviderSettings,
|
||||
ResourceTypeChatInstructionSettings,
|
||||
ResourceTypeMCPServerConfig,
|
||||
ResourceTypeChatModelConfig,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+1
@@ -1483,6 +1483,7 @@ type sqlcQuerier interface {
|
||||
UpdateChatLastTurnSummary(ctx context.Context, arg UpdateChatLastTurnSummaryParams) (int64, error)
|
||||
UpdateChatMCPServerIDs(ctx context.Context, arg UpdateChatMCPServerIDsParams) (Chat, error)
|
||||
UpdateChatModelConfig(ctx context.Context, arg UpdateChatModelConfigParams) (ChatModelConfig, error)
|
||||
UpdateChatModelConfigACLByID(ctx context.Context, arg UpdateChatModelConfigACLByIDParams) (ChatModelConfig, error)
|
||||
UpdateChatPinOrder(ctx context.Context, arg UpdateChatPinOrderParams) error
|
||||
UpdateChatPlanModeByID(ctx context.Context, arg UpdateChatPlanModeByIDParams) (Chat, error)
|
||||
// Stores the client-visible retry payload. retry_state_version is
|
||||
|
||||
@@ -12366,6 +12366,83 @@ func TestDeleteChatModelConfigByID(t *testing.T) {
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
}
|
||||
|
||||
func TestUpdateChatModelConfigACLByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store, _ := dbtestutil.NewDB(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
groupACL database.ChatACL
|
||||
userACL database.ChatACL
|
||||
}{
|
||||
{
|
||||
name: "Replace",
|
||||
groupACL: database.ChatACL{
|
||||
uuid.NewString(): {Permissions: []policy.Action{policy.ActionRead}},
|
||||
},
|
||||
userACL: database.ChatACL{
|
||||
uuid.NewString(): {Permissions: []policy.Action{policy.ActionRead}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Clear",
|
||||
groupACL: database.ChatACL{},
|
||||
userACL: database.ChatACL{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
organization := dbgen.Organization(t, store, database.Organization{})
|
||||
creator := dbgen.User(t, store, database.User{})
|
||||
updater := dbgen.User(t, store, database.User{})
|
||||
config := dbgen.ChatModelConfig(t, store, database.ChatModelConfig{
|
||||
Model: "acl-model-" + uuid.NewString(),
|
||||
DisplayName: "ACL model " + uuid.NewString(),
|
||||
CreatedBy: uuid.NullUUID{UUID: creator.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: creator.ID, Valid: true},
|
||||
IsDefault: true,
|
||||
ContextLimit: 4242,
|
||||
CompressionThreshold: 37,
|
||||
Options: json.RawMessage(`{"temperature":0.25}`),
|
||||
OrganizationID: organization.ID,
|
||||
GroupACL: database.ChatACL{
|
||||
organization.ID.String(): {Permissions: []policy.Action{policy.ActionRead}},
|
||||
},
|
||||
UserACL: database.ChatACL{
|
||||
creator.ID.String(): {Permissions: []policy.Action{policy.ActionRead}},
|
||||
},
|
||||
})
|
||||
|
||||
before, err := store.GetChatModelConfigByID(ctx, config.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := store.UpdateChatModelConfigACLByID(ctx, database.UpdateChatModelConfigACLByIDParams{
|
||||
ID: config.ID,
|
||||
GroupACL: tt.groupACL,
|
||||
UserACL: tt.userACL,
|
||||
UpdatedBy: uuid.NullUUID{UUID: updater.ID, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.groupACL, updated.GroupACL)
|
||||
require.Equal(t, tt.userACL, updated.UserACL)
|
||||
require.Equal(t, uuid.NullUUID{UUID: updater.ID, Valid: true}, updated.UpdatedBy)
|
||||
require.True(t, updated.UpdatedAt.After(before.UpdatedAt))
|
||||
|
||||
expected := before
|
||||
expected.GroupACL = tt.groupACL
|
||||
expected.UserACL = tt.userACL
|
||||
expected.UpdatedBy = uuid.NullUUID{UUID: updater.ID, Valid: true}
|
||||
expected.UpdatedAt = updated.UpdatedAt
|
||||
require.Equal(t, expected, updated)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnabledChatModelConfigsUsesAIProviders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Generated
+55
@@ -6200,6 +6200,8 @@ LEFT JOIN
|
||||
ai_providers ap ON ap.id = cmc.ai_provider_id
|
||||
WHERE
|
||||
cmc.deleted = FALSE
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedChatModelConfigs
|
||||
-- @authorize_filter
|
||||
ORDER BY
|
||||
ap.type::text ASC,
|
||||
cmc.model ASC,
|
||||
@@ -6644,6 +6646,59 @@ func (q *sqlQuerier) UpdateChatModelConfig(ctx context.Context, arg UpdateChatMo
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateChatModelConfigACLByID = `-- name: UpdateChatModelConfigACLByID :one
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
SET
|
||||
group_acl = $1,
|
||||
user_acl = $2,
|
||||
updated_by = $3::uuid,
|
||||
updated_at = NOW()
|
||||
WHERE
|
||||
id = $4::uuid
|
||||
AND deleted = FALSE
|
||||
RETURNING
|
||||
id, model, display_name, created_by, updated_by, enabled, is_default, deleted, deleted_at, created_at, updated_at, context_limit, compression_threshold, options, ai_provider_id, organization_id, group_acl, user_acl
|
||||
`
|
||||
|
||||
type UpdateChatModelConfigACLByIDParams struct {
|
||||
GroupACL ChatACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL ChatACL `db:"user_acl" json:"user_acl"`
|
||||
UpdatedBy uuid.NullUUID `db:"updated_by" json:"updated_by"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateChatModelConfigACLByID(ctx context.Context, arg UpdateChatModelConfigACLByIDParams) (ChatModelConfig, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateChatModelConfigACLByID,
|
||||
arg.GroupACL,
|
||||
arg.UserACL,
|
||||
arg.UpdatedBy,
|
||||
arg.ID,
|
||||
)
|
||||
var i ChatModelConfig
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Model,
|
||||
&i.DisplayName,
|
||||
&i.CreatedBy,
|
||||
&i.UpdatedBy,
|
||||
&i.Enabled,
|
||||
&i.IsDefault,
|
||||
&i.Deleted,
|
||||
&i.DeletedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ContextLimit,
|
||||
&i.CompressionThreshold,
|
||||
&i.Options,
|
||||
&i.AIProviderID,
|
||||
&i.OrganizationID,
|
||||
&i.GroupACL,
|
||||
&i.UserACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const acquireStaleChatDiffStatuses = `-- name: AcquireStaleChatDiffStatuses :many
|
||||
WITH acquired AS (
|
||||
UPDATE
|
||||
|
||||
@@ -26,6 +26,8 @@ LEFT JOIN
|
||||
ai_providers ap ON ap.id = cmc.ai_provider_id
|
||||
WHERE
|
||||
cmc.deleted = FALSE
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedChatModelConfigs
|
||||
-- @authorize_filter
|
||||
ORDER BY
|
||||
ap.type::text ASC,
|
||||
cmc.model ASC,
|
||||
@@ -140,6 +142,20 @@ WHERE
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
-- name: UpdateChatModelConfigACLByID :one
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
SET
|
||||
group_acl = @group_acl,
|
||||
user_acl = @user_acl,
|
||||
updated_by = sqlc.narg('updated_by')::uuid,
|
||||
updated_at = NOW()
|
||||
WHERE
|
||||
id = @id::uuid
|
||||
AND deleted = FALSE
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
-- name: UnsetDefaultChatModelConfigs :exec
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
|
||||
Reference in New Issue
Block a user