Fix: Multitenancy support for ORG (#7155)

* fix: support multitenancy in org

* fix: register and login working now

* fix: changes to migration

* fix: migrations run both on sqlite and postgres

* fix: remove user flags from fe and be

* fix: remove ingestion keys from update

* fix: multitenancy support for apdex settings

* fix: render ts for users correctly

* fix: fix migration to run for new tenants

* fix: clean up migrations

* fix: address comments

* Update pkg/sqlmigration/013_update_organization.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: fix build

* fix: force invites with org id

* Update pkg/query-service/auth/auth.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: address comments

* fix: address comments

* fix: provier with their own dialect

* fix: update dialects

* fix: remove unwanted change

* Update pkg/query-service/app/http_handler.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: different files for types

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
Nityananda Gohain 2025-03-06 15:39:45 +05:30 committed by GitHub
parent 296a444bd8
commit 2d73f91380
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1023 additions and 848 deletions

View File

@ -18,6 +18,7 @@ import (
baseconstants "go.signoz.io/signoz/pkg/query-service/constants" baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
basemodel "go.signoz.io/signoz/pkg/query-service/model" basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -45,7 +46,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return return
} }
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgId, cloudProvider) apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgID, cloudProvider)
if apiErr != nil { if apiErr != nil {
RespondError(w, basemodel.WrapApiError( RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:", apiErr, "couldn't provision PAT for cloud integration:",
@ -124,7 +125,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
)) ))
} }
for _, p := range allPats { for _, p := range allPats {
if p.UserID == integrationUser.Id && p.Name == integrationPATName { if p.UserID == integrationUser.ID && p.Name == integrationPATName {
return p.Token, nil return p.Token, nil
} }
} }
@ -136,7 +137,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
newPAT := model.PAT{ newPAT := model.PAT{
Token: generatePATToken(), Token: generatePATToken(),
UserID: integrationUser.Id, UserID: integrationUser.ID,
Name: integrationPATName, Name: integrationPATName,
Role: baseconstants.ViewerGroup, Role: baseconstants.ViewerGroup,
ExpiresAt: 0, ExpiresAt: 0,
@ -154,7 +155,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
func (ah *APIHandler) getOrCreateCloudIntegrationUser( func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string, ctx context.Context, orgId string, cloudProvider string,
) (*basemodel.User, *basemodel.ApiError) { ) (*types.User, *basemodel.ApiError) {
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider) cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)
integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId) integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
@ -171,19 +172,21 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
zap.String("cloudProvider", cloudProvider), zap.String("cloudProvider", cloudProvider),
) )
newUser := &basemodel.User{ newUser := &types.User{
Id: cloudIntegrationUserId, ID: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider), Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId), Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
CreatedAt: time.Now().Unix(), TimeAuditable: types.TimeAuditable{
OrgId: orgId, CreatedAt: time.Now(),
},
OrgID: orgId,
} }
viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup) viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup)
if apiErr != nil { if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't get viewer group for creating integration user") return nil, basemodel.WrapApiError(apiErr, "couldn't get viewer group for creating integration user")
} }
newUser.GroupId = viewerGroup.ID newUser.GroupID = viewerGroup.ID
passwordHash, err := auth.PasswordHash(uuid.NewString()) passwordHash, err := auth.PasswordHash(uuid.NewString())
if err != nil { if err != nil {

View File

@ -54,7 +54,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
} }
// All the PATs are associated with the user creating the PAT. // All the PATs are associated with the user creating the PAT.
pat.UserID = user.Id pat.UserID = user.ID
pat.CreatedAt = time.Now().Unix() pat.CreatedAt = time.Now().Unix()
pat.UpdatedAt = time.Now().Unix() pat.UpdatedAt = time.Now().Unix()
pat.LastUsed = 0 pat.LastUsed = 0
@ -112,7 +112,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
return return
} }
req.UpdatedByUserID = user.Id req.UpdatedByUserID = user.ID
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
req.UpdatedAt = time.Now().Unix() req.UpdatedAt = time.Now().Unix()
zap.L().Info("Got Update PAT request", zap.Any("pat", req)) zap.L().Info("Got Update PAT request", zap.Any("pat", req))
@ -135,7 +135,7 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
}, nil) }, nil)
return return
} }
zap.L().Info("Get PATs for user", zap.String("user_id", user.Id)) zap.L().Info("Get PATs for user", zap.String("user_id", user.ID))
pats, apierr := ah.AppDao().ListPATs(ctx) pats, apierr := ah.AppDao().ListPATs(ctx)
if apierr != nil { if apierr != nil {
RespondError(w, apierr, nil) RespondError(w, apierr, nil)
@ -157,7 +157,7 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
} }
zap.L().Info("Revoke PAT with id", zap.String("id", id)) zap.L().Info("Revoke PAT with id", zap.String("id", id))
if apierr := ah.AppDao().RevokePAT(ctx, id, user.Id); apierr != nil { if apierr := ah.AppDao().RevokePAT(ctx, id, user.ID); apierr != nil {
RespondError(w, apierr, nil) RespondError(w, apierr, nil)
return return
} }

View File

@ -25,6 +25,7 @@ import (
"go.signoz.io/signoz/ee/query-service/rules" "go.signoz.io/signoz/ee/query-service/rules"
"go.signoz.io/signoz/pkg/http/middleware" "go.signoz.io/signoz/pkg/http/middleware"
"go.signoz.io/signoz/pkg/signoz" "go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"go.signoz.io/signoz/pkg/web" "go.signoz.io/signoz/pkg/web"
@ -340,14 +341,14 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
r := baseapp.NewRouter() r := baseapp.NewRouter()
// add auth middleware // add auth middleware
getUserFromRequest := func(ctx context.Context) (*basemodel.UserPayload, error) { getUserFromRequest := func(ctx context.Context) (*types.GettableUser, error) {
user, err := auth.GetUserFromRequestContext(ctx, apiHandler) user, err := auth.GetUserFromRequestContext(ctx, apiHandler)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if user.User.OrgId == "" { if user.User.OrgID == "" {
return nil, basemodel.UnauthorizedError(errors.New("orgId is missing in the claims")) return nil, basemodel.UnauthorizedError(errors.New("orgId is missing in the claims"))
} }

View File

@ -7,14 +7,14 @@ import (
"go.signoz.io/signoz/ee/query-service/app/api" "go.signoz.io/signoz/ee/query-service/app/api"
baseauth "go.signoz.io/signoz/pkg/query-service/auth" baseauth "go.signoz.io/signoz/pkg/query-service/auth"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/telemetry" "go.signoz.io/signoz/pkg/query-service/telemetry"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap" "go.uber.org/zap"
) )
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) { func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*types.GettableUser, error) {
patToken, ok := authtypes.UUIDFromContext(ctx) patToken, ok := authtypes.UUIDFromContext(ctx)
if ok && patToken != "" { if ok && patToken != "" {
zap.L().Debug("Received a non-zero length PAT token") zap.L().Debug("Received a non-zero length PAT token")
@ -40,9 +40,9 @@ func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler)
} }
telemetry.GetInstance().SetPatTokenUser() telemetry.GetInstance().SetPatTokenUser()
dao.UpdatePATLastUsed(ctx, patToken, time.Now().Unix()) dao.UpdatePATLastUsed(ctx, patToken, time.Now().Unix())
user.User.GroupId = group.ID user.User.GroupID = group.ID
user.User.Id = pat.Id user.User.ID = pat.Id
return &basemodel.UserPayload{ return &types.GettableUser{
User: user.User, User: user.User,
Role: pat.Role, Role: pat.Role,
}, nil }, nil

View File

@ -10,6 +10,7 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao" basedao "go.signoz.io/signoz/pkg/query-service/dao"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces" baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model" basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
) )
@ -39,7 +40,7 @@ type ModelDao interface {
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError) GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError) GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) GetUserByPAT(ctx context.Context, token string) (*types.GettableUser, basemodel.BaseApiError)
ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError)
RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError
} }

View File

@ -14,11 +14,12 @@ import (
baseconst "go.signoz.io/signoz/pkg/query-service/constants" baseconst "go.signoz.io/signoz/pkg/query-service/constants"
basemodel "go.signoz.io/signoz/pkg/query-service/model" basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap" "go.uber.org/zap"
) )
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) { func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*types.User, basemodel.BaseApiError) {
// get auth domain from email domain // get auth domain from email domain
domain, apierr := m.GetDomainByEmail(ctx, email) domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil { if apierr != nil {
@ -42,15 +43,17 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
return nil, apiErr return nil, apiErr
} }
user := &basemodel.User{ user := &types.User{
Id: uuid.NewString(), ID: uuid.NewString(),
Name: "", Name: "",
Email: email, Email: email,
Password: hash, Password: hash,
CreatedAt: time.Now().Unix(), TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
ProfilePictureURL: "", // Currently unused ProfilePictureURL: "", // Currently unused
GroupId: group.ID, GroupID: group.ID,
OrgId: domain.OrgId, OrgID: domain.OrgId,
} }
user, apiErr = m.CreateUser(ctx, user, false) user, apiErr = m.CreateUser(ctx, user, false)
@ -73,7 +76,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
return "", model.BadRequestStr("invalid user email received from the auth provider") return "", model.BadRequestStr("invalid user email received from the auth provider")
} }
user := &basemodel.User{} user := &types.User{}
if userPayload == nil { if userPayload == nil {
newUser, apiErr := m.createUserForSAMLRequest(ctx, email) newUser, apiErr := m.createUserForSAMLRequest(ctx, email)
@ -95,7 +98,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s", return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
redirectUri, redirectUri,
tokenStore.AccessJwt, tokenStore.AccessJwt,
user.Id, user.ID,
tokenStore.RefreshJwt), nil tokenStore.RefreshJwt), nil
} }

View File

