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"
"go.signoz.io/signoz/pkg/query-service/dao"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap"
)
@ -45,7 +46,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return
}
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgId, cloudProvider)
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgID, cloudProvider)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:",
@ -124,7 +125,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
))
}
for _, p := range allPats {
if p.UserID == integrationUser.Id && p.Name == integrationPATName {
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
return p.Token, nil
}
}
@ -136,7 +137,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
newPAT := model.PAT{
Token: generatePATToken(),
UserID: integrationUser.Id,
UserID: integrationUser.ID,
Name: integrationPATName,
Role: baseconstants.ViewerGroup,
ExpiresAt: 0,
@ -154,7 +155,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string,
) (*basemodel.User, *basemodel.ApiError) {
) (*types.User, *basemodel.ApiError) {
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)
integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
@ -171,19 +172,21 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
zap.String("cloudProvider", cloudProvider),
)
newUser := &basemodel.User{
Id: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
CreatedAt: time.Now().Unix(),
OrgId: orgId,
newUser := &types.User{
ID: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
OrgID: orgId,
}
viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup)
if apiErr != nil {
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())
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.
pat.UserID = user.Id
pat.UserID = user.ID
pat.CreatedAt = time.Now().Unix()
pat.UpdatedAt = time.Now().Unix()
pat.LastUsed = 0
@ -112,7 +112,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
return
}
req.UpdatedByUserID = user.Id
req.UpdatedByUserID = user.ID
id := mux.Vars(r)["id"]
req.UpdatedAt = time.Now().Unix()
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)
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)
if 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))
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)
return
}

View File

