[task-416] [mod] repository📆sonder-query los werden
This commit is contained in:
parent
24322588d7
commit
bef69283f6
2
lib/plankton/plankton.d.ts
vendored
2
lib/plankton/plankton.d.ts
vendored
|
|
@ -4597,7 +4597,7 @@ declare namespace lib_plankton.auth.oidc {
|
|||
type type_token = string;
|
||||
/**
|
||||
*/
|
||||
type type_userinfo = {
|
||||
export type type_userinfo = {
|
||||
name: (null | string);
|
||||
label: (null | string);
|
||||
email: (null | string);
|
||||
|
|
|
|||
|
|
@ -21,38 +21,6 @@ along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
|
|||
namespace _zeitbild.api
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
function get_group_name(
|
||||
group_name_raw : string
|
||||
)
|
||||
: string
|
||||
{
|
||||
return lib_plankton.string.coin(
|
||||
"auto-{{name_raw}}",
|
||||
{
|
||||
"name_raw": group_name_raw,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function get_group_label(
|
||||
group_name_raw : string
|
||||
)
|
||||
: string
|
||||
{
|
||||
return lib_plankton.string.coin(
|
||||
"{{name_raw}}",
|
||||
{
|
||||
"name_raw": group_name_raw,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export function register_session_oidc(
|
||||
|
|
@ -103,132 +71,39 @@ namespace _zeitbild.api
|
|||
"execution": async (stuff) => {
|
||||
const data : {
|
||||
token : string;
|
||||
userinfo : {
|
||||
name : (null | string);
|
||||
email : (null | string);
|
||||
groups : (null | Array<string>);
|
||||
};
|
||||
userinfo : lib_plankton.auth.oidc.type_userinfo;
|
||||
redirect_uri_template : string;
|
||||
} = await _zeitbild.auth.oidc_handle_authorization_callback(
|
||||
(stuff.headers["Cookie"] ?? stuff.headers["cookie"] ?? null),
|
||||
stuff.query_parameters
|
||||
);
|
||||
if (data.userinfo.name === null)
|
||||
{
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"IDP did not return user name"
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// groups
|
||||
const group_ids : Array<_zeitbild.type_group_id> = await Promise.all<_zeitbild.type_group_id>(
|
||||
(data.userinfo.groups ?? [])
|
||||
.map(
|
||||
async (group_name_raw) => {
|
||||
const group_name : string = get_group_name(group_name_raw);
|
||||
const group_id_raw : (null | _zeitbild.type_group_id) = await (
|
||||
_zeitbild.repository.group.identify(group_name)
|
||||
.catch(() => Promise.resolve(null))
|
||||
);
|
||||
if (group_id_raw === null)
|
||||
{
|
||||
const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add(
|
||||
{
|
||||
"name": group_name,
|
||||
"label": get_group_label(group_name_raw),
|
||||
}
|
||||
);
|
||||
return group_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
const group_id : _zeitbild.type_group_id = group_id_raw;
|
||||
await _zeitbild.service.group.change(
|
||||
group_id,
|
||||
{
|
||||
"name": group_name,
|
||||
"label": get_group_label(group_name_raw),
|
||||
}
|
||||
);
|
||||
return group_id;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const user_id : _zeitbild.type_user_id = await (async () => {
|
||||
const user_object : _zeitbild.type_user_object = {
|
||||
"name": (data.userinfo.name as string),
|
||||
"groups": group_ids,
|
||||
"email_address": data.userinfo.email,
|
||||
"dav_token": null,
|
||||
};
|
||||
const user_id_raw : (null | _zeitbild.type_user_id) = await (
|
||||
_zeitbild.service.user.identify(data.userinfo.name as string)
|
||||
.catch(() => Promise.resolve(null))
|
||||
);
|
||||
if (user_id_raw === null)
|
||||
{
|
||||
// provision
|
||||
const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add(
|
||||
user_object
|
||||
);
|
||||
lib_plankton.log.info(
|
||||
"user_provisioned",
|
||||
{
|
||||
"id": user_id,
|
||||
"name": user_object.name,
|
||||
}
|
||||
);
|
||||
return user_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
const user_id : _zeitbild.type_user_id = user_id_raw;
|
||||
// update
|
||||
await _zeitbild.service.user.change(
|
||||
user_id,
|
||||
user_object
|
||||
);
|
||||
lib_plankton.log.info(
|
||||
"user_updated",
|
||||
{
|
||||
"id": user_id,
|
||||
"name": user_object.name,
|
||||
}
|
||||
);
|
||||
return user_id;
|
||||
}
|
||||
}) ();
|
||||
const user = await _zeitbild.auth.oidc_adapt_user(data.userinfo);
|
||||
|
||||
const session_key : string = await lib_plankton.session.begin(
|
||||
data.userinfo.name,
|
||||
{
|
||||
"data": {
|
||||
"oidc_token": data.token,
|
||||
const session_key : string = await lib_plankton.session.begin(
|
||||
user.object.name,
|
||||
{
|
||||
"data": {
|
||||
"oidc_token": data.token,
|
||||
}
|
||||
}
|
||||
);
|
||||
return Promise.resolve(
|
||||
{
|
||||
"status_code": 200,
|
||||
"data": lib_plankton.string.coin(
|
||||
"<html><head><meta http-equiv=\"refresh\" content=\"0; url={{url}}\" /></head><body></body></html>",
|
||||
{
|
||||
"url": lib_plankton.string.coin(
|
||||
data.redirect_uri_template,
|
||||
{
|
||||
"session_key": session_key,
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
);
|
||||
return Promise.resolve(
|
||||
{
|
||||
"status_code": 200,
|
||||
"data": lib_plankton.string.coin(
|
||||
"<html><head><meta http-equiv=\"refresh\" content=\"0; url={{url}}\" /></head><body></body></html>",
|
||||
{
|
||||
"url": lib_plankton.string.coin(
|
||||
data.redirect_uri_template,
|
||||
{
|
||||
"session_key": session_key,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
|||
158
source/auth.ts
158
source/auth.ts
|
|
@ -188,11 +188,7 @@ namespace _zeitbild.auth
|
|||
) : Promise<
|
||||
{
|
||||
token : string;
|
||||
userinfo : {
|
||||
name : (null | string);
|
||||
email : (null | string);
|
||||
groups : (null | Array<string>);
|
||||
};
|
||||
userinfo : lib_plankton.auth.oidc.type_userinfo;
|
||||
redirect_uri_template : string;
|
||||
}
|
||||
>
|
||||
|
|
@ -206,11 +202,7 @@ namespace _zeitbild.auth
|
|||
const state : string = data["state"];
|
||||
const result : {
|
||||
token : string;
|
||||
userinfo : {
|
||||
name : (null | string);
|
||||
email : (null | string);
|
||||
groups : (null | Array<string>);
|
||||
};
|
||||
userinfo : lib_plankton.auth.oidc.type_userinfo;
|
||||
} = await lib_plankton.auth.oidc.handle_authorization_callback(
|
||||
_subject_oidc,
|
||||
cookie,
|
||||
|
|
@ -219,11 +211,7 @@ namespace _zeitbild.auth
|
|||
return Promise.resolve<
|
||||
{
|
||||
token : string;
|
||||
userinfo : {
|
||||
name : (null | string);
|
||||
email : (null | string);
|
||||
groups : (null | Array<string>);
|
||||
};
|
||||
userinfo : lib_plankton.auth.oidc.type_userinfo;
|
||||
redirect_uri_template : string;
|
||||
}
|
||||
>(
|
||||
|
|
@ -236,4 +224,144 @@ namespace _zeitbild.auth
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @todo switch for enabling/disabling auto provisioning
|
||||
*/
|
||||
export async function oidc_adapt_user(
|
||||
userinfo : lib_plankton.auth.oidc.type_userinfo
|
||||
)
|
||||
: Promise<
|
||||
{
|
||||
id : _zeitbild.type_user_id;
|
||||
object : _zeitbild.type_user_object;
|
||||
}
|
||||
>
|
||||
{
|
||||
if (userinfo.name === null)
|
||||
{
|
||||
return Promise.reject(new Error("IDP did not return user name"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// groups
|
||||
const group_ids : Array<_zeitbild.type_group_id> = await (async () => {
|
||||
const derive_name : ((group_name_raw : string) => string) = (
|
||||
(group_name_raw) => lib_plankton.string.coin(
|
||||
"auto-{{name_raw}}",
|
||||
{
|
||||
"name_raw": group_name_raw,
|
||||
}
|
||||
)
|
||||
);
|
||||
const derive_label : ((group_name_raw : string) => string) = (
|
||||
(group_name_raw) => lib_plankton.string.coin(
|
||||
"{{name_raw}}",
|
||||
{
|
||||
"name_raw": group_name_raw,
|
||||
}
|
||||
)
|
||||
);
|
||||
return Promise.all<_zeitbild.type_group_id>(
|
||||
(userinfo.groups ?? [])
|
||||
.map(
|
||||
async (group_name_raw) => {
|
||||
const group_name : string = derive_name(group_name_raw);
|
||||
const group_id_raw : (null | _zeitbild.type_group_id) = await (
|
||||
_zeitbild.repository.group.identify(group_name)
|
||||
.catch(() => Promise.resolve(null))
|
||||
);
|
||||
if (group_id_raw === null)
|
||||
{
|
||||
// create
|
||||
const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add(
|
||||
{
|
||||
"name": group_name,
|
||||
"label": derive_label(group_name_raw),
|
||||
}
|
||||
);
|
||||
lib_plankton.log.info(
|
||||
"zeitbild.oidc_adapt_user.auto_provisioned_group",
|
||||
{
|
||||
"id": group_id,
|
||||
"name": group_name,
|
||||
}
|
||||
);
|
||||
return group_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update
|
||||
const group_id : _zeitbild.type_group_id = group_id_raw;
|
||||
await _zeitbild.service.group.change(
|
||||
group_id,
|
||||
{
|
||||
"name": group_name,
|
||||
"label": derive_label(group_name_raw),
|
||||
}
|
||||
);
|
||||
return group_id;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}) ();
|
||||
|
||||
// user
|
||||
const user : {
|
||||
id : _zeitbild.type_user_id;
|
||||
object : _zeitbild.type_user_object;
|
||||
} = await (async () => {
|
||||
const user_id_raw : (null | _zeitbild.type_user_id) = await (
|
||||
_zeitbild.service.user.identify(userinfo.name as string)
|
||||
.catch(() => Promise.resolve(null))
|
||||
);
|
||||
if (user_id_raw === null)
|
||||
{
|
||||
// provision
|
||||
const user_object : _zeitbild.type_user_object = {
|
||||
"name": (userinfo.name as string),
|
||||
"groups": group_ids,
|
||||
"email_address": userinfo.email,
|
||||
"dav_token": null,
|
||||
};
|
||||
const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add(
|
||||
user_object
|
||||
);
|
||||
lib_plankton.log.info(
|
||||
"user_provisioned",
|
||||
{
|
||||
"id": user_id,
|
||||
"name": user_object.name,
|
||||
}
|
||||
);
|
||||
return {"id": user_id, "object": user_object};
|
||||
}
|
||||
else
|
||||
{
|
||||
// update
|
||||
const user_id : _zeitbild.type_user_id = user_id_raw;
|
||||
const user_object : _zeitbild.type_user_object = await _zeitbild.service.user.get(user_id);
|
||||
user_object.name = (userinfo.name as string);
|
||||
user_object.groups = group_ids;
|
||||
user_object.email_address = userinfo.email;
|
||||
await _zeitbild.service.user.change(
|
||||
user_id,
|
||||
user_object
|
||||
);
|
||||
lib_plankton.log.info(
|
||||
"user_updated",
|
||||
{
|
||||
"id": user_id,
|
||||
"name": user_object.name,
|
||||
}
|
||||
);
|
||||
return {"id": user_id, "object": user_object};
|
||||
}
|
||||
}) ();
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,7 +328,6 @@ namespace _zeitbild.repository.calendar
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export async function read(
|
||||
|
|
@ -601,7 +600,6 @@ namespace _zeitbild.repository.calendar
|
|||
|
||||
|
||||
/**
|
||||
* @todo caching
|
||||
*/
|
||||
export async function overview(
|
||||
user_id : (null | _zeitbild.type_user_id)
|
||||
|
|
@ -612,122 +610,152 @@ namespace _zeitbild.repository.calendar
|
|||
>
|
||||
>
|
||||
{
|
||||
type type_data = {
|
||||
hits_core : Array<
|
||||
{
|
||||
key : int;
|
||||
preview : Record<string, any>;
|
||||
}
|
||||
>;
|
||||
hits_access_attributed_group : Array<
|
||||
{
|
||||
key : int;
|
||||
preview : Record<string, any>;
|
||||
}
|
||||
>;
|
||||
hits_access_attributed_user : Array<
|
||||
{
|
||||
key : int;
|
||||
preview : Record<string, any>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
return lib_plankton.cache.get_complex<any, Array<type_overview_entry>>(
|
||||
_zeitbild.cache_regular,
|
||||
"calendar_overview",
|
||||
{
|
||||
"user_id": user_id,
|
||||
},
|
||||
null,
|
||||
() => (
|
||||
lib_plankton.file.read("sql/calendar_overview.sql")
|
||||
.then(
|
||||
(template) => _zeitbild.database.get_implementation().query_free_get(
|
||||
60,
|
||||
async () => lib_plankton.call.convey(
|
||||
{
|
||||
"hits_core": await get_core_store().search(
|
||||
{
|
||||
"template": template,
|
||||
"arguments": {
|
||||
"user_id": user_id,
|
||||
}
|
||||
"expression": "TRUE",
|
||||
"arguments": {}
|
||||
}
|
||||
)
|
||||
)
|
||||
.then(
|
||||
(rows) => Promise.resolve(
|
||||
lib_plankton.call.convey(
|
||||
rows,
|
||||
[
|
||||
(x : Array<Record<string, any>>) => x.map(
|
||||
(row : Record<string, any>) => ({
|
||||
"id": row["id"],
|
||||
"name": lib_plankton.call.convey(
|
||||
row["name"],
|
||||
[
|
||||
// JSON.parse,
|
||||
(x : Array<string>) => x[0],
|
||||
]
|
||||
),
|
||||
"hue": lib_plankton.call.convey(
|
||||
row["hue"],
|
||||
[
|
||||
// JSON.parse,
|
||||
(x : Array<int>) => x[0],
|
||||
(x : int) => (x / hue_scaling),
|
||||
]
|
||||
),
|
||||
/**
|
||||
* @todo use _zeitbild.access_level_determine
|
||||
*/
|
||||
"access_level": _zeitbild.access_level_determine_raw(
|
||||
lib_plankton.call.convey(
|
||||
row["access_public"],
|
||||
),
|
||||
"hits_access_attributed_group": await get_access_attributed_group_chest().search(
|
||||
(user_id === null)
|
||||
?
|
||||
{
|
||||
"expression": "TRUE",
|
||||
"arguments": {}
|
||||
}
|
||||
:
|
||||
{
|
||||
"expression": "(group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id)))",
|
||||
"arguments": {"user_id": user_id}
|
||||
}
|
||||
),
|
||||
"hits_access_attributed_user": await get_access_attributed_user_chest().search(
|
||||
(user_id === null)
|
||||
?
|
||||
{
|
||||
"expression": "TRUE",
|
||||
"arguments": {}
|
||||
}
|
||||
:
|
||||
{
|
||||
"expression": "(user_id = $user_id)",
|
||||
"arguments": {"user_id": user_id}
|
||||
}
|
||||
),
|
||||
},
|
||||
[
|
||||
// transform
|
||||
(data : type_data) => data.hits_core.map(
|
||||
(hit_core) => {
|
||||
const calendar_id : _zeitbild.type_calendar_id = hit_core.key;
|
||||
return {
|
||||
"id": calendar_id,
|
||||
"name": hit_core.preview["name"],
|
||||
"hue": (hit_core.preview["hue"] / hue_scaling),
|
||||
"access_level": _zeitbild.access_level_determine_raw(
|
||||
hit_core.preview["access_public"],
|
||||
(
|
||||
(user_id === null)
|
||||
?
|
||||
null
|
||||
:
|
||||
{
|
||||
"default": decode_access_level(hit_core.preview["access_level_default"]),
|
||||
"group": lib_plankton.call.convey(
|
||||
data.hits_access_attributed_group,
|
||||
[
|
||||
// JSON.parse,
|
||||
(x : Array<boolean>) => x[0],
|
||||
(x : Array<{key : int; preview : Record<string, any>}>) => x.filter(
|
||||
hit_access_attributed_group => (
|
||||
(hit_access_attributed_group.preview.calendar_id === calendar_id)
|
||||
)
|
||||
),
|
||||
(x : Array<{key : int; preview : Record<string, any>}>) => x.map(
|
||||
hit_access_attributed_group => hit_access_attributed_group.preview.level
|
||||
),
|
||||
(x : Array<int>) => x.map(
|
||||
decode_access_level
|
||||
),
|
||||
]
|
||||
),
|
||||
(
|
||||
(user_id === null)
|
||||
?
|
||||
null
|
||||
:
|
||||
{
|
||||
"default": lib_plankton.call.convey(
|
||||
row["access_level_default"],
|
||||
[
|
||||
// JSON.parse,
|
||||
(x : Array<int>) => x[0],
|
||||
decode_access_level,
|
||||
]
|
||||
"user": lib_plankton.call.convey(
|
||||
data.hits_access_attributed_user,
|
||||
[
|
||||
(x : Array<{key : int; preview : Record<string, any>}>) => x.filter(
|
||||
hits_access_attributed_user => (
|
||||
(hits_access_attributed_user.preview.calendar_id === calendar_id)
|
||||
)
|
||||
),
|
||||
"group": lib_plankton.call.convey(
|
||||
row["access_level_attributed_group"],
|
||||
[
|
||||
// JSON.parse,
|
||||
(x : Array<(null | int)>) => x.filter(y => (y !== null)),
|
||||
(x : Array<int>) => x.map(decode_access_level),
|
||||
]
|
||||
(x : Array<{key : int; preview : Record<string, any>}>) => x.map(
|
||||
hits_access_attributed_user => hits_access_attributed_user.preview.level
|
||||
),
|
||||
"user": lib_plankton.call.convey(
|
||||
row["access_level_attributed_user"],
|
||||
[
|
||||
// JSON.parse,
|
||||
(x : Array<(null | int)>) => x.filter(y => (y !== null)),
|
||||
(x : Array<int>) => x.map(decode_access_level),
|
||||
(x : Array<_zeitbild.enum_access_level>) => ((x.length > 0) ? x[0] : null),
|
||||
]
|
||||
(x : Array<int>) => x.map(
|
||||
decode_access_level
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
})
|
||||
),
|
||||
(x : Array<type_overview_entry>) => x.filter(
|
||||
(row) => (
|
||||
! _zeitbild.access_level_order(
|
||||
row.access_level,
|
||||
_zeitbild.enum_access_level.none
|
||||
)
|
||||
(x : Array<_zeitbild.enum_access_level>) => (x[0] ?? null),
|
||||
]
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
(x : Array<type_overview_entry>) => lib_plankton.list.sorted<type_overview_entry>(
|
||||
x,
|
||||
{
|
||||
"compare_element": lib_plankton.order.order_lexicographic_pair_wrapped<type_overview_entry, _zeitbild.enum_access_level, int>(
|
||||
row => row.access_level,
|
||||
row => row.id,
|
||||
{
|
||||
"order_first": (a, b) => _zeitbild.access_level_order(b, a),
|
||||
"order_second": (a, b) => (a <= b)
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
]
|
||||
};
|
||||
}
|
||||
),
|
||||
// only keep visible calendars
|
||||
(x : Array<type_overview_entry>) => x.filter(
|
||||
(row) => (
|
||||
! _zeitbild.access_level_order(
|
||||
row.access_level,
|
||||
_zeitbild.enum_access_level.none
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
// sort by access level and name
|
||||
(x : Array<type_overview_entry>) => lib_plankton.list.sorted<type_overview_entry>(
|
||||
x,
|
||||
{
|
||||
"compare_element": lib_plankton.order.order_lexicographic_pair_wrapped<type_overview_entry, _zeitbild.enum_access_level, int>(
|
||||
row => row.access_level,
|
||||
row => row.id,
|
||||
{
|
||||
"order_first": (a, b) => _zeitbild.access_level_order(b, a),
|
||||
"order_second": (a, b) => (a <= b)
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
SELECT
|
||||
x.id AS id,
|
||||
JSON_AGG(x.name) AS name,
|
||||
JSON_AGG(x.hue) AS hue,
|
||||
JSON_AGG(x.access_public) AS access_public,
|
||||
JSON_AGG(x.access_level_default) AS access_level_default,
|
||||
JSON_AGG(y1.level) AS access_level_attributed_group,
|
||||
JSON_AGG(y2.level) AS access_level_attributed_user
|
||||
FROM
|
||||
calendars AS x
|
||||
LEFT OUTER JOIN calendar_access_attributed_group AS y1 ON ((x.id = y1.calendar_id) AND (y1.group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id))))
|
||||
LEFT OUTER JOIN calendar_access_attributed_user AS y2 ON ((x.id = y2.calendar_id) AND (y2.user_id = $user_id))
|
||||
GROUP BY
|
||||
x.id
|
||||
;
|
||||
|
|
@ -18,7 +18,7 @@ cmd_tsc := ${dir_tools}/typescript/node_modules/.bin/tsc
|
|||
## rules
|
||||
|
||||
.PHONY: default
|
||||
default: node_modules sql ${dir_build}/zeitbild node_modules
|
||||
default: node_modules ${dir_build}/zeitbild node_modules
|
||||
|
||||
.PHONY: node_modules
|
||||
node_modules:
|
||||
|
|
@ -26,13 +26,6 @@ node_modules:
|
|||
@ ${cmd_log} "node modules …"
|
||||
@ ${cmd_cp} -r -u ${dir_lib}/node/node_modules/* ${dir_build}/node_modules/
|
||||
|
||||
.PHONY: sql
|
||||
sql: \
|
||||
$(wildcard ${dir_source}/repositories/sql/*)
|
||||
@ ${cmd_log} "sql …"
|
||||
@ ${cmd_mkdir} ${dir_build}/sql
|
||||
@ ${cmd_cp} -r -u $^ ${dir_build}/sql/
|
||||
|
||||
${dir_temp}/conf.ts: \
|
||||
${dir_source}/conf.ts.tpl \
|
||||
${dir_source}/conf.schema.json
|
||||
|
|
|
|||
Loading…
Reference in a new issue