@ -8,6 +8,7 @@ import (
"go.signoz.io/signoz/ee/query-service/model" "go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model" basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -42,10 +43,10 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem
} }
} else { } else {
p.CreatedByUser = model.User{ p.CreatedByUser = model.User{
Id: createdByUser.Id, Id: createdByUser.ID,
Name: createdByUser.Name, Name: createdByUser.Name,
Email: createdByUser.Email, Email: createdByUser.Email,
CreatedAt: createdByUser.CreatedAt, CreatedAt: createdByUser.CreatedAt.Unix(),
ProfilePictureURL: createdByUser.ProfilePictureURL, ProfilePictureURL: createdByUser.ProfilePictureURL,
NotFound: false, NotFound: false,
} }
@ -95,10 +96,10 @@ func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApi
} }
} else { } else {
pats[i].CreatedByUser = model.User{ pats[i].CreatedByUser = model.User{
Id: createdByUser.Id, Id: createdByUser.ID,
Name: createdByUser.Name, Name: createdByUser.Name,
Email: createdByUser.Email, Email: createdByUser.Email,
CreatedAt: createdByUser.CreatedAt, CreatedAt: createdByUser.CreatedAt.Unix(),
ProfilePictureURL: createdByUser.ProfilePictureURL, ProfilePictureURL: createdByUser.ProfilePictureURL,
NotFound: false, NotFound: false,
} }
@ -111,10 +112,10 @@ func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApi
} }
} else { } else {
pats[i].UpdatedByUser = model.User{ pats[i].UpdatedByUser = model.User{
Id: updatedByUser.Id, Id: updatedByUser.ID,
Name: updatedByUser.Name, Name: updatedByUser.Name,
Email: updatedByUser.Email, Email: updatedByUser.Email,
CreatedAt: updatedByUser.CreatedAt, CreatedAt: updatedByUser.CreatedAt.Unix(),
ProfilePictureURL: updatedByUser.ProfilePictureURL, ProfilePictureURL: updatedByUser.ProfilePictureURL,
NotFound: false, NotFound: false,
} }
@ -170,8 +171,8 @@ func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basem
} }
// deprecated // deprecated
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) { func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*types.GettableUser, basemodel.BaseApiError) {
users := []basemodel.UserPayload{} users := []types.GettableUser{}
query := `SELECT query := `SELECT
u.id, u.id,

View File

@ -11,7 +11,7 @@ import (
saml2 "github.com/russellhaering/gosaml2" saml2 "github.com/russellhaering/gosaml2"
"go.signoz.io/signoz/ee/query-service/sso" "go.signoz.io/signoz/ee/query-service/sso"
"go.signoz.io/signoz/ee/query-service/sso/saml" "go.signoz.io/signoz/ee/query-service/sso/saml"
basemodel "go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/types"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -33,7 +33,7 @@ type OrgDomain struct {
SamlConfig *SamlConfig `json:"samlConfig"` SamlConfig *SamlConfig `json:"samlConfig"`
GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"` GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"`
Org *basemodel.Organization Org *types.Organization
} }
func (od *OrgDomain) String() string { func (od *OrgDomain) String() string {

View File

@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/setFlags';
const setFlags = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.patch(`/user/${props.userId}/flags`, {
...props.flags,
});
return {
statusCode: 200,
error: null,
message: response.data?.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default setFlags;

View File

@ -1,4 +0,0 @@
export default interface ReleaseNoteProps {
path?: string;
release?: string;
}

View File

@ -1,61 +0,0 @@
import { Button, Space } from 'antd';
import setFlags from 'api/user/setFlags';
import MessageTip from 'components/MessageTip';
import { useAppContext } from 'providers/App/App';
import { useCallback } from 'react';
import { UserFlags } from 'types/api/user/setFlags';
import ReleaseNoteProps from '../ReleaseNoteProps';
export default function ReleaseNote0120({
release,
}: ReleaseNoteProps): JSX.Element | null {
const { user, setUserFlags } = useAppContext();
const handleDontShow = useCallback(async (): Promise<void> => {
const flags: UserFlags = { ReleaseNote0120Hide: 'Y' };
try {
setUserFlags(flags);
if (!user) {
// no user is set, so escape the routine
return;
}
const response = await setFlags({ userId: user.id, flags });
if (response.statusCode !== 200) {
console.log('failed to complete do not show status', response.error);
}
} catch (e) {
// here we do not nothing as the cost of error is minor,
// the user can switch the do no show option again in the further.
console.log('unexpected error: failed to complete do not show status', e);
}
}, [setUserFlags, user]);
return (
<MessageTip
show
message={
<div>
You are using {release} of SigNoz. We have introduced distributed setup in
v0.12.0 release. If you use or plan to use clickhouse queries in dashboard
or alerts, you might want to read about querying the new distributed tables{' '}
<a
href="https://signoz.io/docs/operate/migration/upgrade-0.12/#querying-distributed-tables"
target="_blank"
rel="noreferrer"
>
here
</a>
</div>
}
action={
<Space>
<Button onClick={handleDontShow}>Do not show again</Button>
</Space>
}
/>
);
}

View File

@ -1,68 +0,0 @@
import ReleaseNoteProps from 'components/ReleaseNote/ReleaseNoteProps';
import ReleaseNote0120 from 'components/ReleaseNote/Releases/ReleaseNote0120';
import ROUTES from 'constants/routes';
import { useAppContext } from 'providers/App/App';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { UserFlags } from 'types/api/user/setFlags';
import AppReducer from 'types/reducer/app';
interface ComponentMapType {
match: (
path: string | undefined,
version: string,
userFlags: UserFlags | null,
) => boolean;
component: ({ path, release }: ReleaseNoteProps) => JSX.Element | null;
}
const allComponentMap: ComponentMapType[] = [
{
match: (
path: string | undefined,
version: string,
userFlags: UserFlags | null,
): boolean => {
if (!path) {
return false;
}
const allowedPaths: string[] = [
ROUTES.LIST_ALL_ALERT,
ROUTES.APPLICATION,
ROUTES.ALL_DASHBOARD,
];
return (
userFlags?.ReleaseNote0120Hide !== 'Y' &&
allowedPaths.includes(path) &&
version.startsWith('v0.12')
);
},
component: ReleaseNote0120,
},
];
// ReleaseNote prints release specific warnings and notes that
// user needs to be aware of before using the upgraded version.
function ReleaseNote({ path }: ReleaseNoteProps): JSX.Element | null {
const { user } = useAppContext();
const { currentVersion } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const c = allComponentMap.find((item) =>
item.match(path, currentVersion, user.flags),
);
if (!c) {
return null;
}
return <c.component path={path} release={currentVersion} />;
}
ReleaseNote.defaultProps = {
path: '',
};
export default ReleaseNote;

View File

@ -1,21 +1,18 @@
import { Space } from 'antd'; import { Space } from 'antd';
import getAll from 'api/alerts/getAll'; import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { isUndefined } from 'lodash-es'; import { isUndefined } from 'lodash-es';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { AlertsEmptyState } from './AlertsEmptyState/AlertsEmptyState'; import { AlertsEmptyState } from './AlertsEmptyState/AlertsEmptyState';
import ListAlert from './ListAlert'; import ListAlert from './ListAlert';
function ListAlertRules(): JSX.Element { function ListAlertRules(): JSX.Element {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const location = useLocation();
const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', { const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
queryFn: getAll, queryFn: getAll,
cacheTime: 0, cacheTime: 0,
@ -70,7 +67,6 @@ function ListAlertRules(): JSX.Element {
return ( return (
<Space direction="vertical" size="large" style={{ width: '100%' }}> <Space direction="vertical" size="large" style={{ width: '100%' }}>
<ReleaseNote path={location.pathname} />
<ListAlert <ListAlert
{...{ {...{
allAlertRules: data.payload, allAlertRules: data.payload,

View File

@ -281,7 +281,7 @@ function Members(): JSX.Element {
const { joinedOn } = record; const { joinedOn } = record;
return ( return (
<Typography> <Typography>
{dayjs.unix(Number(joinedOn)).format(DATE_TIME_FORMATS.MONTH_DATE_FULL)} {dayjs(joinedOn).format(DATE_TIME_FORMATS.MONTH_DATE_FULL)}
</Typography> </Typography>
); );
}, },

View File

@ -1,14 +1,10 @@
import './DashboardsListPage.styles.scss'; import './DashboardsListPage.styles.scss';
import { Space, Typography } from 'antd'; import { Space, Typography } from 'antd';
import ReleaseNote from 'components/ReleaseNote';
import ListOfAllDashboard from 'container/ListOfDashboard'; import ListOfAllDashboard from 'container/ListOfDashboard';
import { LayoutGrid } from 'lucide-react'; import { LayoutGrid } from 'lucide-react';
import { useLocation } from 'react-router-dom';
function DashboardsListPage(): JSX.Element { function DashboardsListPage(): JSX.Element {
const location = useLocation();
return ( return (
<Space <Space
direction="vertical" direction="vertical"
@ -16,7 +12,6 @@ function DashboardsListPage(): JSX.Element {
style={{ width: '100%' }} style={{ width: '100%' }}
className="dashboard-list-page" className="dashboard-list-page"
> >
<ReleaseNote path={location.pathname} />
<div className="dashboard-header"> <div className="dashboard-header">
<LayoutGrid size={14} className="icon" /> <LayoutGrid size={14} className="icon" />
<Typography.Text className="text">Dashboards</Typography.Text> <Typography.Text className="text">Dashboards</Typography.Text>

View File

@ -1,15 +1,9 @@
import { Space } from 'antd'; import { Space } from 'antd';
import ReleaseNote from 'components/ReleaseNote';
import ServicesApplication from 'container/ServiceApplication'; import ServicesApplication from 'container/ServiceApplication';
import { useLocation } from 'react-router-dom';
function Metrics(): JSX.Element { function Metrics(): JSX.Element {
const location = useLocation();
return ( return (
<Space direction="vertical" style={{ width: '100%' }}> <Space direction="vertical" style={{ width: '100%' }}>
<ReleaseNote path={location.pathname} />
<ServicesApplication /> <ServicesApplication />
</Space> </Space>
); );

View File

@ -21,7 +21,6 @@ import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeatures
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll'; import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
import { LicenseV3ResModel } from 'types/api/licensesV3/getActive'; import { LicenseV3ResModel } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization'; import { Organization } from 'types/api/user/getOrganization';
import { UserFlags } from 'types/api/user/setFlags';
import { OrgPreference } from 'types/reducer/app'; import { OrgPreference } from 'types/reducer/app';
import { USER_ROLES } from 'types/roles'; import { USER_ROLES } from 'types/roles';
@ -158,13 +157,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
} }
}, [orgPreferencesData, isFetchingOrgPreferences]); }, [orgPreferencesData, isFetchingOrgPreferences]);
function setUserFlags(userflags: UserFlags): void {
setUser((prev) => ({
...prev,
flags: userflags,
}));
}
function updateUser(user: IUser): void { function updateUser(user: IUser): void {
setUser((prev) => ({ setUser((prev) => ({
...prev, ...prev,
@ -252,7 +244,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
orgPreferencesFetchError, orgPreferencesFetchError,
licensesRefetch, licensesRefetch,
updateUser, updateUser,
setUserFlags,
updateOrgPreferences, updateOrgPreferences,
updateOrg, updateOrg,
}), }),

View File

@ -3,7 +3,6 @@ import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
import { LicenseV3ResModel } from 'types/api/licensesV3/getActive'; import { LicenseV3ResModel } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization'; import { Organization } from 'types/api/user/getOrganization';
import { PayloadProps as User } from 'types/api/user/getUser'; import { PayloadProps as User } from 'types/api/user/getUser';
import { UserFlags } from 'types/api/user/setFlags';
import { OrgPreference } from 'types/reducer/app'; import { OrgPreference } from 'types/reducer/app';
export interface IAppContext { export interface IAppContext {
@ -26,7 +25,6 @@ export interface IAppContext {
orgPreferencesFetchError: unknown; orgPreferencesFetchError: unknown;
licensesRefetch: () => void; licensesRefetch: () => void;
updateUser: (user: IUser) => void; updateUser: (user: IUser) => void;
setUserFlags: (flags: UserFlags) => void;
updateOrgPreferences: (orgPreferences: OrgPreference[]) => void; updateOrgPreferences: (orgPreferences: OrgPreference[]) => void;
updateOrg(orgId: string, updatedOrgName: string): void; updateOrg(orgId: string, updatedOrgName: string): void;
} }

View File

@ -20,7 +20,6 @@ function getUserDefaults(): IUser {
name: '', name: '',
profilePictureURL: '', profilePictureURL: '',
createdAt: 0, createdAt: 0,
flags: {},
organization: '', organization: '',
orgId: '', orgId: '',
role: 'VIEWER', role: 'VIEWER',

View File

@ -128,7 +128,6 @@ export function getAppContextMock(
name: 'John Doe', name: 'John Doe',
profilePictureURL: '', profilePictureURL: '',
createdAt: 1732544623, createdAt: 1732544623,
flags: {},
organization: 'Nightswatch', organization: 'Nightswatch',
orgId: 'does-not-matter-id', orgId: 'does-not-matter-id',
role: role as ROLES, role: role as ROLES,
@ -326,7 +325,6 @@ export function getAppContextMock(
orgPreferencesFetchError: null, orgPreferencesFetchError: null,
isLoggedIn: true, isLoggedIn: true,
updateUser: jest.fn(), updateUser: jest.fn(),
setUserFlags: jest.fn(),
updateOrg: jest.fn(), updateOrg: jest.fn(),
updateOrgPreferences: jest.fn(), updateOrgPreferences: jest.fn(),
licensesRefetch: jest.fn(), licensesRefetch: jest.fn(),

View File

@ -1,4 +1,3 @@
import { UserFlags } from 'types/api/user/setFlags';
import { User } from 'types/reducer/app'; import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles'; import { ROLES } from 'types/roles';
@ -16,6 +15,5 @@ export interface PayloadProps {
profilePictureURL: string; profilePictureURL: string;
organization: string; organization: string;
role: ROLES; role: ROLES;
flags: UserFlags;
groupId: string; groupId: string;
} }

View File

@ -1,12 +0,0 @@
import { User } from 'types/reducer/app';
export interface UserFlags {
ReleaseNote0120Hide?: string;
}
export type PayloadProps = UserFlags;
export interface Props {
userId: User['userId'];
flags: UserFlags;
}

View File

@ -1,21 +1,27 @@
package app package app
import ( import (
"context" "errors"
"net/http" "net/http"
"strings" "strings"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types/authtypes"
) )
func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) {
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
RespondError(w, &model.ApiError{Err: errors.New("unauthorized"), Typ: model.ErrorUnauthorized}, nil)
return
}
req, err := parseSetApdexScoreRequest(r) req, err := parseSetApdexScoreRequest(r)
if aH.HandleError(w, err, http.StatusBadRequest) { if aH.HandleError(w, err, http.StatusBadRequest) {
return return
} }
if err := dao.DB().SetApdexSettings(context.Background(), req); err != nil { if err := dao.DB().SetApdexSettings(r.Context(), claims.OrgID, req); err != nil {
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
return return
} }
@ -25,7 +31,12 @@ func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getApdexSettings(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getApdexSettings(w http.ResponseWriter, r *http.Request) {
services := r.URL.Query().Get("services") services := r.URL.Query().Get("services")
apdexSet, err := dao.DB().GetApdexSettings(context.Background(), strings.Split(strings.TrimSpace(services), ",")) claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
RespondError(w, &model.ApiError{Err: errors.New("unauthorized"), Typ: model.ErrorUnauthorized}, nil)
return
}
apdexSet, err := dao.DB().GetApdexSettings(r.Context(), claims.OrgID, strings.Split(strings.TrimSpace(services), ","))
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
return return

View File

@ -9,13 +9,14 @@ import (
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
) )
type AuthMiddleware struct { type AuthMiddleware struct {
GetUserFromRequest func(r context.Context) (*model.UserPayload, error) GetUserFromRequest func(r context.Context) (*types.GettableUser, error)
} }
func NewAuthMiddleware(f func(ctx context.Context) (*model.UserPayload, error)) *AuthMiddleware { func NewAuthMiddleware(f func(ctx context.Context) (*types.GettableUser, error)) *AuthMiddleware {
return &AuthMiddleware{ return &AuthMiddleware{
GetUserFromRequest: f, GetUserFromRequest: f,
} }

View File

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go.signoz.io/signoz/pkg/query-service/app/metricsexplorer"
"io" "io"
"math" "math"
"net/http" "net/http"
@ -19,6 +18,8 @@ import (
"text/template" "text/template"
"time" "time"
"go.signoz.io/signoz/pkg/query-service/app/metricsexplorer"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@ -51,6 +52,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/contextlinks" "go.signoz.io/signoz/pkg/query-service/contextlinks"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/postprocess" "go.signoz.io/signoz/pkg/query-service/postprocess"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap" "go.uber.org/zap"
@ -597,8 +599,6 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
router.HandleFunc("/api/v1/user/{id}", am.SelfAccess(aH.editUser)).Methods(http.MethodPut) router.HandleFunc("/api/v1/user/{id}", am.SelfAccess(aH.editUser)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/user/{id}", am.AdminAccess(aH.deleteUser)).Methods(http.MethodDelete) router.HandleFunc("/api/v1/user/{id}", am.AdminAccess(aH.deleteUser)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/user/{id}/flags", am.SelfAccess(aH.patchUserFlag)).Methods(http.MethodPatch)
router.HandleFunc("/api/v1/rbac/role/{id}", am.SelfAccess(aH.getRole)).Methods(http.MethodGet) router.HandleFunc("/api/v1/rbac/role/{id}", am.SelfAccess(aH.getRole)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/rbac/role/{id}", am.AdminAccess(aH.editRole)).Methods(http.MethodPut) router.HandleFunc("/api/v1/rbac/role/{id}", am.AdminAccess(aH.editRole)).Methods(http.MethodPut)
@ -2108,8 +2108,13 @@ func (aH *APIHandler) revokeInvite(w http.ResponseWriter, r *http.Request) {
// listPendingInvites is used to list the pending invites. // listPendingInvites is used to list the pending invites.
func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := r.Context()
invites, err := dao.DB().GetInvites(ctx) claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
RespondError(w, &model.ApiError{Err: errors.New("failed to get org id from context"), Typ: model.ErrorInternal}, nil)
return
}
invites, err := dao.DB().GetInvites(ctx, claims.OrgID)
if err != nil { if err != nil {
RespondError(w, err, nil) RespondError(w, err, nil)
return return
@ -2120,7 +2125,7 @@ func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request)
var resp []*model.InvitationResponseObject var resp []*model.InvitationResponseObject
for _, inv := range invites { for _, inv := range invites {
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgId) org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID)
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
} }
@ -2128,7 +2133,7 @@ func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request)
Name: inv.Name, Name: inv.Name,
Email: inv.Email, Email: inv.Email,
Token: inv.Token, Token: inv.Token,
CreatedAt: inv.CreatedAt, CreatedAt: inv.CreatedAt.Unix(),
Role: inv.Role, Role: inv.Role,
Organization: org.Name, Organization: org.Name,
}) })
@ -2271,13 +2276,15 @@ func (aH *APIHandler) editUser(w http.ResponseWriter, r *http.Request) {
old.ProfilePictureURL = update.ProfilePictureURL old.ProfilePictureURL = update.ProfilePictureURL
} }
_, apiErr = dao.DB().EditUser(ctx, &model.User{ _, apiErr = dao.DB().EditUser(ctx, &types.User{
Id: old.Id, ID: old.ID,
Name: old.Name, Name: old.Name,
OrgId: old.OrgId, OrgID: old.OrgID,
Email: old.Email, Email: old.Email,
Password: old.Password, Password: old.Password,
CreatedAt: old.CreatedAt, TimeAuditable: types.TimeAuditable{
CreatedAt: old.CreatedAt,
},
ProfilePictureURL: old.ProfilePictureURL, ProfilePictureURL: old.ProfilePictureURL,
}) })
if apiErr != nil { if apiErr != nil {
@ -2319,7 +2326,7 @@ func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
return return
} }
if user.GroupId == adminGroup.ID && len(adminUsers) == 1 { if user.GroupID == adminGroup.ID && len(adminUsers) == 1 {
RespondError(w, &model.ApiError{ RespondError(w, &model.ApiError{
Typ: model.ErrorInternal, Typ: model.ErrorInternal,
Err: errors.New("cannot delete the last admin user")}, nil) Err: errors.New("cannot delete the last admin user")}, nil)
@ -2334,37 +2341,6 @@ func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
aH.WriteJSON(w, r, map[string]string{"data": "user deleted successfully"}) aH.WriteJSON(w, r, map[string]string{"data": "user deleted successfully"})
} }
// addUserFlag patches a user flags with the changes
func (aH *APIHandler) patchUserFlag(w http.ResponseWriter, r *http.Request) {
// read user id from path var
userId := mux.Vars(r)["id"]
// read input into user flag
defer r.Body.Close()
b, err := io.ReadAll(r.Body)
if err != nil {
zap.L().Error("failed read user flags from http request for userId ", zap.String("userId", userId), zap.Error(err))
RespondError(w, model.BadRequestStr("received user flags in invalid format"), nil)
return
}
flags := make(map[string]string, 0)
err = json.Unmarshal(b, &flags)
if err != nil {
zap.L().Error("failed parsing user flags for userId ", zap.String("userId", userId), zap.Error(err))
RespondError(w, model.BadRequestStr("received user flags in invalid format"), nil)
return
}
newflags, apiError := dao.DB().UpdateUserFlags(r.Context(), userId, flags)
if !apiError.IsNil() {
RespondError(w, apiError, nil)
return
}
aH.Respond(w, newflags)
}
func (aH *APIHandler) getRole(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getRole(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
@ -2380,7 +2356,7 @@ func (aH *APIHandler) getRole(w http.ResponseWriter, r *http.Request) {
}, nil) }, nil)
return return
} }
group, err := dao.DB().GetGroup(context.Background(), user.GroupId) group, err := dao.DB().GetGroup(context.Background(), user.GroupID)
if err != nil { if err != nil {
RespondError(w, err, "Failed to get group") RespondError(w, err, "Failed to get group")
return return
@ -2416,7 +2392,7 @@ func (aH *APIHandler) editRole(w http.ResponseWriter, r *http.Request) {
} }
// Make sure that the request is not demoting the last admin user. // Make sure that the request is not demoting the last admin user.
if user.GroupId == auth.AuthCacheObj.AdminGroupId { if user.GroupID == auth.AuthCacheObj.AdminGroupId {
adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, auth.AuthCacheObj.AdminGroupId) adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, auth.AuthCacheObj.AdminGroupId)
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, "Failed to fetch adminUsers") RespondError(w, apiErr, "Failed to fetch adminUsers")
@ -2431,7 +2407,7 @@ func (aH *APIHandler) editRole(w http.ResponseWriter, r *http.Request) {
} }
} }
apiErr = dao.DB().UpdateUserGroup(context.Background(), user.Id, newGroup.ID) apiErr = dao.DB().UpdateUserGroup(context.Background(), user.ID, newGroup.ID)
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, "Failed to add user to group") RespondError(w, apiErr, "Failed to add user to group")
return return
@ -2465,7 +2441,7 @@ func (aH *APIHandler) editOrg(w http.ResponseWriter, r *http.Request) {
return return
} }
req.Id = id req.ID = id
if apiErr := dao.DB().EditOrg(context.Background(), req); apiErr != nil { if apiErr := dao.DB().EditOrg(context.Background(), req); apiErr != nil {
RespondError(w, apiErr, "Failed to update org in the DB") RespondError(w, apiErr, "Failed to update org in the DB")
return return
@ -3528,7 +3504,7 @@ func (aH *APIHandler) getUserPreference(
user := common.GetUserFromContext(r.Context()) user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetUserPreference( preference, apiErr := preferences.GetUserPreference(
r.Context(), preferenceId, user.User.OrgId, user.User.Id, r.Context(), preferenceId, user.User.OrgID, user.User.ID,
) )
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
@ -3551,7 +3527,7 @@ func (aH *APIHandler) updateUserPreference(
RespondError(w, model.BadRequest(err), nil) RespondError(w, model.BadRequest(err), nil)
return return
} }
preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.Id) preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.ID)
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
return return
@ -3565,7 +3541,7 @@ func (aH *APIHandler) getAllUserPreferences(
) { ) {
user := common.GetUserFromContext(r.Context()) user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetAllUserPreferences( preference, apiErr := preferences.GetAllUserPreferences(
r.Context(), user.User.OrgId, user.User.Id, r.Context(), user.User.OrgID, user.User.ID,
) )
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
@ -3581,7 +3557,7 @@ func (aH *APIHandler) getOrgPreference(
preferenceId := mux.Vars(r)["preferenceId"] preferenceId := mux.Vars(r)["preferenceId"]
user := common.GetUserFromContext(r.Context()) user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetOrgPreference( preference, apiErr := preferences.GetOrgPreference(
r.Context(), preferenceId, user.User.OrgId, r.Context(), preferenceId, user.User.OrgID,
) )
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
@ -3604,7 +3580,7 @@ func (aH *APIHandler) updateOrgPreference(
RespondError(w, model.BadRequest(err), nil) RespondError(w, model.BadRequest(err), nil)
return return
} }
preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.OrgId) preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.OrgID)
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
return return
@ -3618,7 +3594,7 @@ func (aH *APIHandler) getAllOrgPreferences(
) { ) {
user := common.GetUserFromContext(r.Context()) user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetAllOrgPreferences( preference, apiErr := preferences.GetAllOrgPreferences(
r.Context(), user.User.OrgId, r.Context(), user.User.OrgID,
) )
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)

View File

@ -6,9 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
queues2 "go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
"go.signoz.io/signoz/pkg/query-service/app/integrations/thirdPartyApi"
"math" "math"
"net/http" "net/http"
"strconv" "strconv"
@ -16,6 +13,10 @@ import (
"text/template" "text/template"
"time" "time"
"go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
queues2 "go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
"go.signoz.io/signoz/pkg/query-service/app/integrations/thirdPartyApi"
"github.com/SigNoz/govaluate" "github.com/SigNoz/govaluate"
"github.com/gorilla/mux" "github.com/gorilla/mux"
promModel "github.com/prometheus/common/model" promModel "github.com/prometheus/common/model"
@ -32,6 +33,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/postprocess" "go.signoz.io/signoz/pkg/query-service/postprocess"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate" querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate"
"go.signoz.io/signoz/pkg/types"
) )
var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max", "p50", "p90", "p95", "p99"} var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max", "p50", "p90", "p95", "p99"}
@ -465,8 +467,8 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) {
return &model.GetTTLParams{Type: typeTTL}, nil return &model.GetTTLParams{Type: typeTTL}, nil
} }
func parseUserRequest(r *http.Request) (*model.User, error) { func parseUserRequest(r *http.Request) (*types.User, error) {
var req model.User var req types.User
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
} }
@ -519,8 +521,8 @@ func parseInviteUsersRequest(r *http.Request) (*model.BulkInviteRequest, error)
return &req, nil return &req, nil
} }
func parseSetApdexScoreRequest(r *http.Request) (*model.ApdexSettings, error) { func parseSetApdexScoreRequest(r *http.Request) (*types.ApdexSettings, error) {
var req model.ApdexSettings var req types.ApdexSettings
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
} }
@ -566,8 +568,8 @@ func parseUserRoleRequest(r *http.Request) (*model.UserRole, error) {
return &req, nil return &req, nil
} }
func parseEditOrgRequest(r *http.Request) (*model.Organization, error) { func parseEditOrgRequest(r *http.Request) (*types.Organization, error) {
var req model.Organization var req types.Organization
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
} }

View File

@ -25,6 +25,7 @@ import (
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model" opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
"go.signoz.io/signoz/pkg/query-service/app/preferences" "go.signoz.io/signoz/pkg/query-service/app/preferences"
"go.signoz.io/signoz/pkg/signoz" "go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"go.signoz.io/signoz/pkg/web" "go.signoz.io/signoz/pkg/web"
@ -291,14 +292,14 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
r.Use(middleware.NewLogging(zap.L(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap) r.Use(middleware.NewLogging(zap.L(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap)
// add auth middleware // add auth middleware
getUserFromRequest := func(ctx context.Context) (*model.UserPayload, error) { getUserFromRequest := func(ctx context.Context) (*types.GettableUser, error) {
user, err := auth.GetUserFromReqContext(ctx) user, err := auth.GetUserFromReqContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if user.User.OrgId == "" { if user.User.OrgID == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims")) return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
} }

View File

@ -17,6 +17,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/telemetry" "go.signoz.io/signoz/pkg/query-service/telemetry"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
smtpservice "go.signoz.io/signoz/pkg/query-service/utils/smtpService" smtpservice "go.signoz.io/signoz/pkg/query-service/utils/smtpService"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -61,6 +62,10 @@ func Invite(ctx context.Context, req *model.InviteRequest) (*model.InviteRespons
return nil, errors.New("User already exists with the same email") return nil, errors.New("User already exists with the same email")
} }
claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
return nil, errors.New("failed to extract OrgID from context")
}
// Check if an invite already exists // Check if an invite already exists
invite, apiErr := dao.DB().GetInviteFromEmail(ctx, req.Email) invite, apiErr := dao.DB().GetInviteFromEmail(ctx, req.Email)
if apiErr != nil { if apiErr != nil {
@ -75,23 +80,18 @@ func Invite(ctx context.Context, req *model.InviteRequest) (*model.InviteRespons
return nil, errors.Wrap(err, "invalid invite request") return nil, errors.Wrap(err, "invalid invite request")
} }
claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
return nil, errors.Wrap(err, "failed to extract admin user id")
}
au, apiErr := dao.DB().GetUser(ctx, claims.UserID) au, apiErr := dao.DB().GetUser(ctx, claims.UserID)
if apiErr != nil { if apiErr != nil {
return nil, errors.Wrap(err, "failed to query admin user from the DB") return nil, errors.Wrap(err, "failed to query admin user from the DB")
} }
inv := &model.InvitationObject{ inv := &types.Invite{
Name: req.Name, Name: req.Name,
Email: req.Email, Email: req.Email,
Token: token, Token: token,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now(),
Role: req.Role, Role: req.Role,
OrgId: au.OrgId, OrgID: au.OrgID,
} }
if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil { if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil {
@ -157,7 +157,7 @@ func InviteUsers(ctx context.Context, req *model.BulkInviteRequest) (*model.Bulk
} }
// Helper function to handle individual invites // Helper function to handle individual invites
func inviteUser(ctx context.Context, req *model.InviteRequest, au *model.UserPayload) (*model.InviteResponse, error) { func inviteUser(ctx context.Context, req *model.InviteRequest, au *types.GettableUser) (*model.InviteResponse, error) {
token, err := utils.RandomHex(opaqueTokenSize) token, err := utils.RandomHex(opaqueTokenSize)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to generate invite token") return nil, errors.Wrap(err, "failed to generate invite token")
@ -186,13 +186,13 @@ func inviteUser(ctx context.Context, req *model.InviteRequest, au *model.UserPay
return nil, errors.Wrap(err, "invalid invite request") return nil, errors.Wrap(err, "invalid invite request")
} }
inv := &model.InvitationObject{ inv := &types.Invite{
Name: req.Name, Name: req.Name,
Email: req.Email, Email: req.Email,
Token: token, Token: token,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now(),
Role: req.Role, Role: req.Role,
OrgId: au.OrgId, OrgID: au.OrgID,
} }
if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil { if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil {
@ -211,7 +211,7 @@ func inviteUser(ctx context.Context, req *model.InviteRequest, au *model.UserPay
return &model.InviteResponse{Email: inv.Email, InviteToken: inv.Token}, nil return &model.InviteResponse{Email: inv.Email, InviteToken: inv.Token}, nil
} }
func inviteEmail(req *model.InviteRequest, au *model.UserPayload, token string) { func inviteEmail(req *model.InviteRequest, au *types.GettableUser, token string) {
smtp := smtpservice.GetInstance() smtp := smtpservice.GetInstance()
data := InviteEmailData{ data := InviteEmailData{
CustomerName: req.Name, CustomerName: req.Name,
@ -251,7 +251,12 @@ func RevokeInvite(ctx context.Context, email string) error {
return ErrorInvalidInviteToken return ErrorInvalidInviteToken
} }
if err := dao.DB().DeleteInvitation(ctx, email); err != nil { claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
return errors.New("failed to org id from context")
}
if err := dao.DB().DeleteInvitation(ctx, claims.OrgID, email); err != nil {
return errors.Wrap(err.Err, "failed to write to DB") return errors.Wrap(err.Err, "failed to write to DB")
} }
return nil return nil
@ -272,7 +277,7 @@ func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObje
// TODO(Ahsan): This is not the best way to add org name in the invite response. We should // TODO(Ahsan): This is not the best way to add org name in the invite response. We should
// either include org name in the invite table or do a join query. // either include org name in the invite table or do a join query.
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgId) org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID)
if apiErr != nil { if apiErr != nil {
return nil, errors.Wrap(apiErr.Err, "failed to query the DB") return nil, errors.Wrap(apiErr.Err, "failed to query the DB")
} }
@ -280,13 +285,13 @@ func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObje
Name: inv.Name, Name: inv.Name,
Email: inv.Email, Email: inv.Email,
Token: inv.Token, Token: inv.Token,
CreatedAt: inv.CreatedAt, CreatedAt: inv.CreatedAt.Unix(),
Role: inv.Role, Role: inv.Role,
Organization: org.Name, Organization: org.Name,
}, nil }, nil
} }
func ValidateInvite(ctx context.Context, req *RegisterRequest) (*model.InvitationObject, error) { func ValidateInvite(ctx context.Context, req *RegisterRequest) (*types.Invite, error) {
invitation, err := dao.DB().GetInviteFromEmail(ctx, req.Email) invitation, err := dao.DB().GetInviteFromEmail(ctx, req.Email)
if err != nil { if err != nil {
return nil, errors.Wrap(err.Err, "Failed to read from DB") return nil, errors.Wrap(err.Err, "Failed to read from DB")
@ -303,14 +308,14 @@ func ValidateInvite(ctx context.Context, req *RegisterRequest) (*model.Invitatio
return invitation, nil return invitation, nil
} }
func CreateResetPasswordToken(ctx context.Context, userId string) (*model.ResetPasswordEntry, error) { func CreateResetPasswordToken(ctx context.Context, userId string) (*types.ResetPasswordRequest, error) {
token, err := utils.RandomHex(opaqueTokenSize) token, err := utils.RandomHex(opaqueTokenSize)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to generate reset password token") return nil, errors.Wrap(err, "failed to generate reset password token")
} }
req := &model.ResetPasswordEntry{ req := &types.ResetPasswordRequest{
UserId: userId, UserID: userId,
Token: token, Token: token,
} }
if apiErr := dao.DB().CreateResetPasswordEntry(ctx, req); err != nil { if apiErr := dao.DB().CreateResetPasswordEntry(ctx, req); err != nil {
@ -334,7 +339,7 @@ func ResetPassword(ctx context.Context, req *model.ResetPasswordRequest) error {
return errors.Wrap(err, "Failed to generate password hash") return errors.Wrap(err, "Failed to generate password hash")
} }
if apiErr := dao.DB().UpdateUserPassword(ctx, hash, entry.UserId); apiErr != nil { if apiErr := dao.DB().UpdateUserPassword(ctx, hash, entry.UserID); apiErr != nil {
return apiErr.Err return apiErr.Err
} }
@ -360,7 +365,7 @@ func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) *mode
return model.InternalError(errors.New("Failed to generate password hash")) return model.InternalError(errors.New("Failed to generate password hash"))
} }
if apiErr := dao.DB().UpdateUserPassword(ctx, hash, user.Id); apiErr != nil { if apiErr := dao.DB().UpdateUserPassword(ctx, hash, user.ID); apiErr != nil {
return apiErr return apiErr
} }
@ -369,6 +374,7 @@ func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) *mode
type RegisterRequest struct { type RegisterRequest struct {
Name string `json:"name"` Name string `json:"name"`
OrgID string `json:"orgId"`
OrgName string `json:"orgName"` OrgName string `json:"orgName"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
@ -380,7 +386,7 @@ type RegisterRequest struct {
SourceUrl string `json:"sourceUrl"` SourceUrl string `json:"sourceUrl"`
} }
func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User, *model.ApiError) { func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User, *model.ApiError) {
if req.Email == "" { if req.Email == "" {
return nil, model.BadRequest(model.ErrEmailRequired{}) return nil, model.BadRequest(model.ErrEmailRequired{})
@ -392,8 +398,9 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
groupName := constants.AdminGroup groupName := constants.AdminGroup
// modify this to use bun
org, apierr := dao.DB().CreateOrg(ctx, org, apierr := dao.DB().CreateOrg(ctx,
&model.Organization{Name: req.OrgName, IsAnonymous: req.IsAnonymous, HasOptedUpdates: req.HasOptedUpdates}) &types.Organization{Name: req.OrgName, IsAnonymous: req.IsAnonymous, HasOptedUpdates: req.HasOptedUpdates})
if apierr != nil { if apierr != nil {
zap.L().Error("CreateOrg failed", zap.Error(apierr.ToError())) zap.L().Error("CreateOrg failed", zap.Error(apierr.ToError()))
return nil, apierr return nil, apierr
@ -414,22 +421,24 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
return nil, model.InternalError(model.ErrSignupFailed{}) return nil, model.InternalError(model.ErrSignupFailed{})
} }
user := &model.User{ user := &types.User{
Id: uuid.NewString(), ID: uuid.NewString(),
Name: req.Name, Name: req.Name,
Email: req.Email, Email: req.Email,
Password: hash, Password: hash,
CreatedAt: time.Now().Unix(), TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
ProfilePictureURL: "", // Currently unused ProfilePictureURL: "", // Currently unused
GroupId: group.ID, GroupID: group.ID,
OrgId: org.Id, OrgID: org.ID,
} }
return dao.DB().CreateUser(ctx, user, true) return dao.DB().CreateUser(ctx, user, true)
} }
// RegisterInvitedUser handles registering a invited user // RegisterInvitedUser handles registering a invited user
func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword bool) (*model.User, *model.ApiError) { func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword bool) (*types.User, *model.ApiError) {
if req.InviteToken == "" { if req.InviteToken == "" {
return nil, model.BadRequest(ErrorAskAdmin) return nil, model.BadRequest(ErrorAskAdmin)
@ -459,7 +468,7 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
return &userPayload.User, nil return &userPayload.User, nil
} }
if invite.OrgId == "" { if invite.OrgID == "" {
zap.L().Error("failed to find org in the invite") zap.L().Error("failed to find org in the invite")
return nil, model.InternalError(fmt.Errorf("invalid invite, org not found")) return nil, model.InternalError(fmt.Errorf("invalid invite, org not found"))
} }
@ -492,15 +501,17 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
} }
} }
user := &model.User{ user := &types.User{
Id: uuid.NewString(), ID: uuid.NewString(),
Name: req.Name, Name: req.Name,
Email: req.Email, Email: req.Email,
Password: hash, Password: hash,
CreatedAt: time.Now().Unix(), TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
ProfilePictureURL: "", // Currently unused ProfilePictureURL: "", // Currently unused
GroupId: group.ID, GroupID: group.ID,
OrgId: invite.OrgId, OrgID: invite.OrgID,
} }
// TODO(Ahsan): Ideally create user and delete invitation should happen in a txn. // TODO(Ahsan): Ideally create user and delete invitation should happen in a txn.
@ -510,7 +521,7 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
return nil, apiErr return nil, apiErr
} }
apiErr = dao.DB().DeleteInvitation(ctx, user.Email) apiErr = dao.DB().DeleteInvitation(ctx, user.OrgID, user.Email)
if apiErr != nil { if apiErr != nil {
zap.L().Error("delete invitation failed", zap.Error(apiErr.Err)) zap.L().Error("delete invitation failed", zap.Error(apiErr.Err))
return nil, apiErr return nil, apiErr
@ -525,7 +536,7 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
// Register registers a new user. For the first register request, it doesn't need an invite token // Register registers a new user. For the first register request, it doesn't need an invite token
// and also the first registration is an enforced ADMIN registration. Every subsequent request will // and also the first registration is an enforced ADMIN registration. Every subsequent request will
// need an invite token to go through. // need an invite token to go through.
func Register(ctx context.Context, req *RegisterRequest) (*model.User, *model.ApiError) { func Register(ctx context.Context, req *RegisterRequest) (*types.User, *model.ApiError) {
users, err := dao.DB().GetUsers(ctx) users, err := dao.DB().GetUsers(ctx)
if err != nil { if err != nil {
return nil, model.InternalError(fmt.Errorf("failed to get user count")) return nil, model.InternalError(fmt.Errorf("failed to get user count"))
@ -562,24 +573,24 @@ func Login(ctx context.Context, request *model.LoginRequest, jwt *authtypes.JWT)
return &model.LoginResponse{ return &model.LoginResponse{
UserJwtObject: userjwt, UserJwtObject: userjwt,
UserId: user.User.Id, UserId: user.User.ID,
}, nil }, nil
} }
func claimsToUserPayload(claims authtypes.Claims) (*model.UserPayload, error) { func claimsToUserPayload(claims authtypes.Claims) (*types.GettableUser, error) {
user := &model.UserPayload{ user := &types.GettableUser{
User: model.User{ User: types.User{
Id: claims.UserID, ID: claims.UserID,
GroupId: claims.GroupID, GroupID: claims.GroupID,
Email: claims.Email, Email: claims.Email,
OrgId: claims.OrgID, OrgID: claims.OrgID,
}, },
} }
return user, nil return user, nil
} }
// authenticateLogin is responsible for querying the DB and validating the credentials. // authenticateLogin is responsible for querying the DB and validating the credentials.
func authenticateLogin(ctx context.Context, req *model.LoginRequest, jwt *authtypes.JWT) (*model.UserPayload, error) { func authenticateLogin(ctx context.Context, req *model.LoginRequest, jwt *authtypes.JWT) (*types.GettableUser, error) {
// If refresh token is valid, then simply authorize the login request. // If refresh token is valid, then simply authorize the login request.
if len(req.RefreshToken) > 0 { if len(req.RefreshToken) > 0 {
// parse the refresh token // parse the refresh token
@ -624,17 +635,17 @@ func passwordMatch(hash, password string) bool {
return err == nil return err == nil
} }
func GenerateJWTForUser(user *model.User, jwt *authtypes.JWT) (model.UserJwtObject, error) { func GenerateJWTForUser(user *types.User, jwt *authtypes.JWT) (model.UserJwtObject, error) {
j := model.UserJwtObject{} j := model.UserJwtObject{}
var err error var err error
j.AccessJwtExpiry = time.Now().Add(jwt.JwtExpiry).Unix() j.AccessJwtExpiry = time.Now().Add(jwt.JwtExpiry).Unix()
j.AccessJwt, err = jwt.AccessToken(user.OrgId, user.Id, user.GroupId, user.Email) j.AccessJwt, err = jwt.AccessToken(user.OrgID, user.ID, user.GroupID, user.Email)
if err != nil { if err != nil {
return j, errors.Errorf("failed to encode jwt: %v", err) return j, errors.Errorf("failed to encode jwt: %v", err)
} }
j.RefreshJwtExpiry = time.Now().Add(jwt.JwtRefresh).Unix() j.RefreshJwtExpiry = time.Now().Add(jwt.JwtRefresh).Unix()
j.RefreshJwt, err = jwt.RefreshToken(user.OrgId, user.Id, user.GroupId, user.Email) j.RefreshJwt, err = jwt.RefreshToken(user.OrgID, user.ID, user.GroupID, user.Email)
if err != nil { if err != nil {
return j, errors.Errorf("failed to encode jwt: %v", err) return j, errors.Errorf("failed to encode jwt: %v", err)
} }

View File

@ -6,7 +6,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
) )
@ -48,28 +48,28 @@ func InitAuthCache(ctx context.Context) error {
return nil return nil
} }
func GetUserFromReqContext(ctx context.Context) (*model.UserPayload, error) { func GetUserFromReqContext(ctx context.Context) (*types.GettableUser, error) {
claims, ok := authtypes.ClaimsFromContext(ctx) claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok { if !ok {
return nil, errors.New("no claims found in context") return nil, errors.New("no claims found in context")
} }
user := &model.UserPayload{ user := &types.GettableUser{
User: model.User{ User: types.User{
Id: claims.UserID, ID: claims.UserID,
GroupId: claims.GroupID, GroupID: claims.GroupID,
Email: claims.Email, Email: claims.Email,
OrgId: claims.OrgID, OrgID: claims.OrgID,
}, },
} }
return user, nil return user, nil
} }
func IsSelfAccessRequest(user *model.UserPayload, id string) bool { return user.Id == id } func IsSelfAccessRequest(user *types.GettableUser, id string) bool { return user.ID == id }
func IsViewer(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.ViewerGroupId } func IsViewer(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.ViewerGroupId }
func IsEditor(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.EditorGroupId } func IsEditor(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.EditorGroupId }
func IsAdmin(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.AdminGroupId } func IsAdmin(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.AdminGroupId }
func ValidatePassword(password string) error { func ValidatePassword(password string) error {
if len(password) < minimumPasswordLength { if len(password) < minimumPasswordLength {

View File

@ -4,11 +4,11 @@ import (
"context" "context"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/types"
) )
func GetUserFromContext(ctx context.Context) *model.UserPayload { func GetUserFromContext(ctx context.Context) *types.GettableUser {
user, ok := ctx.Value(constants.ContextUserKey).(*model.UserPayload) user, ok := ctx.Value(constants.ContextUserKey).(*types.GettableUser)
if !ok { if !ok {
return nil return nil
} }

View File

@ -13,28 +13,28 @@ type ModelDao interface {
} }
type Queries interface { type Queries interface {
GetInviteFromEmail(ctx context.Context, email string) (*model.InvitationObject, *model.ApiError) GetInviteFromEmail(ctx context.Context, email string) (*types.Invite, *model.ApiError)
GetInviteFromToken(ctx context.Context, token string) (*model.InvitationObject, *model.ApiError) GetInviteFromToken(ctx context.Context, token string) (*types.Invite, *model.ApiError)
GetInvites(ctx context.Context) ([]model.InvitationObject, *model.ApiError) GetInvites(ctx context.Context, orgID string) ([]types.Invite, *model.ApiError)
GetUser(ctx context.Context, id string) (*model.UserPayload, *model.ApiError) GetUser(ctx context.Context, id string) (*types.GettableUser, *model.ApiError)
GetUserByEmail(ctx context.Context, email string) (*model.UserPayload, *model.ApiError) GetUserByEmail(ctx context.Context, email string) (*types.GettableUser, *model.ApiError)
GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) GetUsers(ctx context.Context) ([]types.GettableUser, *model.ApiError)
GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError) GetUsersWithOpts(ctx context.Context, limit int) ([]types.GettableUser, *model.ApiError)
GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError) GetGroup(ctx context.Context, id string) (*types.Group, *model.ApiError)
GetGroupByName(ctx context.Context, name string) (*types.Group, *model.ApiError) GetGroupByName(ctx context.Context, name string) (*types.Group, *model.ApiError)
GetGroups(ctx context.Context) ([]model.Group, *model.ApiError) GetGroups(ctx context.Context) ([]types.Group, *model.ApiError)
GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError) GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError)
GetOrgByName(ctx context.Context, name string) (*model.Organization, *model.ApiError) GetOrgByName(ctx context.Context, name string) (*types.Organization, *model.ApiError)
GetOrg(ctx context.Context, id string) (*model.Organization, *model.ApiError) GetOrg(ctx context.Context, id string) (*types.Organization, *model.ApiError)
GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError) GetResetPasswordEntry(ctx context.Context, token string) (*types.ResetPasswordRequest, *model.ApiError)
GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError) GetUsersByOrg(ctx context.Context, orgId string) ([]types.GettableUser, *model.ApiError)
GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError) GetUsersByGroup(ctx context.Context, groupId string) ([]types.GettableUser, *model.ApiError)
GetApdexSettings(ctx context.Context, services []string) ([]model.ApdexSettings, *model.ApiError) GetApdexSettings(ctx context.Context, orgID string, services []string) ([]types.ApdexSettings, *model.ApiError)
GetIngestionKeys(ctx context.Context) ([]model.IngestionKey, *model.ApiError) GetIngestionKeys(ctx context.Context) ([]model.IngestionKey, *model.ApiError)
@ -42,29 +42,27 @@ type Queries interface {
} }
type Mutations interface { type Mutations interface {
CreateInviteEntry(ctx context.Context, req *model.InvitationObject) *model.ApiError CreateInviteEntry(ctx context.Context, req *types.Invite) *model.ApiError
DeleteInvitation(ctx context.Context, email string) *model.ApiError DeleteInvitation(ctx context.Context, orgID string, email string) *model.ApiError
CreateUser(ctx context.Context, user *model.User, isFirstUser bool) (*model.User, *model.ApiError) CreateUser(ctx context.Context, user *types.User, isFirstUser bool) (*types.User, *model.ApiError)
EditUser(ctx context.Context, update *model.User) (*model.User, *model.ApiError) EditUser(ctx context.Context, update *types.User) (*types.User, *model.ApiError)
DeleteUser(ctx context.Context, id string) *model.ApiError DeleteUser(ctx context.Context, id string) *model.ApiError
UpdateUserFlags(ctx context.Context, userId string, flags map[string]string) (model.UserFlag, *model.ApiError)
CreateGroup(ctx context.Context, group *types.Group) (*types.Group, *model.ApiError) CreateGroup(ctx context.Context, group *types.Group) (*types.Group, *model.ApiError)
DeleteGroup(ctx context.Context, id string) *model.ApiError DeleteGroup(ctx context.Context, id string) *model.ApiError
CreateOrg(ctx context.Context, org *model.Organization) (*model.Organization, *model.ApiError) CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, *model.ApiError)
EditOrg(ctx context.Context, org *model.Organization) *model.ApiError EditOrg(ctx context.Context, org *types.Organization) *model.ApiError
DeleteOrg(ctx context.Context, id string) *model.ApiError DeleteOrg(ctx context.Context, id string) *model.ApiError
CreateResetPasswordEntry(ctx context.Context, req *model.ResetPasswordEntry) *model.ApiError CreateResetPasswordEntry(ctx context.Context, req *types.ResetPasswordRequest) *model.ApiError
DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError
UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError
UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError
SetApdexSettings(ctx context.Context, set *model.ApdexSettings) *model.ApiError SetApdexSettings(ctx context.Context, orgID string, set *types.ApdexSettings) *model.ApiError
InsertIngestionKey(ctx context.Context, ingestionKey *model.IngestionKey) *model.ApiError InsertIngestionKey(ctx context.Context, ingestionKey *model.IngestionKey) *model.ApiError
} }

View File

@ -3,24 +3,21 @@ package sqlite
import ( import (
"context" "context"
"github.com/jmoiron/sqlx" "github.com/uptrace/bun"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
) )
const defaultApdexThreshold = 0.5 const defaultApdexThreshold = 0.5
func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, services []string) ([]model.ApdexSettings, *model.ApiError) { func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, orgID string, services []string) ([]types.ApdexSettings, *model.ApiError) {
var apdexSettings []model.ApdexSettings var apdexSettings []types.ApdexSettings
query, args, err := sqlx.In("SELECT * FROM apdex_settings WHERE service_name IN (?)", services) err := mds.bundb.NewSelect().
if err != nil { Model(&apdexSettings).
return nil, &model.ApiError{ Where("org_id = ?", orgID).
Err: err, Where("service_name IN (?)", bun.In(services)).
} Scan(ctx)
}
query = mds.db.Rebind(query)
err = mds.db.Select(&apdexSettings, query, args...)
if err != nil { if err != nil {
return nil, &model.ApiError{ return nil, &model.ApiError{
Err: err, Err: err,
@ -38,7 +35,7 @@ func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, services []stri
} }
if !found { if !found {
apdexSettings = append(apdexSettings, model.ApdexSettings{ apdexSettings = append(apdexSettings, types.ApdexSettings{
ServiceName: service, ServiceName: service,
Threshold: defaultApdexThreshold, Threshold: defaultApdexThreshold,
}) })
@ -48,18 +45,16 @@ func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, services []stri
return apdexSettings, nil return apdexSettings, nil
} }
func (mds *ModelDaoSqlite) SetApdexSettings(ctx context.Context, apdexSettings *model.ApdexSettings) *model.ApiError { func (mds *ModelDaoSqlite) SetApdexSettings(ctx context.Context, orgID string, apdexSettings *types.ApdexSettings) *model.ApiError {
// Set the org_id from the parameter since it's required for the foreign key constraint
apdexSettings.OrgID = orgID
_, err := mds.db.NamedExec(` _, err := mds.bundb.NewInsert().
INSERT OR REPLACE INTO apdex_settings ( Model(apdexSettings).
service_name, On("CONFLICT (org_id, service_name) DO UPDATE").
threshold, Set("threshold = EXCLUDED.threshold").
exclude_status_codes Set("exclude_status_codes = EXCLUDED.exclude_status_codes").
) VALUES ( Exec(ctx)
:service_name,
:threshold,
:exclude_status_codes
)`, apdexSettings)
if err != nil { if err != nil {
return &model.ApiError{ return &model.ApiError{
Err: err, Err: err,

View File

@ -7,7 +7,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/telemetry" "go.signoz.io/signoz/pkg/query-service/telemetry"
"go.signoz.io/signoz/pkg/sqlstore" "go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types" "go.signoz.io/signoz/pkg/types"
@ -62,13 +61,13 @@ func (mds *ModelDaoSqlite) initializeOrgPreferences(ctx context.Context) error {
return errors.Errorf("Found %d organizations, expected one or none.", len(orgs)) return errors.Errorf("Found %d organizations, expected one or none.", len(orgs))
} }
var org model.Organization var org types.Organization
if len(orgs) == 1 { if len(orgs) == 1 {
org = orgs[0] org = orgs[0]
} }
// set telemetry fields from userPreferences // set telemetry fields from userPreferences
telemetry.GetInstance().SetDistinctId(org.Id) telemetry.GetInstance().SetDistinctId(org.ID)
users, _ := mds.GetUsers(ctx) users, _ := mds.GetUsers(ctx)
countUsers := len(users) countUsers := len(users)

View File

@ -2,7 +2,6 @@ package sqlite
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"time" "time"
@ -13,33 +12,38 @@ import (
"go.signoz.io/signoz/pkg/types" "go.signoz.io/signoz/pkg/types"
) )
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context, func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context, req *types.Invite) *model.ApiError {
req *model.InvitationObject) *model.ApiError { _, err := mds.bundb.NewInsert().
Model(req).
Exec(ctx)
_, err := mds.db.ExecContext(ctx,
`INSERT INTO invites (email, name, token, role, created_at, org_id)
VALUES (?, ?, ?, ?, ?, ?);`,
req.Email, req.Name, req.Token, req.Role, req.CreatedAt, req.OrgId)
if err != nil { if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return nil return nil
} }
func (mds *ModelDaoSqlite) DeleteInvitation(ctx context.Context, email string) *model.ApiError { func (mds *ModelDaoSqlite) DeleteInvitation(ctx context.Context, orgID string, email string) *model.ApiError {
_, err := mds.db.ExecContext(ctx, `DELETE from invites where email=?;`, email) _, err := mds.bundb.NewDelete().
Model(&types.Invite{}).
Where("org_id = ?", orgID).
Where("email = ?", email).
Exec(ctx)
if err != nil { if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return nil return nil
} }
// TODO: Make this work with org id
func (mds *ModelDaoSqlite) GetInviteFromEmail(ctx context.Context, email string, func (mds *ModelDaoSqlite) GetInviteFromEmail(ctx context.Context, email string,
) (*model.InvitationObject, *model.ApiError) { ) (*types.Invite, *model.ApiError) {
invites := []model.InvitationObject{} invites := []types.Invite{}
err := mds.db.Select(&invites, err := mds.bundb.NewSelect().
`SELECT * FROM invites WHERE email=?;`, email) Model(&invites).
Where("email = ?", email).
Scan(ctx)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
@ -57,11 +61,14 @@ func (mds *ModelDaoSqlite) GetInviteFromEmail(ctx context.Context, email string,
} }
func (mds *ModelDaoSqlite) GetInviteFromToken(ctx context.Context, token string, func (mds *ModelDaoSqlite) GetInviteFromToken(ctx context.Context, token string,
) (*model.InvitationObject, *model.ApiError) { ) (*types.Invite, *model.ApiError) {
// This won't take org id because it's a public facing API
invites := []model.InvitationObject{} invites := []types.Invite{}
err := mds.db.Select(&invites, err := mds.bundb.NewSelect().
`SELECT * FROM invites WHERE token=?;`, token) Model(&invites).
Where("token = ?", token).
Scan(ctx)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
@ -76,11 +83,12 @@ func (mds *ModelDaoSqlite) GetInviteFromToken(ctx context.Context, token string,
return &invites[0], nil return &invites[0], nil
} }
func (mds *ModelDaoSqlite) GetInvites(ctx context.Context, func (mds *ModelDaoSqlite) GetInvites(ctx context.Context, orgID string) ([]types.Invite, *model.ApiError) {
) ([]model.InvitationObject, *model.ApiError) { invites := []types.Invite{}
err := mds.bundb.NewSelect().
invites := []model.InvitationObject{} Model(&invites).
err := mds.db.Select(&invites, "SELECT * FROM invites") Where("org_id = ?", orgID).
Scan(ctx)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -88,13 +96,13 @@ func (mds *ModelDaoSqlite) GetInvites(ctx context.Context,
} }
func (mds *ModelDaoSqlite) CreateOrg(ctx context.Context, func (mds *ModelDaoSqlite) CreateOrg(ctx context.Context,
org *model.Organization) (*model.Organization, *model.ApiError) { org *types.Organization) (*types.Organization, *model.ApiError) {
org.Id = uuid.NewString() org.ID = uuid.NewString()
org.CreatedAt = time.Now().Unix() org.CreatedAt = time.Now()
_, err := mds.db.ExecContext(ctx, _, err := mds.bundb.NewInsert().
`INSERT INTO organizations (id, name, created_at,is_anonymous,has_opted_updates) VALUES (?, ?, ?, ?, ?);`, Model(org).
org.Id, org.Name, org.CreatedAt, org.IsAnonymous, org.HasOptedUpdates) Exec(ctx)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
@ -103,14 +111,18 @@ func (mds *ModelDaoSqlite) CreateOrg(ctx context.Context,
} }
func (mds *ModelDaoSqlite) GetOrg(ctx context.Context, func (mds *ModelDaoSqlite) GetOrg(ctx context.Context,
id string) (*model.Organization, *model.ApiError) { id string) (*types.Organization, *model.ApiError) {
orgs := []model.Organization{} orgs := []types.Organization{}
err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE id=?;`, id)
if err != nil { if err := mds.bundb.NewSelect().
Model(&orgs).
Where("id = ?", id).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
// TODO(nitya): remove for multitenancy
if len(orgs) > 1 { if len(orgs) > 1 {
return nil, &model.ApiError{ return nil, &model.ApiError{
Typ: model.ErrorInternal, Typ: model.ErrorInternal,
@ -125,11 +137,14 @@ func (mds *ModelDaoSqlite) GetOrg(ctx context.Context,
} }
func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context, func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context,
name string) (*model.Organization, *model.ApiError) { name string) (*types.Organization, *model.ApiError) {
orgs := []model.Organization{} orgs := []types.Organization{}
if err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE name=?;`, name); err != nil { if err := mds.bundb.NewSelect().
Model(&orgs).
Where("name = ?", name).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -145,9 +160,11 @@ func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context,
return &orgs[0], nil return &orgs[0], nil
} }
func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError) { func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError) {
orgs := []model.Organization{} var orgs []types.Organization
err := mds.db.Select(&orgs, `SELECT * FROM organizations`) err := mds.bundb.NewSelect().
Model(&orgs).
Scan(ctx)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
@ -155,24 +172,31 @@ func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]model.Organization, *
return orgs, nil return orgs, nil
} }
func (mds *ModelDaoSqlite) EditOrg(ctx context.Context, org *model.Organization) *model.ApiError { func (mds *ModelDaoSqlite) EditOrg(ctx context.Context, org *types.Organization) *model.ApiError {
_, err := mds.bundb.NewUpdate().
Model(org).
Column("name").
Column("has_opted_updates").
Column("is_anonymous").
Where("id = ?", org.ID).
Exec(ctx)
q := `UPDATE organizations SET name=?,has_opted_updates=?,is_anonymous=? WHERE id=?;`
_, err := mds.db.ExecContext(ctx, q, org.Name, org.HasOptedUpdates, org.IsAnonymous, org.Id)
if err != nil { if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous) telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous)
telemetry.GetInstance().SetDistinctId(org.Id) telemetry.GetInstance().SetDistinctId(org.ID)
return nil return nil
} }
func (mds *ModelDaoSqlite) DeleteOrg(ctx context.Context, id string) *model.ApiError { func (mds *ModelDaoSqlite) DeleteOrg(ctx context.Context, id string) *model.ApiError {
_, err := mds.bundb.NewDelete().
Model(&types.Organization{}).
Where("id = ?", id).
Exec(ctx)
_, err := mds.db.ExecContext(ctx, `DELETE from organizations where id=?;`, id)
if err != nil { if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -180,14 +204,10 @@ func (mds *ModelDaoSqlite) DeleteOrg(ctx context.Context, id string) *model.ApiE
} }
func (mds *ModelDaoSqlite) CreateUser(ctx context.Context, func (mds *ModelDaoSqlite) CreateUser(ctx context.Context,
user *model.User, isFirstUser bool) (*model.User, *model.ApiError) { user *types.User, isFirstUser bool) (*types.User, *model.ApiError) {
_, err := mds.bundb.NewInsert().
_, err := mds.db.ExecContext(ctx, Model(user).
`INSERT INTO users (id, name, email, password, created_at, profile_picture_url, group_id, org_id) Exec(ctx)
VALUES (?, ?, ?, ?, ?, ?, ?,?);`,
user.Id, user.Name, user.Email, user.Password, user.CreatedAt,
user.ProfilePictureURL, user.GroupId, user.OrgId,
)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
@ -210,11 +230,15 @@ func (mds *ModelDaoSqlite) CreateUser(ctx context.Context,
} }
func (mds *ModelDaoSqlite) EditUser(ctx context.Context, func (mds *ModelDaoSqlite) EditUser(ctx context.Context,
update *model.User) (*model.User, *model.ApiError) { update *types.User) (*types.User, *model.ApiError) {
_, err := mds.bundb.NewUpdate().
Model(update).
Column("name").
Column("org_id").
Column("email").
Where("id = ?", update.ID).
Exec(ctx)
_, err := mds.db.ExecContext(ctx,
`UPDATE users SET name=?,org_id=?,email=? WHERE id=?;`, update.Name,
update.OrgId, update.Email, update.Id)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -224,8 +248,13 @@ func (mds *ModelDaoSqlite) EditUser(ctx context.Context,
func (mds *ModelDaoSqlite) UpdateUserPassword(ctx context.Context, passwordHash, func (mds *ModelDaoSqlite) UpdateUserPassword(ctx context.Context, passwordHash,
userId string) *model.ApiError { userId string) *model.ApiError {
q := `UPDATE users SET password=? WHERE id=?;` _, err := mds.bundb.NewUpdate().
if _, err := mds.db.ExecContext(ctx, q, passwordHash, userId); err != nil { Model(&types.User{}).
Set("password = ?", passwordHash).
Where("id = ?", userId).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return nil return nil
@ -233,16 +262,24 @@ func (mds *ModelDaoSqlite) UpdateUserPassword(ctx context.Context, passwordHash,
func (mds *ModelDaoSqlite) UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError { func (mds *ModelDaoSqlite) UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError {
q := `UPDATE users SET group_id=? WHERE id=?;` _, err := mds.bundb.NewUpdate().
if _, err := mds.db.ExecContext(ctx, q, groupId, userId); err != nil { Model(&types.User{}).
Set("group_id = ?", groupId).
Where("id = ?", userId).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return nil return nil
} }
func (mds *ModelDaoSqlite) DeleteUser(ctx context.Context, id string) *model.ApiError { func (mds *ModelDaoSqlite) DeleteUser(ctx context.Context, id string) *model.ApiError {
result, err := mds.bundb.NewDelete().
Model(&types.User{}).
Where("id = ?", id).
Exec(ctx)
result, err := mds.db.ExecContext(ctx, `DELETE from users where id=?;`, id)
if err != nil { if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -262,30 +299,19 @@ func (mds *ModelDaoSqlite) DeleteUser(ctx context.Context, id string) *model.Api
} }
func (mds *ModelDaoSqlite) GetUser(ctx context.Context, func (mds *ModelDaoSqlite) GetUser(ctx context.Context,
id string) (*model.UserPayload, *model.ApiError) { id string) (*types.GettableUser, *model.ApiError) {
users := []model.UserPayload{} users := []types.GettableUser{}
query := `select query := mds.bundb.NewSelect().
u.id, Table("users").
u.name, Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
u.email, ColumnExpr("g.name as role").
u.password, ColumnExpr("o.name as organization").
u.created_at, Join("JOIN groups g ON g.id = users.group_id").
u.profile_picture_url, Join("JOIN organizations o ON o.id = users.org_id").
u.org_id, Where("users.id = ?", id)
u.group_id,
g.name as role,
o.name as organization,
COALESCE((select uf.flags
from user_flags uf
where u.id = uf.user_id), '') as flags
from users u, groups g, organizations o
where
g.id=u.group_id and
o.id = u.org_id and
u.id=?;`
if err := mds.db.Select(&users, query, id); err != nil { if err := query.Scan(ctx, &users); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
if len(users) > 1 { if len(users) > 1 {
@ -303,7 +329,7 @@ func (mds *ModelDaoSqlite) GetUser(ctx context.Context,
} }
func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context, func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
email string) (*model.UserPayload, *model.ApiError) { email string) (*types.GettableUser, *model.ApiError) {
if email == "" { if email == "" {
return nil, &model.ApiError{ return nil, &model.ApiError{
@ -312,27 +338,20 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
} }
} }
users := []model.UserPayload{} users := []types.GettableUser{}
query := `select query := mds.bundb.NewSelect().
u.id, Table("users").
u.name, Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
u.email, ColumnExpr("g.name as role").
u.password, ColumnExpr("o.name as organization").
u.created_at, Join("JOIN groups g ON g.id = users.group_id").
u.profile_picture_url, Join("JOIN organizations o ON o.id = users.org_id").
u.org_id, Where("users.email = ?", email)
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
g.id=u.group_id and
o.id = u.org_id and
u.email=?;`
if err := mds.db.Select(&users, query, email); err != nil { if err := query.Scan(ctx, &users); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
if len(users) > 1 { if len(users) > 1 {
return nil, &model.ApiError{ return nil, &model.ApiError{
Typ: model.ErrorInternal, Typ: model.ErrorInternal,
@ -347,34 +366,26 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
} }
// GetUsers fetches total user count // GetUsers fetches total user count
func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) { func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]types.GettableUser, *model.ApiError) {
return mds.GetUsersWithOpts(ctx, 0) return mds.GetUsersWithOpts(ctx, 0)
} }
// GetUsersWithOpts fetches users and supports additional search options // GetUsersWithOpts fetches users and supports additional search options
func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError) { func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]types.GettableUser, *model.ApiError) {
users := []model.UserPayload{} users := []types.GettableUser{}
query := `select query := mds.bundb.NewSelect().
u.id, Table("users").
u.name, Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
u.email, ColumnExpr("g.name as role").
u.password, ColumnExpr("o.name as organization").
u.created_at, Join("JOIN groups g ON g.id = users.group_id").
u.profile_picture_url, Join("JOIN organizations o ON o.id = users.org_id")
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
g.id = u.group_id and
o.id = u.org_id`
if limit > 0 { if limit > 0 {
query = fmt.Sprintf("%s LIMIT %d", query, limit) query.Limit(limit)
} }
err := mds.db.Select(&users, query) err := query.Scan(ctx, &users)
if err != nil { if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
@ -383,54 +394,42 @@ func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]m
} }
func (mds *ModelDaoSqlite) GetUsersByOrg(ctx context.Context, func (mds *ModelDaoSqlite) GetUsersByOrg(ctx context.Context,
orgId string) ([]model.UserPayload, *model.ApiError) { orgId string) ([]types.GettableUser, *model.ApiError) {
users := []model.UserPayload{} users := []types.GettableUser{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
u.group_id = g.id and
u.org_id = o.id and
u.org_id=?;`
if err := mds.db.Select(&users, query, orgId); err != nil { query := mds.bundb.NewSelect().
Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role").
ColumnExpr("o.name as organization").
Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id").
Where("users.org_id = ?", orgId)
err := query.Scan(ctx, &users)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return users, nil return users, nil
} }
func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context, func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context,
groupId string) ([]model.UserPayload, *model.ApiError) { groupId string) ([]types.GettableUser, *model.ApiError) {
users := []model.UserPayload{} users := []types.GettableUser{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
u.group_id = g.id and
o.id = u.org_id and
u.group_id=?;`
if err := mds.db.Select(&users, query, groupId); err != nil { query := mds.bundb.NewSelect().
Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role").
ColumnExpr("o.name as organization").
Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id").
Where("users.group_id = ?", groupId)
err := query.Scan(ctx, &users)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return users, nil return users, nil
@ -452,17 +451,25 @@ func (mds *ModelDaoSqlite) CreateGroup(ctx context.Context,
func (mds *ModelDaoSqlite) DeleteGroup(ctx context.Context, id string) *model.ApiError { func (mds *ModelDaoSqlite) DeleteGroup(ctx context.Context, id string) *model.ApiError {
if _, err := mds.db.ExecContext(ctx, `DELETE from groups where id=?;`, id); err != nil { _, err := mds.bundb.NewDelete().
Model(&types.Group{}).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return nil return nil
} }
func (mds *ModelDaoSqlite) GetGroup(ctx context.Context, func (mds *ModelDaoSqlite) GetGroup(ctx context.Context,
id string) (*model.Group, *model.ApiError) { id string) (*types.Group, *model.ApiError) {
groups := []model.Group{} groups := []types.Group{}
if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE id=?`, id); err != nil { if err := mds.bundb.NewSelect().
Model(&groups).
Where("id = ?", id).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -505,10 +512,13 @@ func (mds *ModelDaoSqlite) GetGroupByName(ctx context.Context,
return &groups[0], nil return &groups[0], nil
} }
func (mds *ModelDaoSqlite) GetGroups(ctx context.Context) ([]model.Group, *model.ApiError) { // TODO(nitya): should have org id
func (mds *ModelDaoSqlite) GetGroups(ctx context.Context) ([]types.Group, *model.ApiError) {
groups := []model.Group{} groups := []types.Group{}
if err := mds.db.Select(&groups, "SELECT * FROM groups"); err != nil { if err := mds.bundb.NewSelect().
Model(&groups).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -516,10 +526,11 @@ func (mds *ModelDaoSqlite) GetGroups(ctx context.Context) ([]model.Group, *model
} }
func (mds *ModelDaoSqlite) CreateResetPasswordEntry(ctx context.Context, func (mds *ModelDaoSqlite) CreateResetPasswordEntry(ctx context.Context,
req *model.ResetPasswordEntry) *model.ApiError { req *types.ResetPasswordRequest) *model.ApiError {
q := `INSERT INTO reset_password_request (user_id, token) VALUES (?, ?);` if _, err := mds.bundb.NewInsert().
if _, err := mds.db.ExecContext(ctx, q, req.UserId, req.Token); err != nil { Model(req).
Exec(ctx); err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
return nil return nil
@ -527,7 +538,11 @@ func (mds *ModelDaoSqlite) CreateResetPasswordEntry(ctx context.Context,
func (mds *ModelDaoSqlite) DeleteResetPasswordEntry(ctx context.Context, func (mds *ModelDaoSqlite) DeleteResetPasswordEntry(ctx context.Context,
token string) *model.ApiError { token string) *model.ApiError {
_, err := mds.db.ExecContext(ctx, `DELETE from reset_password_request where token=?;`, token) _, err := mds.bundb.NewDelete().
Model(&types.ResetPasswordRequest{}).
Where("token = ?", token).
Exec(ctx)
if err != nil { if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err} return &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
@ -535,12 +550,14 @@ func (mds *ModelDaoSqlite) DeleteResetPasswordEntry(ctx context.Context,
} }
func (mds *ModelDaoSqlite) GetResetPasswordEntry(ctx context.Context, func (mds *ModelDaoSqlite) GetResetPasswordEntry(ctx context.Context,
token string) (*model.ResetPasswordEntry, *model.ApiError) { token string) (*types.ResetPasswordRequest, *model.ApiError) {
entries := []model.ResetPasswordEntry{} entries := []types.ResetPasswordRequest{}
q := `SELECT user_id,token FROM reset_password_request WHERE token=?;` if err := mds.bundb.NewSelect().
if err := mds.db.Select(&entries, q, token); err != nil { Model(&entries).
Where("token = ?", token).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
} }
if len(entries) > 1 { if len(entries) > 1 {
@ -554,54 +571,6 @@ func (mds *ModelDaoSqlite) GetResetPasswordEntry(ctx context.Context,
return &entries[0], nil return &entries[0], nil
} }
// CreateUserFlags inserts user specific flags
func (mds *ModelDaoSqlite) UpdateUserFlags(ctx context.Context, userId string, flags map[string]string) (model.UserFlag, *model.ApiError) {
if len(flags) == 0 {
// nothing to do as flags are empty. In this method, we only append the flags
// but not set them to empty
return flags, nil
}
// fetch existing flags
userPayload, apiError := mds.GetUser(ctx, userId)
if apiError != nil {
return nil, apiError
}
for k, v := range userPayload.Flags {
if _, ok := flags[k]; !ok {
// insert only missing keys as we want to retain the
// flags in the db that are not part of this request
flags[k] = v
}
}
// append existing flags with new ones
// write the updated flags
flagsBytes, err := json.Marshal(flags)
if err != nil {
return nil, model.InternalError(err)
}
if len(userPayload.Flags) == 0 {
q := `INSERT INTO user_flags (user_id, flags) VALUES (?, ?);`
if _, err := mds.db.ExecContext(ctx, q, userId, string(flagsBytes)); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
} else {
q := `UPDATE user_flags SET flags = ? WHERE user_id = ?;`
if _, err := mds.db.ExecContext(ctx, q, userId, string(flagsBytes)); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
}
return flags, nil
}
func (mds *ModelDaoSqlite) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, model.BaseApiError) { func (mds *ModelDaoSqlite) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, model.BaseApiError) {
// assume user is valid unless proven otherwise and assign default values for rest of the fields // assume user is valid unless proven otherwise and assign default values for rest of the fields
resp := &model.PrecheckResponse{IsUser: true, CanSelfRegister: false, SSO: false, SsoUrl: "", SsoError: ""} resp := &model.PrecheckResponse{IsUser: true, CanSelfRegister: false, SSO: false, SsoUrl: "", SsoError: ""}

View File

@ -88,11 +88,6 @@ type ChangePasswordRequest struct {
NewPassword string `json:"newPassword"` NewPassword string `json:"newPassword"`
} }
type ResetPasswordEntry struct {
UserId string `json:"userId" db:"user_id"`
Token string `json:"token" db:"token"`
}
type UserRole struct { type UserRole struct {
UserId string `json:"user_id"` UserId string `json:"user_id"`
GroupName string `json:"group_name"` GroupName string `json:"group_name"`

View File

@ -1,46 +1,10 @@
package model package model
import ( import "time"
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
type Organization struct { type ResetPasswordRequest struct {
Id string `json:"id" db:"id"` Password string `json:"password"`
Name string `json:"name" db:"name"` Token string `json:"token"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
IsAnonymous bool `json:"isAnonymous" db:"is_anonymous"`
HasOptedUpdates bool `json:"hasOptedUpdates" db:"has_opted_updates"`
}
// InvitationObject represents the token object stored in the db
type InvitationObject struct {
Id string `json:"id" db:"id"`
Email string `json:"email" db:"email"`
Name string `json:"name" db:"name"`
Token string `json:"token" db:"token"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
Role string `json:"role" db:"role"`
OrgId string `json:"orgId" db:"org_id"`
}
type User struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Email string `json:"email" db:"email"`
Password string `json:"password,omitempty" db:"password"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
ProfilePictureURL string `json:"profilePictureURL" db:"profile_picture_url"`
OrgId string `json:"orgId,omitempty" db:"org_id"`
GroupId string `json:"groupId,omitempty" db:"group_id"`
}
type ApdexSettings struct {
ServiceName string `json:"serviceName" db:"service_name"`
Threshold float64 `json:"threshold" db:"threshold"`
ExcludeStatusCodes string `json:"excludeStatusCodes" db:"exclude_status_codes"` // sqlite doesn't support array type
} }
type IngestionKey struct { type IngestionKey struct {
@ -51,50 +15,3 @@ type IngestionKey struct {
IngestionURL string `json:"ingestionURL" db:"ingestion_url"` IngestionURL string `json:"ingestionURL" db:"ingestion_url"`
DataRegion string `json:"dataRegion" db:"data_region"` DataRegion string `json:"dataRegion" db:"data_region"`
} }
type UserFlag map[string]string
func (uf UserFlag) Value() (driver.Value, error) {
f := make(map[string]string, 0)
for k, v := range uf {
f[k] = v
}
return json.Marshal(f)
}
func (uf *UserFlag) Scan(value interface{}) error {
if value == "" {
return nil
}
b, ok := value.(string)
if !ok {
return fmt.Errorf("type assertion to []byte failed while scanning user flag")
}
f := make(map[string]string, 0)
if err := json.Unmarshal([]byte(b), &f); err != nil {
return err
}
*uf = make(UserFlag, len(f))
for k, v := range f {
(*uf)[k] = v
}
return nil
}
type UserPayload struct {
User
Role string `json:"role"`
Organization string `json:"organization"`
Flags UserFlag `json:"flags"`
}
type Group struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}
type ResetPasswordRequest struct {
Password string `json:"password"`
Token string `json:"token"`
}

View File

@ -20,6 +20,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/version" "go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/types"
) )
const ( const (
@ -206,7 +207,7 @@ type Telemetry struct {
alertsInfoCallback func(ctx context.Context) (*model.AlertsInfo, error) alertsInfoCallback func(ctx context.Context) (*model.AlertsInfo, error)
userCountCallback func(ctx context.Context) (int, error) userCountCallback func(ctx context.Context) (int, error)
userRoleCallback func(ctx context.Context, groupId string) (string, error) userRoleCallback func(ctx context.Context, groupId string) (string, error)
getUsersCallback func(ctx context.Context) ([]model.UserPayload, *model.ApiError) getUsersCallback func(ctx context.Context) ([]types.GettableUser, *model.ApiError)
dashboardsInfoCallback func(ctx context.Context) (*model.DashboardsInfo, error) dashboardsInfoCallback func(ctx context.Context) (*model.DashboardsInfo, error)
savedViewsInfoCallback func(ctx context.Context) (*model.SavedViewsInfo, error) savedViewsInfoCallback func(ctx context.Context) (*model.SavedViewsInfo, error)
} }
@ -223,7 +224,7 @@ func (a *Telemetry) SetUserRoleCallback(callback func(ctx context.Context, group
a.userRoleCallback = callback a.userRoleCallback = callback
} }
func (a *Telemetry) SetGetUsersCallback(callback func(ctx context.Context) ([]model.UserPayload, *model.ApiError)) { func (a *Telemetry) SetGetUsersCallback(callback func(ctx context.Context) ([]types.GettableUser, *model.ApiError)) {
a.getUsersCallback = callback a.getUsersCallback = callback
} }
@ -524,7 +525,7 @@ func getOutboundIP() string {
return string(ip) return string(ip)
} }
func (a *Telemetry) IdentifyUser(user *model.User) { func (a *Telemetry) IdentifyUser(user *types.User) {
if user.Email == DEFAULT_CLOUD_EMAIL { if user.Email == DEFAULT_CLOUD_EMAIL {
return return
} }
@ -534,7 +535,7 @@ func (a *Telemetry) IdentifyUser(user *model.User) {
return return
} }
// extract user group from user.groupId // extract user group from user.groupId
role, _ := a.userRoleCallback(context.Background(), user.GroupId) role, _ := a.userRoleCallback(context.Background(), user.GroupID)
if a.saasOperator != nil { if a.saasOperator != nil {
if role != "" { if role != "" {

View File

@ -17,9 +17,9 @@ import (
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/featureManager" "go.signoz.io/signoz/pkg/query-service/featureManager"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -260,7 +260,7 @@ func (tb *FilterSuggestionsTestBed) mockAttribValuesQueryResponse(
type FilterSuggestionsTestBed struct { type FilterSuggestionsTestBed struct {
t *testing.T t *testing.T
testUser *model.User testUser *types.User
qsHttpHandler http.Handler qsHttpHandler http.Handler
mockClickhouse mockhouse.ClickConnMockCommon mockClickhouse mockhouse.ClickConnMockCommon
} }

View File

@ -23,11 +23,11 @@ import (
opampModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model" opampModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/queryBuilderToExpr" "go.signoz.io/signoz/pkg/query-service/queryBuilderToExpr"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/sqlstore" "go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -441,7 +441,7 @@ func TestCanSavePipelinesWithoutConnectedAgents(t *testing.T) {
// configuring log pipelines and provides test helpers. // configuring log pipelines and provides test helpers.
type LogPipelinesTestBed struct { type LogPipelinesTestBed struct {
t *testing.T t *testing.T
testUser *model.User testUser *types.User
apiHandler *app.APIHandler apiHandler *app.APIHandler
agentConfMgr *agentConf.Manager agentConfMgr *agentConf.Manager
opampServer *opamp.Server opampServer *opamp.Server

View File

@ -17,9 +17,9 @@ import (
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/featureManager" "go.signoz.io/signoz/pkg/query-service/featureManager"
"go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/sqlstore" "go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -338,7 +338,7 @@ func TestConfigReturnedWhenAgentChecksIn(t *testing.T) {
type CloudIntegrationsTestBed struct { type CloudIntegrationsTestBed struct {
t *testing.T t *testing.T
testUser *model.User testUser *types.User
qsHttpHandler http.Handler qsHttpHandler http.Handler
mockClickhouse mockhouse.ClickConnMockCommon mockClickhouse mockhouse.ClickConnMockCommon
} }

View File

@ -23,6 +23,7 @@ import (
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/sqlstore" "go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -367,7 +368,7 @@ func TestDashboardsForInstalledIntegrationDashboards(t *testing.T) {
type IntegrationsTestBed struct { type IntegrationsTestBed struct {
t *testing.T t *testing.T
testUser *model.User testUser *types.User
qsHttpHandler http.Handler qsHttpHandler http.Handler
mockClickhouse mockhouse.ClickConnMockCommon mockClickhouse mockhouse.ClickConnMockCommon
} }

View File

@ -25,6 +25,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/interfaces" "go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes" "go.signoz.io/signoz/pkg/types/authtypes"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
) )
@ -150,10 +151,10 @@ func makeTestSignozLog(
return testLog return testLog
} }
func createTestUser() (*model.User, *model.ApiError) { func createTestUser() (*types.User, *model.ApiError) {
// Create a test user for auth // Create a test user for auth
ctx := context.Background() ctx := context.Background()
org, apiErr := dao.DB().CreateOrg(ctx, &model.Organization{ org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{
Name: "test", Name: "test",
}) })
if apiErr != nil { if apiErr != nil {
@ -170,20 +171,20 @@ func createTestUser() (*model.User, *model.ApiError) {
userId := uuid.NewString() userId := uuid.NewString()
return dao.DB().CreateUser( return dao.DB().CreateUser(
ctx, ctx,
&model.User{ &types.User{
Id: userId, ID: userId,
Name: "test", Name: "test",
Email: userId[:8] + "test@test.com", Email: userId[:8] + "test@test.com",
Password: "test", Password: "test",
OrgId: org.Id, OrgID: org.ID,
GroupId: group.ID, GroupID: group.ID,
}, },
true, true,
) )
} }
func AuthenticatedRequestForTest( func AuthenticatedRequestForTest(
user *model.User, user *types.User,
path string, path string,
postData interface{}, postData interface{},
) (*http.Request, error) { ) (*http.Request, error) {

View File

@ -45,6 +45,9 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
sqlmigration.NewAddIntegrationsFactory(), sqlmigration.NewAddIntegrationsFactory(),
sqlmigration.NewAddLicensesFactory(), sqlmigration.NewAddLicensesFactory(),
sqlmigration.NewAddPatsFactory(), sqlmigration.NewAddPatsFactory(),
sqlmigration.NewModifyDatetimeFactory(),
sqlmigration.NewModifyOrgDomainFactory(),
sqlmigration.NewUpdateOrganizationFactory(sqlStore),
), ),
) )
if err != nil { if err != nil {
@ -62,7 +65,10 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
func NewQueryServiceDBForTests(t *testing.T) sqlstore.SQLStore { func NewQueryServiceDBForTests(t *testing.T) sqlstore.SQLStore {
sqlStore, _ := NewTestSqliteDB(t) sqlStore, _ := NewTestSqliteDB(t)
dao.InitDao(sqlStore) err := dao.InitDao(sqlStore)
if err != nil {
t.Fatalf("could not initialize dao: %v", err)
}
dashboards.InitDB(sqlStore.SQLxDB()) dashboards.InitDB(sqlStore.SQLxDB())
return sqlStore return sqlStore

View File

@ -74,6 +74,9 @@ func New(
return nil, err return nil, err
} }
// add the org migration here since we need to pass the sqlstore
providerConfig.SQLMigrationProviderFactories.Add(sqlmigration.NewUpdateOrganizationFactory(sqlstore))
// Initialize telemetrystore from the available telemetrystore provider factories // Initialize telemetrystore from the available telemetrystore provider factories
telemetrystore, err := factory.NewProviderFromNamedMap( telemetrystore, err := factory.NewProviderFromNamedMap(
ctx, ctx,

View File

@ -35,7 +35,7 @@ func (migration *addPats) Up(ctx context.Context, db *bun.DB) error {
OrgID string `bun:"org_id,type:text,notnull"` OrgID string `bun:"org_id,type:text,notnull"`
Name string `bun:"name,type:varchar(50),notnull,unique"` Name string `bun:"name,type:varchar(50),notnull,unique"`
CreatedAt int `bun:"created_at,notnull"` CreatedAt int `bun:"created_at,notnull"`
UpdatedAt int `bun:"updated_at,type:timestamp"` UpdatedAt int `bun:"updated_at"`
Data string `bun:"data,type:text,notnull"` Data string `bun:"data,type:text,notnull"`
}{}). }{}).
ForeignKey(`("org_id") REFERENCES "organizations" ("id")`). ForeignKey(`("org_id") REFERENCES "organizations" ("id")`).

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/migrate" "github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory" "go.signoz.io/signoz/pkg/factory"
) )
@ -27,6 +28,11 @@ func (migration *modifyOrgDomain) Register(migrations *migrate.Migrations) error
} }
func (migration *modifyOrgDomain) Up(ctx context.Context, db *bun.DB) error { func (migration *modifyOrgDomain) Up(ctx context.Context, db *bun.DB) error {
// only run this for old sqlite db
if db.Dialect().Name() != dialect.SQLite {
return nil
}
// begin transaction // begin transaction
tx, err := db.BeginTx(ctx, nil) tx, err := db.BeginTx(ctx, nil)
if err != nil { if err != nil {

View File

@ -0,0 +1,152 @@
package sqlmigration
import (
"context"
"database/sql"
"errors"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type updateOrganization struct {
store sqlstore.SQLStore
}
func NewUpdateOrganizationFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("update_organization"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
return newUpdateOrganization(ctx, ps, c, sqlstore)
})
}
func newUpdateOrganization(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) {
return &updateOrganization{
store: store,
}, nil
}
func (migration *updateOrganization) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *updateOrganization) Up(ctx context.Context, db *bun.DB) error {
// begin transaction
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// update apdex settings table
if err := updateApdexSettings(ctx, tx); err != nil {
return err
}
// drop user_flags table
if _, err := tx.NewDropTable().IfExists().Table("user_flags").Exec(ctx); err != nil {
return err
}
// add org id to groups table
if exists, err := migration.store.Dialect().ColumnExists(ctx, tx, "groups", "org_id"); err != nil {
return err
} else if !exists {
if _, err := tx.NewAddColumn().Table("groups").ColumnExpr("org_id TEXT").Exec(ctx); err != nil {
return err
}
}
// add created_at to groups table
for _, table := range []string{"groups"} {
if exists, err := migration.store.Dialect().ColumnExists(ctx, tx, table, "created_at"); err != nil {
return err
} else if !exists {
if _, err := tx.NewAddColumn().Table(table).ColumnExpr("created_at TIMESTAMP").Exec(ctx); err != nil {
return err
}
}
}
// add updated_at to organizations, users, groups table
for _, table := range []string{"organizations", "users", "groups"} {
if exists, err := migration.store.Dialect().ColumnExists(ctx, tx, table, "updated_at"); err != nil {
return err
} else if !exists {
if _, err := tx.NewAddColumn().Table(table).ColumnExpr("updated_at TIMESTAMP").Exec(ctx); err != nil {
return err
}
}
}
// since organizations, users has created_at as integer instead of timestamp
for _, table := range []string{"organizations", "users", "invites"} {
if err := migration.store.Dialect().MigrateIntToTimestamp(ctx, tx, table, "created_at"); err != nil {
return err
}
}
// migrate is_anonymous and has_opted_updates to boolean from int
for _, column := range []string{"is_anonymous", "has_opted_updates"} {
if err := migration.store.Dialect().MigrateIntToBoolean(ctx, tx, "organizations", column); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (migration *updateOrganization) Down(ctx context.Context, db *bun.DB) error {
return nil
}
func updateApdexSettings(ctx context.Context, tx bun.Tx) error {
if _, err := tx.NewCreateTable().
Model(&struct {
bun.BaseModel `bun:"table:apdex_settings_new"`
OrgID string `bun:"org_id,pk,type:text"`
ServiceName string `bun:"service_name,pk,type:text"`
Threshold float64 `bun:"threshold,type:float,notnull"`
ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull"`
}{}).
ForeignKey(`("org_id") REFERENCES "organizations" ("id")`).
IfNotExists().
Exec(ctx); err != nil {
return err
}
// get org id from organizations table
var orgID string
if err := tx.QueryRowContext(ctx, `SELECT id FROM organizations LIMIT 1`).Scan(&orgID); err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
if orgID != "" {
// copy old data
if _, err := tx.ExecContext(ctx, `INSERT INTO apdex_settings_new (org_id, service_name, threshold, exclude_status_codes) SELECT ?, service_name, threshold, exclude_status_codes FROM apdex_settings`, orgID); err != nil {
return err
}
}
// drop old table
if _, err := tx.NewDropTable().IfExists().Table("apdex_settings").Exec(ctx); err != nil {
return err
}
// rename new table to old table
if _, err := tx.ExecContext(ctx, `ALTER TABLE apdex_settings_new RENAME TO apdex_settings`); err != nil {
return err
}
return nil
}

View File

@ -86,3 +86,29 @@ func WrapIfNotExists(ctx context.Context, db *bun.DB, table string, column strin
return q.Err(ErrNoExecute) return q.Err(ErrNoExecute)
} }
} }
func GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) {
var columnType string
var err error
if bun.Dialect().Name() == dialect.SQLite {
err = bun.NewSelect().
ColumnExpr("type").
TableExpr("pragma_table_info(?)", table).
Where("name = ?", column).
Scan(ctx, &columnType)
} else {
err = bun.NewSelect().
ColumnExpr("data_type").
TableExpr("information_schema.columns").
Where("table_name = ?", table).
Where("column_name = ?", column).
Scan(ctx, &columnType)
}
if err != nil {
return "", err
}
return columnType, nil
}

View File

@ -0,0 +1,116 @@
package postgressqlstore
import (
"context"
"github.com/uptrace/bun"
)
type PGDialect struct {
}
func (dialect *PGDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error {
columnType, err := dialect.GetColumnType(ctx, bun, table, column)
if err != nil {
return err
}
// bigint for postgres and INTEGER for sqlite
if columnType != "bigint" {
return nil
}
// if the columns is integer then do this
if _, err := bun.ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil {
return err
}
// add new timestamp column
if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " TIMESTAMP").Exec(ctx); err != nil {
return err
}
if _, err := bun.NewUpdate().
Table(table).
Set(column + " = to_timestamp(cast(" + column + "_old as INTEGER))").
Where("1=1").
Exec(ctx); err != nil {
return err
}
// drop old column
if _, err := bun.NewDropColumn().Table(table).Column(column + "_old").Exec(ctx); err != nil {
return err
}
return nil
}
func (dialect *PGDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error {
columnType, err := dialect.GetColumnType(ctx, bun, table, column)
if err != nil {
return err
}
if columnType != "bigint" {
return nil
}
if _, err := bun.ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil {
return err
}
// add new boolean column
if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " BOOLEAN").Exec(ctx); err != nil {
return err
}
// copy data from old column to new column, converting from int to boolean
if _, err := bun.NewUpdate().
Table(table).
Set(column + " = CASE WHEN " + column + "_old = 1 THEN true ELSE false END").
Where("1=1").
Exec(ctx); err != nil {
return err
}
// drop old column
if _, err := bun.NewDropColumn().Table(table).Column(column + "_old").Exec(ctx); err != nil {
return err
}
return nil
}
func (dialect *PGDialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) {
var columnType string
var err error
err = bun.NewSelect().
ColumnExpr("data_type").
TableExpr("information_schema.columns").
Where("table_name = ?", table).
Where("column_name = ?", column).
Scan(ctx, &columnType)
if err != nil {
return "", err
}
return columnType, nil
}
func (dialect *PGDialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) {
var count int
err := bun.NewSelect().
ColumnExpr("COUNT(*)").
TableExpr("information_schema.columns").
Where("table_name = ?", table).
Where("column_name = ?", column).
Scan(ctx, &count)
if err != nil {
return false, err
}
return count > 0, nil
}

View File

@ -18,6 +18,7 @@ type provider struct {
sqldb *sql.DB sqldb *sql.DB
bundb *sqlstore.BunDB bundb *sqlstore.BunDB
sqlxdb *sqlx.DB sqlxdb *sqlx.DB
dialect *PGDialect
} }
func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] {
@ -59,6 +60,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
sqldb: sqldb, sqldb: sqldb,
bundb: sqlstore.NewBunDB(settings, sqldb, pgdialect.New(), hooks), bundb: sqlstore.NewBunDB(settings, sqldb, pgdialect.New(), hooks),
sqlxdb: sqlx.NewDb(sqldb, "postgres"), sqlxdb: sqlx.NewDb(sqldb, "postgres"),
dialect: &PGDialect{},
}, nil }, nil
} }
@ -74,6 +76,10 @@ func (provider *provider) SQLxDB() *sqlx.DB {
return provider.sqlxdb return provider.sqlxdb
} }
func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB { func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB {
return provider.bundb.BunDBCtx(ctx) return provider.bundb.BunDBCtx(ctx)
} }

View File

@ -0,0 +1,115 @@
package sqlitesqlstore
import (
"context"
"github.com/uptrace/bun"
)
type SQLiteDialect struct {
}
func (dialect *SQLiteDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error {
columnType, err := dialect.GetColumnType(ctx, bun, table, column)
if err != nil {
return err
}
if columnType != "INTEGER" {
return nil
}
// if the columns is integer then do this
if _, err := bun.ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil {
return err
}
// add new timestamp column
if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " TIMESTAMP").Exec(ctx); err != nil {
return err
}
// copy data from old column to new column, converting from int (unix timestamp) to timestamp
if _, err := bun.NewUpdate().
Table(table).
Set(column + " = datetime(" + column + "_old, 'unixepoch')").
Where("1=1").
Exec(ctx); err != nil {
return err
}
// drop old column
if _, err := bun.NewDropColumn().Table(table).Column(column + "_old").Exec(ctx); err != nil {
return err
}
return nil
}
func (dialect *SQLiteDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error {
columnType, err := dialect.GetColumnType(ctx, bun, table, column)
if err != nil {
return err
}
if columnType != "INTEGER" {
return nil
}
if _, err := bun.ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil {
return err
}
// add new boolean column
if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " BOOLEAN").Exec(ctx); err != nil {
return err
}
// copy data from old column to new column, converting from int to boolean
if _, err := bun.NewUpdate().
Table(table).
Set(column + " = CASE WHEN " + column + "_old = 1 THEN true ELSE false END").
Where("1=1").
Exec(ctx); err != nil {
return err
}
// drop old column
if _, err := bun.NewDropColumn().Table(table).Column(column + "_old").Exec(ctx); err != nil {
return err
}
return nil
}
func (dialect *SQLiteDialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) {
var columnType string
var err error
err = bun.NewSelect().
ColumnExpr("type").
TableExpr("pragma_table_info(?)", table).
Where("name = ?", column).
Scan(ctx, &columnType)
if err != nil {
return "", err
}
return columnType, nil
}
func (dialect *SQLiteDialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) {
var count int
err := bun.NewSelect().
ColumnExpr("COUNT(*)").
TableExpr("pragma_table_info(?)", table).
Where("name = ?", column).
Scan(ctx, &count)
if err != nil {
return false, err
}
return count > 0, nil
}

View File

@ -17,6 +17,7 @@ type provider struct {
sqldb *sql.DB sqldb *sql.DB
bundb *sqlstore.BunDB bundb *sqlstore.BunDB
sqlxdb *sqlx.DB sqlxdb *sqlx.DB
dialect *SQLiteDialect
} }
func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] {
@ -49,6 +50,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
sqldb: sqldb, sqldb: sqldb,
bundb: sqlstore.NewBunDB(settings, sqldb, sqlitedialect.New(), hooks), bundb: sqlstore.NewBunDB(settings, sqldb, sqlitedialect.New(), hooks),
sqlxdb: sqlx.NewDb(sqldb, "sqlite3"), sqlxdb: sqlx.NewDb(sqldb, "sqlite3"),
dialect: &SQLiteDialect{},
}, nil }, nil
} }
@ -64,6 +66,10 @@ func (provider *provider) SQLxDB() *sqlx.DB {
return provider.sqlxdb return provider.sqlxdb
} }
func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB { func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB {
return provider.bundb.BunDBCtx(ctx) return provider.bundb.BunDBCtx(ctx)
} }

View File

@ -20,6 +20,9 @@ type SQLStore interface {
// SQLxDB returns an instance of sqlx.DB. This is the legacy ORM used. // SQLxDB returns an instance of sqlx.DB. This is the legacy ORM used.
SQLxDB() *sqlx.DB SQLxDB() *sqlx.DB
// Returns the dialect of the database.
Dialect() SQLDialect
// RunInTxCtx runs the given callback in a transaction. It creates and injects a new context with the transaction. // RunInTxCtx runs the given callback in a transaction. It creates and injects a new context with the transaction.
// If a transaction is present in the context, it will be used. // If a transaction is present in the context, it will be used.
RunInTxCtx(ctx context.Context, opts *SQLStoreTxOptions, cb func(ctx context.Context) error) error RunInTxCtx(ctx context.Context, opts *SQLStoreTxOptions, cb func(ctx context.Context) error) error
@ -32,3 +35,10 @@ type SQLStore interface {
type SQLStoreHook interface { type SQLStoreHook interface {
bun.QueryHook bun.QueryHook
} }
type SQLDialect interface {
MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error
MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error
GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error)
ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error)
}

View File

@ -0,0 +1,26 @@
package sqlstoretest
import (
"context"
"github.com/uptrace/bun"
)
type TestDialect struct {
}
func (dialect *TestDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error {
return nil
}
func (dialect *TestDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error {
return nil
}
func (dialect *TestDialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) {
return "", nil
}
func (dialect *TestDialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) {
return false, nil
}

View File

@ -15,10 +15,11 @@ import (
var _ sqlstore.SQLStore = (*Provider)(nil) var _ sqlstore.SQLStore = (*Provider)(nil)
type Provider struct { type Provider struct {
db *sql.DB db *sql.DB
mock sqlmock.Sqlmock mock sqlmock.Sqlmock
bunDB *bun.DB bunDB *bun.DB
sqlxDB *sqlx.DB sqlxDB *sqlx.DB
dialect *TestDialect
} }
func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider { func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider {
@ -38,10 +39,11 @@ func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider {
} }
return &Provider{ return &Provider{
db: db, db: db,
mock: mock, mock: mock,
bunDB: bunDB, bunDB: bunDB,
sqlxDB: sqlxDB, sqlxDB: sqlxDB,
dialect: &TestDialect{},
} }
} }
@ -61,6 +63,10 @@ func (provider *Provider) Mock() sqlmock.Sqlmock {
return provider.mock return provider.mock
} }
func (provider *Provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *Provider) BunDBCtx(ctx context.Context) bun.IDB { func (provider *Provider) BunDBCtx(ctx context.Context) bun.IDB {
return provider.bunDB return provider.bunDB
} }

13
pkg/types/auditable.go Normal file
View File

@ -0,0 +1,13 @@
package types
import "time"
type TimeAuditable struct {
CreatedAt time.Time `bun:"created_at" json:"createdAt"`
UpdatedAt time.Time `bun:"updated_at" json:"updatedAt"`
}
type UserAuditable struct {
CreatedBy string `bun:"created_by" json:"createdBy"`
UpdatedBy string `bun:"updated_by" json:"updatedBy"`
}

View File

@ -1,8 +1,6 @@
package types package types
import ( import (
"time"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
@ -10,69 +8,16 @@ import (
type Organization struct { type Organization struct {
bun.BaseModel `bun:"table:organizations"` bun.BaseModel `bun:"table:organizations"`
ID string `bun:"id,pk,type:text"` TimeAuditable
Name string `bun:"name,type:text,notnull"` ID string `bun:"id,pk,type:text" json:"id"`
CreatedAt int `bun:"created_at,notnull"` Name string `bun:"name,type:text,notnull" json:"name"`
IsAnonymous int `bun:"is_anonymous,notnull,default:0,CHECK(is_anonymous IN (0,1))"` IsAnonymous bool `bun:"is_anonymous,notnull,default:0,CHECK(is_anonymous IN (0,1))" json:"isAnonymous"`
HasOptedUpdates int `bun:"has_opted_updates,notnull,default:1,CHECK(has_opted_updates IN (0,1))"` HasOptedUpdates bool `bun:"has_opted_updates,notnull,default:1,CHECK(has_opted_updates IN (0,1))" json:"hasOptedUpdates"`
}
type Invite struct {
bun.BaseModel `bun:"table:invites"`
ID int `bun:"id,pk,autoincrement"`
Name string `bun:"name,type:text,notnull"`
Email string `bun:"email,type:text,notnull,unique"`
Token string `bun:"token,type:text,notnull"`
CreatedAt int `bun:"created_at,notnull"`
Role string `bun:"role,type:text,notnull"`
OrgID string `bun:"org_id,type:text,notnull"`
}
type Group struct {
bun.BaseModel `bun:"table:groups"`
ID string `bun:"id,pk,type:text" json:"id"`
Name string `bun:"name,type:text,notnull,unique" json:"name"`
}
type User struct {
bun.BaseModel `bun:"table:users"`
ID string `bun:"id,pk,type:text"`
Name string `bun:"name,type:text,notnull"`
Email string `bun:"email,type:text,notnull,unique"`
Password string `bun:"password,type:text,notnull"`
CreatedAt int `bun:"created_at,notnull"`
ProfilePictureURL string `bun:"profile_picture_url,type:text"`
GroupID string `bun:"group_id,type:text,notnull"`
OrgID string `bun:"org_id,type:text,notnull"`
}
type ResetPasswordRequest struct {
bun.BaseModel `bun:"table:reset_password_request"`
ID int `bun:"id,pk,autoincrement"`
Token string `bun:"token,type:text,notnull"`
UserID string `bun:"user_id,type:text,notnull"`
}
type UserFlags struct {
bun.BaseModel `bun:"table:user_flags"`
UserID string `bun:"user_id,pk,type:text,notnull"`
Flags string `bun:"flags,type:text"`
} }
type ApdexSettings struct { type ApdexSettings struct {
bun.BaseModel `bun:"table:apdex_settings"` OrgID string `bun:"org_id,pk,type:text" json:"orgId"`
ServiceName string `bun:"service_name,pk,type:text"` ServiceName string `bun:"service_name,pk,type:text" json:"serviceName"`
Threshold float64 `bun:"threshold,type:float,notnull"` Threshold float64 `bun:"threshold,type:float,notnull" json:"threshold"`
ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull"` ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull" json:"excludeStatusCodes"`
}
type IngestionKey struct {
bun.BaseModel `bun:"table:ingestion_keys"`
KeyId string `bun:"key_id,pk,type:text"`
Name string `bun:"name,type:text"`
CreatedAt time.Time `bun:"created_at,default:current_timestamp"`
IngestionKey string `bun:"ingestion_key,type:text,notnull"`
IngestionURL string `bun:"ingestion_url,type:text,notnull"`
DataRegion string `bun:"data_region,type:text,notnull"`
} }

54
pkg/types/user.go Normal file
View File

@ -0,0 +1,54 @@
package types
import (
"time"
"github.com/uptrace/bun"
)
type Invite struct {
bun.BaseModel `bun:"table:invites"`
OrgID string `bun:"org_id,type:text,notnull" json:"orgId"`
ID int `bun:"id,pk,autoincrement" json:"id"`
Name string `bun:"name,type:text,notnull" json:"name"`
Email string `bun:"email,type:text,notnull,unique" json:"email"`
Token string `bun:"token,type:text,notnull" json:"token"`
CreatedAt time.Time `bun:"created_at,notnull" json:"createdAt"`
Role string `bun:"role,type:text,notnull" json:"role"`
}
type Group struct {
bun.BaseModel `bun:"table:groups"`
TimeAuditable
OrgID string `bun:"org_id,type:text"`
ID string `bun:"id,pk,type:text" json:"id"`
Name string `bun:"name,type:text,notnull,unique" json:"name"`
}
type GettableUser struct {
User
Role string `json:"role"`
Organization string `json:"organization"`
}
type User struct {
bun.BaseModel `bun:"table:users"`
TimeAuditable
ID string `bun:"id,pk,type:text" json:"id"`
Name string `bun:"name,type:text,notnull" json:"name"`
Email string `bun:"email,type:text,notnull,unique" json:"email"`
Password string `bun:"password,type:text,notnull" json:"-"`
ProfilePictureURL string `bun:"profile_picture_url,type:text" json:"profilePictureURL"`
GroupID string `bun:"group_id,type:text,notnull" json:"groupId"`
OrgID string `bun:"org_id,type:text,notnull" json:"orgId"`
}
type ResetPasswordRequest struct {
bun.BaseModel `bun:"table:reset_password_request"`
ID int `bun:"id,pk,autoincrement" json:"id"`
Token string `bun:"token,type:text,notnull" json:"token"`
UserID string `bun:"user_id,type:text,notnull" json:"userId"`
}