@ -25,6 +25,7 @@ import (
"go.signoz.io/signoz/ee/query-service/rules"
"go.signoz.io/signoz/pkg/http/middleware"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.signoz.io/signoz/pkg/web"
@ -340,14 +341,14 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
r := baseapp.NewRouter()
// 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)
if err != nil {
return nil, err
}
if user.User.OrgId == "" {
if user.User.OrgID == "" {
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"
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/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"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)
if ok && patToken != "" {
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()
dao.UpdatePATLastUsed(ctx, patToken, time.Now().Unix())
user.User.GroupId = group.ID
user.User.Id = pat.Id
return &basemodel.UserPayload{
user.User.GroupID = group.ID
user.User.ID = pat.Id
return &types.GettableUser{
User: user.User,
Role: pat.Role,
}, nil

View File

@ -10,6 +10,7 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
)
@ -39,7 +40,7 @@ type ModelDao interface {
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) 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)
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"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"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
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
@ -42,15 +43,17 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
return nil, apiErr
}
user := &basemodel.User{
Id: uuid.NewString(),
Name: "",
Email: email,
Password: hash,
CreatedAt: time.Now().Unix(),
user := &types.User{
ID: uuid.NewString(),
Name: "",
Email: email,
Password: hash,
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
ProfilePictureURL: "", // Currently unused
GroupId: group.ID,
OrgId: domain.OrgId,
GroupID: group.ID,
OrgID: domain.OrgId,
}
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")
}
user := &basemodel.User{}
user := &types.User{}
if userPayload == nil {
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",
redirectUri,
tokenStore.AccessJwt,
user.Id,
user.ID,
tokenStore.RefreshJwt), nil
}

View File

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

View File

@ -11,7 +11,7 @@ import (
saml2 "github.com/russellhaering/gosaml2"
"go.signoz.io/signoz/ee/query-service/sso"
"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"
)
@ -33,7 +33,7 @@ type OrgDomain struct {
SamlConfig *SamlConfig `json:"samlConfig"`
GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"`
Org *basemodel.Organization
Org *types.Organization
}
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 getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications';
import { isUndefined } from 'lodash-es';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { AlertsEmptyState } from './AlertsEmptyState/AlertsEmptyState';
import ListAlert from './ListAlert';
function ListAlertRules(): JSX.Element {
const { t } = useTranslation('common');
const location = useLocation();
const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
queryFn: getAll,
cacheTime: 0,
@ -70,7 +67,6 @@ function ListAlertRules(): JSX.Element {
return (
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<ReleaseNote path={location.pathname} />
<ListAlert
{...{
allAlertRules: data.payload,

View File

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

View File

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

View File

@ -1,15 +1,9 @@
import { Space } from 'antd';
import ReleaseNote from 'components/ReleaseNote';
import ServicesApplication from 'container/ServiceApplication';
import { useLocation } from 'react-router-dom';
function Metrics(): JSX.Element {
const location = useLocation();
return (
<Space direction="vertical" style={{ width: '100%' }}>
<ReleaseNote path={location.pathname} />
<ServicesApplication />
</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 { LicenseV3ResModel } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { UserFlags } from 'types/api/user/setFlags';
import { OrgPreference } from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
@ -158,13 +157,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
}
}, [orgPreferencesData, isFetchingOrgPreferences]);
function setUserFlags(userflags: UserFlags): void {
setUser((prev) => ({
...prev,
flags: userflags,
}));
}
function updateUser(user: IUser): void {
setUser((prev) => ({
...prev,
@ -252,7 +244,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
orgPreferencesFetchError,
licensesRefetch,
updateUser,
setUserFlags,
updateOrgPreferences,
updateOrg,
}),

View File

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

View File

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

View File

@ -128,7 +128,6 @@ export function getAppContextMock(
name: 'John Doe',
profilePictureURL: '',
createdAt: 1732544623,
flags: {},
organization: 'Nightswatch',
orgId: 'does-not-matter-id',
role: role as ROLES,
@ -326,7 +325,6 @@ export function getAppContextMock(
orgPreferencesFetchError: null,
isLoggedIn: true,
updateUser: jest.fn(),
setUserFlags: jest.fn(),
updateOrg: jest.fn(),
updateOrgPreferences: 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 { ROLES } from 'types/roles';
@ -16,6 +15,5 @@ export interface PayloadProps {
profilePictureURL: string;
organization: string;
role: ROLES;
flags: UserFlags;
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
import (
"context"
"errors"
"net/http"
"strings"
"go.signoz.io/signoz/pkg/query-service/dao"
"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) {
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)
if aH.HandleError(w, err, http.StatusBadRequest) {
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)
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) {
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 {
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
return

View File

@ -9,13 +9,14 @@ import (
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
)
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{
GetUserFromRequest: f,
}

View File

@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"go.signoz.io/signoz/pkg/query-service/app/metricsexplorer"
"io"
"math"
"net/http"
@ -19,6 +18,8 @@ import (
"text/template"
"time"
"go.signoz.io/signoz/pkg/query-service/app/metricsexplorer"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
jsoniter "github.com/json-iterator/go"
@ -51,6 +52,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/contextlinks"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/postprocess"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"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.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.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.
func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
invites, err := dao.DB().GetInvites(ctx)
ctx := r.Context()
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 {
RespondError(w, err, nil)
return
@ -2120,7 +2125,7 @@ func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request)
var resp []*model.InvitationResponseObject
for _, inv := range invites {
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgId)
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID)
if apiErr != nil {
RespondError(w, apiErr, nil)
}
@ -2128,7 +2133,7 @@ func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request)
Name: inv.Name,
Email: inv.Email,
Token: inv.Token,
CreatedAt: inv.CreatedAt,
CreatedAt: inv.CreatedAt.Unix(),
Role: inv.Role,
Organization: org.Name,
})
@ -2271,13 +2276,15 @@ func (aH *APIHandler) editUser(w http.ResponseWriter, r *http.Request) {
old.ProfilePictureURL = update.ProfilePictureURL
}
_, apiErr = dao.DB().EditUser(ctx, &model.User{
Id: old.Id,
Name: old.Name,
OrgId: old.OrgId,
Email: old.Email,
Password: old.Password,
CreatedAt: old.CreatedAt,
_, apiErr = dao.DB().EditUser(ctx, &types.User{
ID: old.ID,
Name: old.Name,
OrgID: old.OrgID,
Email: old.Email,
Password: old.Password,
TimeAuditable: types.TimeAuditable{
CreatedAt: old.CreatedAt,
},
ProfilePictureURL: old.ProfilePictureURL,
})
if apiErr != nil {
@ -2319,7 +2326,7 @@ func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
return
}
if user.GroupId == adminGroup.ID && len(adminUsers) == 1 {
if user.GroupID == adminGroup.ID && len(adminUsers) == 1 {
RespondError(w, &model.ApiError{
Typ: model.ErrorInternal,
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"})
}
// 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) {
id := mux.Vars(r)["id"]
@ -2380,7 +2356,7 @@ func (aH *APIHandler) getRole(w http.ResponseWriter, r *http.Request) {
}, nil)
return
}
group, err := dao.DB().GetGroup(context.Background(), user.GroupId)
group, err := dao.DB().GetGroup(context.Background(), user.GroupID)
if err != nil {
RespondError(w, err, "Failed to get group")
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.
if user.GroupId == auth.AuthCacheObj.AdminGroupId {
if user.GroupID == auth.AuthCacheObj.AdminGroupId {
adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, auth.AuthCacheObj.AdminGroupId)
if apiErr != nil {
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 {
RespondError(w, apiErr, "Failed to add user to group")
return
@ -2465,7 +2441,7 @@ func (aH *APIHandler) editOrg(w http.ResponseWriter, r *http.Request) {
return
}
req.Id = id
req.ID = id
if apiErr := dao.DB().EditOrg(context.Background(), req); apiErr != nil {
RespondError(w, apiErr, "Failed to update org in the DB")
return
@ -3528,7 +3504,7 @@ func (aH *APIHandler) getUserPreference(
user := common.GetUserFromContext(r.Context())
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 {
RespondError(w, apiErr, nil)
@ -3551,7 +3527,7 @@ func (aH *APIHandler) updateUserPreference(
RespondError(w, model.BadRequest(err), nil)
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 {
RespondError(w, apiErr, nil)
return
@ -3565,7 +3541,7 @@ func (aH *APIHandler) getAllUserPreferences(
) {
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetAllUserPreferences(
r.Context(), user.User.OrgId, user.User.Id,
r.Context(), user.User.OrgID, user.User.ID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
@ -3581,7 +3557,7 @@ func (aH *APIHandler) getOrgPreference(
preferenceId := mux.Vars(r)["preferenceId"]
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetOrgPreference(
r.Context(), preferenceId, user.User.OrgId,
r.Context(), preferenceId, user.User.OrgID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
@ -3604,7 +3580,7 @@ func (aH *APIHandler) updateOrgPreference(
RespondError(w, model.BadRequest(err), nil)
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 {
RespondError(w, apiErr, nil)
return
@ -3618,7 +3594,7 @@ func (aH *APIHandler) getAllOrgPreferences(
) {
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetAllOrgPreferences(
r.Context(), user.User.OrgId,
r.Context(), user.User.OrgID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)

View File

@ -6,9 +6,6 @@ import (
"encoding/json"
"errors"
"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"
"net/http"
"strconv"
@ -16,6 +13,10 @@ import (
"text/template"
"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/gorilla/mux"
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/utils"
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"}
@ -465,8 +467,8 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) {
return &model.GetTTLParams{Type: typeTTL}, nil
}
func parseUserRequest(r *http.Request) (*model.User, error) {
var req model.User
func parseUserRequest(r *http.Request) (*types.User, error) {
var req types.User
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
@ -519,8 +521,8 @@ func parseInviteUsersRequest(r *http.Request) (*model.BulkInviteRequest, error)
return &req, nil
}
func parseSetApdexScoreRequest(r *http.Request) (*model.ApdexSettings, error) {
var req model.ApdexSettings
func parseSetApdexScoreRequest(r *http.Request) (*types.ApdexSettings, error) {
var req types.ApdexSettings
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
@ -566,8 +568,8 @@ func parseUserRoleRequest(r *http.Request) (*model.UserRole, error) {
return &req, nil
}
func parseEditOrgRequest(r *http.Request) (*model.Organization, error) {
var req model.Organization
func parseEditOrgRequest(r *http.Request) (*types.Organization, error) {
var req types.Organization
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}

View File

@ -25,6 +25,7 @@ import (
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/signoz"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"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)
// add auth middleware
getUserFromRequest := func(ctx context.Context) (*model.UserPayload, error) {
getUserFromRequest := func(ctx context.Context) (*types.GettableUser, error) {
user, err := auth.GetUserFromReqContext(ctx)
if err != nil {
return nil, err
}
if user.User.OrgId == "" {
if user.User.OrgID == "" {
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/utils"
smtpservice "go.signoz.io/signoz/pkg/query-service/utils/smtpService"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap"
"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")
}
claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
return nil, errors.New("failed to extract OrgID from context")
}
// Check if an invite already exists
invite, apiErr := dao.DB().GetInviteFromEmail(ctx, req.Email)
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")
}
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)
if apiErr != nil {
return nil, errors.Wrap(err, "failed to query admin user from the DB")
}
inv := &model.InvitationObject{
inv := &types.Invite{
Name: req.Name,
Email: req.Email,
Token: token,
CreatedAt: time.Now().Unix(),
CreatedAt: time.Now(),
Role: req.Role,
OrgId: au.OrgId,
OrgID: au.OrgID,
}
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
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)
if err != nil {
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")
}
inv := &model.InvitationObject{
inv := &types.Invite{
Name: req.Name,
Email: req.Email,
Token: token,
CreatedAt: time.Now().Unix(),
CreatedAt: time.Now(),
Role: req.Role,
OrgId: au.OrgId,
OrgID: au.OrgID,
}
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
}
func inviteEmail(req *model.InviteRequest, au *model.UserPayload, token string) {
func inviteEmail(req *model.InviteRequest, au *types.GettableUser, token string) {
smtp := smtpservice.GetInstance()
data := InviteEmailData{
CustomerName: req.Name,
@ -251,7 +251,12 @@ func RevokeInvite(ctx context.Context, email string) error {
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 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
// 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 {
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,
Email: inv.Email,
Token: inv.Token,
CreatedAt: inv.CreatedAt,
CreatedAt: inv.CreatedAt.Unix(),
Role: inv.Role,
Organization: org.Name,
}, 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)
if err != nil {
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
}
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)
if err != nil {
return nil, errors.Wrap(err, "failed to generate reset password token")
}
req := &model.ResetPasswordEntry{
UserId: userId,
req := &types.ResetPasswordRequest{
UserID: userId,
Token: token,
}
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")
}
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
}
@ -360,7 +365,7 @@ func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) *mode
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
}
@ -369,6 +374,7 @@ func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) *mode
type RegisterRequest struct {
Name string `json:"name"`
OrgID string `json:"orgId"`
OrgName string `json:"orgName"`
Email string `json:"email"`
Password string `json:"password"`
@ -380,7 +386,7 @@ type RegisterRequest struct {
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 == "" {
return nil, model.BadRequest(model.ErrEmailRequired{})
@ -392,8 +398,9 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
groupName := constants.AdminGroup
// modify this to use bun
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 {
zap.L().Error("CreateOrg failed", zap.Error(apierr.ToError()))
return nil, apierr
@ -414,22 +421,24 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
return nil, model.InternalError(model.ErrSignupFailed{})
}
user := &model.User{
Id: uuid.NewString(),
Name: req.Name,
Email: req.Email,
Password: hash,
CreatedAt: time.Now().Unix(),
user := &types.User{
ID: uuid.NewString(),
Name: req.Name,
Email: req.Email,
Password: hash,
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
ProfilePictureURL: "", // Currently unused
GroupId: group.ID,
OrgId: org.Id,
GroupID: group.ID,
OrgID: org.ID,
}
return dao.DB().CreateUser(ctx, user, true)
}
// 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 == "" {
return nil, model.BadRequest(ErrorAskAdmin)
@ -459,7 +468,7 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
return &userPayload.User, nil
}
if invite.OrgId == "" {
if invite.OrgID == "" {
zap.L().Error("failed to find org in the invite")
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{
Id: uuid.NewString(),
Name: req.Name,
Email: req.Email,
Password: hash,
CreatedAt: time.Now().Unix(),
user := &types.User{
ID: uuid.NewString(),
Name: req.Name,
Email: req.Email,
Password: hash,
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
ProfilePictureURL: "", // Currently unused
GroupId: group.ID,
OrgId: invite.OrgId,
GroupID: group.ID,
OrgID: invite.OrgID,
}
// 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
}
apiErr = dao.DB().DeleteInvitation(ctx, user.Email)
apiErr = dao.DB().DeleteInvitation(ctx, user.OrgID, user.Email)
if apiErr != nil {
zap.L().Error("delete invitation failed", zap.Error(apiErr.Err))
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
// and also the first registration is an enforced ADMIN registration. Every subsequent request will
// 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)
if err != nil {
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{
UserJwtObject: userjwt,
UserId: user.User.Id,
UserId: user.User.ID,
}, nil
}
func claimsToUserPayload(claims authtypes.Claims) (*model.UserPayload, error) {
user := &model.UserPayload{
User: model.User{
Id: claims.UserID,
GroupId: claims.GroupID,
func claimsToUserPayload(claims authtypes.Claims) (*types.GettableUser, error) {
user := &types.GettableUser{
User: types.User{
ID: claims.UserID,
GroupID: claims.GroupID,
Email: claims.Email,
OrgId: claims.OrgID,
OrgID: claims.OrgID,
},
}
return user, nil
}
// 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 len(req.RefreshToken) > 0 {
// parse the refresh token
@ -624,17 +635,17 @@ func passwordMatch(hash, password string) bool {
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{}
var err error
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 {
return j, errors.Errorf("failed to encode jwt: %v", err)
}
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 {
return j, errors.Errorf("failed to encode jwt: %v", err)
}

View File

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

View File

@ -4,11 +4,11 @@ import (
"context"
"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 {
user, ok := ctx.Value(constants.ContextUserKey).(*model.UserPayload)
func GetUserFromContext(ctx context.Context) *types.GettableUser {
user, ok := ctx.Value(constants.ContextUserKey).(*types.GettableUser)
if !ok {
return nil
}

View File

@ -13,28 +13,28 @@ type ModelDao interface {
}
type Queries interface {
GetInviteFromEmail(ctx context.Context, email string) (*model.InvitationObject, *model.ApiError)
GetInviteFromToken(ctx context.Context, token string) (*model.InvitationObject, *model.ApiError)
GetInvites(ctx context.Context) ([]model.InvitationObject, *model.ApiError)
GetInviteFromEmail(ctx context.Context, email string) (*types.Invite, *model.ApiError)
GetInviteFromToken(ctx context.Context, token string) (*types.Invite, *model.ApiError)
GetInvites(ctx context.Context, orgID string) ([]types.Invite, *model.ApiError)
GetUser(ctx context.Context, id string) (*model.UserPayload, *model.ApiError)
GetUserByEmail(ctx context.Context, email string) (*model.UserPayload, *model.ApiError)
GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError)
GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError)
GetUser(ctx context.Context, id string) (*types.GettableUser, *model.ApiError)
GetUserByEmail(ctx context.Context, email string) (*types.GettableUser, *model.ApiError)
GetUsers(ctx context.Context) ([]types.GettableUser, *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)
GetGroups(ctx context.Context) ([]model.Group, *model.ApiError)
GetGroups(ctx context.Context) ([]types.Group, *model.ApiError)
GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError)
GetOrgByName(ctx context.Context, name string) (*model.Organization, *model.ApiError)
GetOrg(ctx context.Context, id string) (*model.Organization, *model.ApiError)
GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError)
GetOrgByName(ctx context.Context, name string) (*types.Organization, *model.ApiError)
GetOrg(ctx context.Context, id string) (*types.Organization, *model.ApiError)
GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError)
GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError)
GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError)
GetResetPasswordEntry(ctx context.Context, token string) (*types.ResetPasswordRequest, *model.ApiError)
GetUsersByOrg(ctx context.Context, orgId string) ([]types.GettableUser, *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)
@ -42,29 +42,27 @@ type Queries interface {
}
type Mutations interface {
CreateInviteEntry(ctx context.Context, req *model.InvitationObject) *model.ApiError
DeleteInvitation(ctx context.Context, email string) *model.ApiError
CreateInviteEntry(ctx context.Context, req *types.Invite) *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)
EditUser(ctx context.Context, update *model.User) (*model.User, *model.ApiError)
CreateUser(ctx context.Context, user *types.User, isFirstUser bool) (*types.User, *model.ApiError)
EditUser(ctx context.Context, update *types.User) (*types.User, *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)
DeleteGroup(ctx context.Context, id string) *model.ApiError
CreateOrg(ctx context.Context, org *model.Organization) (*model.Organization, *model.ApiError)
EditOrg(ctx context.Context, org *model.Organization) *model.ApiError
CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, *model.ApiError)
EditOrg(ctx context.Context, org *types.Organization) *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
UpdateUserPassword(ctx context.Context, hash, userId 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
}

View File

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

View File

@ -7,7 +7,6 @@ import (
"github.com/pkg/errors"
"github.com/uptrace/bun"
"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/sqlstore"
"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))
}
var org model.Organization
var org types.Organization
if len(orgs) == 1 {
org = orgs[0]
}
// set telemetry fields from userPreferences
telemetry.GetInstance().SetDistinctId(org.Id)
telemetry.GetInstance().SetDistinctId(org.ID)
users, _ := mds.GetUsers(ctx)
countUsers := len(users)

View File

@ -2,7 +2,6 @@ package sqlite
import (
"context"
"encoding/json"
"fmt"
"time"
@ -13,33 +12,38 @@ import (
"go.signoz.io/signoz/pkg/types"
)
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context,
req *model.InvitationObject) *model.ApiError {
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context, req *types.Invite) *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 {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) DeleteInvitation(ctx context.Context, email string) *model.ApiError {
_, err := mds.db.ExecContext(ctx, `DELETE from invites where email=?;`, email)
func (mds *ModelDaoSqlite) DeleteInvitation(ctx context.Context, orgID string, email string) *model.ApiError {
_, err := mds.bundb.NewDelete().
Model(&types.Invite{}).
Where("org_id = ?", orgID).
Where("email = ?", email).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
// TODO: Make this work with org id
func (mds *ModelDaoSqlite) GetInviteFromEmail(ctx context.Context, email string,
) (*model.InvitationObject, *model.ApiError) {
) (*types.Invite, *model.ApiError) {
invites := []model.InvitationObject{}
err := mds.db.Select(&invites,
`SELECT * FROM invites WHERE email=?;`, email)
invites := []types.Invite{}
err := mds.bundb.NewSelect().
Model(&invites).
Where("email = ?", email).
Scan(ctx)
if err != nil {
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,
) (*model.InvitationObject, *model.ApiError) {
) (*types.Invite, *model.ApiError) {
// This won't take org id because it's a public facing API
invites := []model.InvitationObject{}
err := mds.db.Select(&invites,
`SELECT * FROM invites WHERE token=?;`, token)
invites := []types.Invite{}
err := mds.bundb.NewSelect().
Model(&invites).
Where("token = ?", token).
Scan(ctx)
if err != nil {
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
}
func (mds *ModelDaoSqlite) GetInvites(ctx context.Context,
) ([]model.InvitationObject, *model.ApiError) {
invites := []model.InvitationObject{}
err := mds.db.Select(&invites, "SELECT * FROM invites")
func (mds *ModelDaoSqlite) GetInvites(ctx context.Context, orgID string) ([]types.Invite, *model.ApiError) {
invites := []types.Invite{}
err := mds.bundb.NewSelect().
Model(&invites).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
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,
org *model.Organization) (*model.Organization, *model.ApiError) {
org *types.Organization) (*types.Organization, *model.ApiError) {
org.Id = uuid.NewString()
org.CreatedAt = time.Now().Unix()
_, err := mds.db.ExecContext(ctx,
`INSERT INTO organizations (id, name, created_at,is_anonymous,has_opted_updates) VALUES (?, ?, ?, ?, ?);`,
org.Id, org.Name, org.CreatedAt, org.IsAnonymous, org.HasOptedUpdates)
org.ID = uuid.NewString()
org.CreatedAt = time.Now()
_, err := mds.bundb.NewInsert().
Model(org).
Exec(ctx)
if err != nil {
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,
id string) (*model.Organization, *model.ApiError) {
id string) (*types.Organization, *model.ApiError) {
orgs := []model.Organization{}
err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE id=?;`, id)
orgs := []types.Organization{}
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}
}
// TODO(nitya): remove for multitenancy
if len(orgs) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
@ -125,11 +137,14 @@ func (mds *ModelDaoSqlite) GetOrg(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}
}
@ -145,9 +160,11 @@ func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context,
return &orgs[0], nil
}
func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError) {
orgs := []model.Organization{}
err := mds.db.Select(&orgs, `SELECT * FROM organizations`)
func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError) {
var orgs []types.Organization
err := mds.bundb.NewSelect().
Model(&orgs).
Scan(ctx)
if err != nil {
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
}
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 {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous)
telemetry.GetInstance().SetDistinctId(org.Id)
telemetry.GetInstance().SetDistinctId(org.ID)
return nil
}
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 {
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,
user *model.User, isFirstUser bool) (*model.User, *model.ApiError) {
_, err := mds.db.ExecContext(ctx,
`INSERT INTO users (id, name, email, password, created_at, profile_picture_url, group_id, org_id)
VALUES (?, ?, ?, ?, ?, ?, ?,?);`,
user.Id, user.Name, user.Email, user.Password, user.CreatedAt,
user.ProfilePictureURL, user.GroupId, user.OrgId,
)
user *types.User, isFirstUser bool) (*types.User, *model.ApiError) {
_, err := mds.bundb.NewInsert().
Model(user).
Exec(ctx)
if err != nil {
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,
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 {
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,
userId string) *model.ApiError {
q := `UPDATE users SET password=? WHERE id=?;`
if _, err := mds.db.ExecContext(ctx, q, passwordHash, userId); err != nil {
_, err := mds.bundb.NewUpdate().
Model(&types.User{}).
Set("password = ?", passwordHash).
Where("id = ?", userId).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
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 {
q := `UPDATE users SET group_id=? WHERE id=?;`
if _, err := mds.db.ExecContext(ctx, q, groupId, userId); err != nil {
_, err := mds.bundb.NewUpdate().
Model(&types.User{}).
Set("group_id = ?", groupId).
Where("id = ?", userId).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
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 {
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,
id string) (*model.UserPayload, *model.ApiError) {
id string) (*types.GettableUser, *model.ApiError) {
users := []model.UserPayload{}
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,
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=?;`
users := []types.GettableUser{}
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.id = ?", 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}
}
if len(users) > 1 {
@ -303,7 +329,7 @@ func (mds *ModelDaoSqlite) GetUser(ctx context.Context,
}
func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
email string) (*model.UserPayload, *model.ApiError) {
email string) (*types.GettableUser, *model.ApiError) {
if email == "" {
return nil, &model.ApiError{
@ -312,27 +338,20 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
}
}
users := []model.UserPayload{}
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
g.id=u.group_id and
o.id = u.org_id and
u.email=?;`
users := []types.GettableUser{}
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.email = ?", 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}
}
if len(users) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
@ -347,34 +366,26 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
}
// 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)
}
// GetUsersWithOpts fetches users and supports additional search options
func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]types.GettableUser, *model.ApiError) {
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
g.id = u.group_id and
o.id = u.org_id`
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")
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 {
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,
orgId string) ([]model.UserPayload, *model.ApiError) {
orgId string) ([]types.GettableUser, *model.ApiError) {
users := []model.UserPayload{}
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=?;`
users := []types.GettableUser{}
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 users, nil
}
func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context,
groupId string) ([]model.UserPayload, *model.ApiError) {
groupId string) ([]types.GettableUser, *model.ApiError) {
users := []model.UserPayload{}
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=?;`
users := []types.GettableUser{}
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 users, nil
@ -452,17 +451,25 @@ func (mds *ModelDaoSqlite) CreateGroup(ctx context.Context,
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 nil
}
func (mds *ModelDaoSqlite) GetGroup(ctx context.Context,
id string) (*model.Group, *model.ApiError) {
id string) (*types.Group, *model.ApiError) {
groups := []model.Group{}
if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE id=?`, id); err != nil {
groups := []types.Group{}
if err := mds.bundb.NewSelect().
Model(&groups).
Where("id = ?", id).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
@ -505,10 +512,13 @@ func (mds *ModelDaoSqlite) GetGroupByName(ctx context.Context,
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{}
if err := mds.db.Select(&groups, "SELECT * FROM groups"); err != nil {
groups := []types.Group{}
if err := mds.bundb.NewSelect().
Model(&groups).
Scan(ctx); err != nil {
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,
req *model.ResetPasswordEntry) *model.ApiError {
req *types.ResetPasswordRequest) *model.ApiError {
q := `INSERT INTO reset_password_request (user_id, token) VALUES (?, ?);`
if _, err := mds.db.ExecContext(ctx, q, req.UserId, req.Token); err != nil {
if _, err := mds.bundb.NewInsert().
Model(req).
Exec(ctx); err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
@ -527,7 +538,11 @@ func (mds *ModelDaoSqlite) CreateResetPasswordEntry(ctx context.Context,
func (mds *ModelDaoSqlite) DeleteResetPasswordEntry(ctx context.Context,
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 {
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,
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.db.Select(&entries, q, token); err != nil {
if err := mds.bundb.NewSelect().
Model(&entries).
Where("token = ?", token).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(entries) > 1 {
@ -554,54 +571,6 @@ func (mds *ModelDaoSqlite) GetResetPasswordEntry(ctx context.Context,
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) {
// 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: ""}

View File

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

View File

@ -1,46 +1,10 @@
package model
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
import "time"
type Organization struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
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 ResetPasswordRequest struct {
Password string `json:"password"`
Token string `json:"token"`
}
type IngestionKey struct {
@ -51,50 +15,3 @@ type IngestionKey struct {
IngestionURL string `json:"ingestionURL" db:"ingestion_url"`
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"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/types"
)
const (
@ -206,7 +207,7 @@ type Telemetry struct {
alertsInfoCallback func(ctx context.Context) (*model.AlertsInfo, error)
userCountCallback func(ctx context.Context) (int, 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)
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
}
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
}
@ -524,7 +525,7 @@ func getOutboundIP() string {
return string(ip)
}
func (a *Telemetry) IdentifyUser(user *model.User) {
func (a *Telemetry) IdentifyUser(user *types.User) {
if user.Email == DEFAULT_CLOUD_EMAIL {
return
}
@ -534,7 +535,7 @@ func (a *Telemetry) IdentifyUser(user *model.User) {
return
}
// 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 role != "" {

View File

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

View File

@ -23,11 +23,11 @@ import (
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/dao"
"go.signoz.io/signoz/pkg/query-service/model"
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/utils"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
@ -441,7 +441,7 @@ func TestCanSavePipelinesWithoutConnectedAgents(t *testing.T) {
// configuring log pipelines and provides test helpers.
type LogPipelinesTestBed struct {
t *testing.T
testUser *model.User
testUser *types.User
apiHandler *app.APIHandler
agentConfMgr *agentConf.Manager
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/dao"
"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/sqlstore"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap"
)
@ -338,7 +338,7 @@ func TestConfigReturnedWhenAgentChecksIn(t *testing.T) {
type CloudIntegrationsTestBed struct {
t *testing.T
testUser *model.User
testUser *types.User
qsHttpHandler http.Handler
mockClickhouse mockhouse.ClickConnMockCommon
}

View File

@ -23,6 +23,7 @@ import (
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap"
)
@ -367,7 +368,7 @@ func TestDashboardsForInstalledIntegrationDashboards(t *testing.T) {
type IntegrationsTestBed struct {
t *testing.T
testUser *model.User
testUser *types.User
qsHttpHandler http.Handler
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/interfaces"
"go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"golang.org/x/exp/maps"
)
@ -150,10 +151,10 @@ func makeTestSignozLog(
return testLog
}
func createTestUser() (*model.User, *model.ApiError) {
func createTestUser() (*types.User, *model.ApiError) {
// Create a test user for auth
ctx := context.Background()
org, apiErr := dao.DB().CreateOrg(ctx, &model.Organization{
org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{
Name: "test",
})
if apiErr != nil {
@ -170,20 +171,20 @@ func createTestUser() (*model.User, *model.ApiError) {
userId := uuid.NewString()
return dao.DB().CreateUser(
ctx,
&model.User{
Id: userId,
&types.User{
ID: userId,
Name: "test",
Email: userId[:8] + "test@test.com",
Password: "test",
OrgId: org.Id,
GroupId: group.ID,
OrgID: org.ID,
GroupID: group.ID,
},
true,
)
}
func AuthenticatedRequestForTest(
user *model.User,
user *types.User,
path string,
postData interface{},
) (*http.Request, error) {

View File

@ -45,6 +45,9 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
sqlmigration.NewAddIntegrationsFactory(),
sqlmigration.NewAddLicensesFactory(),
sqlmigration.NewAddPatsFactory(),
sqlmigration.NewModifyDatetimeFactory(),
sqlmigration.NewModifyOrgDomainFactory(),
sqlmigration.NewUpdateOrganizationFactory(sqlStore),
),
)
if err != nil {
@ -62,7 +65,10 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
func NewQueryServiceDBForTests(t *testing.T) sqlstore.SQLStore {
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())
return sqlStore

View File

@ -74,6 +74,9 @@ func New(
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
telemetrystore, err := factory.NewProviderFromNamedMap(
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"`
Name string `bun:"name,type:varchar(50),notnull,unique"`
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"`
}{}).
ForeignKey(`("org_id") REFERENCES "organizations" ("id")`).

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/migrate"
"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 {
// only run this for old sqlite db
if db.Dialect().Name() != dialect.SQLite {
return nil
}
// begin transaction
tx, err := db.BeginTx(ctx, 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)
}
}
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
bundb *sqlstore.BunDB
sqlxdb *sqlx.DB
dialect *PGDialect
}
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,
bundb: sqlstore.NewBunDB(settings, sqldb, pgdialect.New(), hooks),
sqlxdb: sqlx.NewDb(sqldb, "postgres"),
dialect: &PGDialect{},
}, nil
}
@ -74,6 +76,10 @@ func (provider *provider) SQLxDB() *sqlx.DB {
return provider.sqlxdb
}
func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB {
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
bundb *sqlstore.BunDB
sqlxdb *sqlx.DB
dialect *SQLiteDialect
}
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,
bundb: sqlstore.NewBunDB(settings, sqldb, sqlitedialect.New(), hooks),
sqlxdb: sqlx.NewDb(sqldb, "sqlite3"),
dialect: &SQLiteDialect{},
}, nil
}
@ -64,6 +66,10 @@ func (provider *provider) SQLxDB() *sqlx.DB {
return provider.sqlxdb
}
func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *provider) BunDBCtx(ctx context.Context) bun.IDB {
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() *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.
// 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
@ -32,3 +35,10 @@ type SQLStore interface {
type SQLStoreHook interface {
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)
type Provider struct {
db *sql.DB
mock sqlmock.Sqlmock
bunDB *bun.DB
sqlxDB *sqlx.DB
db *sql.DB
mock sqlmock.Sqlmock
bunDB *bun.DB
sqlxDB *sqlx.DB
dialect *TestDialect
}
func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider {
@ -38,10 +39,11 @@ func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider {
}
return &Provider{
db: db,
mock: mock,
bunDB: bunDB,
sqlxDB: sqlxDB,
db: db,
mock: mock,
bunDB: bunDB,
sqlxDB: sqlxDB,
dialect: &TestDialect{},
}
}
@ -61,6 +63,10 @@ func (provider *Provider) Mock() sqlmock.Sqlmock {
return provider.mock
}
func (provider *Provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}
func (provider *Provider) BunDBCtx(ctx context.Context) bun.IDB {
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
import (
"time"
"github.com/uptrace/bun"
)
@ -10,69 +8,16 @@ import (
type Organization struct {
bun.BaseModel `bun:"table:organizations"`
ID string `bun:"id,pk,type:text"`
Name string `bun:"name,type:text,notnull"`
CreatedAt int `bun:"created_at,notnull"`
IsAnonymous int `bun:"is_anonymous,notnull,default:0,CHECK(is_anonymous IN (0,1))"`
HasOptedUpdates int `bun:"has_opted_updates,notnull,default:1,CHECK(has_opted_updates IN (0,1))"`
}
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"`
TimeAuditable
ID string `bun:"id,pk,type:text" json:"id"`
Name string `bun:"name,type:text,notnull" json:"name"`
IsAnonymous bool `bun:"is_anonymous,notnull,default:0,CHECK(is_anonymous IN (0,1))" json:"isAnonymous"`
HasOptedUpdates bool `bun:"has_opted_updates,notnull,default:1,CHECK(has_opted_updates IN (0,1))" json:"hasOptedUpdates"`
}
type ApdexSettings struct {
bun.BaseModel `bun:"table:apdex_settings"`
ServiceName string `bun:"service_name,pk,type:text"`
Threshold float64 `bun:"threshold,type:float,notnull"`
ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull"`
}
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"`
OrgID string `bun:"org_id,pk,type:text" json:"orgId"`
ServiceName string `bun:"service_name,pk,type:text" json:"serviceName"`
Threshold float64 `bun:"threshold,type:float,notnull" json:"threshold"`
ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull" json:"excludeStatusCodes"`
}

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"`
}